面试官:说一说 Spring Boot 自动配置原理吧,我懵逼了..

01“可爱”的面试官”

       我相信在看到篇文章的小伙伴们都有过比较丰富的面试经验了,现在的面试都很卷,面试官会的你要会,他不会的还要求你会,有些面试官问的问题特别开放,甚至他自己都不知道啥标准答案是啥,我就被问过:“kafka为啥这么快?”,“说说spring吧”,“说说springboot吧”,每当我听见这种问题时候我内心会出现一万匹🦙在奔腾,在咆哮,好想当面怼一句说你da ye呀!我从哪里说起,我从宇宙大爆炸开始说吗?”,不过对于打工人来说我们是弱势啊,怎么办?我们只能比他们更卷才能有卷他的机会。

02 我们说说springBoot吧

       “称赞”完面试官后,我们也该自己反思一下,我们用了这么长时间的SpringBoot,真的了解里面的原理吗?我们可以说得清里面的原理吗?遇到棘手问题我们能够解决吗?我写这篇文章并不仅仅是为了面试,更是为了我们能够深入了解这里面的设计的精髓,还能够在遇到问题时不需要依赖“百度”或“google”,这并不是为了显示出我们解决问题很牛逼,而是网上的文章千篇一律,都是一个模子出来的,大部分都不能解决你的问题,真的很浪费你的时间。

      SpringBoot是什么呢?总体来说提供了以下几大功能:

  •  组件的自动装配

  •  简化组件引入的配置复杂度

  •  提供独立运行jar包和容器

      据我个人的理解SpringBoot的核心就是自动装配,通过自动装配减少组件引入的配置量。spring通过定义一些自动装配的接口,就能让第三方轻松实现spring的自动装配功能。我们可以基于SpringBoot为每一个自定义组件提供一个starter,starter一般只是一个空项目,但是这个项目描述了本组件依赖的其他组件,如下图:

   

   

03Spring注解配置解析大法

       SpringBoot其实只是spring的一层“薄薄”的封装,底层还是依赖spring的大量接口,下面我们从springboot启动开始,看看是怎么一步步自动装配到容器中的。

        在Spring Context 初始化和启动过程中,会按序触发一大堆生命周期方法,其中一个非常重要的方法是postProcessBeanDefinitionRegistry,这个方法来源于接口BeanDefinitionRegistryPostProcessor,我们看下这个接口的注释:

/**
   * Modify the application context's internal bean definition registry after its
   * standard initialization. All regular bean definitions will have been loaded,
   * but no beans will have been instantiated yet. This allows for adding further
   * bean definitions before the next post-processing phase kicks in.
   * @param registry the bean definition registry used by the application context
   * @throws org.springframework.beans.BeansException in case of errors
   */
  void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

      这个生命周期方法允许在bean实例化之前修改内部的bean的定义,调用这个方法前所有的常规的bean定义都已经加载了,但是还没有实例化,这个方法也可以允许我们调用registry.registerBeanDefinition手动注册bean。如果大家对spring的生命周期有疑惑的地方可以参考我整理的一个思维导图,或许有些帮助(https://processon.com/view/61283b51e0b34d3550ed418f?fromnew=1,也可以直接关注公众号“戏说分布式技术”回复spring高清图获取导出的图片格式),小伙伴们点点赞,我可能过段时间可以把整个的spring生命周期方法拿出来写一篇文章

       实现 BeanDefinitionRegistryPostProcessor  接口的一个非常重要的类:ConfigurationClassPostProcessor,这个类是所有配置类的解析的入口,也是SpringBoot实现自动装配的入口,在spring的江湖中可谓是占据半壁江山。下面我们从容器创建开始说说这个类怎么在spring中发挥作用的。

       在spring的Context的创建过程中都直接或间接引用了这个类:

AnnotatedBeanDefinitionReader

/**
   * Create a new AnnotationConfigApplicationContext that needs to be populated
   * through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
   */
  public AnnotationConfigApplicationContext() {
    this.reader = new AnnotatedBeanDefinitionReader(this);
    this.scanner = new ClassPathBeanDefinitionScanner(this);
  }

       在AnnotationConfigApplicationContext实例化的时候,通过构造方法会创建AnnotatedBeanDefinitionReader的实例,实例化reader的时候会触发注册配置类解析器bean的注册动作(下面红色的代码)。

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    Assert.notNull(environment, "Environment must not be null");
    this.registry = registry;
    this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
    AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
  }

       这个方法会判断当前容器中是否已经注册了内部的配置文件解析器,如果没有则进行注册,Spring给这个bean取了个名字叫:org.springframework.context.annotation.internalConfigurationAnnotationProcessor

