dubbo系列之内核SPI拓展机制初识(七)
前言
dubbo为了适应不同的注册中心,底层通讯协议等功能的扩展,采用SPI的思想,即Service Provider Interface , 也就是我们定义了服务接口标砖,让服务商去实现。 jdk通过ServiceLoader类实现了SPI机制的服务查找功能,有兴趣的可以去网上搜一下jdk的SPI思想, , 接下来我们会讲解一下dubbo是如何实现SPI机制,SPI机制一般来说,会提供一个接口,然后供厂商来实现。
JDK实现SPI机制时通过ServiceLoader来 检索META-INF/services/
文件夹下面的配置文件,那么dubbo应该也有类似的东西吧,是的,dubbo是通过ExtensionLoader
来实现文件的读取和解析,跟JDK的ServiceLoader起的是相同的作用。
Dubbo改进了JDK标准的SPI的以下问题:
- JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
- 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。
- 增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。
扩展类定义
@SPI注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
/**
* 默认的扩展名
*/
String value() default "";
}
dubbo的扩展类全部定义在一下三个文件夹里面
META-INF/services/
META-INF/dubbo/
META-INF/dubbo/internal/
dubbo内存实现的各种扩展全部放在这个目录里面。
文件名的规则为:包名+接口名
例:
com.alibaba.dubbo.rpc.Protocol
下面的内容就是com.alibaba.dubbo.rpc.Protocol
文件的具体内容
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
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=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol
rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol
registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
qos=com.alibaba.dubbo.qos.protocol.QosProtocolWrapper
下面以Protocol为例,讲解一下dubbo的SPI机制。
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
/**
* 暴露远程服务:<br>
* 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
* 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br>
* 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br>
*
* @param <T> 服务的类型
* @param invoker 服务的执行体
* @return exporter 暴露服务的引用,用于取消暴露
* @throws RpcException 当暴露服务出错时抛出,比如端口已占用
*/
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
/**
* 引用远程服务:<br>
* 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端export()传入的Invoker对象的invoke()方法。<br>
* 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。<br>
* 3. 当url中有设置check=false时,连接失败不能抛出异常,需内部自动恢复。<br>
*
* @param <T> 服务的类型
* @param type 服务的类型
* @param url 远程服务的URL地址
* @return invoker 服务的本地代理
* @throws RpcException 当连接服务提供方失败时抛出
*/
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
Protocol类上面使用了@SPI注解,默认的扩展实现已经指定为了dubbo, 我们可以看一下他具体使用的地方。
在上一文中我们了解到的ServiceBean的父类ServiceConfig中有下面这一行代码,这是静态常量,在类加载的时候就会进行初始化。
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
ExtensionLoader
每个扩展类都会生成一个ExtensionLoader , 然后通过生成的ExtensionLoader去获取到一个扩展类实现,下面我们可以看一下getExtensionLoader方法,看下dubbo是如何为每个扩展类生成ExtensionLoader对象的。
@SuppressWarnings("unchecked")
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!");
}
// 从缓存的MAP中获取ExtensionLoader实现、
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
// 获取不到则需要 创建一个并放入缓存MAP
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
// 从MAP中取
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
上面的代码总结一下就是分为了两步走
1.从缓存中根据扩展类获取ExtensionLoader
2.获取不到则创建 一个新的。
通俗一点讲,就是这两步,下面看一下ExtensionLoader是如何被创建的。
private ExtensionLoader(Class<?> type) {
this.type = type;
this.objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).
getAdaptiveExtension());
}
两行代码,表面看上去特别简单,其实这里需要注意的是objectFactory
这个变量,这是dubbo的SPI机制实现**依赖注入 ** 的核心所在。我们解读一下这段代码
// 当前的type不是ExtensionFactory类型的,则调用 ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()
// 创建一个ExtensionFactory扩展类实现,如果是,则无需创建,为null即可。
this.objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).
getAdaptiveExtension());
ExtensionFactory
所以,回到我们最初的代码上去,第一次进来,type = Protocol.class , 所以在主动调用ExtensionLoader.getExtensionLoader(ExtensionFactory.class).
getAdaptiveExtension())
去创建一个ExtensionFactory扩展类实现 , 默认的扩展类实现为AdaptiveExtensionFactory
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
// 获取ExtensionFactory的ExtensionLoader
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
// 获取ExtensionFactory支持的其他扩展类实现,
for (String name : loader.getSupportedExtensions()) {
// 通过类名name ,生成其他扩展类实现的ExtensionLoader
list.add(loader.getExtension(name));
}
// 赋予到factories
factories = Collections.unmodifiableList(list);
}
// 该方法用于依赖注入使用,通过类型和bean的名称获取。
@Override
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
// 依次调用扩展工厂类,获取扩展类。
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
}
上面讲解了在实例化Protocol的扩展类加载器的时候,实例化扩展工厂类。 我们首先看一下ExtensionFactory的代码
@SPI
public interface ExtensionFactory {
<T> T getExtension(Class<T> type, String name);
}
这个类是一个加了@SPI注解的接口类,根据dubbo的SPI机制,我们在jar包的资源路径下查看一下com.alibaba.dubbo.common.extension.ExtensionFactory
看一下这个类dubbo提供了哪些实现
adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory
spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory
dubbo 一共提供了三种实现 ,那为啥默认的扩展工厂类是AdaptiveExtensionFactory
呢? 比较在ExtensionFactory的注解中并没有直接指定默认实现。我们来看一下扩展类的实现。
AdaptiveExtensionFactory
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
// 代码省略
}
SpiExtensionFactory
public class SpiExtensionFactory implements ExtensionFactory {
// 代码省略
}
SpringExtensionFactory
public class SpringExtensionFactory implements ExtensionFactory {
// 代码省略
}
在这三个实现类当中,只有AdaptiveExtensionFactory的类上有@Adaptive 注解,其他两个都没有,拥有这个注解的类,在解析过程中会放入
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设置为当前的类。
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)) {
// 代码省略
} else {
// 代码省略
}
}
如果扩展实现类上面,拥有@Adaptive注解的话,则会将cachedAdaptiveClass设置为当前类,所以在获取扩展实现类的时候,就默认直接取到了AdaptiveExtensionFactory
private Class<?> getAdaptiveExtensionClass() {
// 解析资源
getExtensionClasses();
// 扩展实现类中有类里面添加了@Adaptive注解。
if (cachedAdaptiveClass != null) {
// 直接返回cachedAdaptiveClass
return cachedAdaptiveClass;
}
// 通过字节码技术,创建代理对象。
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
@Adaptive注解的定义如下:
这个注解和@SPI注解配置使用,用于它可以标注在SPI接口扩展类上,也可以标注在SPI接口的方法上。如果这个注解标注在SPI接口实现的扩展类上时,获取的SPI实例对象就是标注了@Adaptive注册的类。
ExtensionFactory是作为SPI的依赖注入的功能实现的,AdaptiveExtensionFactory本身不提供依赖注入,但是他里面有一个属性factories ,这个属性中包含了SpiExtensionFactory
, SpringExtensionFactory
这两个实现。
SpiExtensionFactory : 负责解析带有@SPI注解的其他扩展类,用来依赖注入
SpringExtensionFactory:用来获取spring容器中的bean , 用来依赖注入,该类在初始化ServiceBean的时候被赋予了applicationContext,所以是可以获取到的。
至此,ExtensionFactory解析完成,这个解析完了之后,Protocol的ExtensionLoader生成了
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
下一篇文章中会讲解Protocol的ExtensionLoader是如何获取到扩展实现类的。