什么是Dubbo的spi机制?
SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。
在Jdk中也有Spi机制,Dubbo就是基于Jdk的Spi机制进行完善,弥补了jdk的Spi机制的缺点。SPI机制在Dubbo源码中处处可见,因此对SPI机制了解可以帮助更好的学习Dubbo。
Dubbo的spi分类
- 加载固定的扩展类
一种常见思路是读取特定目录下的配置文件,然后解析出全类名,通过反射机制来实例化这个类,然后将这个类放在集合中存起来,如果有需要的时候,直接从集合中取。Dubbo 中的实现也是这么一个思路。不过在 Dubbo 中,实现的更加完善,它实现了 IOC 和 AOP 的功能。IOC 就是说如果这个扩展类依赖其他属性,Dubbo 会自动的将这个属性进行注入。这个功能如何实现?一个常见思路是获取这个扩展类的 setter 方法,调用 setter 方法进行属性注入。AOP 指的是什么?这个说的是 Dubbo 能够为扩展类注入其包装类。比如 DubboProtocol 是 Protocol 的扩展类,ProtocolListenerWrapper 是 DubboProtocol 的包装类。
举例:指定名称random获取随机负载均衡实现类
RandomLoadBalance rd= (RandomLoadBalance)ExtensionLoader.getExtensionLoader(Loadbalance.class).getExtension("random");
总结:加载固定扩展点,根据扩展点查找目录下指定class并反射实例化,然后完成di。
加载指定路径下的文件内容,保存到集合中
会对存在依赖注入的扩展点进行依赖注入
会对存在Wrapper类的扩展点,实现扩展点的包装
- 加载自适应扩展类
先说明下自适应扩展类的使用场景。比如我们有需求,在调用某一个方法时,基于参数选择调用到不同的实现类。和工厂方法有些类似,基于不同的参数,构造出不同的实例对象。在 Dubbo 中实现的思路和这个差不多,不过 Dubbo 的实现更加灵活,它的实现和策略模式有些类似。每一种扩展类相当于一种策略,基于 URL 消息总线,将参数传递给 ExtensionLoader,通过 ExtensionLoader 基于参数加载对应的扩展类,实现运行时动态调用到目标实例上。
@Adaptive 该注解可以声明在类级别上,也可以声明在方法级别
实现原理:
如果修饰在类级别,那么直接返回修饰的类
如果修饰在方法界别,动态创建一个代理类(javassist)
Dubbo 中有两种类型的自适应拓展,一种是手工编码的,一种是自动生成的。手工编码的自适应拓展中可能存在着一些依赖,而自动生成的 Adaptive 拓展则不会依赖其他类。
- 激活扩展点
相当于Spring中的conditional @ConditionalOnBean(TTT.class)
ExtensionLoader extensionLoader=ExtensionLoader.getExtensionLoader(Filter.class); URL url=new URL("","",0); url=url.addParameter("cache","cache”);//根据url获取 指定一个key,如果url匹配到key,则获取匹配到的value名对应的扩展点List filters=extensionLoader.getActivateExtension(url,"cache”);
实现条件:只要url参数中包含CACHE_KEY,那么 CacheFilter就会被激活
@Activate(group = {
CONSUMER, PROVIDER}, value = CACHE_KEY) public class CacheFilter implements Filter {
}
自定义扩展点
以自定义负载均衡器为例
第1步,创建自定义的负载均衡器类,并实现负载均衡器接口
package cn.supfox.dubbo;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import java.util.List;
//实现负载均衡父接口
public class FirstInvokerLoadBalance implements LoadBalance {
//构造函数打印提示,方便测试
public FirstInvokerLoadBalance() {
System.out.println("自定义的Dubbo负载均衡器");
}
/**
* 实现自己的负载均衡逻辑
* @param invokers 所有的提供者信息
* @param url 服务url,包含配置信息
* @param invocation 调用器
* @param <T>
* @return
* @throws RpcException
*/
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
//这里根据自己的需求,选择一个最合适的服务提供者
Invoker invoker = invokers.get(0);
//返回第一个提供者
return invoker;
}
}
第二步,配置自定义的负载均衡器,在resources创建META-INF/services文件夹,并创建org.apache.dubbo.rpc.cluster.LoadBalance文件:
第三步,配置自定义的负载均衡器
到这里,自定义的负载均衡器就可以被Dubbo识别了,写一个测试类验证下
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.rpc.cluster.LoadBalance;
public class FirstInvokerLoadBalanceTest {
public static void main(String[] args) {
//通过ExtensionLoader类获取自定义的负载均衡器,框架源码也是这样获取的
LoadBalance firstInvoker = ExtensionLoader.getExtensionLoader(LoadBalance.class)
.getExtension("firstInvoker");
System.out.println("打印自定义负载均衡器类:"+firstInvoker.getClass());
}
}
运行结果:
可以看到Dubbo扩展器中获取到自定义的负载均衡器啦,Dubbo源码中也是这样获取的。
如何在项目中使用呢?
按上图方式就可以使用到自定义的负载均衡器了。
SPI原理
从上文的测试方法中跟进实现
LoadBalance firstInvoker = ExtensionLoader.getExtensionLoader(LoadBalance.class) .getExtension("firstInvoker");
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 an interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
//为每一类扩展类new一个扩展器
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
getExtension方法
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
Holder<Object> holder = getOrCreateHolder(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方法
private T createExtension(String name) {
//找扩展点Class
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;
//将扩展点实例传入包装器构造器,这样包装器类就拥有了
//扩展点引用。起到ioc,aop的作用
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
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 + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
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方法
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
//加载META-INF/dubbo/internal目录下文件名org.apache开头的扩展点
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
//加载META-INF/dubbo/internal目录下文件名com.alibaba开头的扩展点
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
//加载META-INF/dubbo目录下文件名org.apache开头的扩展点
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
//加载META-INF/dubbo目录下文件名com.alibaba开头的扩展点
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
//加载META-INF/services目录下文件名org.apache开头的扩展点
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
//加载META-INF/services目录下文件名com.alibaba开头的扩展点
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
loadDirectory类就不继续看下去了,它功能是解析每一个扫描到的扩展点文件,解析出类,最终缓存到下面的Holder对象中,其实是一个map,里面key是扩展点名,value是扩展点Class。
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
injectExtension依赖注入实现,通过set方法注入依赖的对象
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (isSetter(method)) {
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
String property = getSetterProperty(method);
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
//调用set方法,注入依赖项
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
总结下扩展点源码的流程
为扩展点类型创建一个ExtensionLoader实例
到配置文件中获取所有的扩展点Class信息
根据名称找到具体的扩展点Class
实例化找到的扩展点
根据set方法注入扩展点依赖项
将扩展点实例传入包装类构造器,实现AOP功能
总结
通过本文我们知道了Dubbo的SPI机制,学会了使用姿势,也知道了怎么扩展,最后还了解了基本实现,这种SPI机制在我们平时开发中也可以使用起来,对系统的扩展性非常好。