文章目录
- SPI机制
- 案例分析
- 建立DriverManager
- 建立MysqlDriver来实现扩展
- 建立OracleDriver来实现扩展
- 测试spitest
- 源码分析
- ServiceLoader类的结构
- reload加载类
- LazyIterator类
- parse解析URL对象方法
- parseLine方法
SPI机制
SPI ,全称为 Service Provider Interface
,是一种服务发现机制。它通过在ClassPath路径下的
META-INF/services
文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了
可能,比如在Springboot,Dubbo、JDBC中都使用到了SPI机制。
案例分析
以驱动加载为例。
分别定义不同的实现,根据调用方引入不同的实现来进行加载具体的实现类。
建立DriverManager
定义一个获取驱动连接信息的接口。
/**
* 公共的接口 com.elite.common.DriverManager
*/
public interface DriverManager {
//获取连接信息
String getConnectionInfo();
}
建立MysqlDriver来实现扩展
/**
* SPI:MySQL对于 getConnectionInfo 的一种实现
*
*/
public class MysqlDriver implements DriverManager
{
@Override
public String getConnectionInfo() {
return "this is mysqldriver";
}
}
此时我们需要在ClassPath下新建META-INF/services,新建一个名称必须是 定义的接口的全类路径,文件中写上接口的实现类的全类路径名称
。
建立OracleDriver来实现扩展
/**
* 扩展实现 com.elite.oracle.OracleDriver
* com.elite.common.DriverManager
*/
public class OracleDriver implements DriverManager {
@Override
public String getConnectionInfo() {
return "这是oracle数据库连接的扩展实现";
}
}
测试spitest
需要在pom.xml引入具体的实现
<dependency>
<groupId>com.elite</groupId>
<artifactId>MysqlDriver</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.elite</groupId>-->
<!-- <artifactId>OracleDriver</artifactId>-->
<!-- <version>1.0-SNAPSHOT</version>-->
<!-- </dependency>-->
<!-- </dependencies>-->
测试代码
import java.util.Iterator;
import java.util.ServiceLoader;
public class Main {
public static void main(String[] args) {
ServiceLoader<DriverManager> providers = ServiceLoader.load(DriverManager.class);
Iterator<DriverManager> iterator = providers.iterator();
while(iterator.hasNext()){
DriverManager next = iterator.next();
String connectionInfo = next.getConnectionInfo();
System.out.println(connectionInfo);
}
}
}
源码分析
ServiceLoader类的结构
//定义了配置文件的路径,这就是为何需要新建配置文件
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
//加载的服务或者接口
private final Class<S> service;
// The class loader used to locate, load, and instantiate providers
//类加载器
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
//ServiceLoader创建时的访问控制上下文
private final AccessControlContext acc;
// Cached providers, in instantiation order
//缓存加载的服务,根据实例化的顺序
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
//加载服务的类
private LazyIterator lookupIterator;
reload加载类
//加载lookupIterator
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
//加载接口类
service = Objects.requireNonNull(svc, "Service interface cannot be null");
//加载类加载器
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
//加载访问权限
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
LazyIterator类
1.加载配置文件
2.解析配置文件
3.解析每一行
4.获取配置的实现类的类名加入list
5.利用反射加载 c =Class.forName(cn, false, loader);
6.转换为具体的class对象添加到providersS p = service.cast(c.newInstance()); providers.put(cn, p);
private class LazyIterator implements Iterator<S>{
Class<S> service;
ClassLoader loader;
//URL对象
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//加载完全类限定名 META-INF/servie+接口类名
String fullName = PREFIX + service.getName();
if (loader == null)
//通过类加载转换为URL对象
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;
}
//parse加载对应的服务
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
//next方法
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
}
}
parse解析URL对象方法
解析URL对象
private Iterator<String> parse(Class<?> service, URL u)throws ServiceConfigurationError {
InputStream in = null;
BufferedReader r = null;
//解析文件的配置实现类名list
ArrayList<String> names = new ArrayList<>();
try {
in = u.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
while ((lc = parseLine(service, u, r, lc, names)) >= 0);
} catch (IOException x) {
fail(service, "Error reading configuration file", x);
} finally {
try {
if (r != null) r.close();
if (in != null) in.close();
} catch (IOException y) {
fail(service, "Error closing configuration file", y);
}
}
return names.iterator();
}
parseLine方法
从配置文件解析单行,添加实现类的明名字到list
private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names)
throws IOException, ServiceConfigurationError {
//读取行数据
String ln = r.readLine();
if (ln == null) {
return -1;
}
//判断#的位置
int ci = ln.indexOf('#');
if (ci >= 0) ln = ln.substring(0, ci);
ln = ln.trim();
int n = ln.length();
if (n != 0) {
if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
fail(service, u, lc, "Illegal configuration-file syntax");
int cp = ln.codePointAt(0);
if (!Character.isJavaIdentifierStart(cp))
fail(service, u, lc, "Illegal provider-class name: " + ln);
for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
cp = ln.codePointAt(i);
if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
fail(service, u, lc, "Illegal provider-class name: " + ln);
}
//提供的服务不包含在添加
if (!providers.containsKey(ln) && !names.contains(ln))
names.add(ln);
}
return lc + 1;
}