上篇我们说到
ExtensionLoader.getExtensionLoader(Class).getExtension(name)
方法内部原理,今天我们继续说一说ExtensionLoader
的其他知识。这一篇内容如下:
- ExtensionLoader.getAdaptiveExtension()源码分析
- 补上上一篇留下的坑位
let’s go!
回忆
让我们先回忆一下ExstionLoder.getAdaptiveExtension()
的使用。分下面几步:
-
首先需要定义一个接口,在接口上加上
@SPI
注解。标识这是一个SPI -
然后我们可以在两个地方来加上
@Adaptive
注解- 在实现类上加上
@Adaptive
注解,代表该类是一个代理类。具体实现类是通过该类get到的。这种方式在Dubbo中很少见,只有少数的那么几个。 - 在接口的方法上加上
@Adaptive
注解,这时候Dubbo内部会帮你动态生成代理类,同样,具体实现类是通过该类get到的。这种方式就很常见了。比如Protocol
中的export()
以及refer()
方法。
- 在实现类上加上
-
那么总结下吧。其实加载方法上和加在类上的区别就是,一个是要你自己手写定义代理,一个就是Dubbo在运行时动态生成。
-
然后在Dubbo规定的目录下,创建一个以接口全限定名为文件名的文件,在里面定义name和实现类。
-
最后,我们可以进行使用啦,代码一般类似下面这样写:
1
2
3
4
5
6
7public static void main(String []args){
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Xx.class);
//获取AdaptiveExtension。其实这里获取的是一个代理类
Xx adaptive = extensionLoader.getAdaptiveExtension();
//这时候会根据参数再去获取真正的实现类。
adaptive.export(url);
}
好的,回忆完,我们继续往下走。
深入
上次我们已经分析了ExtensionLoader.getExtensionLoader(Xx.class);
这部分,那么我就略过了。直接往下面看。也就是extensionLoader.getAdaptiveExtension();
,首先我们来看下源码,一层一层的剥开它的心。
getAdaptiveExtension()
代码如下:
1 | public T getAdaptiveExtension() { |
上面做的事很简单,如下:
- 先从缓存中get我们的AdaptiveExtension。如果get到了,就直接返回。get不到就往下走
- 如果没有就调用
createAdaptiveExtension()
方法创建一个。 - 把创建的实例,put到缓存中。
- 返回实例。
下面我们接着往里面再深入。
createAdaptiveExtension()
源码如下:
1 | private T createAdaptiveExtension() { |
代码做的事,我注释中已经很清楚了。我们现在只需要分析下getAdaptiveExtensionClass()
的代码就行了,依赖注入的代码我们上一篇已经分析了,这里就不再多说。让我们继续往深里造。
getAdaptiveExtensionClass()
代码如下:
1 | private Class<?> getAdaptiveExtensionClass() { |
该方法做了如下几件事:
-
调用
getExtesionClasses()
加载我们的扩展点(如果没有加载的话),在这个里面,上篇文章里有几个地方留有坑位没讲,这里会涉及到一个,且听我分析。-
上次我们在
loadClass()
方法中,有如下判断:1
2
3
4
5
6
7
8
9//判断配置文件中的类
if (clazz.isAnnotationPresent(Adaptive.class)) {
//缓存类级别自适应扩展点
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) {
....//无关代码先省略
} else {
....//无关代码先省略
}-
如上,首先我们拿到class后,会调用
isAnnotationPresent()
方法判断实现类上是否有加@Adaptive
注解。还记得上面我们回忆部分说的规则么。如果实现类上加了@Adaptive
注解,表示什么?没错,表示该类是一个代理类,具体调用的实现类,是通过该类get到的。 -
如果该类上确实有
@Adaptive
注解,就会进入if体,执行cacheAdaptiveClass()
。该方法源码如下:1
2
3
4
5
6
7
8private void cacheAdaptiveClass(Class<?> clazz) {
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());
}
}- 上面的代码清晰明了,如果
cachedAdaptiveClass
为空的话,就会赋值。如果cachedAdaptiveClass
有值,则会比较当前传过来的类是否相等,如果不相等就会报错。所以从这里可以看到,我们的cachedAdaptiveClass
只能有一个,换句话说,我们一个接口只能有一个在类上加@Adaptive
注解的实现类。
- 上面的代码清晰明了,如果
-
-
-
ok。坑位已经填完,我们继续往下说。执行完
getExtensionClasses()
以后,这时候如果有一个在类上加@Adaptive
注解的实现类,那么cachedAdaptiveClass
必定不为空,然后就会直接返回该类,接着就是newInstance()
和依赖注入了。到这里,@Adaptive
注解加在类上的解析过程,以及含义,你是否已经有点数了? -
那么如果该SPI没有一个实现类在类声明上加上
@Adaptive
注解,而是在声明的接口的方法上加了这个注解呢?毕竟我们在回忆中说过,有两种情况。上面是一种情况,那么另一种情况呢? -
我们一起来看看
return cachedAdaptiveClass = createAdaptiveExtensionClass();
这段代码。1
2
3
4
5
6
7
8
9
10private Class<?> createAdaptiveExtensionClass() {
//这里会根据接口动态生成代理类
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
//找ClassLoader
ClassLoader classLoader = findClassLoader();
//可以看到,这里也是通过getAdaptiveExtension()方法获取自适应扩展点
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
//编译成我们的Class对象。
return compiler.compile(code, classLoader);
}从上面代码看,里面无非就是做了下面几件事情
- 调用方法生成我们的代理类的代码。
- 获取classloader。
- 把代码编译成一个Class对象
emm.生成代码部分我们就不看了。但是我们可以尝试下在这一行下断点进行调试,看它生成的代码是什么内容。这里我拿
Protocol
这个接口来举例说明。接口声明代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14"dubbo") (
public interface Protocol {
int getDefaultPort();
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}我们先下个断点,让dubbo跑起来。对了,Dubbo源码中带有demo,也就是
dubbo-demo
模块。我们就拿它来跑一下。跑起来断点后的结果如下:这样看上去并不美观,我们把它copy下,新建一个java文件。最终美化的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
//refer方法代理
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
//export方法代理
public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
}我们暂时不深究生成代码的含义,等后面分析Dubbo的服务注册功能时,再回过头来看一眼。到这里,上面的
createAdaptiveExtensionClass()
最重要的地方我们已经明确了,下面就是获取classLoader,并且编译下生成的文本为Class对象,这个方法就算是完了。然后我们的getAdaptiveExtensionClass()
也执行完毕了。整个@Adaptive
激活扩展点的流程和代码我们都梳理完了,下面总结一下。
总结
整个@Adaptive
注解的加载做了下面这些事情。
-
调用
getAdapitveExtension()
方法时,会先加载一次该接口所有的实现类。这点在上一篇中已经详细分析了。在加载的过程中,如果有实现类的类签名上加上了@Adaptive
注解,就把他缓存起来,也就是缓存一下我们手写的代理类。注意:该代理类在一个接口中只会有一个,如果有多个会抛出异常。 -
把我们的实现类全部都load到内存后,就调用
createAdaptiveExtension()
方法,在该方法中做了以下几件事:- 获取我们的代理类Class对象
- 创建一个该对象实例
- 依赖注入
-
下面我们总结下它是怎么获取代理类的Class对象的
- 首先会判断是否有缓存过我们手写的代理类,也就是类签名上标有
@Adaptive
的类。如果有就直接返回。 - 如果上面的步骤中缓存为null,也就是该类没有一个类签名上标有
@Adaptive
的实现类,所以Dubbo会在内存中为我们自动生成一个代理类。也就是调用new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate()
生成代码。 - 寻找Classloader,编译我们上面的代码为Class对象。
- 首先会判断是否有缓存过我们手写的代理类,也就是类签名上标有
好了,到这里整个流程基本上是完了。虽然没有一行一行代码进行讲解,但是我觉得到这里,你至少应该会明白@Adaptive
整体的流程以及两种@Adaptive
的定义,从代码层面上是怎么体现的,有何不同的。
文章到此就结束了,感谢观看。欢迎提bug ^_^