Java SPI机制
1. 为什么需要SPI
SPI: Service Provider Interface ,服务发现机制. 类似IOC,IOC将装配控制权从程序中移到Spring容器.
在面向对象设计中,推荐的都是基于接口编程, 不直接对实现类进行硬编码, 一旦涉及到实现类,就违反了可插拔原则. 如果需要替换为另外一种实现,就需要修改代码.
为了实现模块在装配的时候不用在程序中动态申明,就需要一种服务发现机制. 这就是SPI.
SPI主要使用就是各种产商(jdbc: 数据库产商)和插件(eclise: 插件).
2. 怎么使用Java SPI
package com.markbolo.samples.spi;
public interface Search{
void search();
}
public class MemorySearch{
@override
public void search(){
system.out.println("Memory Serach.")
}
}
public class FileSearch{
@overrider
}
在接口实现(FileSearch|MemorySearch)项目resources目录加上META-INF/services文件夹. 加上 com.markbolo.samples.spi.Search文件(文件名称为接口的全限定名称). 文件内容: com.markbolo.samples.spi.FileSearch com.markbolo.samples.spi.MemorySearch
public class SpiClient {
public static void main(String[] args){
ServiceLoader<Search> serviceLoader = ServiceLoader.load(Search.class);
for (Search search : serviceLoader) {
search.search();
}
}
}
可以得到console的内容:
FileSearch
MemorySearch
3. SPI解决了什么问题
- 服务发现
- 解耦
以JDBC为例,在SPI机制出现之前,要获取数据驱动就需要通过
Driver driver = Class.forName("com.mysql.jdbc.Driver")
有了SPI之后,就可以通过DriverMananer.getDriver(url)就可以直接获取数据库驱动.
将具体的数据库产商提供的驱动实现类从调用者(要获取Driver|Connection的类)中解耦出来.
Like: com.mysql.jdbc.Driver 是mysql-connector-java 5中的;com.mysql.cj.jdbc.Driver 是mysql-connector-java 6中的
如果还是通过老的方式获取jdbc driver,就会将具体的数据库驱动类硬编码到程序中,要想切换数据库驱动连接版本 5->6, 需要改动的地方: 1. maven数据库jar包 2. 数据库驱动类配置 ; 使用SPI之后,就只需要变更maven中的数据库jdbc jar包的版本即可, DriverManager.getDriver(url) 会自己去发现数据库驱动
4. SPI案例
1. JDBC
DriverManager
// DriverManager初始化数据库驱动:加载到DriverManager中来 ; ps:服务发现
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
static void loadInitialDrivers(){
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 发现数据库产商提供的数据库驱动Driver (这里只发现符合SPI机制的驱动)
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
// 这里还是把类加载了,实际将Driver注册到DriverManager还是靠的Driver实现类中的 静态块注册的
while(driversIterator.hasNext()) {
println(" Loading done by the java.util.ServiceLoader : "+driversIterator.next());
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
// ps: 这里是对不符合SPI机制的Driver进行加载; 还是采用老的模式Class.forName(driverClassName)进行加载的
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
// Class.forName(driverClassName)也还是要靠Driver中的静态代码块将Driver注册到DriverManager中
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
}
mysql Driver中的代码:
static {
// 具体数据库产商提供的Driver在类加载的时候自己注册到DriverManager中
try {
DriverManager.registerDriver(new FabricMySQLDriver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver", var1);
}
}
再插入一句: Spring集成数据源获取Connection也是靠DriverMananger来管理.
org.springframework.jdbc.datasource.DriverManagerDataSource
protected Connection getConnectionFromDriverManager(String url, Properties props) throws SQLException {
// 通过DriverMananger获取数据库连接
return DriverManager.getConnection(url, props);
}
2. Dubbo
后期才完成Dubbo部分,欢迎来看Dubbo SPI
3. Eclipse插件拓展
4. common-logging
5. Hotspot
6. Jetty/Tomcat自定义SessionManager
7. Spring
5. SPI怎么就破坏了双亲委派模型?
5.1 jdbc4.0之前加载数据库驱动
先看不使用SPI是怎么加载mysql数据库驱动:
Driver dirver = Class.forName("com.mysql.jdbc.Driver")
DriverMananger.getConnection("jdbc:mysql://localhost:3306/testdb")
关键的就是通过Class.forName()触发数据库驱动Driver的类加载,然后自己注册到DriverManager:
static {
// 具体数据库产商提供的Driver在类加载的时候自己注册到DriverManager中
try {
DriverManager.registerDriver(new FabricMySQLDriver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver", var1);
}
}
Driver注册到DriverManager之后就可以通过DriverManager来获取数据库连接.
总结:
jdbc4.0之前的版本在Application中使用Class.forName("com.mysql.jdbc.Driver")加载驱动类,这时候
ClassLoder是ApplicationClassLoader:负责加载appliction下面的类,符合双亲委派模型的定义
5.2 jdbc4.0 spi注册driver
在jdbc 4.0之后,就支持SPI方式来注册Driver. 具体怎么实现就不过多描述. 可以分析SPI加载Driver的过程:
- 从mysql jar包中
resources/META-INF/services/java.sql.Driver文件中可发现具体的实现类为:com.mysql.jdbc.Driver - 知道具体的实现类名后, 通过
Class.forName()加载这个实现类, 类加载完成就触发静态块将自己类的实例注册到DriverManager
Question: 在了解SPI加载数据库Driver的过程后, 会发现各种数据库Driver类是在DriverManager中进行的Class.forName来完成类加载工作的.
而DriverMananger是在${java_home}\lib\rt.jar中的,ClassLoader自然就是BootstrapClassLoader. 那么这个时候使用DriverManager的classLoader来加载应用lib目录下数据库驱动jar里面的Driver类自然是加载不到的. 那么怎么解决这个问题?
ps: 这也是双亲委派模型的局限性, 自身缺陷: 父加载器不能加载子加载器路径中的类 (JNDI)
Answer: Driver实现类又必须要要用ApplicationClassLoader才能加载. 看起来唯一的解决方式就是: 通过某种方式获取到ApplicationClassLoader,然后在让ApplicationClassLoader来加载Driver实现类.
这里就是线程上下文加载器ThreadClassLoader.
可以看到在创建serviceLoader实例的时候,用到的就是ThreadClassLoader
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
5.3 线程上下文加载器
thread.getContextClassLoader()获取线程上下文加载器 thread.setContextClassLoader(ClassLoader)设置线程上下文加载器,默认为ApplicationClassLoader.
这里, 本该是BootstrapClassLoader加载的Driver实现类,现在可以通过线程上线文加载器获的子加载器ApplicationClassLoader, 让ApplicationClassLoader来加载本该是父加载器加载的Driver实现类. 很明显就破坏了双亲委派模型.
双亲委派模型: 当一个类加载器收到类加载请求时,不是先尝试自己加载这个类,而是将请求委派给父加载器去完成. 一直到顶级加载器. 只有当父加载器无法完成类加载的时候,子加载器才会去尝试自己加载.
正常的双亲委派模型类加载流程: 类加载请求 --> ApplicationClassLoader --委托--> ExtensionClassLoader --> bootstrapClassLoader --无法完成类加载 --> ExtensionClassLoader --> ApplicationClassLoder. Application在去加载类.
JNDI SPI的类加载请求: 类加载请求 --> BootstrapClassLoader --无法完成类加载 --> getContextLoader获取到ApplictionClassLoader 去加载Driver类.
6. SPI实现原理
通过前面的内容,我们了解到Driver实现类都是通过ServiceLoader.load(Driver.class)获取到的. 并且Driver实现类的类加载请求是通过上下文类加载器加载的.
下面的内容就是详细剖析 1. ServiceLoader是怎么发现Driver实现类的 2. 又是怎么样一种方式类获取到ApplicationClassLoader来加载Driver实现类的.
ServiceLoader实现原理剖析: 源码阅读
public static <S> ServiceLoader<S> load(Class<S> service) {
// 这获取线程上下文加载器来加载Service实现类
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
thred.getContextClassLoader() 返回null就表示采用Appcation ClassLoader
ServiceLoader.load(service, cl):
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
// load的时候构造一个ServiceLoader对象,在构造方法中加载
return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = svc;
loader = cl;
// ServiceLoader构造方法中的加载Service实现类的地方
reload();
}
public void reload() {
providers.clear();
// 加载service实现类还是通过LazyIterator来加载的
lookupIterator = new LazyIterator(service, loader);
}
想要获得到具体的Service实现类,只能通过ServiceLoader<service>.iterator().hasNext()/next()来访问.
ServicerLoader.iterator.hasNext()/next()实际还是委托给lookupIterator:LazyIterator的:
具体委托:
public Iterator<S> iterator() {
// iterator都委托给lookupIterator:LazyIterator
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
// ServiceLoader.iterator.hasNext()委托给lookupIterator:LazyIterator
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
// 同上,ServiceLoader.iterator.next()委托给lookupIterator:LazyIterator
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
ServiceLoader.iterator().hasNext()/next()的具体实现都委托给ServiceLoader.lookupIterator.hasNext()/next(). 而ServiceLoader.load(Service.class)获取到的Service实现类只能通过iterator()来访问. 所以就可以将类加载过程推迟到iterator.next()的时候. 也就是lookupIterator:LazyIterator.next().
LazyIterator:
private class LazyIterator
implements Iterator<S>
{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
public boolean hasNext() {
// 加载META-INF/services/java.sql.Driver 中内容 nextName = 'xxxDriver'
...
}
public S next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 这里用loader: App ClassLoader加载
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated: " + x,
x);
}
throw new Error(); // This cannot happen
}