一、什么是SPI机制
SPI是Service Provider Interface 的简称,即服务提供者接口的意思,SPI说白了就是一种扩展机制,实现了框架和组件的解耦。
- 框架设计者:我们在设计框架的时候留一个接口做为扩展点,然后加载固定路径配置文件中配置的实现类。这样框架不用把扩展的组件写死,由配置文件动态指定。
- 扩展组件者:实现扩展点接口,然后把实现类名写到配置文件中,由框架自己去加载。
二、Java 中的SPI机制使用
案例:比如我们框架要有一个写日志的功能,然后日志具体的写在数据库还是log文件中由具体项目组去实现
第一步:定义一个写日日志的服务接口做扩展点
// 定义一个写日志的服务
public interface LogService{
// 写日志的功能
public void writeLog(String msg);
}
第二步:定义一个工厂类,用来实例化日志服务,静态代码块是关键。
public class LogFactory{
private static LogService logService;
public LogService getLogService(){
return logService;
}
static {
// 关键地方 查找classPath下的META-INFO/services目录下名字为【SPIService全限名.service】 的文件,读取文件中写的类名,通过反射实例化到ServiceLoader里。和spring的依赖注入一样。
ServiceLoader<LogService> load = ServiceLoader.load(LogService.class);
Iterator<LogService> it = load.iterator();
while(it.hasNext()){
LogService service = it.next();
LogFactory.logService = service;
}
}
}
第三步:开发扩展点,打成jar包,由项目组自己去实现
//第一步:实现LogService打成Jar包
public class FileLogService implement LogService{
// 自定义实现,输出到控制台
public void writeLog(String msg){
System.out.print(msg);
}
}
// 第二步:在classPath下的META-INFO/services下创建一个文件名为接口全限名的文件。文件内容写实现类名。
// 如文件名为【com.cn.LogService】 文件内容:com.cn.FileLogService
在Java中有很多这样的案例,如JDBC驱动的加载。JDBC驱动类jar包里有一个java.sql.Driver 的文件,里面写的是驱动类实现的类名。在DriverManager 类里有一个静态代码块,通过ServiceLoader类去加载实现类,大家可以翻翻源码看。
三、Spring 中的SPI机制使用
1. Spring中的SPI机制解决什么问题?
我们知道SpringBoot 默认只扫描启动类同层级和向下目录里的类,注册到Spring容器中,当我们向要被Spring管理的Bean不在Spring项目扫描路径下怎么半呢?
- 方法一:在Spring Boot 项目中配置ComponentScan注解的扫描路径。
- 方法二:通过在Spring Boot 项目中添加@EnableAutoConfiguration注解,并自定义@EnableXXXXConfiguration的注解,通过注解中的方法注入Bean。
- 方法三:手动硬编码进行Bean的注入
由以上可以发现外部jar包里的类和项目耦合很紧,当外部jar包类换名字,换路径等等都需要项目进行修改,有没有更好的方法呢?接下来继续看Spring的SPI机制解决这个问题。
2. Spring Factories机制原理
- 核心类SpringFactoriesLoader
Spring Factories机制通过META-INF/spring.factories文件获取相关的实现类的配置信息,而SpringFactoriesLoader的功能就是读取META-INF/spring.factories,并加载配置中的类。SpringFactoriesLoader主要有两个方法:loadFactories和loadFactoryNames。 - 核心方法一:loadFactoryNames
用于按接口获取Spring Factories文件中的实现类的全称,其方法定义如下所示,其中参数factoryType指定了需要获取哪个接口的实现类,classLoader用于读取配置文件资源。 public static List loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader)
- 核心方法二:lloadFactories
用于按接口获取Spring Factories文件中的实现类的实例,其方法定义如下所示,其中参数factoryType指定了需要获取哪个接口的实现类,classLoader用于读取配置文件资源和加载类。 public static List loadFactories(Class factoryType, @Nullable ClassLoader classLoader)
看完这个大家可能都明白了,它和Java中的SPI 机制套路一样,就是加载META-INF/spring.factories中配置的类,并进行实例化。
3. 在Spring中如何使用
- Spring boot中默认使用了很多factories机制,主要包含:
- ApplicationContextInitializer:用于在spring容器刷新之前初始化Spring
- ConfigurableApplicationContext的回调接口。
- ApplicationListener:用于处理容器初始化各个阶段的事件。
- AutoConfigurationImportListener:导入配置类的时候,获取类的详细信息。
- AutoConfigurationImportFilter:用于按条件过滤导入的配置类。
- EnableAutoConfiguration:指定自动加载的配置类列表。
我们可以自定义一个类,然后通过配置来指定自定义的Bean何时初始化被Spring管理
案例:
- 第一步:定义一个类 cn.com.SpringBeanDemo
@Configruation
public class SpringBeanDemo{
// 这里可以写自己的内容,比如初始化方法、加载配置信息,注入其他Bean对象
}
- 第二步:编写META-INF/spring.factories文件
# 注意写法规则,每个类要换一行,后面用 \ 做换行标记,如有多个实现,用逗号隔开。 注释用# 号做标记,关于spring.factories文件在Spring很多jar包里都有,可以去做参照。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.com.SpringBeanDemo,\
cn.com.SpringBeanDemo2
这样当注解EnableAutoConfiguration 启动生效的时候会把自定义的SpringBeanDemo 进行实例化,交由Spring容器管理。
案例二:
案例一中我们是通过Spring的EnableAutoConfiguration 注解,把SpringBeanDemo 注入到Spring容器中,那我们如何自定义一个类来控制 SpringBeanDemo 的注入呢?
@Service
public class MyAutoConfiguration{
@PostConstruct
public void printService(){
List<String> serviceNames = SpringFactoriesLoader.loadFactoryNames(SpringBeanDemo.class,null);
for (String serviceName:serviceNames){
System.out.println(serviceName);
}
List<SpringBeanDemo> services = SpringFactoriesLoader.loadFactories(SpringBeanDemo.class,null);
for (DemoService demoService:services){
demoService.printName();
}
}
}
通过以上方式我们可以自定义加载配置类的时机,我们可以在SpringFactoriesLoader.loadFactories 加载类的时候做很多我们自己项目的需求。
以上就是SPI的用法啦,大家快快去实践下,看项目上哪些地方可以封装成组件。