背景
在Dubbo源码学习过程中,首先就遇到了spi机制,看了一下别人写的介绍,感觉还是基础的,而且它在jdbc中也有应用。但我却感觉很陌生。总之Java基础需要不断加强,下面总结一下spi。
一、别人已经做了不错的总结了,参考文章
二、使用demo
1.项目java/main/resources/META-INF/services目录下新建文本文件,文件名为com.alibaba.dubbo.cache.CacheFactory;
2.刚才文件中的文本内容为
com.alibaba.dubbo.cache.support.jcache.JCacheFactory
com.alibaba.dubbo.cache.support.lru.LruCacheFactory
3.测试类
public class SpiTest {
public static void main(String[] args){
ServiceLoader<CacheFactory> load = ServiceLoader.load(CacheFactory.class); //ServiceLoader在java.util包中
Iterator<CacheFactory> iterator = load.iterator();
while(iterator.hasNext()){
CacheFactory next = iterator.next();
System.out.println(next);
}
}
}
4.运行效果如下
com.alibaba.dubbo.cache.support.jcache.JCacheFactory@3f99bd52
com.alibaba.dubbo.cache.support.lru.LruCacheFactory@7adf9f5f
Process finished with exit code 0
三、spi源码分析
这么一个简单的例子初看也不知道是干嘛的。感觉就是编写一个接口和几个实现类,然后再根据规则定义个配置文件,最后程序能够根据接口和配置文件,得到所有实现类的实例对象,底层涉及到反射技术。但是具体怎么用?以及Dubbo中spi扩展机制又是什么?这些需要一点点理解消化。先不扯那么远,先来把这个小例子的源码看看,毕竟千里之行始于当下。
<1>ServiceLoader.load(CacheFactory.class)
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();
}
有必要看看ServiceLoad的成员方法了,如下
public final class ServiceLoader<S>
implements Iterable<S>
{
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
private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
...
}
继续
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
继续,来看看这个LazyIterator类
小总结:到这里ServiceLoader.load(CacheFactory.class)源码涉及就这多么,关键就是初始化了LazyIterator类(ServiceLoad的私有内部类)的lookupIterator对象,这个对象看样子好像会比较有用。
<2>load.iterator()
public Iterator<S> iterator() {
return new Iterator<S>() { //返回了一个迭代器A
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator(); //注意,这个providers是类初始化new的一个空的Map,什么时候真正赋值呢?这里需要注意一下!
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext(); //迭代器A的hasNext()方法真正调用的是前面分析的lookupIterator对象的hasNext方法
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next(); ////迭代器A的next()方法真正调用的是前面分析的lookupIterator对象的next方法
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
小总结:Serviceload返回的迭代器,实际是对前面分析中关键对象lookupIterator的封装。所以关键点就是这个私有内部类的实现了。下面继续对它分析。
<3>LazyIterator私有内部类源码
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()); //Class类中的cast(Object obj),强转
providers.put(cn, p); //这里解答了前面提到的providers真正初始话的问题。
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();
}
}
小总结:底层果然是反射,先分析到这
四、JDBC中应用
String url = "jdbc:mysql:///consult?serverTimezone=UTC";
String user = "root";
String password = "root";
Class.forName("com.mysql.jdbc.Driver"); //新的不需要,但有也能运行
Connection connection = DriverManager.getConnection(url, user, password);
新版JDBC不再需要Class.forName()来显式加载jdbc驱动,底层就是利用spi技术提前加载好了。下面是我源码总结图
小总结:这篇文章仅仅小小总结了一下spi,希望以后继续更新更多spi的实际应用分析。回到dubbo学习主线吧(20200309)