if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
      RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
      def.setSource(source);
      beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

     Spring容器在触发postProcessBeanDefinitionRegistry接口的时候会从容器中查询都有哪些bean实现了这个接口,然后把实现的bean统一收集起来提前实例化(特殊功能的bean要提前实例化),然后根据是否实现了PriorityOrdered接口 、是否实现了Ordered接口进行排序依次调用。ConfigurationClassPostProcessor会在该方法中处理带有@Configuration注解的配置类,首先处理的第一个类就是你的主类,即带有@SpringBootApplication注解的类,大家可以看下这个注解相当于@Configuration了,如果不明白去看看源码。扫描到你的主类后,会先处理这个类的内部类,如果内部类是配置类将该类放入栈中,递归处理,这里相当于深度优先了。

      那么什么样的类是配置类呢?    

      翻看下ConfigurationClassUtils代码  

/**
   * Check the given metadata for a full configuration class candidate
   * (i.e. a class annotated with {@code @Configuration}).
   * @param metadata the metadata of the annotated class
   * @return {@code true} if the given class is to be processed as a full
   * configuration class, including cross-method call interception
   */
  public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
    return metadata.isAnnotated(Configuration.class.getName());
  }

  /**
   * Check the given metadata for a lite configuration class candidate
   * (e.g. a class annotated with {@code @Component} or just having
   * {@code @Import} declarations or {@code @Bean methods}).
   * @param metadata the metadata of the annotated class
   * @return {@code true} if the given class is to be processed as a lite
   * configuration class, just registering it and scanning it for {@code @Bean} methods
   */
  public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
    // Do not consider an interface or an annotation...
    if (metadata.isInterface()) {
      return false;
    }

    // Any of the typical annotations found?
    for (String indicator : candidateIndicators) {
      if (metadata.isAnnotated(indicator)) {
        return true;
      }
    }

    // Finally, let's look for @Bean methods...
    try {
      return metadata.hasAnnotatedMethods(Bean.class.getName());
    }
    catch (Throwable ex) {
      if (logger.isDebugEnabled()) {
        logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
      }
      return false;
    }
  }

       成为配置类需要满足以下几个条件

  •    带有@Configuration注解的类  

  •    带有@Component   

  •    带有@ComponentScan   

  •    带有@Import   

  •    带有@ImportResource的  

  •    方法中带有@Bean注解

        处理配置文件内容的顺序为:

1)解析@PropertySources注解

2)处理ComponentScans和ComponentScan注解

      处理过程交给ComponentScanAnnotationParser#parse,parser会解析includeFilters excludeFilters、lazyInit、basePackages和basePackageClasses属性,根据配置的值includeFilters来过滤要解析的类,根据excludeFilters会排除要解析的类等,根据lazyInit来确定是否需要懒加载,basePackages的配置可以使用spring的表达式实现配文件等参数的读取,例如使用${XXX} ,多个表达式以 ,或;或 \t或\n分割,basePackageClasses需要直接指定class对象。这一步会将项目中的bean优先处理

3)处理@Import注解

       我们的入口类即带有@SpringBootApplication注解的类,会添加各种注解,如我们使用spring的异步处理会加上@EnableAsync,如果使用spring集成的kafka会加上注解@EnableKafka等。这些基本注解一般都会加上@Import注解用来导入组件的bean,在解析import注解前需要获取到被Import的类:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {

如EnableAsync注解会解析到import的AsyncConfigurationSelector类,这个过程也是递归处理,处理注解类上的注解。

 4)处理@Bean注解

 5)如果该配置类实现了含有default的方法的接口,并且标注了@Bean这里也会处理。

6)如果当前配置类的父类不为java.lang.Object会递归处理。

      以上的逻辑是spring解析配置的最核心的部分,所有的其他组件都是基于这个逻辑分支生根发芽,理解了这个你就掌握了spring配置解析的的半壁江山了。

04我们的主角“自动装配”
  

       我们在使用spring的组件时候会发现一些很牛逼的操作,比如我们使用spring-security组件的时候发现我仅仅是在pom中引入了啥都没有配置呢就发现已经生效了,这简直变态的令人发指。

       本人本着无神论的原则,坚持要看看这个 “变态的操作”是咋回事。

      于是,我经历过无数次的debug,终于在处理@Import注解的环节发现了一个类AutoConfigurationImportSelector。

       我们看看这个类是怎么来的:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

看代码我们发现这个selector是来自于EnableAutoConfiguration

@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 {}

EnableAutoConfiguration是来自于SpringBootApplication

       我们搞懂了它是怎么来的,接下来研究研究它是怎么工作的,在研究它的工作原理之前先介绍这两个文件:spring-autoconfigure-metadata.properties和spring.factories,几乎在每一个springboot组件的jar包中都会包含这两个文件。

     先看看spring-autoconfigure-metadata.properties文件

      这个文件的路径位于jar包中"META-INF/spring-autoconfigure-metadata.properties",结合AutoConfigurationImportSelector代码,可以看到该类通过AutoConfigurationMetadataLoader.loadMetadata方法加载类路径下所有jar中的spring-autoconfigure-metadata文件。

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
    }
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
        .loadMetadata(this.beanClassLoader);
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    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 StringUtils.toStringArray(configurations);
  }

   这个文件主要是配置配置类的加载条件和配置类的一些属性:

 以spring-boot-autoconfigure-2.0.9.RELEASE.jar中的部分配置为例

