1.概述
SpringBoot的自动装配,减少了原先需要编写xml配置文件或java配置类。本文将分析SpringBoot自动装配的原理(版本是2.3.5)。从注解的作用到源码分析,再到自定义SpringBoot启动器。
2.自动装配原理分析
自动装配的实现和这个注解有密切的关联。该注解是个复合注解,里面包含了三个关键的注解
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
本文先介绍@SpringBootConfiguration和@EnableAutoConfiguration两个注解,@ComponentScan注解放到下一篇博客。
重点分析@EnableAutoConfiguration注解的作用和实现原理。
2.1@SpringBootConfiguration
看到@Configuration注解表明该类是Spring的一个配置类
proxyBeanMethods = true 保证每个@Bean方法被调用多少次返回的组件都是单实例的
proxyBeanMethods = false 每个@Bean方法被调用多少次返回的组件都是新创建的
测试代码
-
proxyBeanMethods = true 默认就是true
控制台输出的结果如下:
获取两次对象都是同一个。 -
proxyBeanMethods = false
控制台输出的结果如下:
此时两个对象是不同的。扫描二维码关注公众号,回复: 13147070 查看本文章
2.2@EnableAutoConfiguration
开启自动装配功能的注解
exclude和excludeName 指定自动装配的类,不进行自动装配。
例如下面的测试代码:
@SpringBootApplication()
public class Boot02Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Boot02Application.class, args);
MybatisAutoConfiguration bean = run.getBean(MybatisAutoConfiguration.class);
System.out.println(bean);
}
}
没有设置exclude或者excludeName的时候,Spring容器当中存在MybatisAutoConfiguration对象。
将测试代码改成如下:
@SpringBootApplication(exclude = MybatisAutoConfiguration.class)
public class Boot02Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Boot02Application.class, args);
MybatisAutoConfiguration bean = run.getBean(MybatisAutoConfiguration.class);
System.out.println(bean);
}
}
// 或者 注意这里要写全限定类名
@SpringBootApplication(excludeName= "org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration")
public class Boot02Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Boot02Application.class, args);
MybatisAutoConfiguration bean = run.getBean(MybatisAutoConfiguration.class);
System.out.println(bean);
}
}
此时出现异常,MybatisAutoConfiguration不存在Spring容器当中
除此之外该注解其中最主要的两个注解是
@AutoConfigurationPackage 、@Import(AutoConfigurationImportSelector.class)
-
@AutoConfigurationPackage
添加该注解的类所在的package 作为自动配置package 进行管理。
所以SpringBoot启动类所在的package作为自动配置的package。
配合@Import(AutoConfigurationPackages.Registrar.class)注解,将Registrar注入。Registrar实现了ImportBeanDefinitionRegistrar接口,该接口中的方法registerBeanDefinitions,用于Bean定义的注入,也就是这里将自动配置的package交给Spring管理。
-
@Import(AutoConfigurationImportSelector.class)
该注解在《Spring中@Import注解的使用和实现原理》博客中已经详细介绍过了,用于Bean的注入。
AutoConfigurationImportSelector实现了DeferredImportSelector接口,该接口继承自ImportSelector接口。我想当然的以为会执行selectImports方法,但是根据debug断点调试,并没有执行该方法。
这是为什么呢?在ConfigurationClassParser的processImports方法中有这样一段代码,如下:
当实现类,实现的接口是DeferredImportSelector的时候,执行if的逻辑。如果它只是实现了父接口ImportSelector,才会执行selectImports方法。handle方法如下:
此时deferredImportSelectors不为null,DeferredImportSelector的实现类被封装成DeferredImportSelectorHolder,添加到deferredImportSelectors当中。
看着集合的名字就有延迟注入的意思,在ConfigurationClassParser的parse方法中遍历完了所有的候选集,则会调用this.deferredImportSelectorHandler.process()。
process方法如下:
遍历deferredImportSelectors集合,每个都调用register方法。这里将AutoConfigurationImportSelector的内部类AutoConfigurationGroup添加到groupings集合当中,并将对应的配置类添加到configurationClasses当中。
两个Map的结果如下:
遍历完deferredImportSelectors之后,调用handler.processGroupImports()
遍历之前放在groupings中的DeferredImportSelectorGrouping对象,调用getImports方法,该方法返回的是延迟注入的类名封装成的Entry结点的迭代器对象。
遍历延迟注入类,调用process方法处理,该方法得到自动配置结点,将其添加到autoConfigurationEntries集合当中。再遍历自动配置结点的所有配置类的类名,添加到entries集合当中。
getAutoConfigurationEntry方法用于得到autoConfigurationEntry,该对象管理了自动配置的类名。getAutoConfigurationEntry方法代码如下:
这里的attributes是根据注解的元数据得到的两个属性exclude和excludeName,前面介绍过了,就是用来移除掉指定自动配置类的自动装配。调用getCandidateConfigurations方法得到候选自动配置类的类名。
之后就是根据attributes来过滤得到的configurations。
接着调用fireAutoConfigurationImportEvents,自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,并触发该接口的onAutoConfigurationImportEvent方法。
getCandidateConfigurations方法如下:
这里有个空判断言,意思是在META-INF/spring.factories下没有找到自动配置类,如果你使用了自定义的包,确保文件是正确的。说明要导入的自动配置类,都记录在了META-INF/spring.factories当中。随便打开一个该文件,就可以看到要自动装配的配置类。
loadFactoryNames方法如下:
此时缓存里竟然已经有了需要的东西,重新打断点,再一次调试发现。在Spring容器启动的时候,构造方法里面就已经会获取SpringFactoriesInstances,加载META-INF/spring.factories目录下的自动配置类。
loadSpringFactories方法如下:
FACTORIES_RESOURCE_LOCATION是一个常量,等于"META-INF/spring.factories",遍历目录下的该文件夹,例如下图是属于/Users/gongsenlin/MavenRespository/org/springframework/boot/spring-boot/2.3.5.RELEASE/spring-boot-2.3.5.RELEASE.jar!/META-INF/spring.factories文件的属性结果。
都添加到cache缓存当中。继续回到getImports方法,上面分析完了process方法,接下来就是调用group的selectImports方法,返回那些应该被注入的类。
利用之前已经保存在autoConfigurationEntries和entries集合当中数据,筛选出要注入的类。
结果如下:
getImports()方法结束,回到了processGroupImports()
遍历上面的31个要注入的类,调用processImports方法。该方法在之前的博客《Spring中@Import注解的使用和实现原理》中分析过了,这里不再赘述,至此就完成了自动装配。
3.自定义启动器
根据上面的的分析,可以想到要实现自动装配,需要编写一个spring.factories文件,放在META-INF目录下。就可以将某个类,交给Spring来管理。
再配合上@Configuration注解、@Bean注解、@Import注解等,就可以实现简单的自动装配。
starter模块:只需要将其他自动配置模块,或依赖的模块添加到pom文件中。
如下:
autoconfigure模块目录结构如下:
其中配置类是MyAutoConfigure,代码如下:
使用@Configuration注解表明这是一个配置类,配合@Import注解和@Bean注解将A类和B类注入Spring容器当中。
META-INF/spring.factories文件不能少,内容如下:
这样就完成了一个简单的自定义启动器。
将这个starter启动器的依赖,添加到需要的项目pom文件当中,就可以实现自动装配了。
启动boot-02项目,A类和B类实现了自动装配。