1 概要介绍及使用方法
1.1 Java SPI
关于 java spi 的介绍可以参见下面这个帖子
Dubbo的扩展点加载从JDK标准的SPI(Service Provider Interface)扩展点发现机制加强而来。
Dubbo改进了JDK标准的SPI的以下问题:
- JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
- 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称, 但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉 了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。
- 增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。
1.2 扩展使用方法:
在扩展类的jar包内,放置扩展点配置文件:META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。
示例:
在协议的实现jar包内放置文本文件:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol,内容为:
xxx=com.alibaba.xxx.XxxProtocol
实现类内容:
package com.alibaba.xxx;import com.alibaba.dubbo.rpc.Protocol;public class XxxProtocol implemenets Protocol { // ...}
加载扩展实现, 在dubbo中都是通过ExtensionLoader实现的, 代码如:
ExtensionLoader.getExtensionLoader(Container.class).getExtension("registry");
2 ExtensionLoader代码分析
我们以方法getExtensionLoader作为入口来分析加载的实现:
public staticExtensionLoader getExtensionLoader(Class type){ if(type==null) throw new IllegalArgumentException("Extensiontype == null"); if(!type.isInterface()){ throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } if(!withExtensionAnnotation(type)){ thrownewIllegalArgumentException("Extensiontype("+type+ ") is not extension, because WITHOUT@" + SPI.class.getSimpleName() + " Annotation!"); } ExtensionLoader loader=(ExtensionLoader )EXTENSION_LOADERS.get(type); if(loader==null){ EXTENSION_LOADERS.putIfAbsent(type,newExtensionLoader (type)); loader=(ExtensionLoader )EXTENSION_LOADERS.get(type); } return loader;}
此方法根据扩展类型, 得到一个扩展加载器(ExtensionLoader)
1, 通过方法开始部分的几个校验可以知道:
· 扩展类泛型类型不能为空
· 必须是一个接口.
· 此接口必须打上@SPI注解
2, 先从EXTENSION_LOADERS缓存里取, 取不到会实例化一个ExtensionLoader, 并且缓存起来;
在ExtensionLoader的构造器中, 会通过AdaptiveExtension方式得到一个ExtensionFactory; AdaptiveExtension及ExtensionFactory后面会再讲到.
3,得到ExtensionLoader之后, 再来看看getExtension(String name)方法
逻辑比较简单,
· 如果name 传一个 "true", 表示取默认扩展; getDefaultExtension(), 实现逻辑见4.
· 先从缓存cachedInstances里取, 取不到通过createExtension方法创建, 完了再放缓存.
4, 获取默认扩展的实现逻辑.getDefaultExtension
5, createExtension方法:
1) 通过getExtensionClasses方法, 取得所有的扩展类缓存, 并从中取得name对应的class. getExtensionClasses实现逻辑见6.
· 根据类型, 从EXTENSION_INSTANCES获得缓存的实例.
· 如果不存在, 则通过class.newInstance创建实例, 并缓存.
· 通过injectExtension方法, 给这个实例注入各种属性.injectExtension实现逻辑, 详见7.
· 如果待扩展的T类型,有Wrapper包装器类(构造函数有T入参),实例化,并且injectExtension:
Set> wrapperClasses = cachedWrapperClasses; if(wrapperClasses != null && wrapperClasses.size() > 0){ for(Class wrapperClass : wrapperClasses){ instance = injectExtension((T)wrapperClass.getConstructor(type).newInstance(instance)); }}return instance;
可以到看最后返回的是最后一个包装器类;之前的包装器类以及最开始最纯粹的扩展类实例, 只是调用了一下构造器方法, 然后传递给后面的包装器类; 这样就能在各级包装器类中添加各种扩展属性级扩展方法(装饰器模式).
包装类在loadFile加载扩展类的时候, 加载到cachedWrapperClasses缓存的.(loadFile实现逻辑详见8)
6.getExtensionClasses 获取所有的扩展类, 实现逻辑
1) 从cachedClasses缓存里取, 如果不存在, 通过loadExtensionClasses方法加载.
2) loadExtensionClasses实现逻辑:
a.通过getExtensionLoader(Class<T>type)初始传进来的type类型上的SPI注解的value值, 解析得到一个cachedDefaultName(这个就是默认扩展了.
b.通过loadFile方法(实现逻辑见8), 加载DUBBO_INTERNAL_DIRECTORY,DUBBO_DIRECTORY,SERVICES_DIRECTORY三个目录下的扩展类汇总到extensionClasses缓存, 最后一起返回.
7.injectExtension方法,给实例注入属性的实现逻辑.
通过反射, 找到只有一个入参的set public方法, 得到参数类型.
通过ExtensionFactory得到这个属性的扩展实例, 如果存在的话, 就注入.
8, loadFile 通过配置文件加载扩展类的实现逻辑:
1)获得指定扩展配置文件名, 如:META-INF/dubbo/internal/com.alibaba.dubbo.common.compiler.Compiler
2)通过类加载器得到配置文件资源Url, 在类路径下可能会找到多个.
3)遍历, 读取配置文件中的每一行, 并解析, 配置行形如:javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler
4)通过class.forName将等号后面的类, 转为类类型.
5)校验:
a.必须是T接口的实现类 type.isAssignableFrom(clazz)
b. 如果这个类有@Adaptive注解. 并且缓存到cachedAdaptiveClass, 如果cachedAdaptiveClass已经有了, 但不是当前解析得到的class, 说明该接口有多个@Adaptive注解扩展类, 报错.关于@Adaptive后面会再分析.
c.如果不是@Adaptive扩展类, 尝试获得该类带有该类类型为入参的构造函数
clazz.getConstructor(type);
存在, 则认为是包装器类, 加入到包装器缓存.
d.如果不是包装器类,
如果配置行, 仅有类名, 没有=号及等号左边的内容, 看看这个类上有没有@Extension注解, 并且注解的值与类名
如果还是没有: 如果扩展类的类名是接口类类名结尾.
clazz.getSimpleName().length()>type.getSimpleName().length()
&&clazz.getSimpleName().endsWith(type.getSimpleName()
那么 扩展名就是去掉接口名之后, 前半部分.
否则报错.
这个扩展名, 允许逗号隔开, 配置多个
从扩展类上获取@Activate注解, 如果有, 存入cachedActivates缓存, 如果扩展名有多个, 只以第一个作为缓存key.
遍历每一个扩展名, 放入缓存, 名称扩展类对应扩展名.同时放入extensionClasses缓存,扩展名对应扩展类.
ExtensionLoader 还可加载Activate类 getActivateExtension()
Adaptive实例,直到扩展点方法执行时才决定调用是一个扩展点实现。
扩展点方法调用会有URL参数(或是参数有URL成员)
这样依赖的扩展点也可以从URL拿到配置信息,所有的扩展点自己定好配置的Key后,配置信息从URL上从最外层传入。
Adaptive实例的逻辑是固定,指定提取的URL的Key,即可以代理真正的实现类上,可以动态生成。
在Dubbo的ExtensionLoader的扩展点类开对应的Adaptive实现是在加载扩展点里动态生成。指定提取的URL的Key通过@Adaptive注解在接口方法上提供。
下面是Dubbo的Transporter扩展点的代码:
public interface Transporter { @Adaptive({"server", "transport"}) Server bind(URL url, ChannelHandler handler) throws RemotingException; @Adaptive({"client", "transport"}) Client connect(URL url, ChannelHandler handler) throws RemotingException;}
对于bind方法表示,Adaptive实现先查找"server"key,如果该Key没有值则找"transport"key值,来决定代理到哪个实际扩展点。
调用扩展如下面的代码:
Protocol refprotocol= ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
下面是getAdaptiveExtension()方法的实现逻辑:
1, 从cachedAdaptiveInstance缓存获取Adaptive类实例, 如果不存在, 调用createAdaptiveExtension()方法创建, 并缓存
2, createAdaptiveExtension方法, 通过getAdaptiveExtensionClass()得到Adaptive类并实例化, 并且通过injectExtension方法注入各属性.
3, getAdaptiveExtensionClass()实现逻辑: 从缓存cachedAdaptiveClass中获取, 取不到调用createAdaptiveExtensionClass方法创建
4, 通过createAdaptiveExtensionClassCode方法生成@Adaptive类字节码, 并且通过Compiler类编译得到, Compiler也是通过ExtensionLoader getAdaptiveExtension得到的.
这里不会递归调用栈溢出吗?
由于在getExtensionClasses()中已经找了cachedAdaptiveClass(见8.5.b), 所以如果cachedAdaptiveClass仍然为空, 会去createAdaptiveExtensionClass() , 这样就会调用堆栈溢出. 这样就不难理解为啥8.5.b中, 如果找到多个@Adaptive注解的类会报异常.
Compiler有一个实现类加了@Adaptive注解, 所以不会再去createAdaptiveExtensionClass.
AdaptiveCompiler
@Adaptive
public class AdaptiveCompilerimplementsCompiler