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加载
ServiceLoader teachers = ServiceLoader.load(Teacher.class);
for(Teacher t : teachers) {
t.say();
}
// 结果为:
chinese
math
english
看结果值,我们知道ServiceLoader加载了上面Teacher的三个实现类,并成功调用了方法。
2.4 ServiceLoader源码解析public final class ServiceLoader implements Iterable{
private static final String PREFIX = "META-INF/services/";
public static ServiceLoader load(Class service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static ServiceLoader load(Class service, ClassLoader loader)
{
return new ServiceLoader(service, loader);
}
private ServiceLoader(Class 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();
// 创建了一个LazyIterator
lookupIterator = new LazyIterator(service, loader);
}
// 我们再来看下ServiceLoader.iterator方法,
public Iterator iterator() {
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{
Class service;
ClassLoader loader;
Enumeration configs = 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 {
PrivilegedAction action = 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
// 加载该路径下的文件,到Enumeration configs中
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加载测试ExtensionLoader extensionLoader = 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加载测试
ExtensionLoader extensionLoader = 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();
/**
* 添加一个方法,入参必须含有URL,必须有@Adaptive注释
* @param url
*/
@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> EXTENSION_LOADERS = new ConcurrentHashMap(64);
private static final ConcurrentMap 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 holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 在这里
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
// createExtension()
private T createExtension(String name) {
// getExtensionClasses()方法很重要,我们先来分析这个方法
// getExtensionClasses()返回的已经是 name->实现类的一个map了
// 所以在这里直接通过name找到了对应的实现类
Class clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// set方法依赖注入
// 这里可以通过spring获取bean的方式或SPI的方式注入属性
injectExtension(instance);
Set> getExtensionClasses() {
Map> loadExtensionClasses() {
cacheDefaultExtensionName();
Map getAdaptiveExtensionClass() {
// 这里与5.2中的过程一样的,中间的过程不再赘述,最终还是到达loadClass()方法,我们直接分析与上面不同的分支
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
// loadClass()
private void loadClass(Map 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注解中指定的