Java的SPI机制和JDBC
SPI使用
我们定义了一个IService
类,由各个服务提供方提供具体的实现
package com.example;
interface IService {
void doServe();
}
服务提供方提供的实现:
package com.provider;
class AService implements IService {
@Override
void doService(){}
}
class BService implements IService {
@Override
void doService(){}
}
Java通过ServiceLoader来进行服务的加载,服务提供方在META-INF/services
中定义接口全限定名文件,在其中添加具体的实现类,有多个换行分隔 eg: META/services/com.example.IService
com.provider.AService
com.provider.BService
服务使用方只需要使用ServiceLoader.loadService(IService.class)
方法就可以获取所有IService.class
的实例:
ServiceLoader<IService> iServices = ServiceLoader.load(IService.class);
for (IService iService : iServices) {
iService.serve();
}
底层机制
public final class ServiceLoader<S> implements Iterable<S> {
private static final String PREFIX = "META-INF/services/";
// 要加载的IService接口
private final Class<S> service;
// 类加载器
private final ClassLoader loader;
// 缓存的已经完成实例化的IService实例
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 待加载的IService类
private LazyIterator lookupIterator;
}
// 拼接 META-INF/services + classname,获取所有文件
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
// 遍历文件,readLine
String ln = r.readLine();
if (!providers.containsKey(ln) && !names.contains(ln)) names.add(ln);
return names.iterator();
// 最后通过 Class.forName()完成加载
c = Class.forName(cn, false, loader);
// 实例化,添加缓存中
S p = service.cast(c.newInstance());
providers.put(cn, p);
JDBC
实例代码 服务注册
我们看一下MySQL的Driver实现,注意static块代码,因为是静态的,所以当完成类加载时就会讲Driver注册到DriverManager
:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
// 通过DriverManager进行注册
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}
服务加载
JDBC中使用java.sql.DriverManager#loadInitialDrivers
方法进行所有Driver
实现的加载
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
// 这里的Iterator是ServiceLoader中的LazyIterator
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next(); // LazyIterator的next方法会创建一个实例
}
} catch(Throwable t) {
// Do nothing
}
获取数据库连接
使用DriverManager.getConnection
获取数据库连接,遍历所有的Driver,尝试连接,获取成功连接的Connection并返回
for(DriverInfo aDriver : registeredDrivers) {
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println("skipping: " + aDriver.getClass().getName());
}
}
打破双亲委派机制
我们说MySQL打破双亲委派机制指的是DriverManager#getConnection
方法中,调用ServieLoader.load
并不使用当前的类加载器,而是当前线程上下文的类加载器
synchronized(DriverManager.class) {
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
因为DriverManager
位于java.rt
包下,而这个包是通过Bootstrap类加载器进行加载的,当在这个类中使用ServiceLoader
时也会使用Bootstrap类加载器进行加载服务实现类--这样当然是不可以的,所以在调用getConnection
方法是如果不指定类加载器的话会从当前线程上下文中获取到AppClassLoader