正文
一、什么是 SPI?
SPI(Service Provider Interface)是一种服务发现机制,它的作用是解耦接口和其具体实现,让各厂商可以自定义自己的扩展实现,并使得程序可以自动使用引入的组件。 什么意思呢?举个例子就清楚了,Java 原生就提供 SPI 机制,比如数据库连接驱动的实现就是 SPI 很好的一个应用,在 Java sql 下提供了 Driver 接口,而具体的驱动程序是由各个数据库厂商实现的,平时我们要连接哪个数据库只需要引入对应的驱动 jar 包就可以了,非常方便,即使数据库变更也一样,我们不需要改变代码。而 Dubbo 的 SPI 机制则是在此基础上提供了更强大的功能,因此,学习了解 Java SPI 更益于深入了解 Dubbo,下面就先来看看 Java SPI 的使用吧。
1. Java SPI 的实现
public interface SPI {
void sayHello(String s);
}
public class SPIImpl1 implements SPI {
@Override public void sayHello(String s) { System.out.println("Hello, " + s + "! I'm one"); }}
public class SPIImpl2 implements SPI {
@Override public void sayHello(String s) { System.out.println("Hello, " + s + "! I'm two"); }}
复制代码
cn.dark.SPIImpl1cn.dark.SPIImpl2
复制代码
ServiceLoader<SPI> load = ServiceLoader.load(SPI.class);for (SPI spi : load) { spi.sayHello("SPI");}
复制代码
输出:
Hello, SPI! I'm oneHello, SPI! I'm two
复制代码
如果需要扩展新的实现,只需要将实现类配置到资源文件中,并引入对应的 Jar 即可。Java SPI 机制就这么简单,其实现原理也很简单,读者们可以自行阅读源码,这里就不再详细分析了,那 Dubbo 的 SPI 有何异同呢?
2. Dubbo SPI 实现原理
由配置文件得到的猜想
Dubbo SPI 是基于 Java 原生 SPI 思想重新实现的一套更加强大的 SPI 机制,类似的你可以在 Dubbo 的 META-INF.dubbo.internal(不止这一个路径,后面在源码中会看到)路径下看到很多以接口全类名命名的配置文件,但是文件内容和 JAVA SPI 有点不一样,以 Protocol 扩展为例:
registry=com.alibaba.dubbo.registry.integration.RegistryProtocolfilter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapperlistener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrappermock=com.alibaba.dubbo.rpc.support.MockProtocolinjvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocoldubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocolrmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocolhessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocolcom.alibaba.dubbo.rpc.protocol.http.HttpProtocolcom.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocolthrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocolmemcached=memcom.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocolredis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol
复制代码
看到这么多扩展类(每一个配置文件中都有很多),我们首先应该思考一个问题:Dubbo 一启动,就加载所有的扩展类么?作为一个优秀的 RPC 框架,肯定不会耗时耗力做这样的无用功,所以肯定会通过一种方式拿到指定的扩展才对。我们可以看到大多是以键值对方式(表示为 extName-value)配置的扩展,那么不难猜测,这里的 extName 就是用来实现上面所说的功能的。 那到底是不是呢?以上纯属猜测,下面就到源码中去验证。
SPI 源码
Dubbo 中实现 SPI 的核心类是 ExtensionLoader,该类并未提供公共的构造方法来初始化,而是通过 getExtensionLoader 方法获取一个 loader 对象:
// loader缓存private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) throw new IllegalArgumentException("Extension type == null"); if(!type.isInterface()) { throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } if(!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); }
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader;}
private final ExtensionFactory objectFactory;private ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());}
复制代码
这里的 class 参数就是扩展点的接口类型,每一个 loader 都需要绑定一个扩展点类型。然后首先从缓存中获取 loader,未获取到就初始化一个 loader 并放入缓存。而在私有构造器初始化的时候我们需要注意 objectFactory 这个变量,先大概有个映像,后面会用到。 拿到 loader 之后,就可以调用 getExtension 方法去获取指定的扩展点了,该方法传入了一个 name 参数,不难猜测这个就是配置文件中的键,可以 debugger 验证一下:
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();public T getExtension(String name) { if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); if ("true".equals(name)) { return getDefaultExtension(); } // 从缓存中获取Holder对象,该对象的值就是扩展对象 Holder<Object> holder = cachedInstances.get(name); if (holder == null) { cachedInstances.putIfAbsent(name, new Holder<Object>()); holder = cachedInstances.get(name); } // 缓存中没有该扩展,说明还没有加载扩展类,就去配置文件中加载并创建对应的扩展对象 // 这里通过双重校验锁的方式保证线程安全,Dubbo中大量运用了该技巧 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 static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();private T createExtension(String 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, (T) clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } // 依赖注入,这里及后面都和当前流程无关,可以先略过,有个印象就好 injectExtension(instance); // 获取包装类并实例化,最后注入依赖对象 Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && wrapperClasses.size() > 0) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + ") could not be instantiated: " + t.getMessage(), t); }}
复制代码
关键点代码就在 getExtensionClasses 方法中,怎么从配置文件中加载扩展类的。而该方法主要是调用了 loadExtensionClasses 方法:
private Map<String, Class<?>> loadExtensionClasses() { // 判断接口上是否标注有 @SPI注解,该注解的值就是默认使用的扩展类, // 赋值给cachedDefaultName变量缓存起来 final SPI defaultAnnotation = type.getAnnotation(SPI.class); if(defaultAnnotation != null) { String value = defaultAnnotation.value(); if(value != null && (value = value.trim()).length() > 0) { String[] names = NAME_SEPARATOR.split(value); if(names.length > 1) { throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names)); } if(names.length == 1) cachedDefaultName = names[0]; } }
// 真正读取配置文件的方法 Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadFile(extensionClasses, DUBBO_DIRECTORY); loadFile(extensionClasses, SERVICES_DIRECTORY); return extensionClasses;}
复制代码
该方法主要是缓存当前扩展接口指定的默认扩展实现类(@SPI 注解指定),并调用 loadFile 读取配置文件,从这里我们可以看到 Dubbo 默认是读取以下 3 个文件夹中的配置文件:
private static final String SERVICES_DIRECTORY = "META-INF/services/"; private static final String DUBBO_DIRECTORY = "META-INF/dubbo/"; private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
复制代码
然后是 loadFile,该方法很长,不全部放上来了,这里提取关键的代码:
String fileName = dir + type.getName();
复制代码
首先通过文件全路径找到对应的文件,并用 BufferedReader 一行行读取文件内容:
String name = null;int i = line.indexOf('=');if (i > 0) { // 配置文件中的键 name = line.substring(0, i).trim(); // 扩展点全类名 line = line.substring(i + 1).trim();}if (line.length() > 0) { // 加载class,如果有些类的依赖jar包未导入,这里就会抛出异常(比如WebserviceProtocol) Class<?> clazz = Class.forName(line, true, classLoader); // 验证当前类型是否是扩展类的父类型 if (! type.isAssignableFrom(clazz)) { throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface."); } // 扩展类是否标注了 @Adaptive 注解,表示为一个自定义的自适应扩展类 // 如果是将其缓存到cachedAdaptiveClass if (clazz.isAnnotationPresent(Adaptive.class)) { if(cachedAdaptiveClass == null) { cachedAdaptiveClass = clazz; } else if (! cachedAdaptiveClass.equals(clazz)) { // 超过一个自定义的自适应扩展类就抛出异常 throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); } } else { try { // 进入到该分支表示为Wrapper装饰扩展类,该类都有一个特征:包含 // 一个有参的构造器,如果没有,就抛出异常进入到另一个分支, // Wrapper类的作用我们后面再分析 clazz.getConstructor(type); // 缓存Wrapper到cachedWrapperClasses中 Set<Class<?>> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); wrappers = cachedWrapperClasses; } wrappers.add(clazz); } catch (NoSuchMethodException e) { // 进入此分支表示为一个普通的扩展类 clazz.getConstructor(); if (name == null || name.length() == 0) { // 由于历史原因,Dubbo最开始配置文件中并不是以K-V来配置的 // 扩展点,而是会通过@Extension注解指定,所以这里会通过 // 该注解去获取到name name = findAnnotationName(clazz); // 由于@Extension废弃使用,但配置文件中仍存在非K-V的配置, // 所以这里是直接通过类名获取简单的name if (name == null || name.length() == 0) { if (clazz.getSimpleName().length() > type.getSimpleName().length() && clazz.getSimpleName().endsWith(type.getSimpleName())) { name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase(); } else { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url); } } } String[] names = NAME_SEPARATOR.split(name); if (names != null && names.length > 0) { // 判断当前扩展点是否标注有@Activate注解,该注解表示 // 该扩展点自动激活 Activate activate = clazz.getAnnotation(Activate.class); if (activate != null) { cachedActivates.put(names[0], activate); } for (String n : names) { if (! cachedNames.containsKey(clazz)) { cachedNames.put(clazz, n); } Class<?> c = extensionClasses.get(n); if (c == null) { extensionClasses.put(n, clazz); } else if (c != clazz) { throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); } } } } }}
复制代码
至此,我们就看到了 Dubbo SPI 的实现全过程,我们也了解了 Dubbo 强大的扩展性是如何实现的,但是这么多扩展,Dubbo 在运行中是如何决定调用哪一个扩展点的方法呢?这就是 Dubbo 另一强大的机制:自适应扩展。(PS:这里需要留意 cachedAdaptiveClass 和 cachedWrapperClasses 两个变量的赋值,后面会用到。)
二、自适应扩展机制
什么是自适应扩展?上文刚刚也说了,Dubbo 中存在很多的扩展类,这些扩展类不可能一开始就全部初始化,那样非常的耗费资源,所以我们应该在使用到该类的时候再进行初始化,也就是懒加载。但是这是比较矛盾的,拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载(官网原话)。所以也就有了自适应扩展机制,那么这个原理是怎样的呢? 首先需要了解 @Adaptive 注解,该注解可以标注在类和方法上:
标注在类上,表明该类为自定义的适配类
标注在方法上,表明需要动态的为该方法创建适配类
当有地方调用扩展类的方法时,首先会调用适配类的方法,然后适配类再根据扩展名称调用 getExtension 方法拿到对应的扩展类对象,最后调用该对象的方法即可。流程就这么简单,下面看看代码怎么实现的。 首先我们回到 ExtensionLoader 的构造方法中:
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
复制代码
其中调用了 getAdaptiveExtension 方法,从方法名不难看出就是去获取一个适配类对象:
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();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("fail to create adaptive instance: " + t.toString(), t); } } } } else { throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } }
return (T) instance;}
复制代码
该方法很简单,就是从缓存中获取适配类对象,未获取到就调用 createAdaptiveExtension 方法加载适配类并通过反射创建对象:
private T createAdaptiveExtension() { try { // 这里又注入了些东西,先略过 return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e); }}
复制代码
调用 getAdaptiveExtensionClass 加载适配类:
private Class<?> getAdaptiveExtensionClass() { // 这里刚刚分析过了,从配置文件中加载配置类 getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass();}
复制代码
cachedAdaptiveClass 这个变量应该还没忘,在 loadFile 里赋值的,即我们自定义的适配扩展类,若没有则调用 createAdaptiveExtensionClass 动态创建:
private Class<?> createAdaptiveExtensionClass() { // 生成适配类的Java代码,主要实现标注了@Adaptive的方法逻辑 String code = createAdaptiveExtensionClassCode(); ClassLoader classLoader = findClassLoader(); // 调用compiler编译
复制代码
评论