#Wed Apr 03 13:51:10 GMT 2019
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration.ConditionalOnClass=com.datastax.driver.core.Cluster,org.springframework.data.cassandra.core.ReactiveCassandraTemplate,reactor.core.publisher.Flux
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.AutoConfigureOrder=-2147483638
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration.AutoConfigureBefore=org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration

  •  配置某个类在某些类之后进行自动配置(下面的配置省略了包名)RestTemplateAutoConfiguratio.AutoConfigureAfter=HttpMessageConvertersAutoConfiguration,这个配置的含义是需要在HttpMessageConvertersAutoConfiguration类解析配置完成后再触发RestTemplateAutoConfiguratio类的自动配置功能。

  • 配置某个类在某个类之前启用自动装配(下面的配置省略了包名)EmbeddedMongoAutoConfiguration.AutoConfigureBefore=

    MongoAutoConfiguration。这个配置的含义是需要在EmbeddedMongoAutoConfiguration类解析配置前必须要触发MongoAutoConfiguration类的自动配置功能。

  • 配置某个类必须某个类存在的情况下才加载(下面的配置省略了包名)

CassandraReactiveDataAutoConfiguration.ConditionalOnClass=SpringLiquibase

这个配置的含义是必须存在类ReactiveCassandraTemplate和Flux的情况下才启动CassandraReactiveDataAutoConfiguration的自动配置。

  • 配置自动装配类的order优先级(同样省略了类的包名)WebMvcAutoConfiguration.AutoConfigureOrder=-2147483638

    上面标注中来的红色字体相当于配置的属性,是固定的。

     再来看看spring.factories.properties文件

还是以spring-boot-autoconfigure-2.0.9.RELEASE.jar中的部分配置为例

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration

# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer

# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider

这里面可配置的包含但不限于:

  • ApplicationContextInitializer:初始化器,spring的context刷新前调用

  • ApplicationListener:应用监听器, 监听spring的event

  • AutoConfigurationImportListener:监听AutoConfigurationImportEvent事件,即自动装配完成会触发该事件

  • AutoConfigurationImportFilter:用来处理配置类的过滤器

  • EnableAutoConfiguration:自动装配的配置类

  • EnvironmentPostProcessor:spring容器环境处理器,添加属性来源PropertySource

  • PropertySourceLoader:propertySource加载器

       还记得上面的AutoConfigurationImportSelector吗?这个类会读取上面的两个文件,我们看下源码:

       这段代码是读取文件“spring-autoconfigure-metadata.properties”

AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
        .loadMetadata(this.beanClassLoader);

这段代码是读取“spring.factories.properties”,这里我们只关心EnableAutoConfiguration的配置,

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
      AnnotationAttributes attributes) {
    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;
  }

        第一个文件是用来配置我们的类在什么情况下加载的,第二个文件中是我们需要自动装配的类的列表,我们需要根据第一个文件中的配置条件来过滤出第二个文件中不需要加载的配置类,这里面过滤是需要依赖一个filter,这个filter就是spring.factories.properties文件中的AutoConfigurationImportFilter即,在spring-boot-autoconfigure-2.0.9.RELEASE.jar中配置的是类OnClassCondition,这个类目前只判断ConditionalOnClass条件,即如果依赖的类不存在则过滤掉不处理。

private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
      AutoConfigurationMetadata autoConfigurationMetadata) {
    // Split the work and perform half in a background thread. Using a single
    // additional thread seems to offer the best performance. More threads make
    // things worse
    int split = autoConfigurationClasses.length / 2;
    OutcomesResolver firstHalfResolver = createOutcomesResolver(
        autoConfigurationClasses, 0, split, autoConfigurationMetadata);
    OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(
        autoConfigurationClasses, split, autoConfigurationClasses.length,
        autoConfigurationMetadata, this.beanClassLoader);
    ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
    ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
    ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
    System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
    System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
    return outcomes;
  }

        getOutcomes方法是收集带有OnClassCondition条件的配置,这里把待过滤的类也就是spring.factories.properties中获取的类分成两半,一半是用当前的主线程处理,另一半是开启一个子线程进行并行处理,所有的处理结果完成后,如果一个类没有依赖其他类 或者依赖的其他类存在,则进行下一步的解析(解析过程见上一个章节 03),否则跳过。

     到此为止,自动装配的基本逻辑都讲完了,至于springBoot的各种Condition注解大家可以自己看看或者如果真的有需要后续再开一篇专门讲讲condition的实现。

    

05小结

      本文主要讲解了spring注解类配置文件的解析过程,springBoot的自动装配是基于以上的基本解析过程进行的扩展,如果大家喜欢可以关注一下公众号“戏说分布式技术”,算是给我的一点鼓励吧!同时也非常愿意接受大家的批评,共同进步!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值