装饰器模式介绍
这里直接引用菜鸟教程的介绍
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
装饰器模式通过将对象包装在装饰器类中,以便动态地修改其行为。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
Dubbo中的应用
先抛出一个问题-装饰器类(包装类)是如何得到的?
先来看下服务导出过程中的一个方法
java
@SuppressWarnings({"unchecked", "rawtypes"})
private void doExportUrl(URL url, boolean withMetaData, RegisterTypeEnum registerType) {
if (!url.getParameter(REGISTER_KEY, true)) {
registerType = RegisterTypeEnum.MANUAL_REGISTER;
}
if (registerType == RegisterTypeEnum.NEVER_REGISTER
|| registerType == RegisterTypeEnum.MANUAL_REGISTER
|| registerType == RegisterTypeEnum.AUTO_REGISTER_BY_DEPLOYER) {
url = url.addParameter(REGISTER_KEY, false);
}
// 拿到代理调用组件Invoker
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
if (withMetaData) {
invoker = new DelegateProviderMetaDataInvoker(invoker, this);
}
// 使用protocol执行导出逻辑
Exporter<?> exporter = protocolSPI.export(invoker);
exporters
.computeIfAbsent(registerType, k -> new CopyOnWriteArrayList<>())
.add(exporter);
}
这里有一个使用protocolSPI
导出invoker
的逻辑,由于涉及到SPI
机制,调试无法进入到具体实现类。
通过在其所有实现类的方法中打断点,可以发现,会首先进入到InvokerCountWrapper
的export
方法
并且,再往下进行会有一个Wrapper
执行链。
那么,我们思考一下,为什么会首先进入到InvokerCountWrapper
中呢?并且,为什么会有一个Wrapper
链呢?
先来看下ServiceConfig
中的下面这个方法,会去获得protocol
的实例
java
@Override
protected void postProcessAfterScopeModelChanged(ScopeModel oldScopeModel, ScopeModel newScopeModel) {
super.postProcessAfterScopeModelChanged(oldScopeModel, newScopeModel);
// 这里,根据spi机制获取protocol实例
protocolSPI = this.getExtensionLoader(Protocol.class).getAdaptiveExtension();
proxyFactory = this.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
}
这里会根据Dubbo
的SPI
机制去获取自适应扩展点,前面DubboSPI
机制解析的文章里有这部分的详细说明,这里直接说结果。
自适应扩展点获取到的是一个代理类,若要创建一个新的代理类(接口实现类没有@Adaptive注解修饰),在执行时根据URL
获得的extName
动态的选择具体实现类的话,会根据URL.getProtocol
来确定extName
,若URL.getProtocol
获取到的是null
,则取接口的@SPI
注解中的default name
来作为extName
。
获取到extName
后
在创建代理类的代码时,会执行getExtension(name)
方法。
这个过程的伪代码如下
java
// 自适应代理类的伪代码示例
public class Xxx$Adaptive implements Xxx {
public void method1(URL url) {
// 根据 URL 参数选择扩展点名称
String extName = url.getParameter("xxx", "default");
// 调用 getExtension 获取具体实例(此处触发 Wrapper 包装)
Xxx extension = ExtensionLoader.getExtensionLoader(Xxx.class).getExtension(extName);
extension.method1(url);
}
}
根据前面DubboSPI
机制深度解析文章,我们可以知道,在getExtension
时,会有一个aop
的过程,这个过程其实就是用包装类来实现的。
java
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
boolean match = (wrapper == null)
|| ((ArrayUtils.isEmpty(wrapper.matches())
|| ArrayUtils.contains(wrapper.matches(), name))
&& !ArrayUtils.contains(wrapper.mismatches(), name));
if (match) {
// aop,这里得到的instance是Wrapper类的实例
instance = injectExtension(
(T) wrapperClass.getConstructor(type).newInstance(instance));
instance = postProcessAfterInitialization(instance, name);
}
}
这里会循环操作所有的包装类,Protocol
的每个Wrapper
类中,都有一个Protocol
全局变量,由(T) wrapperClass.getConstructor(type).newInstance(instance))
这段代码的newInstance(instance)
可以知道,通过有参构造方法去创建的实例,这个参数就是上一个Wrapper
的instance
实例。循环结束后,每个Wrapper
类都包含一个其他Wrapper
类的实例,在调用Protocol#export
方法时,会执行每个Wrapper
自己的逻辑,之后使用构造方法中传进来的protocol
去export
。这就是为什么会形成一个Wrapper
链了。
另外,aop过程结束后,会得到一个包装类的实例,后续使用此包装类实例去实现方法的调用,实现装饰器模式的目的------不改变其结构,进行功能扩展。
包装类的使用
我们回到前面的Exporter<?> exporter = protocolSPI.export(invoker);
现在,我们可以得知,protocolSPI其实是一个包装类,也就是一个装饰器类(调试时,看到的是一个代理类)。
执行第一个export方法时,会先进入到InvokerCountWrapper
java
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
return protocol.export(invoker);
}
InvokerCountWrapper这里又会去使用protocol去继续执行
看下protocol的结构
继续向下到ProtocolFilterWrapper
(不一个一个按顺序看了,直接看一个经典的)
java
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (UrlUtils.isRegistry(invoker.getUrl())) {
return protocol.export(invoker);
}
FilterChainBuilder builder = getFilterChainBuilder(invoker.getUrl());
return protocol.export(builder.buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
}
由此可以看出,装饰器模式下的包装类,在调用原有的功能之前,加上了一些扩展的逻辑,以上述代码为例,就是加上了FilterChainBuilder
的获取,有了这个之后,就可以构建InvokerChain
了(这部分涉及到责任链设计模式,可以看我上一篇文章)
总结
到此,装饰器模式在Dubbo中的应用举例就结束了。其实抛开前面获取装饰器类不管,只看装饰器的作用,就是在不改变原有结构的情况下,包装原有的类,对原有功能进行了扩展。