Java-76 深入浅出 RPC Dubbo Adaptive 与 Filter 拦截器机制全解析:动态扩展与服务链路增强实践

点一下关注吧!!!非常感谢!!持续更新!!!

🚀 AI篇持续更新中!(长期更新)

AI炼丹日志-30-新发布【1T 万亿】参数量大模型!Kimi‑K2开源大模型解读与实践,持续打造实用AI工具指南!📐🤖

💻 Java篇正式开启!(300篇)

目前2025年07月16日更新到:
Java-74 深入浅出 RPC Dubbo Admin可视化管理 安装使用 源码编译、Docker启动

MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务正在更新!深入浅出助你打牢基础!

📊 大数据板块已完成多项干货更新(300篇):

包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈!
大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解

Dubbo 的 Adaptive 功能通过动态生成适配类,实现基于 URL 参数自动选择扩展点实现类,广泛应用于协议选择、负载均衡、容错策略等场景。它以 @Adaptive 注解和 ExtensionLoader 为核心机制,配合 URL 的 key-value 配置,在运行时决定调用逻辑,具备灵活、可扩展、低耦合的优势。同时,Dubbo 还提供基于 Filter 的拦截机制,允许在服务调用链前后注入通用逻辑,如日志记录、访问控制、限流熔断等。开发者只需实现 Filter 接口并通过 SPI 和 @Activate 注解进行注册,即可实现功能增强。Adaptive 与 Filter 的结合,是 Dubbo 高可插拔架构设计的关键体现。

Adaptive 功能详解

功能概述

Dubbo 中的 Adaptive 功能是一种动态适配机制,它能够根据运行时条件自动选择并加载最合适的扩展点实现。该功能的核心是解决在分布式系统中如何根据不同的运行环境和配置动态切换具体实现类的问题。

核心机制

  1. 动态选择机制

    • 通过 getAdaptiveExtension() 方法统一封装指定接口对应的所有扩展点
    • 采用装饰器模式对扩展点进行包装
    • 运行时根据URL参数动态决定使用哪个具体实现
  2. URL驱动机制

    • Dubbo中所有注册信息都通过URL形式处理
    • URL格式示例:dubbo://192.168.1.1:20880/com.service.DemoService?timeout=3000&loadbalance=random
    • 关键参数如协议类型(protocol)、负载均衡策略(loadbalance)等都会影响扩展点选择

实现原理

  1. 自动生成适配类

    • 编译期间通过APT(Annotation Processing Tool)生成适配代码
    • 生成的类名格式为接口名$Adaptive
    • 包含根据URL参数选择具体实现的逻辑
  2. 扩展点加载流程

    复制代码
    1. 解析URL参数
    2. 获取扩展点名称(如protocol=dubbo)
    3. 通过ExtensionLoader加载对应实现
    4. 执行具体方法

典型应用场景

  1. 协议选择

    • 根据URL中的protocol参数(dubbo/http/hessian等)自动选择协议实现
    • 示例:protocol=dubbo 时选择DubboProtocol
  2. 集群容错

    • 根据cluster参数(failover/failfast/failsafe等)选择容错策略
    • 示例:cluster=failover 时使用失败自动切换策略
  3. 负载均衡

    • 根据loadbalance参数(random/roundrobin/leastactive等)选择算法
    • 示例:loadbalance=random 时使用随机算法

自定义扩展

开发人员可以通过以下步骤实现自己的Adaptive扩展:

  1. 在接口方法上添加@Adaptive注解
  2. 指定参数名称(如@Adaptive("loadbalance"))
  3. 在META-INF/dubbo/目录下配置扩展实现
  4. 通过URL参数指定要使用的实现

性能考虑

  1. 适配类在首次调用时生成并缓存
  2. 扩展点实现采用单例模式管理
  3. 通过缓存机制减少反射调用开销

创建接口

API 中的 HelloService 扩展如下,与原先类似,在 sayHello 中增加 Adaptive 注解,并且在参数中提取 URL 参数,注意这里 URL 参数的类为 org.apache.dubbo.common.URL

(其中 SP 可以指定一个字符串参数,用于指明该SPI的默认实现)

创建实现类

(上一篇中已经介绍了扩展点的使用)与上面的 Service 实现代码是类似的,只需要增加 URL 形参即可

DubboAdaptiveMain

最后获取的时候,方式有所改变,需要传入 URL 参数,并且参数中指定具体的实现类参数:

java 复制代码
package icu.wzk;

import icu.wzk.service.WzkHelloService;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;

public class DubboAdaptiveMain {
    public static void main(String[] args) {
        URL url = URL.valueOf("test://localhost/hello?hello.service=dog");
        final WzkHelloService adaptiveExtension =
                ExtensionLoader.getExtensionLoader(WzkHelloService.class).getAdaptiveExtension();
        System.out.println(adaptiveExtension.sayHello(url));
    }
}

PS:

● 这里只是临时测试,为了 URL 的规范,前面的信息均为测试的值即可,关键的点在于 hello.service 参数,这个参数的指定就是具体的实现方式,关于为什么叫 hello.service 是因为这个接口的名称,其中后面的大部分被 dubbo 自动转码为 "." 分割

● 通过 getAdaptiveExtension 来提供一个统一的类来对所有扩展点提供支持(底层对所有的扩展点进行封装)

● 调用时通过参数中增加 URL 对象来实现动态的扩展点使用

● 如果 URL 没有提供该参数,则该方法会使用默认在 SPI 注解中声明的实现

这里我们先运行看看效果:

● 启动 DubboPureMain

● 启动 DubboAdaptiveMain

