视频地址: https://www.bilibili.com/video/BV1yr4y1w7ia
代码仓库: https://gitee.com/crazyliyang/video-teaching
1.关于SPI
SPI机制: SPI的全名为( Service Provider Interface ) 这个是针对厂商或者插件的。
(1)SPI思想
系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,
xml解析模块、jdbc模块的方案等。
向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。
一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。
为了实现在模块装配的时候能在程序里动态指明,这就需要一种服务发现机制。
java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制
(2)SPI约定
当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/
目录里同时创建一个以服务接口命名的文件。
该文件里就是实现该服务接口的具体实现类, 而当外部程序装配这个模块的时候,
就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,
并装载实例化,完成模块的注入。
通过这个约定,就不需要把服务放在代码中了,通过模块被装配的时候就可以发现服务类了。
2、SPI使用案例
common-logging apache 最早提供的日志的门面接口。只有接口,没有实现。具体方案由各提供商实现,
发现日志提供商是通过扫描 META-INF/services/org.apache.commons.logging.LogFactory 配置文件,
通过读取该文件的
2.项目 spring-spi
注意本项目只依赖 Spring
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-boot-parent</artifactId>
<groupId>com.liy.teaching</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-spi</artifactId>
<!-- 注意这里只有 spring 的依赖 -->
<dependencies>
<!-- spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
</project>
核心配置类如下:
/**
* @Author: liyang
* @Date: 2020/11/4 16:51
* @Description: 配置Spring 的组件扫描配包
*
**/
@Configuration
@ComponentScan("com.liy.teaching.spi")
public class MyAppConfig {
}
/**
* @Author: liyang
* @Date: 2020/11/4 16:38
* @Description: 定义了一个配置类, 该配置类 使用@Import注解导入 MyAutoConfigurationRegistrar
*
**/
@Configuration
@Import(MyAutoConfigurationRegistrar.class) // MyAutoConfigurationRegistrar BD 注册器
public class MyAutoConfiguration {
}
public class MyAutoConfigurationRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware {
@Autowired
private ClassLoader beanClassLoader; //类加载器
/**
*
* BeanClassLoaderAware 接口起作用, 注入 ClassLoader, 这里获取到的是: AppClassLoader
*
*/
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println( "setBeanClassLoader: " + classLoader );
this.beanClassLoader = classLoader;
}
/**
* @Author: liyang
* @Date: 2020/11/4 17:09
* @Description: 使用 BeanDefinitionRegistry 注册 BD
*
**/
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// META-INF/spring.factories
/**
* Load the fully qualified class names of factory implementations of the
* given type from { META-INF/spring.factories }, using the given class loader.
* 从{ META-INF/spring.factories }中加载 给定类型名 后边实现类的完全限定类名数组,使用给定的类加载器。
* @param factoryType the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading resources; can be {@code null} to use the default
* @throws IllegalArgumentException if an error occurs while loading factory names
* @see #loadFactories
*/
List<String> classStringList = SpringFactoriesLoader.loadFactoryNames(MyAutoConfiguration.class, beanClassLoader);
System.out.println( "loadFactoryNames : " + classStringList );
// 如果 spring.factories文件为空得到的classStringList为空
if (classStringList.isEmpty()) {
return;
}
for (String classFullName : classStringList) {
try {
Class<?> clazz = beanClassLoader.loadClass(classFullName);
String simpleName = clazz.getSimpleName();
// 如果已经存在该 classFullName 的 BD
if (registry.containsBeanDefinition(classFullName)) {
continue;
}
// 无参构造创建BD
BeanDefinition bd = BeanDefinitionBuilder.rootBeanDefinition(classFullName).getBeanDefinition();
registry.registerBeanDefinition( simpleName, bd);
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
测试接口和其实现类
public interface SpiInterface {
String get();
}
public class SpiInterfaceImpl implements SpiInterface {
private String name = "SpiInterfaceImpl"; // 为了测试区分实现类
@Override
public String get() {
return name;
}
}
public class SpiInterfaceImpl02 implements SpiInterface {
private String otherName = "SpiInterfaceImpl02"; // 为了测试区分
@Override
public String get() {
return otherName;
}
}
// 测试实体
public class Driver {
private String name;
public Driver() {
this.name="Tom";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
META-INF/spring.factories 文件
com.liy.teaching.spi.config.MyAutoConfiguration=\
com.liy.teaching.spi.component.SpiInterfaceImpl02,\
com.liy.teaching.spi.component.Driver
测试:
public class MainTestApp {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyAppConfig.class);
// 从容器中获取Bean实例
SpiInterface spiInterface = context.getBean(SpiInterface.class);
System.out.println(spiInterface.get());
Driver driver = context.getBean(Driver.class);
System.out.println(driver.getName());
context.close();
}
}