美文网首页
2021-02-28_SpringBoot源码学习笔记之启动流程

2021-02-28_SpringBoot源码学习笔记之启动流程

作者: kikop | 来源:发表于2021-03-02 06:41 被阅读0次

    SpringBoot源码学习笔记之启动流程分析

    1概述

    本文基于Spring Boot版本2.1.4(SringBoot最新版本2.4.2。(参考文档: https://docs.spring.io/spring-boot/docs/current/api/)

    2SpringBoot启动流程分析

    2.1分析入口

    @SpringBootApplication
    public class DemoApplication {
    
       public static void main(String[] args) {
          SpringApplication.run(DemoApplication.class, args);
       }
    
    }
    
    // spring-boot-2.1.4.RELEASE-sources.jar!\org\springframework\boot\SpringApplication.java
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
       this.resourceLoader = resourceLoader;
       Assert.notNull(primarySources, "PrimarySources must not be null");
       this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
       //1.选择ApplicationContentType容器类型
       this.webApplicationType = WebApplicationType.deduceFromClasspath();
       //2.初始化,得到 ApplicationContextInitializer.class的springFactories
       setInitializers((Collection) getSpringFactoriesInstances(
             ApplicationContextInitializer.class));
       //3.设置监听器 
       setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
       //4.设置主类(有main的那个类) 
       this.mainApplicationClass = deduceMainApplicationClass();
    }
    

    2.2容器选择deduceFromClasspath

    /**
     * An enumeration of possible types of web application.
     *
     * @author Andy Wilkinson
     * @author Brian Clozel
     * @since 2.0.0
     */
    public enum WebApplicationType {
    
       /**
        * The application should not run as a web application and should not start an
        * embedded web server.
        */
       NONE,
    
       /**
        * The application should run as a servlet-based web application and should start an
        * embedded servlet web server.
        */
       SERVLET,
    
       /**
        * The application should run as a reactive web application and should start an
        * embedded reactive web server.
        */
       REACTIVE;
    

    2.2setInitializers(ApplicationContextInitializer)

    0 = "org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer"
    1 = "org.springframework.boot.context.ContextIdApplicationContextInitializer"
    2 = "org.springframework.boot.context.config.DelegatingApplicationContextInitializer"
    3 = "org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer"
    4 = "org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer"
    5 = "org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener"
    

    2.3setListeners(ApplicationListener)

    0 = "org.springframework.boot.ClearCachesApplicationListener"
    1 = "org.springframework.boot.builder.ParentContextCloserApplicationListener"
    2 = "org.springframework.boot.context.FileEncodingApplicationListener"
    3 = "org.springframework.boot.context.config.AnsiOutputApplicationListener"
    4 = "org.springframework.boot.context.config.ConfigFileApplicationListener"
    5 = "org.springframework.boot.context.config.DelegatingApplicationListener"
    6 = "org.springframework.boot.context.logging.ClasspathLoggingApplicationListener"
    7 = "org.springframework.boot.context.logging.LoggingApplicationListener"
    8 = "org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener"
    9 = "org.springframework.boot.autoconfigure.BackgroundPreinitializer"
    

    2.5run

    /**
     * Run the Spring application, creating and refreshing a new
     * {@link ApplicationContext}.
     * @param args the application arguments (usually passed from a Java main method)
     * @return a running {@link ApplicationContext}
     */
    public ConfigurableApplicationContext run(String... args) {
       StopWatch stopWatch = new StopWatch();
       stopWatch.start();
       ConfigurableApplicationContext context = null;
       Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
       configureHeadlessProperty();
       // 1.获取 spring:SpringApplicationRunListener 
       SpringApplicationRunListeners listeners = getRunListeners(args);
       listeners.starting();
       try {
          ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
          ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
          configureIgnoreBeanInfo(environment);
          Banner printedBanner = printBanner(environment);
          // 2.这里涉及容器的创建、刷新等,类似于Spring
          // org.springframework.boot.web.servlet.context.
          //AnnotationConfigServletWebServerApplicationContext@25ce9dc4, started on Thu Jan 01 08:00:00 CST 1970 
          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;
    }
    

    3源码细节

    3.1getSpringFactoriesInstances

    // spring-boot-2.1.4.RELEASE-sources.jar!\org\springframework\boot\SpringApplication.java
    //根据 type和 classLoader得到springFactoriesInstances
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
          Class<?>[] parameterTypes, Object... args) {
       ClassLoader classLoader = getClassLoader();
       // Use names and ensure unique to protect against duplicates
       // 1.根据 ApplicationContextInitializer 
       Set<String> names = new LinkedHashSet<>(
             SpringFactoriesLoader.loadFactoryNames(type, classLoader));
       // 2.springFactories实例化
       List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
             classLoader, args, names);
       // 3.排序 
       AnnotationAwareOrderComparator.sort(instances);
       return instances;
    }
    
    // spring-core-5.1.6.RELEASE-sources.jar!\org\springframework\core\io\support\SpringFactoriesLoader.java
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
       // 1.得到 org.springframework.context.ApplicationContextInitializer:ConfigurableApplicationContext
       String factoryClassName = factoryClass.getName();
       return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
       // 0 = "org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer"
       // 1 = "org.springframework.boot.context.ContextIdApplicationContextInitializer"
       // 2 = "org.springframework.boot.context.config.DelegatingApplicationContextInitializer"
       // 3 = "org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer"
       // 4 = "org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer"
       // 5 = "org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener"
    }
    
    // spring-core-5.1.6.RELEASE-sources.jar!\org\springframework\core\io\support\SpringFactoriesLoader.java
    
        /**
         * The location to look for factories.
         * <p>Can be present in multiple JAR files.
         */
        public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
       // todo,cache数据来源 
       // 启动的时候会扫描所有jar包下META-INF/spring.factories这个文件。第二段代码的意思是将这些扫描到的文件转成Properties
       // 对象,后面两个核心代码的意思就是说将加载到的Properties对象放入到缓存中
        
       MultiValueMap<String, String> result = cache.get(classLoader);
       if (result != null) {
          return result;
       }
    
       try {
          // 加载路径       
          Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
          result = new LinkedMultiValueMap<>();
          while (urls.hasMoreElements()) {
             URL url = urls.nextElement();
             UrlResource resource = new UrlResource(url);
             Properties properties = PropertiesLoaderUtils.loadProperties(resource);
             for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryClassName = ((String) entry.getKey()).trim();
                for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                   result.add(factoryClassName, factoryName.trim());
                }
             }
          }
          cache.put(classLoader, result);
          return result;
       }
       catch (IOException ex) {
          throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
       }
    }
    

    3.2启动类上的注解

    /**
     * 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
    //1.@SpringBootConfiguration这个注解说明再点进去查看详情发现就是一个@Configuration注解,这说明启动类就是一个配置类。支持Spring以JavaConfig的形式启动。
    @SpringBootConfiguration
    //3.springboot的核心注解
    @EnableAutoConfiguration
    //2.组件扫描的意思,即默认扫描当前package以及其子包下面的spring的注解,例如:@Controller、@Service、@Component等等注解。
    @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 {};
    
    }
    

    3.2.1SpringBootConfiguration

    3.2.2ComponentScan

    3.2.3EnableAutoConfiguration

    该注解是springboot的核心注解,本质都是利用了spring framework的 import功能。

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    //1.
    @AutoConfigurationPackage
    //2.
    @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 {};
    
    }
    

    3.2.3.1AutoConfigurationPackage

    自动配置包,最终依赖的确实@Import这个注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    // 后置处理器,将BeanDef注入到ioc容器
    // 导入类型为:Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    
    }
    

    3.2.3.2AutoConfigurationImportSelector

    自动引入组件

    /**
     * {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
     * auto-configuration}. This class can also be subclassed if a custom variant of
     * {@link EnableAutoConfiguration @EnableAutoConfiguration} is needed.
     *
     * @author Phillip Webb
     * @author Andy Wilkinson
     * @author Stephane Nicoll
     * @author Madhura Bhave
     * @since 1.3.0
     * @see EnableAutoConfiguration
     */
    public class AutoConfigurationImportSelector
          implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
          BeanFactoryAware, EnvironmentAware, Ordered {
    
       private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
    
       private static final String[] NO_IMPORTS = {};
    
       private static final Log logger = LogFactory
             .getLog(AutoConfigurationImportSelector.class);
    
       private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
    
       private ConfigurableListableBeanFactory beanFactory;
    
       private Environment environment;
    
       private ClassLoader beanClassLoader;
    
       private ResourceLoader resourceLoader;
    
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
       if (!isEnabled(annotationMetadata)) {
          return NO_IMPORTS;
       }
       AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
             .loadMetadata(this.beanClassLoader);
       AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
             autoConfigurationMetadata, annotationMetadata);
       return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    
    final class AutoConfigurationMetadataLoader {
    
       protected static final String PATH = "META-INF/"
             + "spring-autoconfigure-metadata.properties";
    
       private AutoConfigurationMetadataLoader() {
       }
    
       public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
          return loadMetadata(classLoader, PATH);
       }
    
    // spring-boot-autoconfigure-2.1.4.RELEASE-sources.jar!/
    // org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java
    // 执行时机:refresh(AbstractApplicationContext.java):invokeBeanFactoryPostProcessors(beanFactory);
                    
    /*
    spring-boot-autoconfigure\2.1.4.RELEASE\spring-boot-autoconfigure-2.1.4.RELEASE.jar!\
    META-INF\spring.factories
    
    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
    */  
    
    /**
     * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
     * of the importing {@link Configuration @Configuration} class.
     * @param autoConfigurationMetadata the auto-configuration metadata
     * @param annotationMetadata the annotation metadata of the configuration class
     * @return the auto-configurations that should be imported
     */
    protected AutoConfigurationEntry getAutoConfigurationEntry(
          AutoConfigurationMetadata autoConfigurationMetadata,
          AnnotationMetadata annotationMetadata) {
       if (!isEnabled(annotationMetadata)) {
          return EMPTY_ENTRY;
       }
       AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 这就是一种自定义SPI的实现方式的功能
        // 获取所有jar中类型为:AutoConfiguration.class的子类,例如:
        // 0 = "org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration"
        // 1 = "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration"
        // 2 = "org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration"
       List<String> configurations = getCandidateConfigurations(annotationMetadata,
             attributes);
       configurations = removeDuplicates(configurations);
       Set<String> exclusions = getExclusions(annotationMetadata, attributes);
       checkExcludedClasses(configurations, exclusions);
       configurations.removeAll(exclusions);
       configurations = filter(configurations, autoConfigurationMetadata);
       fireAutoConfigurationImportEvents(configurations, exclusions);
       return new AutoConfigurationEntry(configurations, exclusions);
    }
    
    /**
     * Return the auto-configuration class names that should be considered. By default
     * this method will load candidates using {@link SpringFactoriesLoader} with
     * {@link #getSpringFactoriesLoaderFactoryClass()}.
     * @param metadata the source metadata
     * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
     * attributes}
     * @return a list of candidate configurations
     */
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
          AnnotationAttributes attributes) {
       // 加载路径:META-INF/spring.factories 
       List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
             getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
       Assert.notEmpty(configurations,
             "No auto configuration classes found in META-INF/spring.factories. If you "
                   + "are using a custom packaging, make sure that file is correct.");
       return configurations;
    }
    
    @Configuration
    @AutoConfigureAfter({JmxAutoConfiguration.class})
    // 条件注解,加载SpringApplicationAdminJmxAutoConfiguration的bean条件
    @ConditionalOnProperty(
        prefix = "spring.application.admin",
        value = {"enabled"},
        havingValue = "true",
        matchIfMissing = false
    )
    public class SpringApplicationAdminJmxAutoConfiguration {
    
    @Configuration
    @ConditionalOnClass({ MBeanExporter.class })
    @ConditionalOnProperty(prefix = "spring.jmx", name = "enabled", havingValue = "true",
          matchIfMissing = true)
    public class JmxAutoConfiguration implements EnvironmentAware, BeanFactoryAware {
    

    4总结

    1. SpringBoot在启动的时候,首先会构造SpringApplication对象,获取并设置Initializers和Listeners,得到main启动类。

    2. 接着调用run()方法,run()方法执行容器刷新,

      2.1.刷新容器的时候,执行到容器的后置处理器invokeBeanFactoryPostProcessors方法时,会去解析启动类上的@SpringBootApplication复合注解。

      2.2.这个注解中有一个@EnableAutoConfiguration注解,表示开启自动配置功能(实际是import :AutoConfigurationPackages.Registrar.class)

      2.3.另外还有个@Import注解引入了一个AutoConfigurationImportSelector这个类(它去扫描我们所有jar包下的META-INF下的spring.factories 自动配置类文件,而从这个配置文件中取找key为EnableAutoConfiguration类的全路径的值,并加载到容器)

    3. 然后在根据这些配置类中的条件注解,来判断是否将这些配置类及关联的Bean在容器中进行实例化,

    4. (判断的条件:主要是判断项目是否有相关jar包或工程是否引入了相关的bean)。

    参考

    1.springboot启动时的一个自动装配过程

    https://www.jianshu.com/p/c50b58b03c60

    相关文章

      网友评论

          本文标题:2021-02-28_SpringBoot源码学习笔记之启动流程

          本文链接:https://www.haomeiwen.com/subject/ausofltx.html