启动 DubboPureMain 的内容如下所示:

启动 DubboAdaptiveMain,然后观察控制台:

可以看到已经顺利调用了,但是这里调用的是之前 SPI 指定的默认的 "dog"实现,如果我们要调用 "human" 呢?

那这里我们也提供一段代码,这里 getExtension("human") 来指定 SPI 具体走的实现类是哪个:

java 复制代码
package icu.wzk;

import icu.wzk.service.WzkHelloService;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;

public class DubboAdaptiveMain {
    public static void main(String[] args) {
        URL url = URL.valueOf("test://localhost/hello?hello.service=dog");
        final WzkHelloService adaptiveExtension =
                ExtensionLoader.getExtensionLoader(WzkHelloService.class).getAdaptiveExtension();
        System.out.println(adaptiveExtension.sayHello(url));

        URL url2 = URL.valueOf("test://localhost/hello?hello.service=dogdog");
        final WzkHelloService adaptiveExtension2 =
                ExtensionLoader.getExtensionLoader(WzkHelloService.class).getExtension("human");
        System.out.println(adaptiveExtension2.sayHello(url2));

    }
}

我们运行代码进行测试观察:

拦截操作

与很多框架一样,Dubbo 也提供了完善的拦截过滤机制,这种机制类似于面向切面编程(AOP)的概念,允许开发者在执行目标程序的前后插入自定义的处理逻辑。Dubbo 的拦截机制通过 Filter 接口实现,为服务调用过程提供了强大的扩展能力。

Dubbo Filter 机制详解

基本工作原理

Dubbo 的 Filter 机制是专门为服务提供方(Provider)和消费方(Consumer)的调用过程设计的拦截器链。每次远程方法调用时,注册的 Filter 都会按照特定顺序被执行,形成完整的调用链。这种设计为开发者提供了极大的灵活性,可以在不改动核心业务代码的情况下增强系统功能。

典型应用场景

  1. 访问控制:实现 IP 白名单/黑名单过滤
  2. 监控统计:记录调用耗时、成功率等指标
  3. 日志记录:记录请求/响应数据用于调试
  4. 参数校验:对输入参数进行合法性校验
  5. 限流熔断:实现服务的流量控制和熔断保护
  6. 上下文传递:在调用链中传递上下文信息

实现方式示例

开发者可以通过实现 org.apache.dubbo.rpc.Filter 接口来创建自定义 Filter。以下是一个简单的 IP 白名单 Filter 实现示例:

java 复制代码
public class WhitelistFilter implements Filter {
    private List<String> allowedIps = Arrays.asList("192.168.1.1", "10.0.0.1");
    
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String clientIp = RpcContext.getContext().getRemoteHost();
        if (!allowedIps.contains(clientIp)) {
            throw new RpcException("IP " + clientIp + " is not allowed");
        }
        return invoker.invoke(invocation);
    }
}

执行流程

Dubbo Filter 的执行遵循以下顺序:

  1. 消费方 Filter 链(由后向前)
  2. 网络传输
  3. 提供方 Filter 链(由前向后)
  4. 实际服务方法调用
  5. 响应沿原路径返回

配置方式

可以通过以下方式配置 Filter:

  1. XML 配置:<dubbo:provider filter="whitelistFilter,monitorFilter"/>
  2. 注解方式:@Activate(group = {Constants.PROVIDER, Constants.CONSUMER})
  3. SPI 扩展:在 META-INF/dubbo/org.apache.dubbo.rpc.Filter 文件中声明

Dubbo 内置了多个实用 Filter,如监控 Filter、异常处理 Filter、上下文传递 Filter 等,开发者可以根据需要组合使用或自定义扩展。

步骤如下

● 实现 org.apache.dobbo.rpc.Filter 接口

● 使用 org.apche.dubbo.common.extension.Activate 接口进行注册 通过 group 可以指定生产端、消费端,如:Active(group = {CommonConstant.CONSUMER})

● 计算方法运行时间的代码实现爱你

● 在 META-INF.dubbo 中新建 org.apache.doubbo.rpc.Filter 文件,并将当前类的全名写入

shell 复制代码
wzkLogFilter = 包名.过滤器的名字

注意:一般类似于这样的功能都是单独开发依赖的,所以再使用方的项目中需只需要引入依赖,在调用接口时,该方法便会自动拦截

对应的目录是这样的:(这里 dubbo 有个错别字,大家自己改正)

自定义拦截

我们实现一个日志的拦截器,这里是主要的代码实现:

java 复制代码
package icu.wzk.filter;

import org.apache.dubbo.rpc.*;

public class WzkLogFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        System.out.println("[自定义WzkLogFilter 之前]" + invocation.getMethodName());
        Result result = invoker.invoke(invocation);
        System.out.println("[自定义WzkLogFilter 之后]" + invocation.getMethodName());
        return result;
    }
}

我们需要在刚才的 Filter 中,加入我们的配置类:

shell 复制代码
wzkLogFilter=icu.wzk.filter.WzkLogFilter

配置情况如下:

激活拦截

我们需要在刚才的类上,加入注解来激活:

java 复制代码
package icu.wzk.filter;

import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;


@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER}, order = -1000)
public class WzkLogFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        System.out.println("[自定义WzkLogFilter 之前]" + invocation.getMethodName());
        Result result = invoker.invoke(invocation);
        System.out.println("[自定义WzkLogFilter 之后]" + invocation.getMethodName());
        return result;
    }
}

测试程序

我们启动之前编写的:

● DubboPureMain

● AnnotationConsumerMain

就可以看到控制台输出的结果如下所示: