好久没写博客,趁最近有时间赶紧复盘一下,查漏补缺。
两年前我第一次看自动装配原理的时候,真的是看的我一脸懵逼,但是经过了项目的磨炼和自己的探索,会发现,这些东西真的不能着急,会消化不了,看过就忘,还看的想睡觉,但是在真正的学习过程中,总是会有意无意的接触到相关方面的知识,比如视频,就忍不住点进去看,就比如今天,不然不会有这篇文章。废话说完了,正篇开始~
自动装配,顾名思义,就是不需要你手动装配,在spring中装配的意思我认为就是把想要的类初始化创建并注册到spring容器中,那自动装配就是自动注册,在自己正在开发的项目中引入的第三方依赖配置类不需要自己去再次配置也能自动注册到spring容器中
可能有小伙伴问:在自己项目中写的类加了某些注解(@Component,@Service,@Controller
等等)不就是会自动注册到spring容器中嘛?我说是的确实如此,同时得建立在你SpringbootApplication.run()
方法传入的class所在的包路径同级以及以下的才被会扫描到,这和springboot的默认包扫描机制有关,除非你额外手动加了@ComponentScan("包路径")
才能扫描到指定的,好了说到这,我说的自动注册并不是说把我们自己开发中的项目的某些类自动注册,而是第三方依赖。
当初我不了解自动装配原理,然后又去写了自己项目,比如说我自己创建一个auth项目里面封装了对授权认证相关的操作(生成随机验证码图片,生成证书,验证证书,认证拦截器以及其配置类等等),然后把这个项目通过maven去install到本地仓库,然后我另一个项目去引入这个auth项目(变成第三方依赖了),这个时候经过验证发现,auth项目里面的所有配置并未生效,就是提示在spring容器中找不到依赖,这是为啥?因为第三方包路径可能和你正在开发的项目不一样扫描不到,而且还因为那本身就是个第三方项目,spring默认不直接进行包扫描,所以自然就不生效,那如果要生效的话,咋办呢?这就是今天这篇文章的主题《自动装配》原理,学会了这个你就可以去写自己的starter了,舒服的很!!!
首先要了解自动装配,我们可以去项目引入的第三方依赖看看,会发现有META-INF/spring.factories
文件
这个文件是一个关键,很类似java的spi(services provider interface),是JDK内置的一种服务提供发现机制,但是spring的做的更好,因为java的spi是在META-INF/services目录下,以需要加载的接口的全限定类名作为文件名,文件内容放需要加载的实现类的全限定类名(多个就换行),如果有多个需要加载的接口就需要写多个文件,麻烦的很,下面是示例
首先在META-INF/services/目录里创建一个以com.zeit.Demo01.java的文件
//com.zeit.IDemo接口
public interface IDemo {
void printMsg();
}
//com.zeit.Demo01 类
public class Demo01 implements IDemo{
public void printMsg() {
System.out.println("Hello Demo01 !!!");
}
}
//com.zeit.Demo02 类
public class Demo02 implements IDemo{
public void printMsg() {
System.out.println("Hello Demo02 !!!");
}
}
测试
public class SPIDemo {
public static void main(String[] args) {
ServiceLoader<IDemo> serviceLoader = ServiceLoader.load(IDemo.class);
//serviceLoader 中包含了IDemo所有的实现类
for (IDemo demo: serviceLoader) {
demo.printMsg();
}
//以上只做简单的遍历然后调用方法,实际业务中是应该获取自己想要的
}
}
可以看到其实也挺不错的,但是相对于spring的spi机制那可差的比较多,spring的spi可以在META-INF
下面创建spring.factories文件,然后内容写上
com.zeit.IDemo=\
com.zeit.Demo01\
com.zeit.Demo02
但是我们现在用的是springboot项目,一般来说都是用springboot的自动装配,所以我们不是按上面这样写
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zeit.Demo01\
com.zeit.Demo02
这样就能自动装配了,至于为啥非的写EnableAutoConfiguration,因为首先这里写啥是要对应自己的业务,比如springboot自身的spring.factories里面就写了很多,这都是根据springboot开发者他们自己想要实现的来定的
那我们其实我们在springboot的基础上我们只关心我们自己的配置类能不能被自动注册到spring容器中,自动装配工作spring内部已经为我们做好了.
接下来我们来看springboot中是怎么一步步实现自动装配的,首先就是我们写在spring.factories
里面的这个org.springframework.boot.autoconfigure.EnableAutoConfiguration
,这个是复合注解,其中@EnableAutoConfiguration上加的@Import(AutoConfigurationImportSelector.class)注解表示在我们开启了自动装配的时候,默认给我们装配上AutoConfigurationImportSelector这个类,这个类给我们实现了spring的自动装配,所以我们可以先从这个类的代码入手来debug。
进入这个类最核心的方法为selectImports方法
这段代码先是调用isEnabled方法验证是否能进行自动装配,首先就是检查我们的application.yaml文件中是否有spring.boot.enableautoconfiguration
配置,如果没有就默认为true,否则就取配置中的值,false则直接返回空数组,就不进行自动装配。
然后调用getAutoConfigurationEntry
方法,首先AnnotationAttributes attributes = getAttributes(annotationMetadata);
获取了我们run方法传入的类上或者类上加的注解里面加的@EnableAutoConfiguration
注解的值(Class<?>[] exclude和String[] excludeName)这些类是需要排除、不做自动装配的类,
接下来的是核心的部分,List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
返回的是从所有模块中的spring.factories读取到key为org.springframework.boot.autoconfigure.EnableAutoConfiguration
的值,也就是需要自动装配的全限定类名列表,getCandidateConfigurations
内部看图
调用关系SpringFactoriesLoader.loadFactoryNames
->loadSpringFactories
->loadFactoryNames
loadFactoryNames方法内部
最后这个方法返回了Map<String, List<String>>
,值如图
然后在loadSpringFactories
方法内重新组装成List<String>
类型,最终返回到getAutoConfigurationEntry
方法中(也就是selectImports方法里面调用的)
过滤过程中可能涉及到的@ConditionalOnClass,@ConditionalOnMissBean等等
总之经过重重筛选最后留下来的才是真正会进行自动装配的类,最后spring拿到了这些全限定类名,进行Bean的初始化创建等,这个不多说啦。好了文章到此差不多就结束了,来做个总结!
总结
@EnableAutoConfiguration是用来启动自动配置的,是个复合注解,其上面的@Import注解指定加载AutoConfigurationImportSelector这个类,AutoConfigurationImportSelector又实现了这个DeferredImportSelector,DeferredImportSelector是延迟加载选择器,延迟加载第三方依赖中spring.factories中扫描出的自动配置类,这保证了我们自己项目中的重新定义的Bean不会被第三方依赖的相同的Bean给覆盖,
并且这些选择器的父接口都是ImportSelector,DeferredImportSelector去继承了它。
获取自动配置类是通过一种伪SPI的机制,也就是spring自己实现的机制,通过在MATE-INF下创建spring.factories文件,org.springframework.boot.autoconfigure.EnableAutoConfiguration作为key,一个或多个自动配置类作为值,springboot启动的时候通过SpringFactoriesLoader.loadFactoryNames
去将其内容读取出来,经过重重检查筛选过滤之后,最后保留下来的就是需要自动装配的类,然后spring利用java的反射机制进行Bean的初始化创建。