文章目录
- 1、概述
- 2、@SpringBootApplication
- 1)注解语义
- 2)@SpringBootApplication属性别名
- 3)@SpringBootApplication标注非引导类
- 3、@EnableAutoConfiguration(自动配置的过程)
- 1)@AutoConfigurationPackage
- `AutoConfigurationPackages.Registrar`类:
- 2)@Import(AutoConfigurationImportSelector.class)
- 0> AutoConfigurationImportSelector读取自动装配class的流程
- 1、自动配置按需加载的原理
- 2、容错兼容
- 3、 用户配置优先(也可以说是外部配置项修改组件行为)
- 4、 查看自动配置情况
- 4、自动装配生命周期
- 1)排序自动装配组件
- 5、自定义一个自动装配类(starter)
- 1)命名规则
- 2)demo
- 6、如何使自动装配失效?
- 7、后续
1、概述
-
SpringBoot自动配置不是配置XML,自动配置的是一些Java Config类;也就是说Spring Boot自动装配的对象是Spring Bean。
-
SpringBoot自动配置的类内容取决于我们在应用的Class Path下添加的JAR文件依赖。
-
自动装配类能够打包到外部的JAR文件中,并且将被SpringBoot装载;
-
自动装配也能被关联到“starter"中,这些”starter“提供自动装配的代码及关联的依赖;
-
spring-boot-autoconfigure
是Spring Boot核心模块(JAR),其中提供了大量的内建自动装配@Configuration类,它们统一存放在org.springframework.boot.autoconfigure包或子包下; -
自动装配的类均配置在
META-INF/spring.factories
(spring-boot-autoconfigure)资源中;META-INF/spring.factories
属于Java Properties文件格式; -
其中激活自动装配注解@EnableAutoConfiguration充当该Properties的Key,而自动装配类为Value。
复制代码1
2
3
4
5
6
7
8# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration= org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration, org.springframework.boot.autoconfigure.aop.AopAutoConfiguration, org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration, org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration, org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,
2、@SpringBootApplication
1)注解语义
SpringBoot的自动装配机制由@SpringBootApplication
注解体现,@SpringBootApplication内部包含三个注解:
-
@SpringBootConfiguration
(就是一个@Configuration配置类)、 -
@ComponentScan(basePackages="")
(激活@Component的扫描,扫描时排除指定类)-
是一种综合性技术手段;它重新深度整合Spring注解编程模型,@Enable模块驱动及条件装配等Spring framework原生特性
-
@ComponentScan中排除了
AutoConfigurationExcludeFilter
类(即:永远排除其他同时标注@Configuration和@EnableAutoConfiguration的类)
-
-
@EnableAutoConfiguration
(核心所在,负责激活Spring Boot自动装配机制) -
即:@SpringBootApplication注解等同于@Configuration + @EnableAutoConfiguration + @ComponScan注解。
**然而:**我们可以选择不使用@SpringBootApplication,只要将注解@EnableAutoConfiguration 标注在一个@Configuration类上,即可实现自动装配。
- 但是它能减少多注解所带来的配置成本;比如
@ComponentScan
的basePackages
属性被@SpringBootApplication
的scanBasePackages
属性做别名。
2)@SpringBootApplication属性别名
@AliasFor
注解用于桥接其他注解的属性,其能够将一个或多个注解的属性“别名”在某个注解中。
1
2
3
4
5
6
7
8
9public @interface SpringBootApplication { .... @AliasFor( annotation = ComponentScan.class, attribute = "basePackageClasses" ) Class<?>[] scanBasePackageClasses() default {}; }
1
2@SpringBootApplication(scanBasePackages = {"com.saint.spring.controller"})
@SpringBootApplication的scanBasePackages属性将 @ComponentScan扫描的位置重定向到我们指定的位置;即@SpringBootApplication利用@AliasFor注解别名了@CompoentScan注解的basePackages()属性。
3)@SpringBootApplication标注非引导类
可以将@SpringBootApplication注解加载任一类ClassA上(注意scanBasePackages属性的值),然后在引导类的main()
方法中run(ClassA.class, args)
。
- @SpringBootApplication并非限定标注于引导类。
- @SpringBootApplication和@EnableAutoConfiguration均能激活自动装配的特性。但对于被标注类的Bean类型则存在差异。
@SpringBootApplication“继承”了@Configuration 拥有CGLIB提升特性:
- 正常@Bean的声明方式为“轻量模式”(Lite)
- 在@Configuration下声明的@Bean则属于”完全模式“(Full),会执行CGLIB提升的操作。即:@Configuration类有CGLIB提升。
关于CGLIb提升:
- 在java配置类中加@Configuration,下面的声明@bean的方法,就只会被调一次,也就是初始化的时候,哪怕是下面的方法直接互相引用,返回的new的对象的构造方法也只会调一次;
- 而如果不加@Configuration,那么下面的方法如果有相互调用,那么返回的new的对象的构造方法就会被调多次;
3、@EnableAutoConfiguration(自动配置的过程)
@EnableAutoConfiguration又由两部分组成,分别为:@AutoConfigurationPackage
(负责加载默认包加载/扫描路径)、@Import(AutoConfigurationImportSelector.class)
(自动装配后续实现);
1)@AutoConfigurationPackage
@AutoConfigurationPackage注解中通过@Import注解导入了AutoConfigurationPackages.Registrar
类;
1
2
3
4
5
6
7
8
9@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { .... }
AutoConfigurationPackages.Registrar
类:
AutoConfigurationPackages.Registrar
类的registerBeanDefinitions()
方法中:
- 入参metadata为启动类全路径名;例如:com.saint.StartApplication;
new PackageImports(metadata).getPackageNames()
表示默认的包加载/扫描路径,即启动类所在的目录;例如:com.saint
1
2
3
4
5
6
7
8
9
10
11
12
13
14static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImports(metadata)); } }
注意:如果我们依赖jar包中类所文件目录层级和当前程序扫描的包路径有交叉(比如:都已com.group.demo开头)是可以不用在spring.factories
文件中写EnableAutoConfiguration = 某某类的。
2)@Import(AutoConfigurationImportSelector.class)
主要是SpringBoot自动配置的一些特征:
- 通过@Import注入
AutoConfigurationImportSelector
类,AutoConfigurationImportSelector
实现了ImportSelector接口。 selectImports()
方法会去扫描META-INF/Spring.factories
的配置文件,所有组件自动装配均在其中实现;
0> AutoConfigurationImportSelector读取自动装配class的流程
-
在其
selectImports(AnnotationMetadata)
方法中调用自己的getAutoConfigurationEntry(AnnotationMetadata)
方法拿到所有自动配置的节点;这里分为六步; -
第一步,利用Spring Framework工厂机制的加载器
SpringFactoriesLoader
,通过SpringFactoriesLoader#loadFactoryNames(Class, ClassLoader)
方法读取所有META-INF/spring.factories
资源中@EnableAutoConfiguration
所关联的自动装配Class集合。 -
第二步,利用Set不可重复性对
自动装配Class集合
进行去重,因为自动装配组件存在重复定义的情况; -
第三步,读取当前配置类所标注的@EnableAutoConfiguration注解的属性exclude和excludeName,并与
spring.autoconfigure.exclude
配置属性的值 合并为自动装配class排除集合。 -
第四步,校验自动装配Class排除集合的合法性、并排除掉
自动装配Class排除集合
中的所有Class(不需要自动装配的Class)。 -
第五步,再次过滤候选自动装配Class集合中不符合条件装配的Class成员;
-
最后一步,触发自动装配的导入事件
AutoConfigurationImportEvent
,发布事件到到Listener(Listener中会绑定BeanFactory,将通过过滤的自动装配候选类、已经排除的自动装配类信息记录到条件评估报告器ConditionEvaluationReport
中)。我们在application.yml文件中指定
debug: true
来查看自动装配情况时,就是ConditionEvaluationReport输出的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } // 加载自动装配的元信息 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } // 1. 获取@EnableAutoConfiguration标注类的元信息 AnnotationAttributes attributes = getAttributes(annotationMetadata); // 2. 返回自动装配类的候选类名集合 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 3. 移除重复对象,因为 自动装配组件存在重复定义的情况 configurations = removeDuplicates(configurations); // 4. 自动装配组件的排除名单 Set<String> exclusions = getExclusions(annotationMetadata, attributes); // 5.1. 检查自动装配Class排除集合的合法性 checkExcludedClasses(configurations, exclusions); // 5.2 排除掉不需要自动装配的Class configurations.removeAll(exclusions); // 6. 进一步过滤 configurations = getConfigurationClassFilter().filter(configurations); // 7. 触发自动装配的导入事件,事件包括候选的装配组件类名单和排除名单。 fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
我们先着重看一下getCandidateConfigurations()
是如何获取到所有的自动装配类的?
phase1> getCandidateConfigurations() --> 获取自动装配类:
获取所有META-INF/Spring.factories的配置文件,进而获取所有的自动配置类;
- 利用SpringFactoriesLoader#loadFactoryNames(Class, ClassLoader)方法,
SpringFactoriesLoader
是Spring Framework工厂机制的加载器;loadFactoryNames()原理如下:- 搜索指定ClassLoader下所有的META-INF/spring.fatories资源内容;
- 将搜索到的资源内容作为Properties文件读取,合并为一个Key为
接口的全类名
、Value为实现类全类名 列表
的Map,作为方法的返回值; - 最后从上一步返回的Map中查找并返回方法指定类型 对应的
实现类全类名列表
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16protected 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; } /** * SpringFactoriesLoader#loadFactoryNames() */ public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { // 先从缓存中获取 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()) { /** * 查找所有我们依赖的jar包,并找到对应有META-INF/spring.factories⽂件,然后获取⽂件中的内容 * * 第一次循环:file:/.../org/springframework/spring-beans/5.2.12.RELEASE/spring-beans-5.2.12.RELEASE.jar!/META-INF/spring.factories * 第二次循环:file:/.../org/springframework/boot/spring-boot/2.3.7.RELEASE/spring-boot-2.3.7.RELEASE.jar!/META-INF/spring.factories * 第三次循环:file:/../org/springframework/boot/spring-boot-autoconfigure/2.3.7.RELEASE/spring-boot-autoconfigure-2.3.7.RELEASE.jar!/META-INF/spring.factories */ URL url = urls.nextElement(); // 获取资源 UrlResource resource = new UrlResource(url); // 获取资源的内容 Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
-
第一次扫描:
-
第二次扫描:
-
第三次扫描:
SpringBoot扫描到META-INF/spring.factories⽂件之后,META-INF/spring.factories文件中的内容很多,比如spring-boot-autoconfigure-2.3.7.RELEASE.jar!/META-INF/spring.factories
中它的org.springframework.boot.autoconfigure.EnableAutoConfiguration
key对应很多的Value,我们如何知道哪个需要自动加载、哪个不需要?
1、自动配置按需加载的原理
比如:org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
1
2
3
4
5
6
7
8
9@Configuration(proxyBeanMethods = false) // 没有引入RabbitTemplate和Channel就不会初始化RabbitAutoConfiguration到IOC容器 @ConditionalOnClass({ RabbitTemplate.class, Channel.class }) @EnableConfigurationProperties(RabbitProperties.class) @Import(RabbitAnnotationDrivenConfiguration.class) public class RabbitAutoConfiguration { .... }
- 其中
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
便体现了SpringBoot自动配置的按需加载,即:只有RabbitTemplate.class和Channel.class都存在时,才会自动配置RabbitAutoConfiguration。 - 相关的注解还有很多,比如:@ConditionalOnMissingClass、@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnProperty
2、容错兼容
比如用户创建了一个名称不正确(不符合SpringBoot要求)的类型的Bean,SpringBoot会对其进行自动容错兼容。
比如自动装载Servlet时,org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
中上传文件的处理器MultipartResolver
,如果用户创建了一个名称不为multipartResolver
的MultipartResolver类,SpringBoot会自动将其名称转换为标准名称multipartResolver
:
1
2
3
4
5
6
7
8
9
10
11@Bean // 作用在当前方法上,要求IOC中必须存在MultipartResolver类型的Bean @ConditionalOnBean(MultipartResolver.class) // 要求IOC容器中不存在名称为multipartResolver的bean @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) public MultipartResolver multipartResolver(MultipartResolver resolver) { // Detect if the user has created a MultipartResolver but named it incorrectly // 调⽤者为IOC,resolver就是从IOC中获取到的类型为MultipartResolver的bean(是MultipartResolver类型的但是名称不是“multipartResolver”) return resolver; // 保存到IOC中的bean的名称是“multipartResolver” }
1
2public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
3、 用户配置优先(也可以说是外部配置项修改组件行为)
自定义配置优先级高于系统的默认配置。比如在WebMvcAutoConfiguration
类针对View进行解析的InternalResourceViewResolver
的prefix和suffix设置。
1
2
3
4
5
6
7
8
9@Bean // 如果容器中没有@ConditionalOnMissingBean名称为“defaultViewResolver”的InternalResourceViewResolver类型的Bean,则去创建(对于IOC容器而言,如果你没有创建,我帮你去创建;如果你创建了,那我就以你为主) public InternalResourceViewResolver defaultViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix(this.mvcProperties.getView().getPrefix()); resolver.setSuffix(this.mvcProperties.getView().getSuffix()); return resolver; }
比如,自定义配置:
1
2
3spring.mvc.view.prefix = "aaa" spring.mvc.view.suffix="html"
4、 查看自动配置情况
yaml文件中配置如下内容:
1
2debug=true
控制台输出:
1
2
3
4
5
6
7
8
9
10
11Negative matches: ----------------- ActiveMQAutoConfiguration: Did not match: - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition) AopAutoConfiguration.AspectJAutoProxyingConfiguration: Did not match: - @ConditionalOnClass did not find required class 'org.aspectj.weaver.Advice' (OnClassCondition)
phase last > fireAutoConfigurationImportEvents() --> 自动装配事件:
- 首先获取所有的自动装配事件监听器
AutoConfigurationImportListener
; AutoConfigurationImportListener
有别于传统的SpringApplicationListener
的实现。- ApplicationListener与Spring应用上下文ConfigurableApplicationContext紧密关联,监听Spring ApplicationContext。
- 而AutoConfigurationImportListener则是自定义的java EventListener实现,仅监听
AutoConfigurationImportEvent
,不过其实例同样可以被SpringFactoriedLoader
加载,也就是说SpringBoot框架层面为开发人员提供了扩展的途径; - 所以我们可以自定义
AutoConfigurationImportListener
的实现类;
- 执行所有的自动装配事件。
1
2
3
4
5
6
7
8
9
10
11
12
13private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) { // 1. 获取所有的自动装配事件监听器AutoConfigurationImportListener List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners(); if (!listeners.isEmpty()) { AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions); for (AutoConfigurationImportListener listener : listeners) { invokeAwareMethods(listener); // 2. 执行自动装配事件 listener.onAutoConfigurationImportEvent(event); } } }
自定义AutoConfigurationImportListener:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/** * 自定义监听器(监听autoImport) */ public class CustomAutoConfigurationImportListener implements AutoConfigurationImportListener { @Override public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) { // 获取当前ClassLoader ClassLoader classLoader = event.getClass().getClassLoader(); // 候选的自动装配Class名单 List<String> candidates = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, classLoader); // 实际的自动装配Class名单 List<String> configurations = event.getCandidateConfigurations(); // 排除的自动装配Class名单 Set<String> exclusions = event.getExclusions(); // 输出各自数量 System.out.printf("自动装配Class名单 - 候选数量: %d, 实际数量: %d, 排除数量: %s n", candidates.size(), configurations.size(), exclusions.size()); // 输出实际和排除的自动装配Class名单 System.out.println("实际的自动装配Class名单:"); event.getCandidateConfigurations().forEach(System.out::println); System.out.println("排除的自动装配Class名单:"); event.getExclusions().forEach(System.out::println); } }
META-INF/spring.factories中添加如下信息
1
2
3org.springframework.boot.autoconfigure.AutoConfigurationImportListener = com.saint.spring.autoconfigureImportlistener.CustomAutoConfigurationImportListener
1
2
3
4
5
6
7
8
9
10
11@EnableAutoConfiguration(exclude = SpringApplicationAdminJmxAutoConfiguration.class) public class EnableAutoConfigurationBootstrap { public static void main(String[] args) { new SpringApplicationBuilder(EnableAutoConfigurationBootstrap.class) .web(WebApplicationType.NONE) // 非Web应用 .run(args) // 运行 .close(); // 关闭当前上下文 } }
- 从数量上来看,META-INF/spring.factories资源配置的自动装配Class名单要远多于实际装载。
- 这是因为部分类被AutoConfigurationImportSelector#filter方法过滤掉了。
4、自动装配生命周期
自动装配类的@Order很大,也就是优先级极低。要等所有@Configuration class加载完,它才会加载。SpringBoot官网提供了两种方式对自动装配组件组件进行排序。
1)排序自动装配组件
两种自动装配组件的排序手段:
- 绝对自动装配顺序 --> @AutoConfigureOrder
- 与Spring Framework @Order注解的语义相同;
- 相对自动装配顺序 --> @AutoConfigureBefore 和 @AutoConfigureAfter(推荐使用)。
一般不建议对自动装配组件的顺序进行排序,因为这要求开发人员要了解所有的自动装配组件的顺序,显然不太现实。
我们可以了解大体的排序规则;
- 如果自动装配Class集合中未包含@AutoConfigureOrder等顺序注解,则他们按照字母顺序依次加载。
- 如果存在,当
AutoConfigurationClasses
与AutoConfigurationClass
建立映射关系后,具体的@AutoConfigureOrder排序规则由AutoConfigurationClass#getOrder()方法决定:
- 首先读取自动装配类的@AutoConfigureOrder的配置值,如果不存在,则使用默认值(很小);
- 接着对@AutoConfigureBefore 和@AutoConfigureAfter进行排序。
- 因为兼容性问题,建议使用@AutoConfigureBefore或@AutoConfigureAfter的name()属性方法。
自动装配Class:
- META-INF/spring-autoconfigure-metadata.properties是自动装配Class预处理元信息配置的资源。
- 当该资源文件存在自动装配Class的注解元信息配置时,自动装配Class无须ClassLoader加载,即可得到所需的元信息,减少了运行时的计算消耗。
如果自动装配Class集合中未包含@AutoConfigureOrder等顺序注解,则他们是按照字母顺序依次加载的。
5、自定义一个自动装配类(starter)
1)命名规则
-
自动装配Class类 --> XxxAutoConfiguration
-
自动装配package命名模式 --> r o o t − p a c k a g e . a u t o c o n f i g u r e . {root-package}.autoconfigure. root−package.autoconfigure.{module-package}
复制代码1
2org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
-
starter命名模式 --> ${module}-spring-boot-starter
2)demo
见博文:SprinBoot自定义自动装配类与xxx-spring-boot-starter
6、如何使自动装配失效?
SpringBoot提供了两种方式:
- 代码配置方式
- 配置类型安全的属性方法:@EnableAutoConfiguration.exclude()
- 配置排除类名的属性方法:@EnableAutoConfiguration.excludeName()
- 外部化配置方式
- 配置属性:spring.autoconfigure.exclude
7、后续
对于@EnableAutoConfiguration注解中@Import注解导入的AutoConfigurationImportSelector
类何时加载,即何时加载自动装配的类?,见博文:《SpringBoot启动流程五》:你确定你真的知道SpringBoot自动装配原理吗(两万字图文源码分析)
自动装配入口的代码执行流程图:
最后
以上就是个性胡萝卜最近收集整理的关于《SpringBoot系列九》:SpringBoot自动装配机制原理的全部内容,更多相关《SpringBoot系列九》内容请搜索靠谱客的其他文章。
发表评论 取消回复