文章

过滤器(一)之ClassLoaderFilter

本文基于 Dubbo 2.6.1 版本,望知悉。

1. 概述

从本文开始,我们来分享 Dubbo 的过滤器们。在 ProtocolFilterWrapper 中,在服务引用和暴露时,#buildInvokerChain(invoker, key, group) 方法中,基于 Dubbo SPI Active 机制,加载匹配对应的过滤器数组,创建带有过滤器链的 Invoker 对象。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
 * 创建带 Filter 链的 Invoker 对象
 *
 * @param invoker Invoker 对象
 * @param key 获取 URL 参数名
 * @param group 分组
 * @param <T> 泛型
 * @return Invoker 对象
 */
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
    Invoker<T> last = invoker;
    // 获得过滤器数组
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
    // 倒序循环 Filter ,创建带 Filter 链的 Invoker 对象
    if (!filters.isEmpty()) {
        for (int i = filters.size() - 1; i >= 0; i--) {
            final Filter filter = filters.get(i);
            final Invoker<T> next = last;
            last = new Invoker<T>() {

                @Override
                public Class<T> getInterface() {
                    return invoker.getInterface();
                }

                @Override
                public URL getUrl() {
                    return invoker.getUrl();
                }

                @Override
                public boolean isAvailable() {
                    return invoker.isAvailable();
                }

                @Override
                public Result invoke(Invocation invocation) throws RpcException {
                    return filter.invoke(next, invocation);
                }

                @Override
                public void destroy() {
                    invoker.destroy();
                }

                @Override
                public String toString() {
                    return invoker.toString();
                }
            };
        }
    }
    return last;
}

具体的过滤器链的执行过程,在前面的 Dubbo RPC 调用,已经分享,这里就不重复解释啦。

我们把服务提供者和消费者的过滤器统一整理如下:

过滤器整理

  • 黄色部分,代表过滤器在服务提供者和消费者上的顺序。若为空,则表示无该过滤器。
  • 蓝色部分,EchoFilter 等等,已经在其他文章里分享。
    • dubbo-filter-cache 的 CacheFilter, dubbo-filter-validation 的 ValidationFilter ,未罗列在表格中,也会在其他文章里分享。
  • 绿色部分 ,CompatibleFilter 等等,目前未开启 Dubbo SPI @Adaptive 注解,我们就不写了。

本文分享 ClassLoaderFilter 。后续的文章,也会是每篇文章分享 1-2 个过滤器,根据实际用途的情况。

2. Filter

《精尽 Dubbo 源码分析 —— 核心流程一览》「4.4 Filter」 中,已经详细分享。

3. ClassLoaderFilter

com.alibaba.dubbo.rpc.filter.ClassLoaderFilter ,实现 Filter 接口,类加载器切换过滤器实现类。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Activate(group = Constants.PROVIDER, order = -30000)
public class ClassLoaderFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // 获得原来的类加载器
        ClassLoader ocl = Thread.currentThread().getContextClassLoader();
        // 切换当前线程的类加载器为服务接口的类加载器
        Thread.currentThread().setContextClassLoader(invoker.getInterface().getClassLoader());
        // 服务调用
        try {
            return invoker.invoke(invocation);
        } finally {
            // 切换当前线程的类加载器为原来的类加载器
            Thread.currentThread().setContextClassLoader(ocl);
        }
    }

}
  • 第 7 行:调用 Thread#getContextClassLoader() 方法,获得原来的类加载器。
  • 第 9 行:调用 Thread#setContextClassLoader(ClassLoader) 方法,切换当前线程的类加载器为服务接口的类加载器。
  • 第 12 行:调用 Invoker#invoke(invocation) 方法,服务调用。
  • 第 15 行:调用 Thread#setContextClassLoader(ClassLoader) 方法,切换当前线程的类加载器为原来的类加载器。

笔者看到这个过滤器,表示一脸懵逼,于是开始 Google 探索之路。

1、于是搜到 《ISSUE#1406:classloaderFilter》,内容如下:

ISSUE#1406

使用我大有道词典翻译:

在设计目的中,切换到加载了接口定义的类加载器,以便实现与相同的类加载器上下文一起工作。

2、那么什么情况下会有多个 ClassLoader 的情况呢?再于是搜到 《ISSUE#178:项目有多个ClassLoad时使用dubbo出错;》,内容如下:

ISSUE#178

开始检索 pf4j 是什么东东?

FROM 《Java 的插件框架 PF4J》

PF4J 是一个 Java 的插件框架,为第三方提供应用扩展的渠道。使用 PF4J 你可以轻松将一个普通的 Java 应用转成一个模块化的应用。PF4J 本身非常轻量级,只有 50KB 左右,目前只依赖了 slf4j。Gitblit 项目使用的就是 PF4J 进行插件管理。

看到此处,笔者已经进入了云里和雾里。所以,我仅仅是抛个砖,暂时还没去测试。再所以,胖友如果感兴趣,可以自己去研究下下。我的猜测是,使用 PF4J 加载不同的服务实现类的 Jar ,不同的 Jar 的类加载器不同。

有熟悉这块的胖友,如果有错误,请立即斧正我。哈哈哈。

666. 彩蛋

知识星球

过滤器的小火车,开起来落。

本文由作者按照 CC BY 4.0 进行授权