大纲
1.Java spi 机制简介
2.dubbo 重新实现的spi对比原生的有哪些改进
3.dubbo spi 源码流程分析
1.Java spi机制简介
spi是一种扩展加载机制, SPI 是英文Service Provider Interface的缩写 允许将接口的定义和实现解耦.在传统的面向接口编程中,接口的实现需要写死在代码中.对于一些框架来说十分不便.需要更换实现时还需要去修改源码,因此jdk官方提供了一种机制,将接口的实现配置在配置文件中,可以灵活的进行更换,例如jdbc的驱动加载等.
要使用JAVA的spi也十分简单,主要满足3个条件就阔以:
-
有一个接口,并在工程中的/META-INF/services 文件夹中建一个以该接口全类名为文件名的文件
-
为该接口编写若干个实现类,并将实现类的全类名配置到上面建的配置文件中
-
实现类中必须含有一个无参构造方法(显式隐式均可)
满足以上条件后就可以使用了,下面看一个代码栗子.
package org.xrq.test.spi;
public interface SpiService {
public void hello();
}
两个实现类 分别为SpiServiceA B
package org.xrq.test.spi;
public class SpiServiceA implements SpiService {
public void hello() {
System.out.println("SpiServiceA.Hello");
}
}
package org.xrq.test.spi;
public class SpiServiceB implements SpiService {
public void hello() {
System.out.println("SpiServiceB.Hello");
}
}
META-INF/services下的配置文件
文件名为接口的全限定名org.xrq.test.spi.SpiService:
org.xrq.test.spi.SpiServiceA
org.xrq.test.spi.SpiServiceB
这样就ojbk了 让我们写个测试类来试试.
public class SpiTest {
@Test
public void testSpi() {
ServiceLoader<SpiService> serviceLoader = ServiceLoader.load(SpiService.class);
Iterator<SpiService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
SpiService spiService = iterator.next();
spiService.hello();
}
}
}
结果很简单 控制台依次输出了 SpiServiceA.Hello和 SpiServiceB.Hello
这是一个示例,如果有兴趣分析其内部实现可以看看ServiceLoader类的源码 1.7与1.8略有不同,但原理都是一样的.其实都是通过配置文件拿到全类名,再通过反射的方式去创建类并调用的.由于本文重点是讲dubbo的spi类,因此对java的介绍就到此为止.
2.dubbo 重新实现的spi对比原生的有哪些改进
这一点我直接copy官方文档里的说法如下:
- JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。这一点看上面的例子就知道,在使用时无法指定加载哪一个,会一次性全部都加载出来
- 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
- 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。这一点在dubbo源码中大量使用,来实现包装类.
在我看来1,2点是对不足的改进 而第3点可以称为功能的增强. 除了这几点,还有最大的区别就是实现了自适应扩展点和自动激活的扩展点,这一点在下面会详细讲解.
3.dubbo spi 源码流程分析
3.1综述
有了上面jdk spi的例子,其实dubbo的spi也是在其基础上增强的.首先配置文件的位置变化了,变为了以下三个地方:
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/";
其中第一点其实是为了兼容java的spi,而三个配置文件加载顺序是3>2>1 而优先级是1>2>3 因为后面的相同的key会覆盖前面的.
随便打开一个dubbo源码中 META-INF/dubbo/internal/目录 可以发现dubbo spi中配置文件长这样:
adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler
jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler
javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler
对比发现变成了key-value的形式. 因为可以根据传入key来精准度的寻找特定实现,所以可以实现按需加载而非启动时全部加载.
此外dubbo 还扩展了java spi的功能,例如自适应扩展类(adaptive) 和自动激活扩展类.可以根据注解和传参实现动态生成实现类代码,肥肠灵活. 并大量利用缓存来提高性能,spi用到的类按需加载,且只加载一次.后续都从缓存中获取.
3.1 普通扩展类的加载实现
代码示例
同样的举个栗子.我们复用上面java spi的接口和两个实现类,不同的是接口声明上需要加上@SPI注解,可以暂时理解为一种标记. 此外配置文件需要作一些修改,文件名不变,仍为接口全类名:
spiServiceA=org.xrq.test.spi.SpiServiceA
spiServiceB=org.xrq.test.spi.SpiServiceB
同样的写个测试类来测试下:
@Test
public void testSpi() {
ExtensionLoader<SpiService> serviceLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
SpiService spiServiceA = serviceLoader.getExtension("spiServiceA");
SpiService spiServiceB = serviceLoader.getExtension("spiServiceB");
spiServiceA.hello();
spiServiceB.hello();
}
同样,控制台依次输出了 SpiServiceA.Hello和 SpiServiceB.Hello. 这里可以看到,可以通过传入key作为参数来获取某个特定的实现.
原理分析
源码的主入口在com.alibaba.dubbo.common.extension.ExtensionLoader 类中.首先看下该类一些相关的成员变量:
/**
* 拓展加载器集合
*
* key:拓展接口
*/
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
/**
* 拓展实现类集合
*
* key:拓展实现类
* value:拓展对象。
*
* 例如,key 为 Class<AccessLogFilter>
* value 为 AccessLogFilter 对象
*/
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
// ============================== 对象属性 ==============================
/**
* 拓展接口。
* 例如,Protocol
*/
private final Class<?> type;
/**
* 对象工厂
*
* 用于调用 {@link #injectExtension(Object)} 方法,向拓展对象注入依赖的属性。
*
* 例如,StubProxyFactoryWrapper 中有 `Protocol protocol` 属性。
*/
private final ExtensionFactory objectFactory;
/**
* 缓存的拓展名与拓展类的映射。
*
* 和 {@link #cachedClasses} 的 KV 对调。
*
* 通过 {@link #loadExtensionClasses} 加载
*/
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
/**
* 缓存的拓展实现类集合。
*
* 不包含如下两种类型:
* 1. 自适应拓展实现类。例如 AdaptiveExtensionFactory
* 2. 带唯一参数为拓展接口的构造方法的实现类,或者说拓展 Wrapper 实现类。例如,ProtocolFilterWrapper 。
* 拓展 Wrapper 实现类,会添加到 {@link #cachedWrapperClasses} 中
*
* 通过 {@link #loadExtensionClasses} 加载
*/
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
/**
* 缓存的拓展对象集合
*
* key:拓展名
* value:拓展对象
*
* 例如,Protocol 拓展
* key:dubbo value:DubboProtocol
* key:injvm value:InjvmProtocol
*
* 通过 {@link #loadExtensionClasses} 加载
*/
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
/**
* 缓存的默认拓展名
*
* 通过 {@link SPI} 注解获得
*/
private String cachedDefaultName;
/**
* 创建 {@link #cachedAdaptiveInstance} 时发生的异常。
*
* 发生异常后,不再创建,参见 {@link #createAdaptiveExtension()}
*/
private volatile Throwable createAdaptiveInstanceError;
3.1.1 getExtensionLoader(Class type)
变量上都已经加了注释… 首先从 ExtensionLoader serviceLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
这段代码中 可以看到ExtensionLoader 这个对象本身是通过getExtensionLoader(Class type) 获取到的 先看下它的实现:
/**
* 拓展接口。
* 例如,Protocol
*/
private final Class<?> type;
/**
* 对象工厂
*
* 用于调用 {@link #injectExtension(Object)} 方法,向拓展对象注入依赖的属性。
*
* 例如,StubProxyFactoryWrapper 中有 `Protocol protocol` 属性。
*/
private final ExtensionFactory objectFactory;
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.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
代码逻辑比较简单,主要就是校验传入的是接口以及必须有@SPI注解,然后先从缓存里获取ExtensionLoader对象(对自身也做了缓存),获取不到就构造一个ExtensionLoader并放进缓存中,最终返回.
3.1.2 getExtension(String name)
下面我们分析getExtension(String name)方法:
/**
* 缓存的拓展对象集合
*
* key:拓展名
* value:拓展对象
*
* 例如,Protocol 拓展
* key:dubbo value:DubboProtocol
* key:injvm value:InjvmProtocol
*
* 通过 {@link #loadExtensionClasses} 加载
*/
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
// 查找 默认的 拓展对象
if ("true".equals(name)) {
return getDefaultExtension();
}
// 从 缓存中 获得对应的拓展对象
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(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;
}
这里首先仍然是优先检查缓存中有无对应的key,此处的key即为配置文件中配置的key.如果没有就调用createExtension(String name)方法进行创建
/**
* 拓展实现类集合
*
* key:拓展实现类
* value:拓展对象。
*
* 例如,key 为 Class<AccessLogFilter>
* value 为 AccessLogFilter 对象
*/
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
/**
* 创建拓展名的拓展对象,并缓存。
*
* @param name 拓展名
* @return 拓展对象
*/
@SuppressWarnings("unchecked")
private T createExtension(String name) {
// 获得拓展名对应的拓展实现类
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);
// 若查找到包装类 则依次创建 Wrapper 拓展对象 装饰器模式 一层套一层
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
//若查找到wrapper类,条件是该类有一个以该接口为参数的构造函数.参考装饰模式
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
这里首先通过getExtensionClasses() 方法获取到一个 Map<String, Class<?>> 这个方法就不展开了 其实就是遍历上面那三个配置文件的内容,拿到配置的key-value键值对,注意这里的value是具体实现类的class对象.key就是配置文件中配置的key.获取到实例对象后,调用injectExtension(instance)方法注入属性:
/**
* 注入依赖的属性,类似于Spring的简化IOC实现
*
* @param instance 拓展对象
* @return 拓展对象
*/
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) { // setting && public 方法
// 获得属性的类型
Class<?> pt = method.getParameterTypes()[0];
try {
// 获得属性名
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
// 获得属性值 从优先从spi中获取,再从spring上下文中获取,因为对象是用treeSet自然排序存储的.
Object object = objectFactory.getExtension(pt, property);
// 将上面拿到的属性值 set到对象中
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
这个方法就是遍历该对象中的方法,找到setXXX这种方法,然后从配置文件中拿到对应的属性注入进去.逻辑比较简单
然后回到这段代码
// 若查找到包装类 则依次创建 Wrapper 拓展对象 装饰器模式 一层套一层
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
//若查找到wrapper类,条件是该类有一个以该接口为参数的构造函数.参考装饰模式
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
这里举个例子吧 ,参考ProtocolFilterWrapper和
大纲
1.Java spi 机制简介
2.dubbo 重新实现的spi对比原生的有哪些改进
3.dubbo spi 源码流程分析
1.Java spi机制简介
spi是一种扩展加载机制, SPI 是英文Service Provider Interface的缩写 允许将接口的定义和实现解耦.在传统的面向接口编程中,接口的实现需要写死在代码中.对于一些框架来说十分不便.需要更换实现时还需要去修改源码,因此jdk官方提供了一种机制,将接口的实现配置在配置文件中,可以灵活的进行更换,例如jdbc的驱动加载等.
要使用JAVA的spi也十分简单,主要满足3个条件就阔以:
-
有一个接口,并在工程中的/META-INF/services 文件夹中建一个以该接口全类名为文件名的文件
-
为该接口编写若干个实现类,并将实现类的全类名配置到上面建的配置文件中
-
实现类中必须含有一个无参构造方法(显式隐式均可)
满足以上条件后就可以使用了,下面看一个代码栗子.
package org.xrq.test.spi;
public interface SpiService {
public void hello();
}
两个实现类 分别为SpiServiceA B
package org.xrq.test.spi;
public class SpiServiceA implements SpiService {
public void hello() {
System.out.println("SpiServiceA.Hello");
}
}
package org.xrq.test.spi;
public class SpiServiceB implements SpiService {
public void hello() {
System.out.println("SpiServiceB.Hello");
}
}
META-INF/services下的配置文件
文件名为接口的全限定名org.xrq.test.spi.SpiService:
org.xrq.test.spi.SpiServiceA
org.xrq.test.spi.SpiServiceB
这样就ojbk了 让我们写个测试类来试试.
public class SpiTest {
@Test
public void testSpi() {
ServiceLoader<SpiService> serviceLoader = ServiceLoader.load(SpiService.class);
Iterator<SpiService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
SpiService spiService = iterator.next();
spiService.hello();
}
}
}
结果很简单 控制台依次输出了 SpiServiceA.Hello和 SpiServiceB.Hello
这是一个示例,如果有兴趣分析其内部实现可以看看ServiceLoader类的源码 1.7与1.8略有不同,但原理都是一样的.其实都是通过配置文件拿到全类名,再通过反射的方式去创建类并调用的.由于本文重点是讲dubbo的spi类,因此对java的介绍就到此为止.
2.dubbo 重新实现的spi对比原生的有哪些改进
这一点我直接copy官方文档里的说法如下:
- JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。这一点看上面的例子就知道,在使用时无法指定加载哪一个,会一次性全部都加载出来
- 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
- 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。这一点在dubbo源码中大量使用,来实现包装类.
在我看来1,2点是对不足的改进 而第3点可以称为功能的增强. 除了这几点,还有最大的区别就是实现了自适应扩展点和自动激活的扩展点,这一点在下面会详细讲解.
3.dubbo spi 源码流程分析
3.1综述
有了上面jdk spi的例子,其实dubbo的spi也是在其基础上增强的.首先配置文件的位置变化了,变为了以下三个地方:
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/";
其中第一点其实是为了兼容java的spi,而三个配置文件加载顺序是3>2>1 而优先级是1>2>3 因为后面的相同的key会覆盖前面的.
随便打开一个dubbo源码中 META-INF/dubbo/internal/目录 可以发现dubbo spi中配置文件长这样:
adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler
jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler
javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler
对比发现变成了key-value的形式. 因为可以根据传入key来精准度的寻找特定实现,所以可以实现按需加载而非启动时全部加载.
此外dubbo 还扩展了java spi的功能,例如自适应扩展类(adaptive) 和自动激活扩展类.可以根据注解和传参实现动态生成实现类代码,肥肠灵活. 并大量利用缓存来提高性能,spi用到的类按需加载,且只加载一次.后续都从缓存中获取.
3.1 普通扩展类的加载实现
代码示例
同样的举个栗子.我们复用上面java spi的接口和两个实现类,不同的是接口声明上需要加上@SPI注解,可以暂时理解为一种标记. 此外配置文件需要作一些修改,文件名不变,仍为接口全类名:
spiServiceA=org.xrq.test.spi.SpiServiceA
spiServiceB=org.xrq.test.spi.SpiServiceB
同样的写个测试类来测试下:
@Test
public void testSpi() {
ExtensionLoader<SpiService> serviceLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
SpiService spiServiceA = serviceLoader.getExtension("spiServiceA");
SpiService spiServiceB = serviceLoader.getExtension("spiServiceB");
spiServiceA.hello();
spiServiceB.hello();
}
同样,控制台依次输出了 SpiServiceA.Hello和 SpiServiceB.Hello. 这里可以看到,可以通过传入key作为参数来获取某个特定的实现.
原理分析
源码的主入口在com.alibaba.dubbo.common.extension.ExtensionLoader 类中.首先看下该类一些相关的成员变量:
/**
* 拓展加载器集合
*
* key:拓展接口
*/
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
/**
* 拓展实现类集合
*
* key:拓展实现类
* value:拓展对象。
*
* 例如,key 为 Class<AccessLogFilter>
* value 为 AccessLogFilter 对象
*/
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
// ============================== 对象属性 ==============================
/**
* 拓展接口。
* 例如,Protocol
*/
private final Class<?> type;
/**
* 对象工厂
*
* 用于调用 {@link #injectExtension(Object)} 方法,向拓展对象注入依赖的属性。
*
* 例如,StubProxyFactoryWrapper 中有 `Protocol protocol` 属性。
*/
private final ExtensionFactory objectFactory;
/**
* 缓存的拓展名与拓展类的映射。
*
* 和 {@link #cachedClasses} 的 KV 对调。
*
* 通过 {@link #loadExtensionClasses} 加载
*/
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
/**
* 缓存的拓展实现类集合。
*
* 不包含如下两种类型:
* 1. 自适应拓展实现类。例如 AdaptiveExtensionFactory
* 2. 带唯一参数为拓展接口的构造方法的实现类,或者说拓展 Wrapper 实现类。例如,ProtocolFilterWrapper 。
* 拓展 Wrapper 实现类,会添加到 {@link #cachedWrapperClasses} 中
*
* 通过 {@link #loadExtensionClasses} 加载
*/
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
/**
* 缓存的拓展对象集合
*
* key:拓展名
* value:拓展对象
*
* 例如,Protocol 拓展
* key:dubbo value:DubboProtocol
* key:injvm value:InjvmProtocol
*
* 通过 {@link #loadExtensionClasses} 加载
*/
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
/**
* 缓存的默认拓展名
*
* 通过 {@link SPI} 注解获得
*/
private String cachedDefaultName;
/**
* 创建 {@link #cachedAdaptiveInstance} 时发生的异常。
*
* 发生异常后,不再创建,参见 {@link #createAdaptiveExtension()}
*/
private volatile Throwable createAdaptiveInstanceError;
3.1.1 getExtensionLoader(Class type)
变量上都已经加了注释… 首先从 ExtensionLoader serviceLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
这段代码中 可以看到ExtensionLoader 这个对象本身是通过getExtensionLoader(Class type) 获取到的 先看下它的实现:
/**
* 拓展接口。
* 例如,Protocol
*/
private final Class<?> type;
/**
* 对象工厂
*
* 用于调用 {@link #injectExtension(Object)} 方法,向拓展对象注入依赖的属性。
*
* 例如,StubProxyFactoryWrapper 中有 `Protocol protocol` 属性。
*/
private final ExtensionFactory objectFactory;
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.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
代码逻辑比较简单,主要就是校验传入的是接口以及必须有@SPI注解,然后先从缓存里获取ExtensionLoader对象(对自身也做了缓存),获取不到就构造一个ExtensionLoader并放进缓存中,最终返回.
3.1.2 getExtension(String name)
下面我们分析getExtension(String name)方法:
/**
* 缓存的拓展对象集合
*
* key:拓展名
* value:拓展对象
*
* 例如,Protocol 拓展
* key:dubbo value:DubboProtocol
* key:injvm value:InjvmProtocol
*
* 通过 {@link #loadExtensionClasses} 加载
*/
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
// 查找 默认的 拓展对象
if ("true".equals(name)) {
return getDefaultExtension();
}
// 从 缓存中 获得对应的拓展对象
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(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;
}
这里首先仍然是优先检查缓存中有无对应的key,此处的key即为配置文件中配置的key.如果没有就调用createExtension(String name)方法进行创建
/**
* 拓展实现类集合
*
* key:拓展实现类
* value:拓展对象。
*
* 例如,key 为 Class<AccessLogFilter>
* value 为 AccessLogFilter 对象
*/
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
/**
* 创建拓展名的拓展对象,并缓存。
*
* @param name 拓展名
* @return 拓展对象
*/
@SuppressWarnings("unchecked")
private T createExtension(String name) {
// 获得拓展名对应的拓展实现类
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);
// 若查找到包装类 则依次创建 Wrapper 拓展对象 装饰器模式 一层套一层
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
//若查找到wrapper类,条件是该类有一个以该接口为参数的构造函数.参考装饰模式
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
这里首先通过getExtensionClasses() 方法获取到一个 Map<String, Class<?>> 这个方法就不展开了 其实就是遍历上面那三个配置文件的内容,拿到配置的key-value键值对,注意这里的value是具体实现类的class对象.key就是配置文件中配置的key.获取到实例对象后,调用injectExtension(instance)方法注入属性:
/**
* 注入依赖的属性,类似于Spring的简化IOC实现
*
* @param instance 拓展对象
* @return 拓展对象
*/
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) { // setting && public 方法
// 获得属性的类型
Class<?> pt = method.getParameterTypes()[0];
try {
// 获得属性名
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
// 获得属性值 从优先从spi中获取,再从spring上下文中获取,因为对象是用treeSet自然排序存储的.
Object object = objectFactory.getExtension(pt, property);
// 将上面拿到的属性值 set到对象中
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
这个方法就是遍历该对象中的方法,找到setXXX这种方法,然后从配置文件中拿到对应的属性注入进去.逻辑比较简单
然后回到这段代码
// 若查找到包装类 则依次创建 Wrapper 拓展对象 装饰器模式 一层套一层
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
//若查找到wrapper类,条件是该类有一个以该接口为参数的构造函数.参考装饰模式
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
这里举个例子吧 ,参考ProtocolFilterWrapper和ProtocolListenerWrapper,在实际使用中,通过包装的方式实现了AOP的功能, 调用的是Protocol的真正实现类,例如DubboProtocol,在这基础上实现了过滤器和监听器功能,是对原接口功能的一种增强.在配置文件中,符合条件的类(,条件是该类有一个以该接口为参数的构造函数)的实现类都会被视作包装类.
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;
public ProtocolFilterWrapper(Protocol protocol) {
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
}
包装完后就return了,这样就得到了一个真正的实例对象(可能经过包装),即可调用所实现的逻辑.十分的灵活…
3.2 自适应扩展类的加载实现
自适应扩展类是dubbo spi的精华,当然也要稍微复杂一些.不过这也是阅读dubbo源码中比较重要的一个环节,因为dubbo在源码中大量使用了这一方式来动态的创建类,并且框架强大的扩展性也是来源于此,使得围绕dubbo的许多组件都能进行二次开发和扩展.
下面还是以一段示例代码来进行说明.
@SPI("spiServiceA")
public interface SpiService {
@Adaptive("myKey")
void hello(URL url);
}
//省略B的实现类
public class SpiServiceA implements SpiService {
@Override
public void hello(URL url) {
System.out.println("SpiServiceA.Hello");
}
}
@Test
public void testSpi() {
ExtensionLoader<SpiService> serviceLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
SpiService spiService = serviceLoader.getAdaptiveExtension();
spiService.hello(URL.valueOf("dubbo://localhost:8080?myKey=spiServiceB"));
}
3.2.1 @Adaptive注解
观察接口 发现在方法上添加了@Adaptive 注解,查看源码
/**
* Provide helpful information for {@link ExtensionLoader} to inject dependency extension instance.
*
* 在 {@link ExtensionLoader} 生成 Extension 的 Adaptive Instance 时,为 {@link ExtensionLoader} 提供信息。
*
* `@Adaptive` 可添加类或方法上。这两种方式表现不同:
*
* 1. 当在 类 上时,直接使用被注解的类。也因此,一个拓展,只允许最多注解一个类,否则会存在多个会是冲突。
* 2. 当在方法上时,使用 {@link ExtensionLoader#createAdaptiveExtensionClass()} 方法,创建自适应( Adaptive )拓展类。
*
* 如上逻辑,处理的入口方法为 {@link ExtensionLoader#getAdaptiveExtensionClass()}
*
* @see ExtensionLoader
* @see URL
*/
@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 {};
}
注释写的比较清楚 这个注解可以作用在类上和方法上,
1.当在 类 上时,直接使用被注解的类,跳过代码动态生成和编译环节。也因此,一个拓展,只允许最多注解一个类,否则会存在多个会是冲突。在dubbo中使用得比较少,只有AdaptiveExtensionFactory等少数类使用到类上,表示这个类由人工编码完成.
2.当在方法上时,使用 {@link ExtensionLoader#createAdaptiveExtensionClass()} 方法,创建自适应( Adaptive )拓展类。此外还可以通过传入的URL 中携带的参数值,来动态指定要使用的方法实现类.
3.2.2 getAdaptiveExtension()方法
查看测试类可知,要获取一个自适应类的入口是getAdaptiveExtension()方法,下面开搞:
/**
* 获得自适应拓展对象
*
* @return 拓展对象
*/
@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);
}
}
}
// 若之前创建报错,则直接抛出异常 IllegalStateException
} else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
3.2.3 createAdaptiveExtension()
以上流程比较简单,核心代码在于createAdaptiveExtension()方法,我们来看看如下实现
/**
* 创建自适应拓展对象
*
* @return 拓展对象
*/
@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
这里injectExtension()方法之前已经分析过,忘记了的小伙伴可以上一节看下,主要就是一个IOC的实现.
这里的重点在于getAdaptiveExtensionClass()方法.
3.2.4 getAdaptiveExtensionClass()
/**
* @return 自适应拓展类
*/
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
之前说过,对于同一个接口只会生成一个自适应扩展类,因此这里直接看下缓存里有没有,没有的话再进行创建.值得注意的是,如果@Adaptive注解添加在一个类上,那在之前调用loadFile()方法扫描所有配置文件并缓存实现类时候,会将这个类缓存到cachedAdaptiveClass中,这样if()中的判断为true,这里直接返回不往下走了
3.2.5 createAdaptiveExtensionClass()
/**
* 自动生成自适应拓展的代码实现,并编译后返回该类。
*
* @return 类
*/
private Class<?> createAdaptiveExtensionClass() {
// 自动生成自适应拓展的代码实现的字符串
String code = createAdaptiveExtensionClassCode();
// 编译代码,并返回该类
ClassLoader classLoader = findClassLoader();
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
这段代码主要讲通过某些规则去生成自适应类的代码.注意这里是String类型的,再通过一个编译工具类将其编译成一个可以使用的Class文件,为什么不通过反射的方式直接使用呢,这里主要还是考虑到效率的因素,毕竟字节码文件执行的效率肯定是高于反射执行的.
3.2.6 createAdaptiveExtensionClassCode()
/**
* 自动生成自适应拓展的代码实现的字符串
*
* @return 代码字符串
*/
private String createAdaptiveExtensionClassCode() {
StringBuilder codeBuidler = new StringBuilder();
// 遍历方法数组,判断有 @Adaptive 注解
Method[] methods = type.getMethods();
boolean hasAdaptiveAnnotation = false;
for (Method m : methods) {
if (m.isAnnotationPresent(Adaptive.class)) {
hasAdaptiveAnnotation = true;
break;
}
}
// no need to generate adaptive class since there's no adaptive method found.
// 完全没有Adaptive方法,则不需要生成Adaptive类
if (!hasAdaptiveAnnotation)
throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
// 生成代码:package 和 import
codeBuidler.append("package " + type.getPackage().getName() + ";");
codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
// 生成代码:类名
codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adaptive" + " implements " + type.getCanonicalName() + " {");
// 循环方法
for (Method method : methods) {
Class<?> rt = method.getReturnType(); // 返回类型
Class<?>[] pts = method.getParameterTypes(); // 参数类型数组
Class<?>[] ets = method.getExceptionTypes(); // 异常类型数组
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512); // 方法体的代码
// 非 @Adaptive 注解,生成代码:生成的方法为直接抛出异常。因为,非自适应的接口不应该被调用。
if (adaptiveAnnotation == null) {
code.append("throw new UnsupportedOperationException(\"method ")
.append(method.toString()).append(" of interface ")
.append(type.getName()).append(" is not adaptive method!\");");
// @Adaptive 注解,生成方法体的代码
} else {
// 寻找 Dubbo URL 参数的位置
int urlTypeIndex = -1;
for (int i = 0; i < pts.length; ++i) {
if (pts[i].equals(URL.class)) {
urlTypeIndex = i;
break;
}
}
// found parameter in URL type
// 有类型为URL的参数,生成代码:生成校验 URL 非空的代码
if (urlTypeIndex != -1) {
// Null Point check
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
urlTypeIndex);
code.append(s);
s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex);
code.append(s);
}
// did not find parameter in URL type
// 参数没有URL类型
else {
String attribMethod = null;
// find URL getter method
// 找到参数的URL属性 。例如,Invoker 有 `#getURL()` 方法。
LBL_PTS:
for (int i = 0; i < pts.length; ++i) {
Method[] ms = pts[i].getMethods();
for (Method m : ms) {
String name = m.getName();
if ((name.startsWith("get") || name.length() > 3)
&& Modifier.isPublic(m.getModifiers())
&& !Modifier.isStatic(m.getModifiers())
&& m.getParameterTypes().length == 0
&& m.getReturnType() == URL.class) { // pubic && getting 方法
urlTypeIndex = i;
attribMethod = name;
break LBL_PTS;
}
}
}
// 未找到,抛出异常。
if (attribMethod == null) {
throw new IllegalStateException("fail to create adaptive class for interface " + type.getName()
+ ": not found url parameter or url attribute in parameters of method " + method.getName());
}
// 生成代码:校验 URL 非空
// Null point check
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
urlTypeIndex, pts[urlTypeIndex].getName());
code.append(s);
s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
code.append(s);
// 生成 `URL url = arg%d.%s();` 的代码
s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod);
code.append(s);
}
String[] value = adaptiveAnnotation.value();
// value is not set, use the value generated from class name as the key
// 没有设置Key,则使用“扩展点接口名的点分隔 作为Key
if (value.length == 0) {
char[] charArray = type.getSimpleName().toCharArray();
StringBuilder sb = new StringBuilder(128);
for (int i = 0; i < charArray.length; i++) {
if (Character.isUpperCase(charArray[i])) {
if (i != 0) {
sb.append(".");
}
sb.append(Character.toLowerCase(charArray[i]));
} else {
sb.append(charArray[i]);
}
}
value = new String[]{sb.toString()};
}
// 判断是否有 Invocation 参数
boolean hasInvocation = false;
for (int i = 0; i < pts.length; ++i) {
if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
// 生成代码:校验 Invocation 非空
// Null Point check
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
code.append(s);
// 生成代码:获得方法名
s = String.format("\nString methodName = arg%d.getMethodName();", i);
code.append(s);
// 标记有 Invocation 参数
hasInvocation = true;
break;
}
}
// 默认拓展名
String defaultExtName = cachedDefaultName;
// 获得最终拓展名的代码字符串,例如:
// 【简单】1. url.getParameter("proxy", "javassist")
// 【复杂】2. url.getParameter(key1, url.getParameter(key2, defaultExtName))
String getNameCode = null;
for (int i = value.length - 1; i >= 0; --i) { // 倒序的原因,因为是顺序获取参数,参见【复杂】2. 的例子
if (i == value.length - 1) {
if (null != defaultExtName) {
if (!"protocol".equals(value[i]))
if (hasInvocation) // 当【有】 Invocation 参数时,使用 `URL#getMethodParameter()` 方法。
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else // 当【非】 Invocation 参数时,使用 `URL#getParameter()` 方法。
getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
else // 当属性名是 "protocol" ,使用 `URL#getProtocl()` 方法获取。
getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
} else {
if (!"protocol".equals(value[i]))
if (hasInvocation)
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); // 此处的 defaultExtName ,可以去掉的。
else
getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
else
getNameCode = "url.getProtocol()";
}
} else {
if (!"protocol".equals(value[i]))
if (hasInvocation)
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
else
getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
}
}
// 生成代码:获取参数的代码。例如:String extName = url.getParameter("proxy", "javassist");
code.append("\nString extName = ").append(getNameCode).append(";");
// check extName == null?
String s = String.format("\nif(extName == null) " +
"throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
type.getName(), Arrays.toString(value));
code.append(s);
// 生成代码:拓展对象,调用方法。例如
// `com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class)
// .getExtension(extName);` 。
s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
code.append(s);
// return statement
if (!rt.equals(void.class)) {
code.append("\nreturn ");
}
s = String.format("extension.%s(", method.getName());
code.append(s);
for (int i = 0; i < pts.length; i++) {
if (i != 0)
code.append(", ");
code.append("arg").append(i);
}
code.append(");");
}
// 生成方法
codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "(");
for (int i = 0; i < pts.length; i++) {
if (i > 0) {
codeBuidler.append(", ");
}
codeBuidler.append(pts[i].getCanonicalName());
codeBuidler.append(" ");
codeBuidler.append("arg" + i);
}
codeBuidler.append(")");
if (ets.length > 0) {
codeBuidler.append(" throws "); // 异常
for (int i = 0; i < ets.length; i++) {
if (i > 0) {
codeBuidler.append(", ");
}
codeBuidler.append(ets[i].getCanonicalName());
}
}
codeBuidler.append(" {");
codeBuidler.append(code.toString());
codeBuidler.append("\n}");
}
// 生成类末尾的 `}`
codeBuidler.append("\n}");
// 调试,打印生成的代码
if (logger.isDebugEnabled()) {
logger.debug(codeBuidler.toString());
}
return codeBuidler.toString();
}
这段代码比较长 大家直接看注释吧…提几个要点
- 动态生成类的时候会检查方法上的@Adaptive注解,仅对加了注解的方法生成实现的代码,对于其他方法的实现都是直接抛异常.表示不能被调用
- 为了减少import XXX 这种导包的代码,除了必须的工具类ExtensionLoader.其他生成的代码都使用全类名进行声明,
- 会在方法的参数签名上寻找Url参数类型 ,如果找到会生成一个校验Url非空的代码,
- 如果上一步没找到会进一步从传参对象的属性里去找Url属性对象,同时通过getXXX方法去获得Url对象,如果还没找到,代码会抛出异常,这说明Url是必须的一个参数.
- 如果没有设置Key,即@Adaptive的value值, 则使用“扩展点接口名的点分隔 作为Key .
- 当指定的key 是"protocol"时 直接从url.getProtocal() 方法中获取参数值.
下面贴一个例子,看看生成的类长啥样
package com.alibaba.dubbo.common.extensionloader.ext1;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class SpiService$Adaptive implements com.alibaba.dubbo.common.extensionloader.ext1.SpiService {
public void hello(com.alibaba.dubbo.common.URL arg0) {
if (arg0 == null) {
throw new IllegalArgumentException("url == null");
}
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("spi.service", "spiServiceA");
if (extName == null) {
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.common.extensionloader.ext1.SpiService) name from url(" + url.toString() + ") use keys([spi.service])");
}
com.alibaba.dubbo.common.extensionloader.ext1.SpiService extension = (com.alibaba.dubbo.common.extensionloader.ext1.SpiService) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.extensionloader.ext1.SpiService.class).getExtension(extName);
extension.hello(arg0);
}
}
看最后两句代码, 其实是通过getExtension(String name) 方法,传入从Url参数获取到的实际值,从而拿到真正的实现类,实际调用的是真正实现类的方法,这也是一种AOP的思想.
3.2.6 代码编译
代码编译这块就不详细讲解了,从以下代码可以
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
这里也是使用的自适应类去生成编译工具类,这里默认使用的是JavassistCompiler 进行编译,从Compile类的SPi注解的值可以看出.编译完毕后就得到了Class对象,从而动态的构造出了一个实现类对象.