zoukankan      html  css  js  c++  java
  • SpringBoot启动流程分析(五):SpringBoot自动装配原理实现

    SpringBoot系列文章简介

    SpringBoot源码阅读辅助篇:

      Spring IoC容器与应用上下文的设计与实现

    SpringBoot启动流程源码分析:

    1. SpringBoot启动流程分析(一):SpringApplication类初始化过程
    2. SpringBoot启动流程分析(二):SpringApplication的run方法
    3. SpringBoot启动流程分析(三):SpringApplication的run方法之prepareContext()方法
    4. SpringBoot启动流程分析(四):IoC容器的初始化过程
    5. SpringBoot启动流程分析(五):SpringBoot自动装配原理实现
    6. SpringBoot启动流程分析(六):IoC容器依赖注入

    笔者注释版Spring Framework与SpringBoot源码git传送门:请不要吝啬小星星

    1. spring-framework-5.0.8.RELEASE
    2. SpringBoot-2.0.4.RELEASE

    自定义Starter:

      SpringBoot应用篇(一):自定义starter

    一、前言

      上一篇文章,通过分析refresh()方法中的invokeBeanFactoryPostProcessors()方法,分析了IoC容器的初始化过程,这一节从代码上如下所示,接上一节ConfigurationClassParser类中的parse()方法,接着分析SpringBoot的自动装配原理。

     1 // ConfigurationClassParser类
     2 public void parse(Set<BeanDefinitionHolder> configCandidates) {
     3     this.deferredImportSelectors = new LinkedList<>();
     4     for (BeanDefinitionHolder holder : configCandidates) {
     5         BeanDefinition bd = holder.getBeanDefinition();
     6         try {
     7             // 如果是SpringBoot项目进来的,bd其实就是前面主类封装成的 AnnotatedGenericBeanDefinition(AnnotatedBeanDefinition接口的实现类)
     8             if (bd instanceof AnnotatedBeanDefinition) {
     9                 parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
    10             } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
    11                 parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
    12             } else {
    13                 parse(bd.getBeanClassName(), holder.getBeanName());
    14             }
    15         } catch (BeanDefinitionStoreException ex) {
    16             throw ex;
    17         } catch (Throwable ex) {
    18             throw new BeanDefinitionStoreException(
    19                     "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
    20         }
    21     }
    22     // 加载默认的配置---》(对springboot项目来说这里就是自动装配的入口了)
    23     processDeferredImportSelectors();
    24 }

    二、SpringBoot自动装配原理。

    2.1、@SpringBootApplication注解

      对这个注解详细大家一定非常熟悉了。再来好好看看这个注解。

     1 @Target(ElementType.TYPE)
     2 @Retention(RetentionPolicy.RUNTIME)
     3 @Documented
     4 @Inherited
     5 @SpringBootConfiguration
     6 @EnableAutoConfiguration
     7 @ComponentScan(excludeFilters = {
     8         @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
     9         @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    10 public @interface SpringBootApplication {
    11     ...
    12 }

      接着看@EnableAutoConfiguration

    1 @Target(ElementType.TYPE)
    2 @Retention(RetentionPolicy.RUNTIME)
    3 @Documented
    4 @Inherited
    5 @AutoConfigurationPackage
    6 @Import(AutoConfigurationImportSelector.class)
    7 public @interface EnableAutoConfiguration {
    8     ...
    9 }

      OK,看到@Import(AutoConfigurationImportSelector.class)导入了一个重要的类AutoConfigurationImportSelector。

    2.2、AutoConfigurationImportSelector

     1 // AutoConfigurationImportSelector类
     2 //自动装配
     3 @Override
     4 public String[] selectImports(AnnotationMetadata annotationMetadata) {
     5     if (!isEnabled(annotationMetadata)) {
     6         return NO_IMPORTS;
     7     }
     8     AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
     9             .loadMetadata(this.beanClassLoader);
    10     AnnotationAttributes attributes = getAttributes(annotationMetadata);
    11     //获取所有的自动配置类(META-INF/spring.factories中配置的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类)
    12     List<String> configurations = getCandidateConfigurations(annotationMetadata,
    13             attributes);
    14     configurations = removeDuplicates(configurations);
    15     //需要排除的自动装配类(springboot的主类上 @SpringBootApplication(exclude = {com.demo.starter.config.DemoConfig.class})指定的排除的自动装配类)
    16     Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    17     checkExcludedClasses(configurations, exclusions);
    18     //将需要排除的类从 configurations remove掉
    19     configurations.removeAll(exclusions);
    20     configurations = filter(configurations, autoConfigurationMetadata);
    21     fireAutoConfigurationImportEvents(configurations, exclusions);
    22     return StringUtils.toStringArray(configurations);
    23 }

      至于怎么从章节一中提到的ConfigurationClassParser类中的parse()===>processDeferredImportSelectors()==>AutoConfigurationImportSelector#selectImports(),篇幅有限不做过多介绍。

       List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); 

      我们来看一下getCandidateConfigurations()方法是怎么拿到这些自动配置类的。

    // AutoConfigurationImportSelector类
    1
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, 2 AnnotationAttributes attributes) { 3 List<String> configurations = SpringFactoriesLoader.loadFactoryNames( 4 getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); 5 Assert.notEmpty(configurations, 6 "No auto configuration classes found in META-INF/spring.factories. If you " 7 + "are using a custom packaging, make sure that file is correct."); 8 return configurations; 9 }

      是不是又看到一个十分熟悉的方法loadFactoryNames(),没错,其实我们在分析SpringBoot启动流程的第一篇文章的时候,就已经分析了,SpringBoot是如何从META-INF/spring.factories中加载指定key的value的。ok,我们在这里再次回顾一遍。

      看看loadFactoryNames()方法

    // SpringFactoriesLoader类
    1
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { 2 String factoryClassName = factoryClass.getName(); 3 return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); 4 }

      debug,看看要从META-INF/spring.factories中加载的类的key,如下图所示:org.springframework.boot.autoconfigure.EnableAutoConfiguration

      回到selectImports()方法,debug,跳过List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);看一下configurations

      竟然有110个,那这些类都在哪里呢?看spring-boot-autoconfigure(当然在SpringBoot的工程中,也不止这一个依赖包中存在该配置文件)工程下的META-INF/spring.factories,我们能看到org.springframework.boot.autoconfigure.EnableAutoConfiguration定义了一大堆。

      其中还有一个com.demo.starter.config.DemoConfig是我自定义的starter。如下所示,我在测试工程中添加了自定义starter的依赖,所以SpringBoot就能扫描到。

    1 <dependency>
    2     <groupId>com.demo</groupId>
    3     <artifactId>demo-spring-boot-starter</artifactId>
    4     <version>0.0.1-RELEASE</version>
    5 </dependency>

       继续看Set<String> exclusions = getExclusions(annotationMetadata, attributes);方法,该方法是排除主类上@SpringBootApplication注解上排除的自动装配的类。比如我们在该注解上排除我们自定义starter的自动装配的类,@SpringBootApplication(exclude = {com.demo.starter.config.DemoConfig.class})(当然也可以用excludeName进行排除),那么在后面的configurations.removeAll(exclusions);方法中将会删除我们的com.demo.starter.config.DemoConfig.class。

      configurations = filter(configurations, autoConfigurationMetadata);该行代码将会过滤掉不需要装配的类。过滤的逻辑有很多,比如我们常用的@ConditionXXX注解。如下所示:

     1 @ConditionalOnBean:容器中有指定的Bean 
     2 @ConditionalOnClass:当类路径下有指定的类
     3 @ConditionalOnExpression:基于SpEL表达式作为判断条件  
     4 @ConditionalOnJava:基于JVM版本作为判断条件  
     5 @ConditionalOnJndi:在JNDI存在的条件下查找指定的位置  
     6 @ConditionalOnMissingBean:当容器中没有指定Bean的情况下  
     7 @ConditionalOnMissingClass:当类路径下没有指定的类
     8 @ConditionalOnNotWebApplication:当前项目不是Web项目
     9 @ConditionalOnProperty:配置文件中指定的属性是否有指定的值  
    10 @ConditionalOnResource:类路径下是否有指定的资源  
    11 @ConditionalOnSingleCandidate:当指定Bean在容器中只有一个,或者虽然有多个但是指定首选Bean
    12 @ConditionalOnWebApplication:当前项目是Web项目的条件下

       至于如何将这些类解析成BeanDefinition并注册进beanDefinition中的,和上一节讲的过程是一样的,不再赘述了。

      debug,跳过refresh()方法中的invokeBeanFactoryPostProcessors(beanFactory);方法。如下图所示,最终在beanFactory的BeanDefinitionMap中找到了自定义starter中的自动装配的类。

      综合本文和上一篇博文我们详细的梳理了IoC容器的初始化过程,到此IoC容器的初始化过程就结束了。

      

      原创不易,转载请注明出处。

      如有错误的地方还请留言指正。

  • 相关阅读:
    hive数据类型
    hive类型转化错误,会错误提示指定分区参数
    [ORACLE]java.sql.SQLRecoverableException: IO Error: Connection rese
    oracle start with connect by prior 递归查询用法
    redis参数改进建议
    Android Studio教程--从Github 下载一个Repository
    Android Studio教程--Android项目分享到Github
    Android Studio Gradle Build Running 特别慢的问题探讨
    The Genymotion Virtual device could not obtain an IP address解决办法
    Android Studio教程--给Android Studio安装Genymotion插件
  • 原文地址:https://www.cnblogs.com/hello-shf/p/11057861.html
Copyright © 2011-2022 走看看