主要注解
SPI
@SPI 注解可以使用在类、接口和枚举类上,Dubbo 框架中都是使用在接口上。它的主要作用就是标记这个接口是一个Dubbo SPI 接口,即是一个扩展点,可以有多个不同的内置或用户定义的实现。运行时需要通过配置找到具体的实现类。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
String value() default "";
}
通过 value 属性,可以传入不同的参数来设置这个接口的默认实现类。
Adaptive
博客示例
http://t.csdn.cn/3OIpQ
@Adaptive 注解可以标记在类、接口、枚举类和方法上,在整个 Dubbo框架中,只有 AdaptiveExtensionFactory和AdaptiveCompiler 等使用在类级别上,其余都标注在方法上。
如果标注在接口的方法上,即方法级别注解,则可以通过参数动态获得实现类。方法级别注解在第一次 getExtension 时,会自动生成和编译一个动态的 Adaptive 类,从而达到动态实现类的效果。
下面是自动生成的 Transporter$Adaptive#bind实现代码。
public Server bind(URL arg0, ChannelHandler argl)throws RemotingException (
...
org.apache.dubbo.common.URL url = arg0;
// 通过 @Adaptive 注解中的两个 key 去寻找实现类的名称
String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
...
try {
// 根据URL中的参数,尝试获取真正的扩展点实现类
extension = (org.apache.dubbo.remoting.Transporter)ExtensionLoader
.getExtensionLoader(org.apache.dubbo.remoting.Transporter.class)
.getExtension(extName);
} catch(Exception e) {
...
// 如果获取失败,则使用SPI注解中指定的默认实现Netty
extension = (org.apache.dubbo.remoting.Transporter) ExtensionLoader
.getExtensionLoader(org, apache dubbo.remoting.Transporter.class)
.getExtension("netty");
}
// 最终会调用具体扩展点实现类的bind方法
return extension.bind(arg0, argl);
}
当该注解放在实现类上,则整个实现类会直接作为默认实现,不再自动生成上述代码。在扩展点接口的多个实现里,只能有一个实现上可以加 @Adaptive 注解。如果多个实现类都有该注解则会抛出异常。
@Adaptive 注解的源代码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
String[] value() default {};
}
Adaptive 可以传入多个 key 值,在初始化 Adaptive 注解的接口时,会先对传入的 URL 进行 key 值匹配,第一个key没匹配上则匹配第二个,以此类推。直到所有的key匹配完毕,如果还没有匹配到, 则会使用驼峰规则匹配,如果也没匹配到,则会抛出 IllegalStateException 异常。
@Adaptive 放在实现类上,主要是为了直接固定对应的实现而不需要动态生成代码实现,就像策略模式直接确定实现类。实现方式:ExtensionLoader 中会缓存两个与 @Adaptive 有关的对象,一个缓存在 cachedAdaptiveClass 中, 即 Adaptive 具体实现类的 Class 类型。另外一个缓存在 cachedAdaptivelnstance 中,即 Class 的具体实例化对象。在扩展点初始化时,如果发现实现类有 @Adaptive 注解,则直接赋值给 cachedAdaptiveClass,后续实例化类的时候,就不会再动态生成代码,直接实例化 cachedAdaptiveClass,并把实例缓存到 cachedAdaptivelnstance 中。如果注解在接口方法上, 则会根据参数,动态获得扩展点的实现,会生成 Adaptive 类,再缓存到 cachedAdaptivelnstance 中。
Activate
@Activate 可以标记在类、接口、枚举类和方法上。主要使用在有多个扩展点实现、需要根据不同条件被激活的场景中,如Filter 需要多个同时激活,因为每个 Filter 实现的是不同的功能。Activate 参数如下。
String[] group() URL 中的分组如果匹配则激活,则可以设置多个
String[] value() 查找 URL 中如果含有该 key 值,则会激活
String[] before() 填写扩展点列表,表示哪些扩展点要在本扩展点之前
String[] after() 同上,表示哪些需要在本扩展点之后
int order() 整型,直接的排序信息
拓展点特性
扩展类一共包含四种特性:自动包装、自动加载、自适应和自动激活。
1)自动包装
自动包装是一种被缓存的扩展类,ExtensionLoader 在加载扩展时,如果发现这个扩展类包含其他扩展点作为构造函数的参数,则这个扩展类就会被认为是 Wrapper 类。
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;
public ProtocolFilterWrapper(Protocol protocol) {
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
....
}
ProtocolFilterWrapper 虽然继承了 Protocol 接口,但是其构造函数中又注入了一个 Protocol 类型的参数。因此ProtocolFilterWrapper 会被认定为 Wrapper 类。这是一种装饰器模式,把通用的抽象逻辑进行封装或对子类进行增强,让子类可以更加专注具体的实现。
2)自动加载
除了在构造函数中传入其他扩展实例,我们还经常使用 setter 方法设置属性值。如果某个扩展类是另外一个扩展点类的成员属性,并且拥有 setter 方法,那么框架也会自动注入对应的扩展点实例。ExtensionLoader 在执行扩展点初始化的时候,会自动通过 setter方法注入对应的实现类。如果扩展类属性是一个接口,它有多种实现,具体注入哪一个实现类就涉及第三个特性一一自适应。
3)自适应
在 Dubbo SPI 中,使用 @Adaptive 注解,可以动态地通过 URL 中的参数来确定要使用哪个具体的实现类。从而解决自动加载中的实例注入问题。©Adaptive 注解使用示例。
@SPI("netty")
public interface Transporter {
@Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
Server bind(URL url, ChannelHandler handler) throws RemotingException;
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
Client connect(URL url, ChannelHandler handler) throws RemotingException;
@Adaptive 传入了两个 Constants 中的参数,它们的值分别是 server 和 transporter 。当外部调用 Transporter#bind方法时,会动态从传入的参数 URL 中提取 key 参数 server 的value值,如果能匹配上某个扩展实现类则直接使用对应的实现类;如果未匹配上,则继续通过第二个key参数 transporter 提取 valu值。如果都没匹配上,则抛出异常。即 @Adaptive 中传入了多个参数,则依次进行实现类的匹配,直到最后抛出异常。
4)自动激活
使用 @Activate 注解,可以标记对应的扩展点默认被激活启用。该注解还可以通过传入不同的参数,设置扩展点在不同的条件下被自动激活。主要的使用场景是某个扩展点的多个实现类需要同时启用(比如 Filter 扩展点)。
ExtensionLoader
每个接口有一个对应的ExtensionLoader,包含如下成员变量
public class ExtensionLoader<T> {
//配置文件预设目录
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/";
//静态变量,存储工厂模式创建的各接口对应的ExtensionLoader
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
//存储各个接口实现类对应的实例
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
//接口类型
private final Class<?> type;
private final ExtensionFactory objectFactory;
//key为拓展类,value为配置文件中拓展名
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
//key为配置文件中拓展名,value为拓展类
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
//key为配置文件中name,值为Activate注解
private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
//拓展实例
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
//Adaptive类缓存
private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
private volatile Class<?> cachedAdaptiveClass = null;
//SPI注解中的值作为默认name
private String cachedDefaultName;
//记录是否创建AdaptiveInstance是否发生过错误
private volatile Throwable createAdaptiveInstanceError;
//包装类缓存
private Set<Class<?>> cachedWrapperClasses;
private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();
}