最近完成一个需求,使用阿里Arms需要在log里面加上traceId,但是发现dubbo异常 被ExceptionFilter捕获 并打印  打印不出traceI,然后百度搜索如何重写Filter

参考了这篇文章

https://www.jianshu.com/p/7e7076212bd0

重写ExceptionFilter

1.新增一个dubboExceptionFilter类

标红部分 是我改动电脑 其他都是复制原来的ExceptionFilter

@Activate(
        group = {"provider"}
)
public class ArmsDubboExceptionFilter implements Filter {
    private final Logger logger;

    public ArmsDubboExceptionFilter() {
        this(LoggerFactory.getLogger(ArmsDubboExceptionFilter.class));
    }

    public ArmsDubboExceptionFilter(Logger logger) {
        this.logger = logger;
    }

    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        try {
            setSpan(invocation);
            Result result = invoker.invoke(invocation);
            if (result.hasException() && GenericService.class != invoker.getInterface()) {
                try {
                    Throwable exception = result.getException();
                    if (!(exception instanceof RuntimeException) && exception instanceof Exception) {
                        return result;
                    } else {
                        try {
                            Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                            Class<?>[] exceptionClassses = method.getExceptionTypes();
                            Class[] arr$ = exceptionClassses;
                            int len$ = exceptionClassses.length;

                            for (int i$ = 0; i$ < len$; ++i$) {
                                Class<?> exceptionClass = arr$[i$];
                                if (exception.getClass().equals(exceptionClass)) {
                                    return result;
                                }
                            }
                        } catch (NoSuchMethodException var11) {
                            return result;
                        }

                        this.logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
                        String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                        String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                        if (serviceFile != null && exceptionFile != null && !serviceFile.equals(exceptionFile)) {
                            String className = exception.getClass().getName();
                            if (!className.startsWith("java.") && !className.startsWith("javax.")) {
                                return (Result) (exception instanceof RpcException ? result : new RpcResult(new RuntimeException(StringUtils.toString(exception))));
                            } else {
                                return result;
                            }
                        } else {
                            return result;
                        }
                    }
                } catch (Throwable var12) {
                    this.logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + var12.getClass().getName() + ": " + var12.getMessage(), var12);
                    return result;
                }
            } else {
                return result;
            }
        } catch (RuntimeException var13) {
            this.logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + var13.getClass().getName() + ": " + var13.getMessage(), var13);
            throw var13;
        } finally {
            ArmsUtils.remove();
        }
    }

    /*
     * 因为通过arms sdk拿不到traceId(与阿里工程师沟通 貌似是bug 暂时自己再协议头里面获取 并存入线程缓存 供log appender使用)
     * @param invocation
     */
    public void setSpan(Invocation invocation) {
        try {
            String sampled = invocation.getAttachment("X-B3-Sampled");
            ArmsUtils.setSpan(invocation.getAttachment("X-B3-TraceId"), invocation.getAttachment("EagleEye-RpcID"), invocation.getAttachment("X-B3-SpanId"), sampled != null && sampled.equals("1"));
        } catch (Exception e) {
            logger.error("写入span异常", e);
        }
    }
}

2.在/src/main/resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter新增一个文件com.alibaba.dubbo.rpc.Filter

内容

DubboExceptionFilter=com.biz.core.armslog.ArmsDubboExceptionFilter

3.soa-provider.xml配置

   <!-- 延迟暴露服务,表示延迟到Spring容器初始化完成时暴露服务; 不重试 filter自定义一个dubboExceptionFilter -exception表示替换了默认的ExceptionFilter 增加arms日志打印-->
    <dubbo:provider delay="-1" retries="0" filter="DubboExceptionFilter,-exception"/>

阅读源码 理解为什么这么写

首先需要理解dubbo SPI的实现原理 前一篇有些

ProtocolFilterWrapper

为dubboFilter的包装类 用来为生成filter执行链

/**
     * 为invoker生成filter调用链条 invoker为执行对象
     * @param invoker 执行对象
     * @param key 为参数名
     * @param group 等于@Activate(group = {"provider"})
     * @param <T>
     * @return
     */
    private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        final Invoker<T> last = invoker;
        /**
         * 使用dubbo SPI获取默认和用户自定义的filter
         * url为暴露的服务地址
         * keyinjvm://127.0.0.1/com.biz.soa.service.promotion.backend.OfflineExtend.PromotionOfflineService?anyhost=true&application=soa-promotion-provider&bind.ip=10.37.129.2&bind.port=23888&default.delay=-1&default.retries=0&default.service.filter=DubboExceptionFilter,-exception&delay=-1&dispatcher=message&dubbo=2.6.2&generic=false&interface=com.biz.soa.service.promotion.backend.OfflineExtend.PromotionOfflineService&methods=downLoadPromotion,updaPromotion,queryPromotionOffline,getOfflineProducts,SavePromotion&pid=81391&register=false&side=provider&threadpool=fixed&threads=500&timestamp=1574842487417
         * group为provider (如果是consumer则是consumer)
         * */
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
        if (!filters.isEmpty()) {
            /**
             * 这里是循环生成filer调用链条  filter1->filter2->invoke
             */
            for(int i = filters.size() - 1; i >= 0; --i) {
                final Filter filter = (Filter)filters.get(i);
                last = new Invoker<T>() {
                    public Class<T> getInterface() {
                        return invoker.getInterface();
                    }

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

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

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

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

                    public String toString() {
                        return invoker.toString();
                    }
                };
            }
        }

        return last;
    }

