SpringBootApplication注解解析(流程分析)

本文深入解析了Spring Boot应用中的核心注解@SpringBootApplication的功能与实现机制,包括组件扫描、自动配置包路径设定及自动配置类加载过程。

SpringBootApplication 注解解析 version 2.0


1. @SpringBootApplication是什么

  • SpringBootApplication 注解表示此类springboot的启动类
    • 现在感觉这句话很怪,我觉得表示是springboot的启动类的话,应该是由main函数所在的位置决定
    • 此注解倒是可以表示此类是spirngboot的启动注解类,但是你具体指明那个类作为启动注解类也不是由这个决定的,而且在main函数中传递的第一个参数决定的
    • 目前觉得sprngbootApplication标注的位置能表示的只有在默认不加其他注解的情况下,将标注在此类的所在路径作为默认的扫描路径

2. @SpringBootApplicaiton注解详细


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

2.1 @SpringBootConfiguration

  • @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuraion
    
  • 表示这个一个SpringBoot的配置类, 可以理解为这是SpringBoot的配置类的注解, 和Spring的@Configuration一样, 其内部就是一个@Configuration


2.2 @SpringBootApplication中的@ComponentScan的作用

  • @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    
  • @ComponentScan注解是什么

    • 用于扫描注解使用,提供一系列的可配置参数,用于指定扫描路径
  • SpringBootApplication注解中没有指定basePackages,所以其默认的basePackages是此注解所在类的包

  • 另外此注解中还使用excludeFilters,其含义是排除指定过滤方式的组件,其使用了TypeFilter接口定义了二个Filter,改写其内部的match方法用于拦截组件的注入

    • TypeExcludeFilter.class
      • 此Filter用于获取所有的继承TypeExcludeFilter类的类,调用他们的match方法
      • 简单的说就是TypeExcludeFilter类其实现了TypeFilter接口,做出了一个新的类型(用于过滤继承了TypeExcludeFilter类的过滤器),从而无需使用@ComponentScan注解自己定义过滤器,可以直接写一个过滤器加入到容器即可完成指定类型的过滤
    • AutoConfigurationExcludeFilter.class
      • 此过滤器是用于过滤所有的自动配置类
      • 为什么要过滤所有的自动配置类呢,因为在另一个注解@EnableAutoConfiguration中已经将其加入到了容器

@ComponentScan 是怎么生效的呢?

  • 以下提供几个断点,用于自己调试用
    • SpringApplication.class#ConfigurableApplicationContext(方法)#prepareContext(方法)
      • 此方法会根据你启动类传入的第一个参数(也就是标有注解的类),将此类加入到spring ioc 容器中去
      • 查看此方法的前后变化可以看context中beanfacory的beandifinitionmap的个数是不是变了,多了一个你标有注解的那个类
    • SpringApplication.class#ConfigurableApplicationContext(方法)#refreshContext(方法)
      • 此方法会解析你的@springApplication注解,然后实例化通过此注解引入的bean, 此方法中会调用各种的后置处理器等
    • AbstractApplicationContext.class#refresh(方法)#invokeBeanFactoryPostProcess(方法)
      • 此方法会创建bean工厂后置处理器,用于解析你的@springbootApplication注解,会将注解等都转化为ioc容器中的bean定义,以便在后面实例化他们
      • 在此方法里面会实例化一个 ConfigurationClassPostProcessor ,此类用于是配置类的后置处理器,就在此方法中会解析我们的注解
    • ConfigurationClassPostProcessor.class#processConfigBeanDefinitions(方法)#parser.parser(方法)
      • 此方法就会具体调用ConfigurationClassParser.class来解析
    • ConfigurationClassParser.class#doProcessConfigurationClass(方法)
      • 此方法就是解析的具体逻辑了,根据你标注的注解分别的解析他们,例如就有@ComponentScan注解,然后又会调用对应的注解解析器来解析他们
    • ComponentScanAnnotationParser.class#parser(方法)
      • 在这个方法里面你就可以看到她是如何获取注解的参数内容的,以及没有参数时,basePackage是此类的位置所在的包,然后他们就会扫描,扫描完就会将他们加入到容器中

2.3 @SpringBootApplication 中的 @EnableAutoConfiguration的作用

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)

@Import(AutoConfigurationImportSelector.class)

  • AutoConfigurationImportSelector.class 实现了ImportSelector接口(这是使用@Import导入组件的方式之一),此接口用于批量导入一些组件,其内部有一个方法

  • @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
       if (!isEnabled(annotationMetadata)) {
          return NO_IMPORTS;
       }
       AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
       return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    
  • selectImports方法中返回的全类名会被导入容器

  • 看看其会导入哪些组件吧?

    • getAutoConfigurationEntry 获取自动配置类

    • protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
         if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
         }
         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 = getConfigurationClassFilter().filter(configurations);
         fireAutoConfigurationImportEvents(configurations, exclusions);
         return new AutoConfigurationEntry(configurations, exclusions);
      }
      
    • 接着进入getCandidateConfigurations 内部

     List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
    
    • 此代码就是用于获取spring.factories中自动配置类

