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中已经将其加入到了容器
- TypeExcludeFilter.class
@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是此类的位置所在的包,然后他们就会扫描,扫描完就会将他们加入到容器中
- SpringApplication.class#ConfigurableApplicationContext(方法)#prepareContext(方法)
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 就是实现了此方法的
- 返回一系列类后又会进入递归的解析配置类的环节
- SpringApplication.class#ConfigurableApplicationContext(方法)#prepareContext(方法)
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)
本文深入解析了Spring Boot应用中的核心注解@SpringBootApplication的功能与实现机制,包括组件扫描、自动配置包路径设定及自动配置类加载过程。
953

被折叠的 条评论
为什么被折叠?