ExtensionLoader

ExtensionLoader.getExtensionLoader实现

 /**
     * 根据class获取对应的ExtensionLoader
     * @param type
     * @param <T>
     * @return
     */
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        } else if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        } else if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        } else {
            //因为是泛型对应的扩展点 提前初始化了一个对应类型的 ExtensionLoader到 这里就会获取到ExtensionLoader<Filter> 的对象
            ExtensionLoader<T> loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
            if (loader == null) {
                EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
                loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
            }

            return loader;
        }
    }

ExtensionLoader.getActivateExtension

    /**
     * 获取所有filter 默认的或者用户配置的
     * @param url
     * @param values
     * @param group
     * @return
     */
    public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> exts = new ArrayList();
        List<String> names = values == null ? new ArrayList(0) : Arrays.asList(values);
        String name;
        /**
         * 这里是加载默认的filter 如果配置了 -default 将忽略所有默认过滤器
         */
        if (!((List)names).contains("-default")) {
            this.getExtensionClasses();
            Iterator i$ = this.cachedActivates.entrySet().iterator();

            while(i$.hasNext()) {
                Map.Entry<String, Activate> entry = (Map.Entry)i$.next();
                /**
                 * 获得spi配置的key名字 如dubboSPI配置: exception=com.alibaba.dubbo.rpc.filter.ExceptionFilter
                 */
                name = (String)entry.getKey();
                Activate activate = (Activate)entry.getValue();
                /**
                 * 这里匹配Activate的配置的group 只找出provider的
                 */
                if (this.isMatchGroup(group, activate.group())) {
                    T ext = this.getExtension(name);
                    /**
                     * 因为我们配置了-exception  所以这里不会加载                     * isActive如果注解配置了value@Activate(group={"provider"},value={"token"}) 则会在parameter检查是否有传递token 参数 如果有才加入到过滤器
                     */
                    if (!((List)names).contains(name) && !((List)names).contains("-" + name) && this.isActive(activate, url)) {
                        exts.add(ext);
                    }
                }
            }
            /**
             * 排序 可以看ActivateCompartor.COMPARATOR实现 这个排序实现是根据 注解的order 值来排的
             */
            Collections.sort(exts, ActivateComparator.COMPARATOR);
        }
        /**
         * 下面是加载用户的Filter
         * <dubbo:provider delay="-1" retries="0" filter="DubboExceptionFilter,-exception"/>
         */
        List<T> usrs = new ArrayList();

        for(int i = 0; i < ((List)names).size(); ++i) {
            name = (String)((List)names).get(i);
            //-开头的的表示剔除 不执行逻辑
            if (!name.startsWith("-") && !((List)names).contains("-" + name)) {
                if ("default".equals(name)) {
                    if (!usrs.isEmpty()) {
                        exts.addAll(0, usrs);
                        usrs.clear();
                    }
                } else {
                    T ext = this.getExtension(name);
                    usrs.add(ext);
                }
            }
        }

        if (!usrs.isEmpty()) {
            exts.addAll(usrs);
        }

        return exts;
    }
 private boolean isActive(Activate activate, URL url) {
        //获得注解桑的value
        String[] keys = activate.value();
        //如果没有值直接返回true
        if (keys.length == 0) {
            return true;
        } else {
            String[] arr$ = keys;
            int len$ = keys.length;

            label34:
            //遍历values
            for(int i$ = 0; i$ < len$; ++i$) {
                String key = arr$[i$];
                //获得参数列表迭代器 参数列表为xml配置哦
                Iterator i$ = url.getParameters().entrySet().iterator();

                String k;
                String v;
                do {
                    do {
                        if (!i$.hasNext()) {
                            continue label34;
                        }

                        Map.Entry<String, String> entry = (Map.Entry)i$.next();
                        k = (String)entry.getKey();
                        v = (String)entry.getValue();
                        //参数包含了 过滤器信息 同时值不能为空 就加入过滤器
                    } while(!k.equals(key) && !k.endsWith("." + key));
                } while(!ConfigUtils.isNotEmpty(v));

                return true;
            }

            return false;
        }
    }

看完之后是不是一切都明朗了。。我们的com.alibaba.dubbo.rpc.Filter 配置 是SPI实现方式 实现动态注入

