【Java实战】源码解析Java SPI(Service Provider Interface )机制原理
一、背景知识
在阅读开源框架源码时,发现许多框架都支持SPI(Service Provider Interface ),前面有篇文章JDBC对Driver的加载时应用了SPI,参考【Hibernate实战】源码解析Hibernate参数绑定及PreparedStatement防SQL注入原理,于是借着JDBC对Driver的加载实现,分析下SPI机制。
二、什么是SPI
Service Provider Interface (SPI) is an API intended to be implemented or extended by a third party. It can be used to enable framework extension and replaceable components.
简单翻译下:
A service provider interface (SPI) is the set of public interfaces and abstract classes that a service defines. The SPI defines the classes and methods available to your application. A service provider implements the SPI. An application with extensible services will allow you, vendors, and perhaps even customers to add service providers without modifying the original application.
三、SPI原理
1、JDBC示例程序
public static void JDBCExample(){
try {
//Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/hhl?useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=25&prepStmtCacheSqlLimit=2048&characterEncoding=utf8&useSSL=false",
"root", "123456");
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM product p WHERE p.productName=?");
preparedStatement.setString(1,"Mango");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
System.out.println(resultSet.getString(1));
}
resultSet.close();
preparedStatement.close();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 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");
}
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;
}
// 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() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> 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{
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);
}
}
}
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); 2
Iterator<Driver> driversIterator = loadedDrivers.iterator(); 3
try{
while(driversIterator.hasNext()) { 4
driversIterator.next(); 5
}
} catch(Throwable t) {
// Do nothing
}
2、逐行看下 java.util.ServiceLoader.java
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(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();
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
// Private inner class implementing fully-lazy provider lookup
//
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;
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
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);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
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
}
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);
}
}
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);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
创建改对象的原因就是为了实现延迟服务提供者查找。延迟到什么时候,继续看代码。
3、loadedDrivers.iterator()
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();
}
};
}
创建了一个内部类Iterator,用于操作缓存providers和延迟加载类lookupIterator
4、driversIterator.hasNext()
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
用到了LazyIterator中的hasNext()
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 {
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;
}
private static final String PREFIX = "META-INF/services/";
com.mysql.jdbc.Driver com.mysql.fabric.jdbc.FabricMySQLDriver
private Iterator<String> parse(Class<?> service, URL u)
throws ServiceConfigurationError
{
InputStream in = null;
BufferedReader r = null;
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();
}
5、driversIterator.next()
接着看Next
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
起初走lookupIterator.next()
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);
}
}
直接走nextService
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
}
这里会加载并初始化获取到的驱动,例如com.mysql.jdbc.Driver,这里还是需要Class.forName。采用
S p = service.cast(c.newInstance());
providers.put(cn, p);
初始化驱动,因此驱动需要有一个默认的构造函数。
至此,利用ServiceLoader加载并初始化驱动的操作就完成了。那么那么多驱动,要选择哪个驱动呢,就是根据url确定
jdbc:mysql://127.0.0.1:3306/hhl
如上url就会选择com.mysql.jdbc.Driver进行连接操作,其判断由驱动自己去做,由驱动中的acceptsURL及parseURL判断驱动支持的url,具体可看代码com.mysql.jdbc.NonRegisteringDriver.java
总结:SPI的机制就是在不修改原有程序的基础上实现扩展
当服务的提供者,提供了服务接口(java.sql.Driver)的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里指定。
【Java实战】源码解析Java SPI(Service Provider Interface )机制原理的更多相关文章
- java的spi(Service Provider Interface)机制及源码(java8)
1.什么是java的spi spi 全称为 service provider interface 即 服务提供接口,用来作为服务的扩展发现.在运行时动态添加接口的实现,是对接口的实现类的创建管理. 2 ...
- SPI(Service Provider Interface)机制
JAVA SPI 约定如下:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/ 目录中同时创建一个以服务接口命名的文件,该文件中的内容就是实现该服务接口的具体 ...
- 【转】Java HashMap 源码解析(好文章)
.fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...
- Java集合类源码解析:Vector
[学习笔记]转载 Java集合类源码解析:Vector 引言 之前的文章我们学习了一个集合类 ArrayList,今天讲它的一个兄弟 Vector.为什么说是它兄弟呢?因为从容器的构造来说,Vec ...
- Java——LinkedHashMap源码解析
以下针对JDK 1.8版本中的LinkedHashMap进行分析. 对于HashMap的源码解析,可阅读Java--HashMap源码解析 概述 哈希表和链表基于Map接口的实现,其具有可预测的迭 ...
- Dubbo源码解析之SPI(一):扩展类的加载过程
Dubbo是一款开源的.高性能且轻量级的Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用.智能容错和负载均衡,以及服务自动注册和发现. Dubbo最早是阿里公司内部的RPC框架,于 ...
- Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析
Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析 Volley之所以高效好用,一个在于请求重试策略,一个就在于请求结果缓存. 通过上一篇文章http://www.cnblogs.com ...
- [源码解析] TensorFlow 分布式环境(8) --- 通信机制
[源码解析] TensorFlow 分布式环境(8) --- 通信机制 目录 [源码解析] TensorFlow 分布式环境(8) --- 通信机制 1. 机制 1.1 消息标识符 1.1.1 定义 ...
- JAVA SPI(Service Provider Interface)原理、设计及源码解析(其一)
背景 团队内部轮流技术分享,其他人都是分享源码,我每次都是设计和架构,感觉自己太特立独行.这次我要合群点,分享点源码. 概念 Service Provider Interface:服务提供方接口.是一 ...
随机推荐
- 20145335郝昊《Java程序设计》课程总结
20145335郝昊<Java程序设计>课程总结 1.读书笔记汇总 第一周学习总结:http://www.cnblogs.com/20145335hh/p/5244638.html 第二周 ...
- Quick Launcher FAQ
Q: Which category can be previewed and selected in Finder? A: All the categories can be selected in ...
- 百度开源分布式id生成器uid-generator源码剖析
百度uid-generator源码 https://github.com/baidu/uid-generator snowflake算法 uid-generator是基于Twitter开源的snowf ...
- [BZOJ4756]Promotion Counting
Description The cows have once again tried to form a startup company, failing to remember from past ...
- Codeforces - 828E DNA Evolution —— 很多棵树状数组
题目链接:http://codeforces.com/contest/828/problem/E E. DNA Evolution time limit per test 2 seconds memo ...
- SaltStack执行状态收集入库-第五篇
实验目标 1.salt执行的状态然后结果写入MySQL可以方便查询执行salt执行的历史记录 实现方法 1.使用salt的return功能,是minion直接写入MySQL(相对比较麻烦) 2.使用m ...
- mybatis动态插入数据(使用trim标签)
知识点: 当向一张表里插入数据,传入的参数不固定时,使用到mybatis的动态插入(trim标签) 参考博客:https://blog.csdn.net/h12kjgj/article/details ...
- 2017 ACM/ICPC Asia Regional Qingdao Online - 1008 Chinese Zodiac
2017-09-17 13:28:04 writer:pprp 签到题:1008 Chinese Zodiac #include <iostream> #include <strin ...
- Python学习札记(十) Function1 函数调用
参考:调用函数 Note 1.Python内置了很多函数,我们可以直接对这些函数进行调用.Python官网:Built in Fuctions eg.调用abs函数(取绝对值函数): >> ...
- from: can't read /var/mail/xxx 解决方法
在执行一个发包脚本的时候,遇到了如下问题: from: can't read /var/mail/scapy.all 原因:脚本是没有问题的,但它并不是可以被python执行的可执行文件. 解决方法: ...