一、前言
想必很多小伙伴在面试或者学习springboot的时候或多或少都了解过Springboot的自动配置,我在面试的时候也被问到过,那么这次在公司的项目中也用到了这一块儿知识,比如自动配置类不加@Configuration,而是将这个模块所有的自动配置类全部写到该模块下的spring.factories文件下实现自动配置,那么这里就想要记录一下:
二、SPI
SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。
多数开发人员可能不熟悉,因为这个是针对厂商或者插件的。在 java.util.ServiceLoader 的文档里有比较详细的介绍。
简单总结下 Java SPI 机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案、xml解析模块、jdbc模块的方案等。面向的对象设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及了具体的实现类,就违反了可插拔的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
我们来演示一下这种机制
1、首先写一个接口,由于我也玩原神,所以这里举一个原神的小例子:
package mbw;
public interface YuanShen {
void Attack();
}
2、然后自定义两个实现类实现刚才的接口:
package mbw;
public class Water implements YuanShen {
@Override
public void Attack() {
System.out.println("神里流-水囿");
}
}
package mbw;
public class Fire implements YuanShen {
@Override
public void Attack() {
System.out.println("蹦蹦炸弹");
}
}
3、最后要在ClassPath路径下配置添加一个文件。文件名字是接口的全限定类名,内容是实现类的全限定类名,多个实现类用换行符分隔。
效果测试:
可以通过ServiceLoader.load或者Service.providers方法拿到实现类的实例。
package mbw;
import java.util.ServiceLoader;
public class MainClass {
public static void main(String[] args) {
ServiceLoader<YuanShen> load = ServiceLoader.load(YuanShen.class);
//或者Service.providers()方法获取我们的Iterator对象
//Iterator<YuanShen> providers = Service.providers(YuanShen.class);
//循环获取所需的对象
for (YuanShen next : load) {
next.Attack();
}
}
}
结果如图:通过spi机制也可以像我们new对象一样获取实例!
三、Spring Boot 如何将依赖包中的 bean 注册到容器中?
那么了解了SPI,其实一种Springboot自动配置原理类似的机制就很好理解了:
在 Spring Boot 项目中,怎样将 pom.xml 文件里面添加的依赖中的 bean 注册到 Spring Boot 项目的 Spring 容器中呢?
你可能会首先想到使用 @ComponentScan 注解,遗憾的是 @ComponentScan 注解只能扫描 Spring Boot 项目包内的 bean 并注册到 Spring 容器中,项目依赖包中的 bean 不会被扫描和注册。此时,我们需要使用 @EnableAutoConfiguration 注解来注册项目依赖包中的 bean。而 spring.factories 文件,可用来记录项目包外需要注册的 bean 类名。spring.factories利用了SPI机制,来实现模块的扩展配置。
使用 spring.factories 文件有什么好处呢?假如我们封装了一个插件,该插件提供给其他开发人员使用。我们可以在 spring.factories 文件中指定需要自动注册到 Spring 容器的 bean 和一些配置信息。使用该插件的开发人员只需少许配置,甚至不进行任何配置也能正常使用。
四、实现原理
项目启动时@EnableAutoConfiguration 通过SpringFactoriesLoader 加载所有META-INF下的spring.factories文件,根据EnableAutoConfiguration.class加载所有配置类。
所以说,如果你想让 Spring Boot 自动将你的库的某个类的实例注册到 Spring 容器中,就需要按照其要求提供 META-INF/spring.factories 文件。
我们可以演示一下:
比如现在我新建一个Springboot的项目,然后在里面新建一个类,并写一个简单的方法,注意这里我并没有将它注册进容器:
然后新建一个Testconfig类,内部我们通过@Bean去将我们刚刚的TestMain类注册为bean,并且调用里面的test方法为后面方便验证,但是这里我同样也没有给这个类加上@Configuration注解标识它为自动配置类,那么按道理是不可能自动配置成功的
然后就是主角登场
我们在我们的resources下新建META-INF文件夹并新建spring.factories文件,内容如下:
这样我们就已经完成了之前配置类的自动配置,并且会去执行里面所有的带有@Bean的方法并将返回值注册为Bean.
我们回到TestConfig
可以看到类左边有类似的标记:
那么下面实践一下
我们新建一个模块,然后将上面的项目作为依赖导入到该模块中:
我们新建一个TestPackage类,注入TestMain这个类
然后测试,如果之前自动配置成功,那么测试一定会通过
如果自动配置失败,TestMain一定没有注入进容器,那么测试肯定不通过:
测试通过,那么说明自动配置成功。
大家还可以试一试如果将TestConfig中@Bean去掉,再测试下就会发现失败了,因为TestMain并没有注册进容器。