dubbo-源码阅读之Filter实现原理的更多相关文章

  1. 【Dubbo源码阅读系列】服务暴露之本地暴露

    在上一篇文章中我们介绍 Dubbo 自定义标签解析相关内容,其中我们自定义的 XML 标签 <dubbo:service /> 会被解析为 ServiceBean 对象(传送门:Dubbo ...

  2. 【Dubbo源码阅读系列】之远程服务调用(上)

    今天打算来讲一讲 Dubbo 服务远程调用.笔者在开始看 Dubbo 远程服务相关源码的时候,看的有点迷糊.后来慢慢明白 Dubbo 远程服务的调用的本质就是动态代理模式的一种实现.本地消费者无须知道 ...

  3. 【Dubbo源码阅读系列】服务暴露之远程暴露

    引言 什么叫 远程暴露 ?试着想象着这么一种场景:假设我们新增了一台服务器 A,专门用于发送短信提示给指定用户.那么问题来了,我们的 Message 服务上线之后,应该如何告知调用方服务器,服务器 A ...

  4. 【Dubbo源码阅读系列】之 Dubbo SPI 机制

    最近抽空开始了 Dubbo 源码的阅读之旅,希望可以通过写文章的方式记录和分享自己对 Dubbo 的理解.如果在本文出现一些纰漏或者错误之处,也希望大家不吝指出. Dubbo SPI 介绍 Java ...

  5. Dubbo源码阅读顺序

    转载: https://blog.csdn.net/heroqiang/article/details/85340958 Dubbo源码解析之配置解析篇,主要内容是<dubbo:service/ ...

  6. Android源码阅读-Filter过滤器

    Filter 顺便看看,Android中过滤器是怎么实现的? 注释中提到,Filter一般通过继承Filterable实现 具体实现 这是SimpleAdapter出现的一个过滤首字母item的一个过 ...

  7. Dubbo源码阅读-服务导出

    Dubbo服务导出过程始于Spring容器发布刷新事件,Dubbo在接收到事件后,会立即执行服务导出逻辑.整个逻辑大致可分为三个部分,第一部分是前置工作,主要用于检查参数,组装URL.第二部分是导出服 ...

  8. dubbo源码阅读之负载均衡

    负载均衡 在之前集群的文章中,我们分析了通过监听注册中心可以获取到多个服务提供者,并创建多个Invoker,然后通过集群类如FailoverClusterInvoker将多个Invoker封装在一起, ...

  9. dubbo源码阅读之自适应扩展

    自适应扩展机制 刚开始看代码,其实并不能很好地理解dubbo的自适应扩展机制的作用,我们不妨先把代码的主要逻辑过一遍,梳理一下,在了解了代码细节之后,回过头再来思考自适应扩展的作用,dubbo为什么要 ...

随机推荐

  1. java synchronized的四种用法

    一 修饰方法 Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来 ...

  2. ubuntu下安装chrome谷歌浏览器

    百度“chrome”然后登录谷歌浏览器官网下载deb包 cd到下载的目录下 sudo dpkg -i google-chrome*; 提示缺少依赖包,打入如下命令 sudo apt-get -f in ...

  3. Python之 set的特点

    set的内部结构和dict很像,唯一区别是不存储value,因此,判断一个元素是否在set中速度很快. set存储的元素和dict的key类似,必须是不变对象,因此,任何可变对象是不能放入set中的. ...

  4. MYSQL如何优化?

    MYSQL如何优化?结合你的经验 1.数据库的设计尽量把数据库设计的更小的占磁盘空间.1).尽可能使用更小的整数类型.(mediumint就比int更合适).2).尽可能的定义字段为not null, ...

  5. WaitForSingleObject的作用[转]

    在多线程的情况下,有时候我们会希望等待某一线程完成了再继续做其他事情(比如主线程等待子线程结束完之后,自己再结束),要实现这个目的,可以使用Windows API函数WaitForSingleObje ...

  6. jQuery遍历之向上遍历

    1.parent $(document).ready(function(){ $("a").parent().css({border:"4px solid black&q ...

  7. maven eclipse远程部署tomcat

    pom.xml tomcat 配置信息  <properties><project.build.sourceEncoding>utf8</project.build.so ...

  8. 手写Spring事务框架

    Spring事务基于AOP环绕通知和异常通知 编程事务 声明事务 Spring事务底层使用编程事务+AOP进行包装的   = 声明事务 AOP应用场景:  事务 权限 参数验证 什么是AOP技术 AO ...

  9. 小鱼易连 for mac如何使用?小鱼易连 mac版使用教程

    小鱼易连 for mac如何使用?小鱼易连 mac版是参加远程会议人士的首选,高效极致,简单流畅,视频流畅,语音清晰,无需专用网络的功能深受用户的喜欢,它提供的文件和电脑的共享,让你的会议更加高效.下 ...

  10. 【leetcode】640. Solve the Equation

    题目如下: 解题思路:本题的思路就是解析字符串,然后是小学时候学的解方程的思想,以"2x+3x-6x+1=x+2",先把左右两边的x项和非x项进行合并,得到"-x+1=x ...