SPI
机制
简介
为Service Provider Interface,简单的总结下java spi机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制
整体机制如下图:
SPI
实际上就是基于接口的编程+策略模式+配置文件组合实现的动态加载机制
系统设计的各个抽象,往往有很多不同的实现方案,在面向对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可插拔原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能够在不同程序里动态指名,这就需要一种服务发现机制。
Java SPI
机制就是提供这样一个机制:为某个接口寻找服务发现的机制。有点类似IOC
的思想,就是将装配的控制权移到程序之外,在模块化设中这个机制尤为重要,所以SPI
的核心思想,就是解耦。
类加载机制
双亲委派模型
java
中的类加载器负责加载来自文件系统、网络或者其他来源的类文件。
jvm
的类加载器默认使用的是双亲委派
模式。
类加载规则
自上而下:尝试去加载类
自下而上:检测类是否已加载
具体描述:
当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层的BootstrapClassLoader
,只有当父加载器无法完成加载任务时,才会尝试自己来加载。
好处:
使用双亲委派模式可以保证类加载的安全性,不管是哪个加载器加载这个类,最终都是委托给顶层的Bootstrap Classloader
来加载的,只有父类无法加载才尝试加载,这样就可以保证任何类加载器最终得到的都是同样的一个Object
对象。
缺陷:
子类加载器可以使用父类加载器已经加载的类,反过来则不行,这就意味着子类加载器要是无法加载类,父类更加没办法加载,这就意味着双亲委派模型并不能保证所有类的加载问题。
例子:
对于SPI
接口,都是由核心类库提供的,但是却是由第三方类库去实现,这样就存在一个问题.
SPI
的接口是由AppClassloader
来加载,BootStrapClassLoader
是无法找到实现的,因为它只加载java
的核心类库,它也不能代理给AppClassloader
,因为它是最顶层的类加载器。
解决方案:
使用线程上下文类加载器ContextClassloader
加载
类加载器
-
Bootstrap Classloader
加载jdk
自带的rt
包下的类文件,是所有类加载的父类 -
Extension Classloader
加载扩展类库jre/lib/etc
或者java.ext.dirs
系统目录下的类 -
System Classloader
加载classpath
环境变量中加载类文件
ContextClassLoader
线程上下文类加载器
jdk1.2
开始引入,通过Thread.getContextClassLoader()和setContextClassLoader()
来获取和设置类加载器,由此来加载实现了SPI
接口的实现类
使用场景
概括的说,适用于:调用者根据实际使用需要,启用、扩展、或者替换框架的实现策略
比较常见的例子:
- 数据库驱动加载接口实现类
jdbc
加载不同类型的数据库驱动 - 日志门面接口实现类加载
SLF4J
加载不同提供商的日志实现类 Spring
Dubbo
使用介绍
要使用java SPI
,需要遵循以下约定:
- 当服务提供者提供了接口一种具体实现后,在
jar
包的META-INF/services
目录下创建一个以接口全限名为命名的文件,内容为实现类的全限定名 - 接口实现类所在的
jar
包放在主程序的classpath
中 - 主程序提供
java.uti.ServiceLoader
动态装载实现模块,它通过扫描META-INF/services
目录下的配置文件找到实现类的全限定名,把类加载到JVM
-
SPI
的实现类必须携带一个不带参数的构造方法
示例代码
1.定义顶级接口JdbcSpiInterface
public interface JdbcSpiInterface {
/**
* 获取数据库连接
*
* @param user 用户名
* @param password 密码
* @param driverClassName 驱动类
*/
void getJdbcConnection(String user, String password, String driverClassName);
}
2.新增2个实现类MysqlJdbc、OracleJdbc
public class OracleJdbc implements JdbcSpiInterface {
@Override
public void getJdbcConnection(String user, String password, String driverClassName) {
System.out.println("Oracle数据库驱动连接");
}
}
public class MysqlJdbc implements JdbcSpiInterface {
@Override
public void getJdbcConnection(String user, String password, String driverClassName) {
System.out.println("Mysql数据库驱动连接");
}
}
3.编写测试方法
public class Test {
public static void main(String[] args) {
ServiceLoader<JdbcSpiInterface> shouts = ServiceLoader.load(JdbcSpiInterface.class);
for (JdbcSpiInterface s : shouts) {
s.getJdbcConnection("user", "password", "cp.ds.xds.Class");
}
}
}
- 测试结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KEyMicIo-1591170766365)(C:\Users\dell\AppData\Roaming\Typora\typora-user-images\1590026903115.png)]
Spring
中SPI
扩展机制
简介
在Springboot
的自动装配过程中,最终会加载META-INF/spring.factories
文件,而加载的过程是由SpringFactoriesLoader
加载的。从CLASSPATH
下的每个jar
包中搜索所有META-INF/spring.factories
配置文件,其实这里不仅仅是去ClassPath
路径下查询,而是会扫描所有路径下的jar包,只不过这个文件只会在ClassPath
下的jar
包中.
源码
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// spring.factories文件的格式为:key=value1,value2,value3
// 从所有的jar包中找到META-INF/spring.factories文件
// 然后从文件中解析出key=factoryClass类名称的所有value值
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
// 取得资源文件的URL
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
// 遍历所有的URL
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 根据资源文件URL解析properties文件,得到对应的一组@Configuration类
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
// 组装数据,并返回
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
参考文档:
https://www.jianshu.com/p/0d196ad23915