ServiceLoader
接口实现类加载器
成员变量
PREFIX:接口实现类加载路径前缀 jar 包"META-INF/services/"
service:需要加载的接口
loader:类加载器
providers(Map):接口实现类-》对象 key:实现类全限定名,value:实现类实例
lookupIterator :多个实现类遍历器,ServiceLoader 内部实现遍历
加载流程
// 默认使用当前线程上下文类加载器,继续往下看
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();
}
// 初始化service 遍历器
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
// 还是简单初始化了遍历器内部变量,重点在哪呢???
//还是从接口实现类实例获取流程入手
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
接口实现类实例获取流程
ServiceLoader.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();
}
};
}
乍一看用的是SericeLoader 内部遍历器LazyIterator(还记得加载流程已经创建这个遍历器对象不?),下面重点分析LazyIterator
成员变量
Class<S> service; 当前要加载的接口
ClassLoader loader; 当前类加载器
Enumeration<URL> configs = null; 接口实现类配置文件,具体待后仔细看
Iterator<String> pending = null; 接口实现类遍历器(同一个接口可以有多个实现了in)
String nextName = null; 下一个要加载的实现类全限定名
每个成员变量含义请稍后仔细看。
成员方法
hasNext 判断是否还有实现类
next 接口实现类实例获取
下面分别分析两个成员方法,注:每个方法都有访问控制 AccessController 里面提供一些native 方法 具体有兴趣的可继续分析,在此不往下讨论。
// 里面继续调 hasNextService
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/"
//在此完整的配置文件路径变为 "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;
}
// 没有什么逻辑,多去文件,解析文件每行parseLine ,
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();
}
//作用是解析配置文件中实现类名称,具体配置,配置内容限制可自行了解一下
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;
}
//next 里面使用 nextService
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
}
至此核心流程已经完事,其他一些细节感兴趣的可以自行查看源码
原生 SPI 应用场景之JDBC
还记得 使用原生JDBC 操作步骤吗
1.加载数据库驱动(MySql,Oracle)
2.获取数据库连接
3.执行SQL
还有人知道第一步有多少种方式? 1.new com.mysql.jdbc.Driver() 以Mysql 为例 2.Class.forName(“com.mysql.jdbc.Driver”)
下面简单看一下每一种方式 com.mysql.jdbc.Driver()
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
以上可以看见里面什么都没干,只是用DriverManager 注册了一下Mysql 驱动 间接引起 DriverManager类加载,只是初始化代码块(类加载三步骤自行复习吧)
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() {
//利用原生SPI 加载具体数据库驱动实现,具体流程可参考SPI 加载流程
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;
}
});
Class.forName(“com.mysql.jdbc.Driver”) 只是调用方式不同,最终还是会引起com.mysql.jdbc.Driver 静态初始化块代码执行-》DriverManager 加载
总结
SPI 优点:接口定义,实现完全分开,使用方可以只关注自己需要用到的部分。 JDK 原生SPI 支持的缺点:可能会加载实例化所有实现类,实现类的获取没那么灵活。