正文
一、什么是 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.SPIImpl1
cn.dark.SPIImpl2
复制代码
ServiceLoader<SPI> load = ServiceLoader.load(SPI.class);
for (SPI spi : load) {
spi.sayHello("SPI");
}
复制代码
输出:
Hello, SPI! I'm one
Hello, 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.RegistryProtocol
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=memcom.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=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编译
复制代码
评论