在上一篇文章Dubbo进阶(五):Dubbo扩展点加载机制(上)中对比介绍了Java SPI
和Dubbo SPI
,这篇文章主要讲解一下Dubbo SPI机制的一些注解。
@SPI注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
/**
* default extension name
*/
String value() default "";
}
这个注解标识的接口,是一个扩展点接口。此注解标记的接口,就表示是一个可扩展的接口。
@Adaptive注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
/**
* Decide which target extension to be injected. The name of the target extension is decided by the parameter passed
* in the URL, and the parameter names are given by this method.
* <p>
* If the specified parameters are not found from {@link URL}, then the default extension will be used for
* dependency injection (specified in its interface's {@link SPI}).
* <p>
* For example, given <code>String[] {"key1", "key2"}</code>:
* <ol>
* <li>find parameter 'key1' in URL, use its value as the extension's name</li>
* <li>try 'key2' for extension's name if 'key1' is not found (or its value is empty) in URL</li>
* <li>use default extension if 'key2' doesn't exist either</li>
* <li>otherwise, throw {@link IllegalStateException}</li>
* </ol>
* If the parameter names are empty, then a default parameter name is generated from interface's
* class name with the rule: divide classname from capital char into several parts, and separate the parts with
* dot '.', for example, for {@code org.apache.dubbo.xxx.YyyInvokerWrapper}, the generated name is
* <code>String[] {"yyy.invoker.wrapper"}</code>.
*
* @return parameter names in URL
*/
String[] value() default {};
}
这个注解有两种用法:可以作用在类上,或者作用在方法上。
- 注解在类上。Dubbo中有
AdaptiveExtensionFactory
和AdaptiveCompiler
。ExtensionLoader的工作过程需要这两个特殊的类。 - 注解在方法上。注解在接口的方法上,除了上面两个类之外,所有的都是注解在方法上。ExtensionLoader根据接口定义动态的生成适配器代码,并实例化这个生成的动态类。被
@Adaptive
注解的方法会生成具体的方法实现。没有注解的方法生成的实现都是抛不支持的操作异常UnsupportedOperationException(不支持的操作)
。
@Activate注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
/**
* Activate the current extension when one of the groups matches. The group passed into
* {@link ExtensionLoader#getActivateExtension(URL, String, String)} will be used for matching.
*
* @return group names to match
* @see ExtensionLoader#getActivateExtension(URL, String, String)
*/
String[] group() default {};
/**
* Activate the current extension when the specified keys appear in the URL's parameters.
* <p>
* For example, given <code>@Activate("cache, validation")</code>, the current extension will be return only when
* there's either <code>cache</code> or <code>validation</code> key appeared in the URL's parameters.
* </p>
*
* @return URL parameter keys
* @see ExtensionLoader#getActivateExtension(URL, String)
* @see ExtensionLoader#getActivateExtension(URL, String, String)
*/
String[] value() default {};
/**
* Relative ordering info, optional
* Deprecated since 2.7.0
*
* @return extension list which should be put before the current one
*/
@Deprecated
String[] before() default {};
/**
* Relative ordering info, optional
* Deprecated since 2.7.0
*
* @return extension list which should be put after the current one
*/
@Deprecated
String[] after() default {};
/**
* Absolute ordering info, optional
*
* @return absolute ordering info
*/
int order() default 0;
}
注解在扩展点实现类或方法上,并注明被激活的条件(group/value等),ExtensionLoader可根据具体的条件信息进行扩展点实现的选择(激活)。
什么是ExtensionLoader
ExtensionLoader
是Dubbo SPI
扩展点实现查找的工具类,与Java SPI中的ServiceLoader类似。Dubbo约定扩展点配置文件放在classpath下的/META-INF/dubbo,/META-INF/dubbo/internal,/META-INF/services
目录下,配置文件名为接口的全限定名,配置文件内容为配置名=扩展实现类的全限定名
。
ExtensionLoader在Dubbo源码org.apache.dubbo.common.extension.ExtensionLoader
中。
public class ExtensionLoader<T> {
private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
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/";
// ......
}
已下内容来自官网
扩展点特性
- 扩展点自动包装
自动包装扩展点的 Wrapper
类。ExtensionLoader
在加载扩展点时,如果加载到的扩展点有拷贝构造函数,则判定为扩展点 Wrapper
类。
Wrapper类内容:
package com.alibaba.xxx;
import org.apache.dubbo.rpc.Protocol;
public class XxxProtocolWrapper implements Protocol {
Protocol impl;
public XxxProtocolWrapper(Protocol protocol) { impl = protocol; }
// 接口方法做一个操作后,再调用extension的方法
public void refer() {
//... 一些操作
impl.refer();
// ... 一些操作
}
// ...
}
Wrapper
类同样实现了扩展点接口,但是 Wrapper
不是扩展点的真正实现。它的用途主要是用于从 ExtensionLoader
返回扩展点时,包装在真正的扩展点实现外。即从 ExtensionLoader
中返回的实际上是 Wrapper
类的实例,Wrapper
持有了实际的扩展点实现类。
扩展点的 Wrapper
类可以有多个,也可以根据需要新增。
通过 Wrapper
类可以把所有扩展点公共逻辑移至 Wrapper
中。新加的 Wrapper
在所有的扩展点上添加了逻辑,有些类似 AOP
,即 Wrapper
代理了扩展点。
- 扩展点自动装配
加载扩展点时,自动注入依赖的扩展点。加载扩展点时,扩展点实现类的成员如果为其它扩展点类型,ExtensionLoader
在会自动注入依赖的扩展点。ExtensionLoader
通过扫描扩展点实现类的所有 setter 方法来判定其成员。即 ExtensionLoader
会执行扩展点的拼装操作。
示例:有两个为扩展点 CarMaker
(造车者)、WheelMaker
(造轮者)
接口类如下:
public interface CarMaker {
Car makeCar();
}
public interface WheelMaker {
Wheel makeWheel();
}
CarMaker
的一个实现类:
public class RaceCarMaker implements CarMaker {
WheelMaker wheelMaker;
public setWheelMaker(WheelMaker wheelMaker) {
this.wheelMaker = wheelMaker;
}
public Car makeCar() {
// ...
Wheel wheel = wheelMaker.makeWheel();
// ...
return new RaceCar(wheel, ...);
}
}
ExtensionLoader
加载 CarMaker
的扩展点实现 RaceCarMaker
时,setWheelMaker
方法的 WheelMaker 也是扩展点则会注入 WheelMaker
的实现。
这里带来另一个问题,ExtensionLoader
要注入依赖扩展点时,如何决定要注入依赖扩展点的哪个实现。在这个示例中,即是在多个WheelMaker
的实现中要注入哪个。
这个问题在下面一点扩展点自适应中说明。
- 扩展点自适应
ExtensionLoader
注入的依赖扩展点是一个 Adaptive
实例,直到扩展点方法执行时才决定调用是哪一个扩展点实现。
Dubbo 使用 URL 对象(包含了Key-Value)传递配置信息。
扩展点方法调用会有URL参数(或是参数有URL成员)
这样依赖的扩展点也可以从URL拿到配置信息,所有的扩展点自己定好配置的Key后,配置信息从URL上从最外层传入。URL在配置传递上即是一条总线。
示例:有两个为扩展点 CarMaker
、WheelMaker
接口类如下:
public interface CarMaker {
Car makeCar(URL url);
}
public interface WheelMaker {
Wheel makeWheel(URL url);
}
CarMaker
的一个实现类:
public class RaceCarMaker implements CarMaker {
WheelMaker wheelMaker;
public setWheelMaker(WheelMaker wheelMaker) {
this.wheelMaker = wheelMaker;
}
public Car makeCar(URL url) {
// ...
Wheel wheel = wheelMaker.makeWheel(url);
// ...
return new RaceCar(wheel, ...);
}
}
当上面执行
// ...
Wheel wheel = wheelMaker.makeWheel(url);
// ...
时,注入的 Adaptive
实例可以提取约定 Key
来决定使用哪个 WheelMaker
实现来调用对应实现的真正的 makeWheel
方法。如提取 wheel.type
, key
即 url.get("wheel.type")
来决定 WheelMake
实现。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
值,来决定代理到哪个实际扩展点。
- 扩展点自动激活
对于集合类扩展点,比如:Filter
, InvokerListener
,、ExportListener
、 TelnetHandler
,、StatusChecker
等,可以同时加载多个实现,此时,可以用自动激活来简化配置,如:
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
@Activate // 无条件自动激活
public class XxxFilter implements Filter {
// ...
}
或
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
@Activate("xxx") // 当配置了xxx参数,并且参数为有效值时激活,比如配了cache="lru",自动激活CacheFilter。
public class XxxFilter implements Filter {
// ...
}
或
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
@Activate(group = "provider", value = "xxx") // 只对提供方激活,group可选"provider"或"consumer"
public class XxxFilter implements Filter {
// ...
}