上文书留的疑问,这两句到底在干啥
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
Exporter<?> exporter = protocol.export(invoker);
exporters.add(exporter);
首先我们去看proxyFactory.getInvoker 方法,proxyFactory是一个全局变量,点开看一下。保持了dubbo代码一贯清爽的风格,又没有文档注释。乏开心……
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
dubbo源码中到处都是 ExtensionLoader.getExtensionLoader(XXXX.class),为避免影响阅读,先要了解下这个类的用途,点进ExtensionLoader这个类去看看大佬们做了些啥,点进去一看,大佬们留注释了。喜极而泣。
package com.alibaba.dubbo.common.extension
/**
* Dubbo使用的扩展点获取。<p>
* <ul>
* <li>自动注入关联扩展点。</li>
* <li>自动Wrap上扩展点的Wrap类。</li>
* <li>缺省获得的的扩展点是一个Adaptive Instance。
* </ul>
*
* @see <a href="http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Service%20Provider">JDK5.0的自动发现机制实现</a>
*
* @author william.liangf
* @author ding.lid
*
* @see com.alibaba.dubbo.common.extension.SPI
* @see com.alibaba.dubbo.common.extension.Adaptive
* @see com.alibaba.dubbo.common.extension.Activate
*/
public class ExtensionLoader<T> {
//...............//
}
通过dubbo中少有的文档注释,可以提炼几个关键字SPI,Adaptive,Activate
其中SPI,我之前转过一篇文章《Java spi机制浅谈》
文章有说
java spi的具体约定如下 :
当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。
jdk提供服务实现查找的一个工具类:java.util.ServiceLoader
等等ServiceLoader和ExtensionLoader命名颇像啊,ExtensionLoader.getExtensionLoader(XXXX.class)与ServiceLoader.load(Search.class); 使用方式也颇像啊,点进去看看getExtensionLoader方法都做了些啥,以上文protocol 为例
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
该protocol属性的生成过程如下
@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!");
}
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {//EXTENSION_LOADERS是线程安全的map对象(ConcurrentMap),典型的单例,避免针对同一接口生成多个ExtensionLoader实例
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
ExtensionLoader.getExtensionLoader(Protocol.class) 会返回一个ExtensionLoader 的一个实例,后续还通过这个ExtensionLoader 调用了getAdaptiveExtension() 方法?那好继续点击去看。这年后英语在编程领域当道,真是妨碍汉语发展,坐等广电要求编程领域使用汉语编程[滑稽],adaptive是个啥意思呢?ps:这里强烈吐槽百度的翻译结果和例句。
adaptive指有适应性的;倾向于适应能力的,为适应能力而设计的,合适于适应能力的或具有适应能力的。
@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
//从缓存中取已经创建好的实例
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if(createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
//*注意,主要逻辑全都是在这一行。
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
}
}
}
}
else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
很好createAdaptiveExtension(),又是一个unchecked的方法。
@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
try {
//先调用 getAdaptiveExtensionClass().newInstance() 方法,那么我们又要了解getAdaptiveExtensionClass() 方法
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
}
}
getAdaptiveExtensionClass() 实现如下。
private Class<?> getAdaptiveExtensionClass() {
//该方法除了一堆缓存操作,内部会执行loadExtensionClasses();方法
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
以上代码我们知道,getAdaptiveExtensionClass() 内部会调用loadExtensionClasses()方法
// 此方法已经getExtensionClasses方法同步过。
private Map<String, Class<?>> loadExtensionClasses() {
// 拿到@SPI 注解
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if(defaultAnnotation != null) { //如果找到该注解
String value = defaultAnnotation.value(); //获得扩展点名
if(value != null && (value = value.trim()).length() > 0) { //如果指定了扩展点名
//NAME_SEPARATOR 是一个这样的正则表达式 Pattern.compile("\\s*[,]+\\s*") 作用可想而知
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<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
//依次加载三个路径下的配置文件
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadFile(extensionClasses, DUBBO_DIRECTORY);
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
上文中loadFile(extensionClasses, XXXXX);方法中使用的几个常量如下。
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/";
以 loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY); 为例,看一下META-INF/dubbo/internal下的文件
以 META-INF\dubbo\internal\com.alibaba.dubbo.rpc.Protocol 文件为例,其文件内容:
registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
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
可见,文件内容按key=value的格式编写,key为扩展点名称,value为扩展点逻辑类名。loadFile的处理逻辑如下,遍历指定目录的文件,按行读取文件内容,判断=号右边的类是否为当前扩展点的实现,如果是将=号左边的作为key,右边的类对象作为value,存储在参数的map中
ps:还有一种配置方式,但已不使用,祥见com.alibaba.dubbo.common.extension.SPI
类的文档注释,loadFile方法依旧兼容老的配置方式。
dubbo的扩展点还有一个误区,即其支持包装类的配置,包装类需实现扩展点接口,并含有一个以当前扩展点接口为唯一参数的构造方法。一个典型的包装类配置如下:
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
/**
* ListenerProtocol
*
* @author william.liangf
*/
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;
// 注意
public ProtocolFilterWrapper(Protocol protocol){
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
/*******/
}
然后我们继续回到getAdaptiveExtensionClass()方法,其在getExtensionClasses();方法后,继续调用createAdaptiveExtensionClass();方法,这个方法比较复杂,只做大概总结。
1.在扩展点所在包名下创建一个类
2.类名规则"public class " + type.getSimpleName() + "$Adpative" + " implements " + type.getCanonicalName()
(反射相关知识)
3.对扩展点接口的每个方法做一个封装,这里分为两个处理分支
3.1 方法上没有@Adaptive 注解,以com.alibaba.dubbo.rpc.Protocol
的void destroy(); 方法为例,默认实现为以下,即会抛出UnsupportedOperationException异常。
public void destroy() {
throw new UnsupportedOperationException(
"method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
3.2 方法上有 @Adaptive 注解,这个需要注意啦
3.2.1 会判断方法上是不是有类型为com.alibaba.dubbo.common.URL
类型的参数,如果没有对应类型的参数,则依次判断参数的类中是否有类型为com.alibaba.dubbo.common.URL
的属性,如果都没有则方法实现抛出IllegalStateException异常,如果有与URL有关的参数,则已当前扩展点名为参数,调用 String extName = url.getParameter(扩展点名,默认值); 然后调用ExtensionLoader.getExtensionLoader(扩展点类.class).getExtension(extName); 一个典型的样例如下(为保持一致,这里依旧使用protocol的扩展点,protocol有特殊处理,但不影响阅读理解):
public com.alibaba.dubbo.rpc.Exporter export(
com.alibaba.dubbo.rpc.Invoker arg0)
throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null) {
throw new IllegalArgumentException(
"com.alibaba.dubbo.rpc.Invoker argument == null");
}
if (arg0.getUrl() == null) {
throw new IllegalArgumentException(
"com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
}
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = ((url.getProtocol() == null) ? "dubbo"
: url.getProtocol());
if (extName == null) {
throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" +
url.toString() + ") use keys([protocol])");
}
//其实最终都在这
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class)
.getExtension(extName);
return extension.export(arg0);
}
最后调用的getExtension(extName) 来找到扩展点的具体实现,其实看到这感觉已经不需要继续分析getExtension方法了,因为我们上边看到dubbo通过loadFile方法加载了当前扩展点的实现类的对应关系,
在上文所例的文件中有这样一行,
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
而我们通过上边样例中的String extName = ((url.getProtocol() == null) ? "dubbo": url.getProtocol());
取到的extName 恰恰为dubbo,因为看到这里其实整个扩展点的加载机制已经很清晰了,getExtension(String name) 无非是返回指定名字的扩展类实例,这里抄一个道友的对该方法的简单描述:
getExtension(String name)方法:返回指定名字的扩展类实例,
1)如果指定名字的扩展类不存在,则抛异常。
2)如果指定的名字为“true”,则返回默认的实现类实例,即name= cachedDefaultName;
3)从ExtensionLoader.cachedInstances:ConcurrentHashMap变量中获取该name的实例;
4)若Map中没有该name的实例,则调用createExtension方法创建该实例,并保存到缓存中。
注意:如果当前扩展点含有上文所说的包装类,则会初始化扩展点类的基础上,返回包装类的对象。一个典型的情况是,Protocol的适应性扩展点为dubbo,该方法会返回包装类com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
的实例
关于扩展点的小结:
所谓适应性扩展点的实现,其实是与URL息息相关的。
1.要求方法上必须有Adaptive 注解
2.方法上必须有与com.alibaba.dubbo.common.URL 有关的参数(包含属性,或直接参数)
3.URL的参数中必须有对应的扩展点名称的值或者扩展点有指定默认值,否则会抛出IllegalStateException 异常。
4.可以通过在接口上的SPI注解上定义扩展点的默认值(extName),例:@SPI(FailoverCluster.NAME)
其他参考Adaptive 的文档注释(赞):
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
/**
* 从{@link URL}的Key名,对应的Value作为要Adapt成的Extension名。
* <p>
* 如果{@link URL}这些Key都没有Value,使用 用 缺省的扩展(在接口的{@link SPI}中设定的值)。<br>
* 比如,<code>String[] {"key1", "key2"}</code>,表示
* <ol>
* <li>先在URL上找key1的Value作为要Adapt成的Extension名;
* <li>key1没有Value,则使用key2的Value作为要Adapt成的Extension名。
* <li>key2没有Value,使用缺省的扩展。
* <li>如果没有设定缺省扩展,则方法调用会抛出{@link IllegalStateException}。
* </ol>
* <p>
* 如果不设置则缺省使用Extension接口类名的点分隔小写字串。<br>
* 即对于Extension接口{@code com.alibaba.dubbo.xxx.YyyInvokerWrapper}的缺省值为<code>String[] {"yyy.invoker.wrapper"}</code>
*
* @see SPI#value()
*/
String[] value() default {};
}