Dubbo中大量使用了SPI的技术,实现各调用层之间的解耦。所以,了解SPI技术的实现还是很有必要的。
JDK本身也有SPI的实现,Dubbo对该技术实现了增强。
1.SPISPI(Service Provider Interface),是java提供的一套用来被第三方实现或扩展的接口。而SPI技术就是用来查找这些扩展实现。
概念相对比较难懂,读者可以参考这篇博文(笔者是没太读懂):https://www.cnblogs.com/happyframework/archive/2013/09/17/3325560.html
我们直接上示例,示例相对更好明白些。
2.JDK的SPI 2.1 提供接口和一个实现类// Teacher
package xw.demo.adaptive;
public interface Teacher {
void say();
}
// 实现类,提供三个实现类
package xw.demo.adaptive;
public class ChineseTeacher implements Teacher {
@Override
public void say() {
System.out.println("chinese");
}
}
public class EnglishTeacher implements Teacher {
@Override
public void say() {
System.out.println("english");
}
}
public class MathTeacher implements Teacher {
@Override
public void say() {
System.out.println("math");
}
}
2.2 创建文件
在当前项目resources目录下 新建meta-INF/services目录,并在这个目录创建一个与接口全限定名一致的文件(即xw.demo.adaptive.Teacher),文件内容为实现类的全限定名。如下所示
2.3 ServiceLoader加载
ServiceLoaderteachers = ServiceLoader.load(Teacher.class); for(Teacher t : teachers) { t.say(); } // 结果为: chinese math english
看结果值,我们知道ServiceLoader加载了上面Teacher的三个实现类,并成功调用了方法。
2.4 ServiceLoader源码解析public final class ServiceLoaderimplements Iterable{ private static final String PREFIX = "meta-INF/services/"; public staticServiceLoaderload(Classservice) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } public staticServiceLoaderload(Classservice, ClassLoader loader) { return new ServiceLoader<>(service, loader); } private ServiceLoader(Classsvc, 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(); // 创建了一个LazyIterator lookupIterator = new LazyIterator(service, loader); } // 我们再来看下ServiceLoader.iterator方法, public Iteratoriterator() { return new Iterator() { Iterator> 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(); // 都交由LazyIterator来实现了 return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; } }
经过上面的一系列调用分析,我们知道,ServiceLoader.iterator 的next()调用都交由LazyIterator来实现了
private class LazyIterator implements Iterator{ Classservice; ClassLoader loader; Enumerationconfigs = null; Iterator pending = null; String nextName = null; private LazyIterator(Class service, ClassLoader loader) { this.service = service; this.loader = loader; } ... public S next() { if (acc == null) { // 直接交由nextService实现 return nextService(); } else { PrivilegedActionaction = new PrivilegedAction() { public S run() { return nextService(); } }; return AccessController.doPrivileged(action, acc); } } private S nextService() { // 调用hasNextService()实现 if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class> c = null; try { c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { ... } ... try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { ... } throw new Error(); // This cannot happen } private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { // 本例中即meta-INF/services/xw.demo.DemoService String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else // 加载该路径下的文件,到Enumerationconfigs中 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即文件内容,本例中即xw.demo.DemoServiceImpl nextName = pending.next(); return true; } }
代码不算复杂,主要就是解析meta-INF/services目录下的以接口全限定名为文件名的文件,解析文件的内容,并创建对应Class的实例
2.5 ServiceLoader的应用JDK提供的ServiceLoader技术,有很多广泛的应用。最为大家所熟悉的应该就是DriverManger了。
DriverManger在加载的时候,就依据ServiceLoader的方式扫描了一下当前有哪些Driver的实现,并主动加载。
public class DriverManager {
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
String drivers;
...
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
// 通过ServiceLoader加载
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
...
String[] driversList = drivers.split(":");
for (String aDriver : driversList) {
try {
// 创建对应Driver的实例
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
}
}
}
}
当我们引入mysql-connection-java.jar包时,会发现
也是按照标准方式来实现的自动添加Driver
3.Dubbo的SPI(基础版)Dubbo也有自己的SPI,我们先来看其实现,然后再总结其优势。
3.1 提供接口和实现类package xw.demo.adaptive;
// 较上文中的不同点就是多加了一个@SPI注解
@SPI
public interface Teacher {
void say();
}
// 三个实现类与上面示例相同,不再赘述
3.2 创建文件
在meta-INF/dubbo目录下创建名为xw.demo.adaptive.Teacher的文件,内容如下
注意:也可以将该文件创建在meta-INF/services目录下,Dubbo也是支持的,但是为了与JDK原生的使用方式做一定的区分,特地写在meta-INF/dubbo目录下
3.3 ExtensionLoader加载测试ExtensionLoaderextensionLoader = ExtensionLoader.getExtensionLoader(Teacher.class); Teacher teacher = extensionLoader.getExtension("chinese"); teacher.say(); // 结果如下: chinese
总结:相比较JDK的加载方式而言,Dubbo的这种方式,可以精准的加载对应的实现类,而其他我们不需要的实现类,则不需要被加载。
4.Dubbo的SPI(Wrap版)Dubbo在SPI的使用中,有很多包装类的实现。不动声色的就将实现类包装了一层,类似于动态代理的模式。
4.1 提供接口和实现类同3.1,就是多一个Wrap的实现类(共四个实现类,另外三个同上,ChineseTeacher、MathTeacher、EnglishTeacher)
package xw.demo.adaptive;
public class TeacherWrapper implements Teacher {
// 需要有一个Teacher类型的属性
private Teacher teacher;
public TeacherWrapper(Teacher teacher) {
if (teacher == null) {
throw new IllegalArgumentException("teacher == null");
}
this.teacher = teacher;
}
@Override
public void say() {
System.out.println("start wrap...");
teacher.say();
}
}
并将该实现类添加到上面的xw.demo.adaptive.Teacher文件中,如下
4.2 ExtensionLoader加载测试
ExtensionLoaderextensionLoader = ExtensionLoader.getExtensionLoader(Teacher.class); Teacher teacher = extensionLoader.getExtension("chinese"); teacher.say(); // 结果如下: start wrap... chinese
与3.3的测试结果有所不同,这里多了TeacherWrapper类的一层封装,所以对ChineseTeacher.say()进行调用时,先打印了封装类的输出语句。
Dubbo有很多关于封装类的使用场景,例如关于Protocol的封装类
ProtocolFilterWrapper、ProtocolListenerWrapper,这两个类在对外提供服务时会用到。
5.Dubbo的SPI(Adaptive方式)相较于3这种简单的使用方式,Dubbo源码中更常使用的是Adaptive方式,我们先来看下示例。
5.1 提供接口和实现类package xw.demo.adaptive;
@SPI("chinese")
public interface Teacher {
void say();
@Adaptive
void teach(URL url);
}
// 实现类
public class ChineseTeacher implements Teacher {
@Override
public void say() {
System.out.println("chinese");
}
@Override
public void teach(URL url) {
System.out.println("teach chinese");
}
}
public class MathTeacher implements Teacher {
@Override
public void say() {
System.out.println("math");
}
@Override
public void teach(URL url) {
System.out.println("teach math");
}
}
public class EnglishTeacher implements Teacher {
@Override
public void say() {
System.out.println("english");
}
@Override
public void teach(URL url) {
System.out.println("teach english");
}
}
5.2 创建文件
同3.2,不再赘述
5.3 ExtensionLoader加载测试5.3.1 测试情况一
Teacher teacher = ExtensionLoader.getExtensionLoader(Teacher.class).getAdaptiveExtension();
URL url = URL.valueOf("test://localhost/test");
teacher.teach(url);
// 结果为
teach chinese
5.3.2 测试情况二
Teacher teacher = ExtensionLoader.getExtensionLoader(Teacher.class).getAdaptiveExtension();
// url有所不同
URL url = URL.valueOf("test://localhost/test?teacher=english");
teacher.teach(url);
// 结果为
teach english
5.3.3 测试情况三
// 给MathTeacher添加@Adaptive注解
@Adaptive
public class MathTeacher implements Teacher {
@Override
public void say() {
System.out.println("math");
}
@Override
public void teach(URL url) {
System.out.println("teach math");
}
}
// 测试代码
Teacher teacher = ExtensionLoader.getExtensionLoader(Teacher.class).getAdaptiveExtension();
// 两种url都可以
URL url = URL.valueOf("test://localhost/test?teacher=english");
// URL url = URL.valueOf("test://localhost/test");
teacher.teach(url);
// 结果为
teach math
总结:Adaptive的这种方式在Dubbo中使用较为频繁。通过这三种测试结果我们可以总结出:
1.当在实现类上指定了@Adaptive时,其级别最高,ExtensionLoader.getAdaptiveExtension()默认就返回该实现类
2.当在URL中指定了条件(本例中为teacher=english),则ExtensionLoader.getAdaptiveExtension()会返回条件中指定的实现类
3.当没有任何指定时,则使用默认值,也就是在接口的@SPI注解中指定的(本例为chinese)
注意:总结2中的条件,这个teacher是怎么来的呢?实际就是我们接口的小写之后的字母(slow(Teacher))。
如果接口是多字母组成的会怎么办呢?例如ExtAdaptive,那么不同字母之间就用.来分割,最终变成ext.adaptive
6.Dubbo SPI源码解析Dubbo的SPI实现了这么多层次的使用,源码也是比较有意思的,我们一起来看下
6.1 ExtensionLoader.getExtensionLoader()解析public class ExtensionLoader{ private static final ConcurrentMap , ExtensionLoader>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64); private static final ConcurrentMap , Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64); // 当前关注的类型 private final Class> type; public static ExtensionLoader getExtensionLoader(Class type) { ... ExtensionLoader loader = (ExtensionLoader ) EXTENSION_LOADERS.get(type); if (loader == null) { // 直接创建对应类型的ExtensionLoader EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader (type)); loader = (ExtensionLoader ) EXTENSION_LOADERS.get(type); } return loader; } // 构造方法 private ExtensionLoader(Class> type) { // type即为本例中的SpiDemoService this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); } }
ExtensionLoader.getExtensionLoader()没有太多花哨的东西,就是设置了ExtensionLoader的基本属性
6.2 ExtensionLoader.getExtension(name)方法分析(先分析简单版本)public class ExtensionLoader{ // extensionLoader.getExtension(name) 方法分析 public T getExtension(String name) { ... final Holder
总体还是稍微有点绕的,简单来说,就是在寻找Interface对应name的实现类时,通过扫描ClassPath下指定的三个目录meta-INF/dubbo/internal/、meta-INF/dubbo/、meta-INF/services/,找到对应Interface名的文件,然后加载文件内容,这时比对name,将符合的name的value值实例化为对象。
6.3 ExtensionLoader.getAdaptiveExtension(name)方法分析(Adaptive)public class ExtensionLoader{ public T getAdaptiveExtension() { Object instance = cachedAdaptiveInstance.get(); if (instance == null) { if (createAdaptiveInstanceError != null) { ... } synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { // 在这里 instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t); } } } } return (T) instance; } // createAdaptiveExtension() private T createAdaptiveExtension() { try { // getAdaptiveExtensionClass()主要点 return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); } } // getAdaptiveExtensionClass() private Class> getAdaptiveExtensionClass() { // 这里与5.2中的过程一样的,中间的过程不再赘述,最终还是到达loadClass()方法,我们直接分析与上面不同的分支 getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); } // loadClass() private void loadClass(Map > extensionClasses, java.net.URL resourceURL, Class> clazz, String name, boolean overridden) throws NoSuchMethodException { if (!type.isAssignableFrom(clazz)) { ... } // 见4.3.3 测试情况三,当类上有@Adaptive注解时,直接将当前该类赋值给 ExtensionLoader.cachedAdaptiveClass属性 // 后续返回getAdaptiveExtensionClass()方法时,会看到,cachedAdaptiveClass不为空,则会直接将当前类返回回去。所以带有@Adaptive注解的实现类优先级最高 if (clazz.isAnnotationPresent(Adaptive.class)) { cacheAdaptiveClass(clazz, overridden); // 什么样的才是wrapperClass,把当前接口当做构造函数的入参就叫包装类 } else if (isWrapperClass(clazz)) { cacheWrapperClass(clazz); } else { // 最终获取对应的class信息(本例中就是在这里获取) clazz.getConstructor(); if (StringUtils.isEmpty(name)) { name = findAnnotationName(clazz); if (name.length() == 0) { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); } } String[] names = NAME_SEPARATOR.split(name); if (ArrayUtils.isNotEmpty(names)) { cacheActivateClass(clazz, names[0]); for (String n : names) { cacheName(clazz, n); saveInExtensionClass(extensionClasses, clazz, n, overridden); } } } } }
那么问题来了,针对4.3.1 4.3.2的这两种测试情况怎么看这个代码呢?我们回到getAdaptiveExtensionClass()方法
public class ExtensionLoader{ private Class> getAdaptiveExtensionClass() { getExtensionClasses(); // 非4.3.3情况,则cachedAdaptiveClass会为空 if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } // 逻辑走到这 return cachedAdaptiveClass = createAdaptiveExtensionClass(); } private Class> createAdaptiveExtensionClass() { // 通过javassist的方式动态生成其代理类,具体我们不再深入,我们来看下生成的代理类的具体代码 String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); ClassLoader classLoader = findClassLoader(); org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader); } }
通过javassist的方式生成的动态代理类代码(针对本例而言)
public class Teacher$Adaptive implements xw.demo.adaptive.Teacher {
public void teach(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) {
throw new IllegalArgumentException("url == null");
}
org.apache.dubbo.common.URL url = arg0;
// 供调用方手动设置参数,参数名为teacher,具体使用方式参见4.3.2 测试情况二
// 默认值为chinese,是由Teacher接口@SPI("chinese")指定的
String extName = url.getParameter("teacher", "chinese");
if (extName == null) {
throw new IllegalStateException("Failed to get extension (xw.demo.adaptive.Teacher) name from url (" + url.toString() + ") use keys([teacher])");
}
// 最终回归到原生的调用方式getExtension(name),类似于3.3的方式
xw.demo.adaptive.Teacher extension = (xw.demo.adaptive.Teacher) ExtensionLoader.getExtensionLoader(xw.demo.adaptive.Teacher.class).getExtension(extName);
extension.teach(arg0);
}
public void say() {
throw new UnsupportedOperationException("The method public abstract void xw.demo.adaptive.Teacher.say() of interface xw.demo.adaptive.Teacher is not adaptive method!");
}
}
总结:
我们再把上面的获取代理类的顺序重复一下:
1.当在实现类上指定了@Adaptive时,其级别最高,ExtensionLoader.getAdaptiveExtension()默认就返回该实现类
2.当在URL中指定了条件,则ExtensionLoader.getAdaptiveExtension()会返回条件中指定的实现类
3.当没有任何指定时,则使用默认值,也就是在接口的@SPI注解中指定的



