zoukankan      html  css  js  c++  java
  • Spring Boot系列——Spring Boot如何启动

    Spring Boot启动过程

    ​上篇《Spring Boot系列——5分钟构建一个应用》介绍了如何快速创建一个Spring Boot项目并运行。虽然步骤少流程简单,为开发者省去了很多重复性的配置工作,但是其底层实现并没有这么简单。

    这篇,我们就通过入口类TutorialApplication看看Spring Boot是如何启动的。

    注解

    写过Spring Boot都知道需要有一个入口类,就是本例子中的TutorialApplication,而这个类上面必不可上的需要有一个@SpringBootApplication注解。

    点击进入该注解,我们可以发现其是一个复合注解,包括@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan。

    
    /**
     * Indicates a {@link Configuration configuration} class that declares one or more
     * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
     * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
     * annotation that is equivalent to declaring {@code @Configuration},
     * {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
     *
     * @author Phillip Webb
     * @author Stephane Nicoll
     * @since 1.2.0
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = {
          @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
          @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {
    
       /**
        * Exclude specific auto-configuration classes such that they will never be applied.
        * @return the classes to exclude
        */
       @AliasFor(annotation = EnableAutoConfiguration.class)
       Class<?>[] exclude() default {};
    
       /**
        * Exclude specific auto-configuration class names such that they will never be
        * applied.
        * @return the class names to exclude
        * @since 1.3.0
        */
       @AliasFor(annotation = EnableAutoConfiguration.class)
       String[] excludeName() default {};
    
       /**
        * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
        * for a type-safe alternative to String-based package names.
        * @return base packages to scan
        * @since 1.3.0
        */
       @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
       String[] scanBasePackages() default {};
    
       /**
        * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
        * scan for annotated components. The package of each class specified will be scanned.
        * <p>
        * Consider creating a special no-op marker class or interface in each package that
        * serves no purpose other than being referenced by this attribute.
        * @return base packages to scan
        * @since 1.3.0
        */
       @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
       Class<?>[] scanBasePackageClasses() default {};
    
    }
    

    @SpringBootConfiguration

    该注解底层其实就是@Configuration注解。熟悉Spring的发展里程碑就知道这是Java Config的配置形式。

    通过该注解修饰,表示该类是一个配置类。

    @EnableAutoConfiguration

    该注解其实也是一个复合注解。

    
    /**
     * Enable auto-configuration of the Spring Application Context, attempting to guess and
     * configure beans that you are likely to need. Auto-configuration classes are usually
     * applied based on your classpath and what beans you have defined. For example, if you
     * have {@code tomcat-embedded.jar} on your classpath you are likely to want a
     * {@link TomcatServletWebServerFactory} (unless you have defined your own
     * {@link ServletWebServerFactory} bean).
     * <p>
     * When using {@link SpringBootApplication}, the auto-configuration of the context is
     * automatically enabled and adding this annotation has therefore no additional effect.
     * <p>
     * Auto-configuration tries to be as intelligent as possible and will back-away as you
     * define more of your own configuration. You can always manually {@link #exclude()} any
     * configuration that you never want to apply (use {@link #excludeName()} if you don't
     * have access to them). You can also exclude them via the
     * {@code spring.autoconfigure.exclude} property. Auto-configuration is always applied
     * after user-defined beans have been registered.
     * <p>
     * The package of the class that is annotated with {@code @EnableAutoConfiguration},
     * usually via {@code @SpringBootApplication}, has specific significance and is often used
     * as a 'default'. For example, it will be used when scanning for {@code @Entity} classes.
     * It is generally recommended that you place {@code @EnableAutoConfiguration} (if you're
     * not using {@code @SpringBootApplication}) in a root package so that all sub-packages
     * and classes can be searched.
     * <p>
     * Auto-configuration classes are regular Spring {@link Configuration} beans. They are
     * located using the {@link SpringFactoriesLoader} mechanism (keyed against this class).
     * Generally auto-configuration beans are {@link Conditional @Conditional} beans (most
     * often using {@link ConditionalOnClass @ConditionalOnClass} and
     * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).
     *
     * @author Phillip Webb
     * @author Stephane Nicoll
     * @see ConditionalOnBean
     * @see ConditionalOnMissingBean
     * @see ConditionalOnClass
     * @see AutoConfigureAfter
     * @see SpringBootApplication
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    
       String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
       /**
        * Exclude specific auto-configuration classes such that they will never be applied.
        * @return the classes to exclude
        */
       Class<?>[] exclude() default {};
    
       /**
        * Exclude specific auto-configuration class names such that they will never be
        * applied.
        * @return the class names to exclude
        * @since 1.3.0
        */
       String[] excludeName() default {};
    
    }
    

    其实现也是通过类似@Import的方式注入AutoConfigurationImportSelector类,并借助该类将所有符合条件的Configuration注解修饰的配置类加载到Spring Boot容器中。从classpath中搜索所有的META-INF/spring.factories配置文件,将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration对应配置项通过反射的形式实例化为标注了@Configuration和javaconfig形式的IOC容器配置类,然后汇总为一个并加载到ioc容器中。

    @ComponentScan

    这个注解就不需要多介绍了吧,其作用自动扫描加载符合条件的bean。

    SpringApplication

    从项目的入口第一个碰到的就是SpringApplication类。

    
    @SpringBootApplication
    public class TutorialApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(TutorialApplication.class, args);
        }
    }
    

    进入该类的静态方法run,可以看到其在构造SpringApplication对象

    
    public static ConfigurableApplicationContext run(Class<?>[] primarySources,
          String[] args) {
       return new SpringApplication(primarySources).run(args);
    }
    

    进入SpringApplication构造方法,可以看到

    
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
       this.resourceLoader = resourceLoader;
       Assert.notNull(primarySources, "PrimarySources must not be null");
       this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
       this.webApplicationType = deduceWebApplicationType();
       setInitializers((Collection) getSpringFactoriesInstances(
             ApplicationContextInitializer.class));
       setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
       this.mainApplicationClass = deduceMainApplicationClass();
    }
    

    主要做了如下几件事:

    • 加载Source,这里只有只有Application

    • 推断WebApplicationType,该枚举有三种类型NONE、SERVLET、REACTIVE。

    • 设置初始化器变量setInitializers,初始化后得到6个初始化变量,这些类在上面提到的spring.factories中可以找到

    • 设置监听器,与上面setInitializers实现类似,最终得到如下10个listeners

    • 最后推断带有main函数的所在类,即入口类,这里就是TutorialApplication
    
    private Class<?> deduceMainApplicationClass() {
       try {
          StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
          for (StackTraceElement stackTraceElement : stackTrace) {
             if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
             }
          }
       }
       catch (ClassNotFoundException ex) {
          // Swallow and continue
       }
       return null;
    }
    

    run方法

    看完SpringApplication是如何初始化后,我们来看下这个后面的run方法具体做了哪些事。

    
    public ConfigurableApplicationContext run(String... args) {
       StopWatch stopWatch = new StopWatch();
       stopWatch.start();
       ConfigurableApplicationContext context = null;
       Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
       configureHeadlessProperty();
       SpringApplicationRunListeners listeners = getRunListeners(args);
       listeners.starting();
       try {
          ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
          ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
          configureIgnoreBeanInfo(environment);
          Banner printedBanner = printBanner(environment);
          context = createApplicationContext();
          exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
          prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
          refreshContext(context);
          afterRefresh(context, applicationArguments);
          stopWatch.stop();
          if (this.logStartupInfo) {
             new StartupInfoLogger(this.mainApplicationClass)
                   .logStarted(getApplicationLog(), stopWatch);
          }
          listeners.started(context);
          callRunners(context, applicationArguments);
       }
       catch (Throwable ex) {
          handleRunFailure(context, ex, exceptionReporters, listeners);
          throw new IllegalStateException(ex);
       }
    
       try {
          listeners.running(context);
       }
       catch (Throwable ex) {
          handleRunFailure(context, ex, exceptionReporters, null);
          throw new IllegalStateException(ex);
       }
       return context;
    }
    
    • StopWatch,这是一个spring-core中的工具类,用来给程序运行计时(对于经常遇到需要计算某方法或接口耗时的场景,这个比System.currentTimeMillis好用)

    • configureHeadlessProperty配置,设置系统属性 java.awt.headless,这里设置为 true,表示运行在服务器端,在没有显示器和鼠标键盘的模式下工作,模拟输入输出设备功能。

    • 遍历listeners并启动

    • 封装入参args为AppliationArguments对象

    • 打印banner(就是我们启动时看到的spring标识)

    • 后面就是初始化上下文并加载上下文,具体实现就不进去看了

    如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

  • 相关阅读:
    AMD64 Instruction-Level Debugging With dbx
    Solaris 10上安装Oracle 11g
    Dave-oracle
    SSD 下的 MySQL IO 优化
    vmware 网络工作方式
    PLSQL Developer 配置Oralce11g连接 转
    LINUX下的21个特殊符号 转
    linux 内核调试相关资料
    mysql 源代码编绎
    Windows Performance Toolkit
  • 原文地址:https://www.cnblogs.com/bigdataZJ/p/springboot-start.html
Copyright © 2011-2022 走看看