文章目录
一、SPI简介
一套成熟的框架在扩展性方面必然需要完备,duboo扩展点加载机制更是dubbo具有强大扩展性的一个重要因素,几乎贯穿整个Dubbo框架,Dubbo所有的扩展组件几乎都基于Dubbo SPI实现。dubbo的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。Dubbo SPI由dubbo自己实现,兼容jdk SPI,功能比jdk SPI更加强大,增加了对IoC和AOP的支持,分别体现在依赖注入和自适应扩展机制。
本文源码版本为Dubbo 2.6.5
先简单来了解下SPI机制。
1.JDK SPI
SPI是一种服务发现机制。JDK SPI通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
下面写个demo使用一下jdk spi:
定义接口 Service:
public interface Service {
String execute();
}
两个实现类:
public class ServiceImpl1 implements Service {
public String execute() {
return this.getClass().getSimpleName()+"#execute()";
}
}
public class ServiceImpl2 implements Service {
public String execute() {
return this.getClass().getSimpleName()+"#execute()";
}
}
然后在classpath下新建文件夹META-INF/services,然后新建文件,文件名为接口的全限定名,内容为实现类全限定名,多个实现类用换行符分隔,
com.yozzs.test.spi.Service
com.yozzs.test.spi.impl.ServiceImpl1
com.yozzs.test.spi.impl.ServiceImpl2
然后写个测试类跑一下:
import com.yozzs.test.spi.Service;
import java.util.Iterator;
import java.util.ServiceLoader;
public class TestMain {
public static void main(String[] args) {
ServiceLoader<Service> loader = ServiceLoader.load(Service.class);
Iterator<Service> iterator = loader.iterator();
while(iterator.hasNext()){
Service service = iterator.next();
System.out.println(service.execute());
}
}
}
运行结果:
ServiceImpl1#execute()
ServiceImpl2#execute()
可见spi机制可以通过接口类型自动加载文件里定义的实现类。
Dubbo SPI与jdk SPI原理相同,功能上有一定的增强。
2.Dubbo SPI
Dubbo SPI约定,在扩展类的 jar 包内 ,放置扩展点配置文件 META-INF/dubbo/接口全限定名
,或者 META-INF/dubbo/internal/接口全限定名
,内容为:配置名=扩展实现类全限定名
,多个实现类用换行符分隔。
以扩展协议为例,dubbo支持自定义扩展协议,扩展协议需要实现org.apache.dubbo.rpc.Protocol
接口。(dubbo 2.7之前是com.alibaba.dubbo.rpc.Protocol
),扩展协议一般封装到jar包使用,在协议的实现 jar 包内放置文本文件:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol
,内容为:
unionpay=com.yozzs.test.rpc.protocol.Unionpay
fincontrol=com.yozzs.test.rpc.protocol.Fincontrol
这里定义了两个自定义协议,实现类需要实现Protocol接口,key就是协议命,可在dubbo配置文件标签中引用。
<dubbo:protocol name="unionpay" />
dubbo扩展点加载主要源码在ExtensionLoader类中,可以写个如下测试类去测试一下:
public class DubboSPITest {
@Test
public void sayHello() throws Exception {
ExtensionLoader<Protocol> extensionLoader =
ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol unionpay= extensionLoader.getExtension("unionpay");
system.out.println(unionpay.getDefaultPort());
}
}
二、Dubbo 扩展点机制源码阅读
上面的测试代码很简单,首先通过ExtensionLoader.getExtensionLoader(type)
方法获取指定接口类型的的扩展类加载器实例,然后用该实例的getExtension()
方法获取接口实现类的实例。
1.ExtensionLoader的属性
首先来看看ExtensionLoader类有哪些属性:
public class ExtensionLoader<T> {
private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
//下面三个常量约定了dubbo spi配置文件的路径名,可以看出兼容了jdk spi
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/";
//配置文件内容value的多类的格式
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
//缓存本类的实例,getExtensionLoader方法就从这个Map里取,取不到才调用私有构造器去new
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
//缓存扩展点接口实现类的实例
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
//扩展点接口的类型,由getExtensionloader()方法传入
private final Class<?> type;
//依赖注入时调用该对象的getExtension方法获取注入所需的对象
private final ExtensionFactory objectFactory;
//缓存类型名称
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
//Holder类只有一个object类型volatile属性,相当于一层包装
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
//下面是各种各样的缓存
private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
private volatile Class<?> cachedAdaptiveClass = null;
private String cachedDefaultName;
private volatile Throwable createAdaptiveInstanceError;
private Set<Class<?>> cachedWrapperClasses;
private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();
//.........
}
可以看出ExtensionLoader这个类的属性大多是一些缓存,后面生成的一些实例都保存在对应的缓存中,需要时先从缓存拿,拿不到再生成实例,这个过程后面多处代码会通过双重检测的方法来保证线程安全。
还有一个特别重要的属性:Class<?> type,它是ExtensionLoader要自动加载的接口类型,由用户通过参数传进来,也就是getExtensionLoader方法的参数。
2.getExtensionLoader()方法获取加载器实例
ExtensionLoader的构造器私有,对外提供getExtensionLoader()方法获取实例。代码很简单:
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!");
}
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 ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
获取扩展类加载器代码很简单,
- 先做几个判断,入参非空,入参必须是接口类型,入参接口必须有@SPI注解
- 然后从缓存中获取实例,取不到的话调用构造器创建实例,创建实例时给type属性和objectFatory属性赋值。这里的objectFatory属性有三种实现,其中一个是适配器,准确来说是两种,后面依赖注入的时候再去看。
- 创建了实例要放进缓存。
获取到扩展类加载器实例就可以调用该实例的getExtension()
方法获取扩展接口的实现类的实例了。
3.getExtension()方法获取扩展点的实现类实例
这个方法就是Dubbo SPI的核心功能方法,方法里有多层调用,最上面两层方法是主要逻辑。先画个图来梳理一下调用关系。
getExtension() 首先检查缓存,缓存未命中则创建拓展对象
|
|----> createExtension() 加载所有拓展类、创建拓展对象、依赖注入、包装成wapper对象
|
|----> injectExtension() 拓展对象依赖注入(setter注入)
|
|----> getExtensionClasses() 加载所有拓展类,从缓存中获取,若无则加载
|
|----> loadExtensionClasses() 判断@spi注解内容是否合法
|
|----> loadDirectory() 解析@spi注解,加载配置文件为url资源
|
|----> loadResource() 读取配置文件并解析,反射加载类,并缓存
|
|----> loadClass() 判断类型,分别操作缓存
上图就是Dubbo SPI加载扩展点的流程,还有一些关于自适应加载功能的代码暂且不看,下面一层一层地往下看代码:
getExtension()
public T getExtension(String name) {//name为配置文件的key,也就是扩展点名称
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
//获取默认的扩展实现类
return getDefaultExtension();
}
//Holder类只有一个object类型volatile属性,相当于一层包装
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
//cachedInstances是ConcurrentHashmap,putIfAbsent线程安全
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中,缓存实例
holder.set(instance);
}
}
}
return (T) instance;
}
这段代码比较简单,首先检查缓存,缓存没取到则创建实例,下面看创建实例的方法:
createExtension()
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, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 给实例注入依赖
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
// 循环创建wrapper实例
for (Class<?> wrapperClass : wrapperClasses) {
// 以instance为参数,反射调用构造器创建wrapper实例,然后给该实例注入依赖,
// 最后赋值给instance
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()方法,加载所有扩展类
- 查缓存,查不到则通过反射创建实例,并放入缓存
- injectExtension()方法,给创建的实例注入依赖
- 将实例包装为对应Wrapper对象
这里的注入依赖的方法就是Dubbo IoC的体现,查看injectExtension()方法源码可以看到依赖注入采用setter方式。
而使用Wrapper对象包装扩展实例可以实现AOP特性。举个例子,ProtocolFilterWrapper
类就包装了DubboProtocol
类,一些通用抽象的判断逻辑全部放到ProtocolFilterWrapper
类的export()
方法,该方法核心功能是调用DubboProtol
类的export()
方法。这里ProtocolFilterWrapper
就相当于一个代理对象,对被代理的DubboProtocol
对象进行功能增强。
injectExtension()
private T injectExtension(T instance) {
try {
// objectFactory属性可获取注入所需的依赖属性
if (objectFactory != null) {
//遍历扩展类的方法,找到set方法
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
//@DisableInject注解表示不注入依赖
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// 依赖对象类型
Class<?> pt = method.getParameterTypes()[0];
try {
// 依赖对象名
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
// 从 ObjectFactory 中获取依赖对象
// objectFactory是ExtensionFactory接口类型,有三种实现
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 反射调用set方法注入依赖对象
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
这是一个简单setter注入的实现,需要注意的是通过依赖对象类型和依赖对象名称从objectFactory中获取依赖对象的过程。
objectFactory是ExtensionFactory接口类型,有三种实现:
- AdaptiveExtensionFactory
- SpiExtensionFactory
- SpringExtensionFactory
从ExtensionLoader的私有构造器看到,初始化ExtensionLoader实例默认objectFactory设置AdaptiveExtensionFactory
类型,它是一个适配器,不是具体实现,通过AdaptiveExtensionFactory
对象的getExtension()
方法,会先进行判断,然后调用SpiExtensionFactory
或SpringExtensionFactory
的同名方法去获取实例。这两种类型就是dubbo支持的原生容器方式和spring容器方式。
看完依赖注入再来看看dubbo如何加载所有扩展类的。
getExtensionClasses()
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
这层代码非常简单,双重检查操作缓存。点进去loadExtensionClasses()方法:
loadExtensionClasses()
// 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));
}
// 缓存默认扩展名称,也就是@SPI注解的value
if (names.length == 1) cachedDefaultName = names[0];
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
loadDirectory(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
loadExtensionClasses 方法总共做了两件事情,
- 对 SPI 注解进行解析
- 调用 loadDirectory 方法加载指定文件夹配置文件。
看loadDirectory方法如何加载配置文件指定的所有扩展类:
loadDirectory()
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
String fileName = dir + type.getName();
try {
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();
// 加载资源
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}
这一层就是使用类加载器加载指定名字的配置文件,加载资源的方法再看loadResource()方法:
loadResource()
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) {
// 读取配置文件的key 和 value
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
// 加载类,并通过loadClass方法对类进行缓存
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);
}
}
loadResource() 方法用于读取配置文件并解析,并通过反射加载类。注意,这里Class.forName()方法只是生成Class<?>对象,没有初始化扩展点实例,也就是前面说的dubbo不初始化所有扩展点,只按需要初始化。然后通过loadClass方法操作缓存:
loadClass()
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.");
}
// 检测目标类上是否有 Adaptive 注解,该注解用于自适应加载扩展点
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)) {// 检查clazz是否有Wrapper类型,有的话放进对应类型缓存
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} else {// 普通扩展类
// 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
clazz.getConstructor();
if (name == null || name.length() == 0) {
// 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 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 (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());
}
}
}
}
}
loadClass()方法主要是操作各种缓存。
三、总结
既然都是SPI的功能,Dubbo SPI的原理跟JDK SPI是差不多的,都是通过类加载器去加载并解析配置文件,加载到类型信息,然后反射生成实例,反射调用setter方法进行依赖注入,得到完整的扩展点实例。Dubbo SPI的相关主要在ExtensionLoader类里,值得注意的是该类加入了许多缓存,Class缓存和实例缓存分开,获取时并不会直接实例化所有Class,在加载过程中多处操作缓存,一定程度上可以提升性能。
本文涉及的源码只是Dubbo SPI的基本功能,Dubbo还支持许多高级特性,例如自适应扩展机制,默认使用javasisst动态编译代码,同时支持jdk编译器。后续可以深入学习一下。