说实话,在手机这种窄屏设备上,不是很适合看源码分析,还是建议你打开电脑,clone一下dubbo代码,然后继续...我尽量把读者当小白,事无巨细的介绍,就怕漏了你想要的。
我想要什么?我想要你关注一下,转发一下呗,自行车就不要了。
在上一篇介绍了JDK SPI的使用以及大致实现,其中提到了JDK SPI的最大缺陷就是:不管你需要哪个实现类,java.util.ServiceLoader 会为所有实现类都创建对象。
Dubbo为了解决这个问题自己搞了一套SPI,对应的配置文件也改成了kV的格式,例如:
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
其中key就是一个简单的标记,我们指定了”dubbo“,Dubbo SPI根据配置文件就知道我们要的是 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 实现类
Dubbo SPI 核心实现是ExtensionLoader【在dubbo-common模块中的extension包中】,对应java.util.ServiceLoader,其使用方式如下:
// 查找Protocol接口的实现——DubboProtocol,并创建其对象
Protocol protocol =
ExtensionLoader
.getExtensionLoader(Protocol.class)
.getExtension("dubbo");
知道是干啥的了,知道咋用了,就不叨叨那些没用的了,看代码吧!
1、getExtensionLoader()方法
ExtensionLoader.getExtensionLoader()方法其实就是查缓存,没啥特别的╮(╯_╰)╭,缓存没查到就创建新对象,show you code:
// 全局的缓存,晓得伐
private static final ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS
= new ConcurrentHashMap<>();
private final Class type; // 用来记录查询
public static ExtensionLoader getExtensionLoader(Class type) {
// 检测一堆东西...例如,检测type是否为一个接口,检测是否被@SPI注解标识(略)
// @SPI注解的功能后面说
// 每个接口对应一个ExtensionLoader实例,没有就创建,EXTENSION_LOADERS是个缓存
ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
}
return loader;
}
2、getExtension()方法
跟着我走,别懵逼。getExtension()方法干了两件事:一个是根据传入name查找相应的实现类,二是创建(实现类的)对象返回:
// 又是一个缓存,存name->对象Holder之间的映射关系
private final ConcurrentMap> cachedInstances = new ConcurrentHashMap<>();
// 先来说一下Holder是个啥?就是一个泛型容器,一个volatile变量+getter/setter方法,
// 后面会经常见它,它会与后面的synchronized解决并发问题
public T getExtension(String name) {
Holder
3、createExtension()方法
先明确createExtension()方法从哪里加载配置文件呢?createExtension()方法会调用loadExtensionClasses()方法加载配置文件:
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/";
private Map> loadExtensionClasses() {
cacheDefaultExtensionName(); // 获取@SPI注解中指定的默认实现
// 从"/META-INF/dubbo/""/META-INF/dubbo/internal/"、"/META-INF/services/"三个指定目录
// 查找配置文件
Map> extensionClasses = new HashMap<>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
loadDirectory()方法会解析KV格式的配置文件,对于每一行都会调用loadClass()方法进行处理
private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class clazz, String name) throws NoSuchMethodException {
// 检查加载到实现类是否实现了type接口(略)
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz); // 记录@Adaptive注解标记的实现类
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz); // 记录wrapper实现类
} else {
clazz.getConstructor(); // 尝试获取一下无参构造方法
// 配置文件里面没指定key,就从实现类的@Extension注解上找(略)
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n); // 缓存实现类到key的映射
// 将key到实现类Class的映射关系记录到extensionClasses集合
saveInExtensionClass(extensionClasses, clazz, name);
}
}
}
@Adaptive注解和Wrapper的事情我们下一篇再说,这里只要知道会把key到Class的映射缓存起来就行。
加载完成Class就可以实例化了,createExtension()方法具体实现如下:
// 没错,EXTENSION_INSTANCES又是一个全局的缓存,key是实现类的Class,value是相应的对象
private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
private T createExtension(String name) {
// 到指定位置查找配置文件,其中会解析配置文件,并加载
Class clazz = getExtensionClasses().get(name);
// 查询对象缓存
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) { // 缓存没有就创建
EXTENSION_INSTANCES.putIfAbsent(clazz,
clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance); // 注入到某个地方,暂且不说
// Wrapper类的处理(略)
return instance;
// 这里为了方便阅读,省略了try/catch
}
好了,Dubbo SPI的基本实现就介绍的差不多了。
下一篇继续围绕Dubbo SPI,介绍@Adaptive注解、Wrapper、@Activate是怎么玩的。