装饰器设计模式在Dubbo中的应用—源码级解析

装饰器模式介绍

这里直接引用菜鸟教程的介绍

装饰器模式(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机制,调试无法进入到具体实现类。

通过在其所有实现类的方法中打断点,可以发现,会首先进入到InvokerCountWrapperexport方法

并且,再往下进行会有一个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();
}

这里会根据DubboSPI机制去获取自适应扩展点,前面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)可以知道,通过有参构造方法去创建的实例,这个参数就是上一个Wrapperinstance实例。循环结束后,每个Wrapper类都包含一个其他Wrapper类的实例,在调用Protocol#export方法时,会执行每个Wrapper自己的逻辑,之后使用构造方法中传进来的protocolexport。这就是为什么会形成一个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中的应用举例就结束了。其实抛开前面获取装饰器类不管,只看装饰器的作用,就是在不改变原有结构的情况下,包装原有的类,对原有功能进行了扩展。

相关推荐
coderzpw1 小时前
设计模式中的“万能转换器”——适配器模式
设计模式·适配器模式
三金C_C8 小时前
单例模式解析
单例模式·设计模式·线程锁
ShareBeHappy_Qin10 小时前
设计模式——设计模式理念
java·设计模式
木子庆五11 小时前
Android设计模式之代理模式
android·设计模式·代理模式
前端_ID林13 小时前
前端必须知道的设计模式
设计模式
麦客奥德彪16 小时前
设计模式分类与应用指南
设计模式
小宋要上岸16 小时前
设计模式-单例模式
单例模式·设计模式
程序员JerrySUN16 小时前
设计模式 Day 1:单例模式(Singleton Pattern)详解
单例模式·设计模式
古力德18 小时前
代码重构之[过长参数列表]
设计模式·代码规范
OpenSeek20 小时前
【设计模式】面向对象的设计模式概述
设计模式·c#·设计原则