1)使用场景
日常开发中,常根据接口调用实现类,但是如何确定某个具体的实现类包名,进而实例化实现类呢?
如果扫描所有的实现类class文件,然后去看其是否属于该接口,效率无疑很低。
所以便有了“基于约定”的方式,SPI 的出现便是基于约定配置实现 接口 与 实现类 的解耦。
2)概述
SPI 全名 (Service Provider Interface)
服务提供者根据提供的接口完成一种实现后,在jar包的/META-INF/services下新建一个文件,文件名为接口的全限定名,内容为实现类的全限定名
接口提供者方可以使用serviceloader加载该实现类。实现了接口和实现类的解耦。
3)Mysql的jar包实现
Class.forName("xxx")
早期使用mysql的jar包时,总是需要先手动Class.forName来加载mysql驱动。
现在可以直接使用 DriverManager.getConnection来获取连接。
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
如同,该静态代码块存在于DriverManager中,当调用DriverManage.getConnection时,由于类的加载机制会先调用该类的父类和本身的静态变量、静态代码段等等。在该代码段里,DriverManager完成了驱动的加载。(loadInitialDrivers)
private 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;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//使用serviceloader来加载驱动
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
//如果设置了驱动的全限定类名,则完成手动加载
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
分析图中代码:
-
System.getProperty(“jdbc.drivers”)查找有jvm变量是否存在全限定类名。
因为可能手动System.setProperty来指明了驱动的全限定类名。
如果存在这种情况,则手动加载驱动 Class.forName(aDriver, true, ClassLoader.getSystemClassLoader())。 -
主要使用了Serviceloader来加载Driver类的实现类,即mysql驱动。
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
所以在DriverManager里面最终是调用的ServiceLoader来加载驱动。
4)ServiceLoader内部实现
点进ServiceLoader来观察如何加载的驱动
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
ServiceLoader实现了iterator接口,查看其重写的iterator()方法
- 主要是调用了LazyIterator ,而该类是ServiceLoader的一个内部类。
- 除了完成加载功能外,还有着懒加载的特性,即调用hasNext时才开始加载驱动。
继续观察LazyIterator的next和hasNext方法的实现内容。
//定义文件扫描的约定路径
private static final String PREFIX = "META-INF/services/";
//....
private class LazyIterator implements Iterator<S> {
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// 加载 "META-INF/services/"下的文件
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);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
}
hasNextService中:
- String fullName = PREFIX + service.getName();
ClassLoader.getSystemResources(fullName);
加载 META-INF/services/ 下的文件获取 实现类的全限定类名
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
//...
//...
//...
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//加载驱动
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);
}
throw new Error(); // This cannot happen
}
在next方法中 使用Class.forName(cn, false, loader);最终完成了实现类的加载(cn即是实现类的全限定类名)
5)Spring SPI——SpringBoot自动装载
在SpringBoot中,可以在项目的 META/spring.factories下指定 装载类的全限定类名。
同 Java spi一样,SpringBoot 的自动装载也使用了这种约定的设计理念。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
如下图SpringBoot 通过SpringFactoriesLoader 来加载第三方(各种Starter)的类。
通过这种方式可以将第三方的类注册到spring容器中
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
private SpringFactoriesLoader() {
}
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryType, "'factoryType' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
}
List<T> result = new ArrayList(factoryImplementationNames.size());
Iterator var5 = factoryImplementationNames.iterator();
while(var5.hasNext()) {
String factoryImplementationName = (String)var5.next();
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
//...
//...
//...
}