@import 注解是怎么生效的呢?

  • 以下提供几个断点,用于自己调试用
    • SpringApplication.class#ConfigurableApplicationContext(方法)#prepareContext(方法)
      • 此方法会根据你启动类传入的第一个参数(也就是标有注解的类),将此类加入到spring ioc 容器中去
      • 查看此方法的前后变化可以看context中beanfacory的beandifinitionmap的个数是不是变了,多了一个你标有注解的那个类
    • SpringApplication.class#ConfigurableApplicationContext(方法)#refreshContext(方法)
      • 此方法会解析你的@springApplication注解,然后实例化通过此注解引入的bean, 此方法中会调用各种的后置处理器等
    • AbstractApplicationContext.class#refresh(方法)#invokeBeanFactoryPostProcess(方法)
      • 此方法会创建bean工厂后置处理器,用于解析你的@springbootApplication注解,会将注解等都转化为ioc容器中的bean定义,以便在后面实例化他们
      • 在此方法里面会实例化一个 ConfigurationClassPostProcessor ,此类用于是配置类的后置处理器,就在此方法中会解析我们的注解
    • ConfigurationClassPostProcessor.class#processConfigBeanDefinitions(方法)#parser.parser(方法)
      • 此方法就会具体调用ConfigurationClassParser.class来解析
    • ConfigurationClassParser.class#doProcessConfigurationClass(方法)
      • 此方法就是解析的具体逻辑了,根据你标注的注解分别的解析他们,例如就有@Import注解
    • ConfigurationClassParser.class#doProcessConfigurationClass(方法)#processImports(方法)
      • 此方法内部会调用实现了ImportSelector.class的类,调用他们的selectImports()方法,而我们的AutoConfigurationImportSelector 就是实现了此方法的
      • 返回一系列类后又会进入递归的解析配置类的环节

2.4 @AutoConfigurationPackage

  • 看了很多的讲解,大多数说将扫描@SpringBootApplication标注下的包的所有组件加入到容器中, 这显然是错的,从上面的流程我们看到是由@ComponentScan注解执行的

  • 那么@AutoConfigurationPackage干了什么呢?

    • @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Inherited
      @Import(AutoConfigurationPackages.Registrar.class)
      
    • 其使用了@Import导入了组件,并且采用的是实现了ImportBeanDefinitionRegistrar接口的方式导入的,这种方式也被称为手动导入组件,此接口有个方法registerBeanDefinitions,在此内部手动导入我们要的组件

    • @Override
      public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
         register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
      }
      
      • 可以看到其获取了自己所在路径的包名
    • public static void register(BeanDefinitionRegistry registry, String... packageNames) {
         if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
         }
         else {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            registry.registerBeanDefinition(BEAN, beanDefinition);
         }
      }
      
      • 手动向容器中注入了一个名叫AutoConfigurationPackages的组件
    • 总结

      • @AutoConfigurationPackage注解会将注解所在的类的包名的信息加入到容器,并名叫AutoConfigurationPackages组件
      • 并没有加入其他的组件,她就是一个存包路径的一个bean,你可以将很多的bacePackage路径加入进去

那么@AutoConfigurationPackages加入到容器的这个组件是干嘛用的呢?

  • 既然此注解写着自动配置包的含义,那么其肯定和自动配置有关系咯

  • 找Mybatis自动配置的类,其中有一个注册组件的方法

      public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
          if (!AutoConfigurationPackages.has(this.beanFactory)) {
              MybatisAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
          } else {
              MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
              List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
              if (MybatisAutoConfiguration.logger.isDebugEnabled()) {
                  packages.forEach((pkg) -> {
                      MybatisAutoConfiguration.logger.debug("Using auto-configuration base package '{}'", pkg);
                  });
              }
      
              BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
              builder.addPropertyValue("processPropertyPlaceHolders", true);
              builder.addPropertyValue("annotationClass", Mapper.class);
              builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
              BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
              Stream.of(beanWrapper.getPropertyDescriptors()).filter((x) -> {
                  return x.getName().equals("lazyInitialization");
              }).findAny().ifPresent((x) -> {
                  builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
              });
              registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
          }
      }
    
    • 可以看到其在内部用了AutoConfigurationPackages.get(this.beanFactory);

    • public static List<String> get(BeanFactory beanFactory) {
         try {
            return beanFactory.getBean(BEAN, BasePackages.class).get();
         }
         catch (NoSuchBeanDefinitionException ex) {
            throw new IllegalStateException("Unable to retrieve @EnableAutoConfiguration base packages");
         }
      }
      
    • 在此内部就获取了容器中的AutoConfigurationPackages组件

    • 我们都知道Mybatis有一个自己的注解@Mapper

    • 其作用就用于给@Mapper扫描用的,AutoConfigurationPackages组件中加入的包名就是@Mapper扫描的路径

  • 总结

    • @ComponentScan是用于自己的组件的扫描, @AutoConfigurationPackages 所加入的包路径,就是自动配置类中组件要扫描的路径,一个内用,一个外用

总结

  • @SpringBootApplication的作用
    • 扫描自身包所有路径下的所有组件(@ComponentScan)
    • 配置自动配置的注解所要扫描的路径(@AutoConfigurationPackages ,AutoConfigurationPackages.Registrar.class)
    • 加载所有的自动配置类到容器(AutoConfigurationImportSelector.class)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值