前言
对于使用过 Spring 技术的同学一定对 AOP 不会陌生,通常使用AOP 在方法的前后插入其它的逻辑。如使用 Spring AOP 实现日志记录、鉴权等操作。
那在 dubbo 中是如何实现这样的功能的呢?在 dubbo 中有一种特殊的类叫做 Wrapper
类。通过装饰者模式,使用包装类包装原始的扩展类实例。在原始扩展点前后插入其它的逻辑,从而实现类似 AOP 的功能。
此篇文章是对 【dubbo系列】 08-dubbo SPI 机制 的一个补充。
什么是 Wrapper
类
那什么样的类才是一个 Dubbo 中的一个 Wrapper 类呢?下面可以从 ExtensionLoader
的源码中了解到如何判断一个 Wrapper 类。其源码基于 dubbo 2.7.2.
/**
* 判断是否是一个包装类
*/
private boolean isWrapperClass(Class<?> clazz) {
try {
//
clazz.getConstructor(type);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
从源码中很容易之到,通过 clazz.getConstructor(type)
方法,获得其拷贝的构造函数。其中type 是接口,clazz是接口的扩展实现类。
我们以 dubbo 中的 ProtocolFilterWrapper
类进行分析,具体什么样的类可以称为一个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;
}
...
}
其中,Protocol
是一个接口,ProtocolFilterWrapper
是其一个实现类,ProtocolFilterWrapper
有一个构造方法,其参数是 Protocol
,我们就说其是一个复制的构造函数,ProtocolFilterWrapper
就是一个 Wrapper
类。
如何配置一个 Wrapper
在Dubbo 中 Wrapper 也是一个扩展类,同其它扩展类的配置是一样的,也是在 META-INF/dubbo
文件夹下配置。比如 ProtocolFilterWrapper
其配置就在 META-INF\dubbo\internal\com.alibaba.dubbo.rpc.Protocol
下面。
filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=org.apache.dubbo.rpc.support.MockProtocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=org.apache.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=org.apache.dubbo.rpc.protocol.hessian.HessianProtocol
http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
org.apache.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=org.apache.dubbo.rpc.protocol.thrift.ThriftProtocol
native-thrift=org.apache.dubbo.rpc.protocol.nativethrift.ThriftProtocol
memcached=org.apache.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=org.apache.dubbo.rpc.protocol.redis.RedisProtocol
rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
xmlrpc=org.apache.dubbo.xml.rpc.protocol.xmlrpc.XmlRpcProtocol
registry=org.apache.dubbo.registry.integration.RegistryProtocol
qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper
Wrapper 是何时加载及使用的
其源码在 ExtensionLoader
类中,关于 ExtensionLoader
可以参考 【dubbo系列】 08-dubbo SPI 机制
private void cacheWrapperClass(Class<?> clazz) {
if (cachedWrapperClasses == null) {
cachedWrapperClasses = new ConcurrentHashSet<>();
}
cachedWrapperClasses.add(clazz);
}
其流程是在加载配置文件时,如果是一个wrapper类,则把这个缓存起来。其不会加入“配置项名称”到“配置类”的映射关系表中。
当在调用 getExtension
方法去获得一个扩展类实例时,会做以下操作:查询是否有 Wrapper
类,如果有包装类,就会用反射创建一个 Wrapper 的实例,然后注入一个原始的扩展类实例。
源码如下:
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例
// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
简单实例
定义接口、Wrapper 和实现类
@SPI public interface HelloService { void sayHello(); } public class HelloServiceWrapper implements HelloService { private HelloService helloService; public HelloServiceWrapper(HelloService helloService) { this.helloService = helloService; } @Override public void sayHello() { System.out.println("wrapper begin"); this.helloService.sayHello(); System.out.println("wrapper end"); } } public class HelloServiceImpl implements HelloService { @Override public void sayHello() { System.out.println("hello"); } }
例子中我们定义了一个接口,一个 Wrapper 类,以及一个普通的实例类。HelloServiceWrapper 是一个 Wrapper 类。重写了
sayHello
方法,在其前后增加了打印语句。HelloServiceImpl
是一个普通扩展类。增加配置文件
在
META-INF/dubbo
目录下新增文件,文件名为接口的全限名。wrapper = com.joyxj.spi.HelloServiceWrapper helloServiceImpl = com.joyxj.spi.HelloServiceImpl
测试
@Test public void test() { ExtensionLoader<HelloService> loader = ExtensionLoader.getExtensionLoader(HelloService.class); HelloService helloService = loader.getExtension("helloServiceSecondImpl"); helloService.sayHello(); }
执行方法,输出:
wrapper begin hello wrapper end