在上一篇文章中大概分析了一下Java SPI 的工作原理与源码,现在进入真正的主题DUBBO SPI的原理和源码的学习。
PS: 使用的源码版本为dubbo github上 2.6.x
老规矩,我们先来看一下DUBBO SPI的使用步骤:
- 定义一个接口,并在接口上使用 @SPI 注解,来声明这个接口是一个扩展点。例如:
@SPI
public interface Foo {
// ...
}
- 定义接口的实现类
public class FooImpl implements Foo {
// ...
}
3.在 jar 包的META-INF/dubbo下面创建一个接口全限定名的文件 xxx.xxx.Foo, 接口全限定名文件中以键值对的形式填写实现类 hello=xxx.xxx.FooImpl
4.在需要使用这个扩展点的地方,调用 ExtensionLoader.getExtensionLoader(Foo.class).getExtension(...) 方法,来获取指定的扩展点实现。例如:
Foo foo = ExtensionLoader.getExtensionLoader(Foo.class).getExtension("fooImpl");
以上就是DUBBO SPI 的基本用法和步骤,现在我们来看一下它的工作原理。
Dubbo SPI 工作原理分析
Foo foo = ExtensionLoader.getExtensionLoader(Foo.class).getExtension("fooImpl");
以上这行代码中的ExtensionLoader类就是Dubbo SPI中的核心类,ExtensionLoader中包含了四大核心功能点:
- 扩展点的加载
- 扩展点的依赖注入
- 扩展点的自适应
- 扩展点的激活
正所谓知己知彼,百战不殆。在我们分析这些功能之前,我们先来看看ExtensionLoader类的一肚子“坏水”中,都有哪些东西。
ExtensionLoader类的成员变量
我直接把代码贴上来,上面我都加了注解
public class ExtensionLoader<T> {
private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
//1、配置类 与ServiceLoader的加载目录相同,这说明 Dubbo 也会加载 Java SPI 目录里的扩展配置。
private static final String SERVICES_DIRECTORY = "META-INF/services/";
//1、配置类 用户扩展点加载目录,一般在二次开发扩展时都放在这个目录里面
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
//1、配置类 Dubbo 内部扩展点加载目录,Dubbo 内部实现的扩展点配置文件都放在这个目录下。
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
//2、全局容器类
//是ExtensionLoader的容器,保存所有的ExtensionLoader对象,key= 扩展点类型,value= ExtensionLoader对象
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
//扩展点实例容器,保存了所有实例化后的扩展点对象,key= 扩展点 Class,value= 扩展点对象
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
// ==============================
//扩展点类型,Dubbo SPI要求这里的扩展点类型必须是接口。
private final Class<?> type;
//扩展点对象工厂,用于依赖注入时,获取依赖的扩展点对象。
//ExtensionLoader<ExtensionFactory>的objectFactory为null,其他所有的ExtensionLoader的objectFactory都是AdaptiveExtensionFactory。
//AdaptiveExtensionFactory里面是对ExtensionFactory的包装,包含了SpiExtensionFactory和SpringExtensionFactory。
//AdaptiveExtensionFactory也可以说是获取扩展点的一层代理,底层都是从SpiExtensionFactory和SpringExtensionFactory获取扩展点。
private final ExtensionFactory objectFactory;
//功能工具类主要是作为工具用在逻辑处理中,包括cachedNames、cachedClasses、cachedActivates。
//扩展点类型-扩展点定义 key 的映射关系容器,可以通过扩展点 Class 获取 key
////如:"class org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol" -> dubbo
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
//扩展点定义 key- 扩展点类型的映射关系容器,可以通过 key 获取扩展点 Class
//dubbo -> "class org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol"
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
//激活的扩展点容器,key= 扩展点定义 key,value=@Activate对象
////如:token -> {$Proxy7@2886} "@org.apache.dubbo.common.extension.Activate(after=[], value=[token], before=[], group=[provider], order=0)"
private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();
//局部容器类 主要用于保存当前扩展点ExtensionLoader里的数据。
//包括cachedInstances、cachedAdaptiveInstance和cachedAdaptiveClass。
//扩展点对象Holder的容器,用于保存已经实例化的扩展点对象Holder
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
//自适应扩展点对象持有者,持有了 Dubbo 生成的自适应扩展点对象
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
//自适应扩展点 Class 持有者,持有了 Dubbo 生成的自适应扩展点类。
private volatile Class<?> cachedAdaptiveClass = null;
//默认扩展点key,@SPI注解里的value
private String cachedDefaultName;
private volatile Throwable createAdaptiveInstanceError;
private Set<Class<?>> cachedWrapperClasses;
private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();
以上就是ExtensionLoader 中的比较重要的成员变量,详细可以看注释。
ExtensionLoader中还有几个比较重要的方法,涵盖了其核心功能点:
- getExtensionLoader(Class class):获取ExtensionLoader对象。
- getExtension(String key):根据key获取扩展点。
- getAdaptiveExtension():获取自适应扩展点。
- getActivateExtension(URL, String, String):获取激活的扩展点集合。
ExtensionLoader 的初始化
//ExtensionLoader.getExtensionLoader
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!");
}
//接口必须有@SPI 的注解
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
//根据扩展点类型获取ExtensionLoader。
//EXTENSION_LOADERS是保存ExtensionLoader对象的容器Map,key=扩展点类,value=ExtensionLoader对象
//EXTENSION_LOADERS的定义:ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>()
//由此可知,每种扩展点都拥有一个ExtensionLoader,全都保存在EXTENSION_LOADERS里。
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
//实例化ExtensionLoader,并保存到EXTENSION_LOADERS里
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
private ExtensionLoader(Class<?> type) {
this.type = type;
//初始化objectFactory,这里主要用于依赖注入
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
通过上面的代码,可以看到从EXTENSION_LOADERS中获取对应的 ExtensionLoader对象,如果没有则初始化一个新的放到map中去,构造方法中就是简单的两行代码,objectFactory的初始化我们后面详细说。至此ExtensionLoader 对象就已经初始化完成了。
SPI 扩展点的获取实现
我们现在来看是如何获取扩展点的
//ExtensionLoader#getExtension
public T getExtension(String name) {
//扩展点名不能为空
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
//获取默认扩展点
if ("true".equals(name)) {
return getDefaultExtension();
}
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
//从cachedInstances中获取扩展点Holder,如果没有就创建一个新的Holder
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(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(name) 接着往里面走:
//com.alibaba.dubbo.common.extension.ExtensionLoader#createExtension
private T createExtension(String name) {
//获取扩展点Class,
//此处的getExtensionClasses()是关键,包含了解析配置文件、加载类等逻辑。
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//先从全局扩展点容器EXTENSION_INSTANCES里获取,如果没有就创建个新的
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//依赖注入,这里是依赖注入的关键代码,下面会单独解释。
injectExtension(instance);
//如果包含包装类,则返回包装类。
//备注:所谓包装类,就是对原类的增强类,类名为XXXWrapper、且有一个XXXWrapper(XXX)的构造函数。如ProtocolFilterWrapper
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
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()是关键,包含了解析配置文件、加载类等逻辑。
private Map<String, Class<?>> getExtensionClasses() {
//cachedClasses是扩展点名-扩展点Class的Map容器的Holder
Map<String, Class<?>> classes = cachedClasses.get();
//同步处理,防止重复加载扩展点Class
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//加载扩展点Class
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
// synchronized in getExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((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<扩展点名,扩展点Class>
//这里需要注意的是,以下三个目录中的配置文件都会加载进去:
//1、META-INF/services/;2、META-INF/dubbo/;3、META-INF/dubbo/internal/
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
loadDirectory(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
从上面的源码中,我们终于可以看到loadDirectory() 方法加载了三个配置文件的路径:
//1、META-INF/services/;2、META-INF/dubbo/;3、META-INF/dubbo/internal/ 到这里已经可以推断出,扩展点的加载都是在这里完成的,我们继续跟进去。
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
//拼接文件名,如META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
String fileName = dir + type.getName();
try {
//初始化ClassLoader
//获取所有配置文件路径,这里注意可能存在多个文件,所以可能存在多个URL
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
//解析文件,并加载扩展点Class
//解析过程简单来说就是
// 1、逐行读取文件
// 2、解析出key和Class
// 3、加载Class,并保存到各个缓存容器里,如cacheNames、cachedAdaptiveClass等
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
try {
String line;
while ((line = reader.readLine()) != null) {
final int ci = line.indexOf('#');
if (ci >= 0) line = line.substring(0, ci);
line = line.trim();
if (line.length() > 0) {
try {
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) {
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
} finally {
reader.close();
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
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.");
}
//看是否是自适应类
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 if (isWrapperClass(clazz)) {
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} else {
clazz.getConstructor();
if (name == null || name.length() == 0) {
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 (names != null && names.length > 0) {
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());
}
}
}
}
}
从上面的代码总结出大概的流程就是解析文件,并加载扩展点Class 解析过程做了这几个步骤:
- 逐行读取文件
- 解析出key和Class
- 加载Class,并保存到各个缓存容器里,如cacheNames、cachedAdaptiveClass等
至此,扩张点的加载就已经完成了。下一篇继续学习后面的核心功能~