您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

如何将 Dubbo Filter 拦截器原理运用到日志拦截器中?

时间:2023-08-06 12:29:42  来源:今日头条  作者:老马啸西风

业务背景

我们希望可以在使用日志拦截器时,定义属于自己的拦截器方法。

实现的方式有很多种,我们分别来看一下。

拓展阅读

JAVA 注解结合 spring aop 实现自动输出日志[1]

java 注解结合 spring aop 实现日志traceId唯一标识[2]

java 注解结合 spring aop 自动输出日志新增拦截器与过滤器[3]

如何动态修改 spring aop 切面信息?让自动日志输出框架更好用[4]

如何将 dubbo filter 拦截器原理运用到日志拦截器中?[5]

v1-基本版本

接口

最常见的定义方式,在方法执行前后,异常,finally 提供钩子函数。

package com.Github.houbb.auto.log.api;


/**
 * autoLog 拦截器
 * @author binbin.hou
 * @since 0.0.10
 */
public interface IAutoLogInterceptor {


    /**
     * 执行之前
     * @param interceptorContext 拦截器上下文
     * @since 0.0.10
     */
    void beforeHandle(IAutoLogInterceptorContext interceptorContext);


    /**
     * 执行之后
     * @param interceptorContext 拦截器上下文
     * @param result 方法执行结果
     * @since 0.0.10
     */
    void afterHandle(IAutoLogInterceptorContext interceptorContext,
                     final Object result);


    /**
     * 异常处理
     * @param interceptorContext 拦截器上下文
     * @param exception 异常
     * @since 0.0.10
     */
    void exceptionHandle(IAutoLogInterceptorContext interceptorContext, Exception exception);


    /**
     * finally 中执行的代码
     * @param interceptorContext 拦截器上下文
     * @since 0.0.10
     */
    void finallyHandle(IAutoLogInterceptorContext interceptorContext);


}

工具中统一使用拦截器

package com.github.houbb.auto.log.core.core.impl;
/**
 * @author binbin.hou
 * @since 0.0.7
 */
public class SimpleAutoLog implements IAutoLog {


    /**
     * 自动日志输出
     *
     * @param context 上下文
     * @return 结果
     * @since 0.0.7
     */
    @Override
    public Object autoLog(IAutoLogContext context) throws Throwable {
        //1. 日志唯一标识
        // ... 省略
        List<IAutoLogInterceptor> autoLogInterceptors = null;


        try {
            // ... 省略其他逻辑
            // 获取拦截器
            autoLogInterceptors = autoLogInterceptors(autoLog);


            //1.2 autoLog
            if(CollectionUtil.isNotEmpty(autoLogInterceptors)) {
                for(IAutoLogInterceptor interceptor : autoLogInterceptors) {
                    interceptor.beforeHandle(autoLogContext);
                }
            }


            //2. 执行结果
            Object result = context.process();


            //2.1 方法执行后
            if(CollectionUtil.isNotEmpty(autoLogInterceptors)) {
                for(IAutoLogInterceptor interceptor : autoLogInterceptors) {
                    interceptor.afterHandle(autoLogContext, result);
                }
            }


            //2.2 返回方法
            return result;
        } catch (Exception exception) {
            if(CollectionUtil.isNotEmpty(autoLogInterceptors)) {
                for(IAutoLogInterceptor interceptor : autoLogInterceptors) {
                    interceptor.exceptionHandle(autoLogContext, exception);
                }
            }


            throw new AutoLogRuntimeException(exception);
        } finally {
            // 先执行日志
            if(CollectionUtil.isNotEmpty(autoLogInterceptors)) {
                for(IAutoLogInterceptor interceptor : autoLogInterceptors) {
                    interceptor.finallyHandle(autoLogContext);
                }
            }
        }
    }


    /**
     * 创建拦截器列表
     * @param autoLog 注解
     * @return 结果
     * @since 0.0.10
     */
    private List<IAutoLogInterceptor> autoLogInterceptors(final AutoLog autoLog) {
        List<IAutoLogInterceptor> resultList = new ArrayList<>();
        if(ObjectUtil.isNull(autoLog)) {
            return resultList;
        }


        Class<? extends IAutoLogInterceptor>[] interceptorClasses = autoLog.interceptor();
        if(ArrayUtil.isEmpty(interceptorClasses)) {
            return resultList;
        }


        // 循环创建
        for(Class<? extends IAutoLogInterceptor> clazz : interceptorClasses) {
            IAutoLogInterceptor traceIdInterceptor = createAutoLogInterceptor(clazz);
            resultList.add(traceIdInterceptor);
        }


        return resultList;
    }




    /**
     * 创建拦截器
     * @param clazz 类
     * @return 实体
     * @since 0.0.10
     */
    private IAutoLogInterceptor createAutoLogInterceptor(final Class<? extends IAutoLogInterceptor> clazz) {
        if(IAutoLogInterceptor.class.equals(clazz)) {
            return new AutoLogInterceptor();
        }


        return ClassUtil.newInstance(clazz);
    }


}

自定义实现拦截器

我们想自定义拦截器方法时,只需要实现对应的接口即可。

/**
 * 自定义日志拦截器
 * @author binbin.hou
 * @since 0.0.12
 */
public class MyAutoLogInterceptor extends AbstractAutoLogInterceptor {


    @Override
    protected void doBefore(AutoLog autoLog, IAutoLogInterceptorContext context) {
        System.out.println("自定义入参:" + Arrays.toString(context.filterParams()));
    }


    @Override
    protected void doAfter(AutoLog autoLog, Object result, IAutoLogInterceptorContext context) {
        System.out.println("自定义出参:" + result);
    }


    @Override
    protected void doException(AutoLog autoLog, Exception exception, IAutoLogInterceptorContext context) {
        System.out.println("自定义异常:");
        exception.printStackTrace();
    }


}

方法的不足

这种方式可以实现常见的功能,但是依然不够优雅。

我们还是无法非常灵活的定义自己的拦截器实现,就像我们使用 aop 增强,或者 dubbo filter 一样。

感兴趣的小伙伴可以移步学习一下,此处不做展开。

Dubbo-02-dubbo invoke filter 链式调用原理[6]

模拟 dubbo filter

实现 Invoker

类似 dubbo invoke,直接在以前的类中初始化即可。

AutoLogInvoker autoLogInvoker = new AutoLogInvoker(context);
Invocation invocation = new CommonInvocation();
invocation.setAttachment(AutoLogAttachmentKeyConst.AUTO_LOG_CONTEXT, context);
invocation.setAttachment(AutoLogAttachmentKeyConst.AUTO_LOG_START_TIME, startTimeMills);
invocation.setAttachment(AutoLogAttachmentKeyConst.AUTO_LOG_FILTER_PARAMS, filterParams);


Invoker chAInInvoker = InvokerChainBuilder.buildInvokerChain(autoLogInvoker);
Result autoLogResult = chainInvoker.invoke(invocation);

其中 AutoLogInvoker 只是对方法的执行。

实现拦截器

这是的方法增强就是类似 dubbo filter 链式调用实现的,自定义的时候也会方便很多。

不需要拘泥于方法的执行位置,直接编写我们的增强逻辑即可。

package com.github.houbb.auto.log.core.support.interceptor.chain;


import com.alibaba.fastjson.JSON;
import com.github.houbb.auto.log.annotation.AutoLog;
import com.github.houbb.auto.log.api.IAutoLogContext;
import com.github.houbb.auto.log.core.constant.AutoLogAttachmentKeyConst;
import com.github.houbb.common.filter.annotation.FilterActive;
import com.github.houbb.common.filter.api.CommonFilter;
import com.github.houbb.common.filter.api.Invocation;
import com.github.houbb.common.filter.api.Invoker;
import com.github.houbb.common.filter.api.Result;
import com.github.houbb.common.filter.exception.CommonFilterException;
import com.github.houbb.heaven.util.lang.StringUtil;
import com.github.houbb.heaven.util.lang.reflect.ClassUtil;
import com.github.houbb.heaven.util.lang.reflect.ReflectMethodUtil;
import com.github.houbb.id.api.Id;
import com.github.houbb.id.core.core.Ids;
import com.github.houbb.id.core.util.IdThreadLocalHelper;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;


import java.lang.reflect.Method;


/**
 * 默认的日志拦截器
 */
@FilterActive(order = Integer.MIN_VALUE)
public class AutoLogCommonFilter implements CommonFilter {


    private static final Log LOG = LogFactory.getLog(AutoLogCommonFilter.class);


    /**
     * 是否需要处理日志自动输出
     * @param autoLog 上下文
     * @return 结果
     * @since 0.0.10
     */
    protected boolean enableAutoLog(final AutoLog autoLog) {
        if(autoLog == null) {
            return false;
        }


        return autoLog.enable();
    }


    /**
     * 获取方法描述
     * @param method 方法
     * @param autoLog 注解
     * @return 结果
     * @since 0.0.10
     */
    protected String getMethodDescription(Method method, AutoLog autoLog) {
        String methodName = ReflectMethodUtil.getMethodFullName(method);


        if(autoLog != null
            && StringUtil.isNotEmpty(autoLog.description())) {
            methodName += "#" + autoLog.description();
        }


        return methodName;
    }


    /**
     * 获取 traceId
     * @param autoLog 日志注解
     * @return 结果
     * @since 0.0.10
     */
    protected String getTraceId(AutoLog autoLog) {
        //1. 优先看当前线程中是否存在
        String oldId = IdThreadLocalHelper.get();
        if(StringUtil.isNotEmpty(oldId)) {
            return formatTraceId(oldId);
        }


        //2. 返回对应的标识
        Id id = getActualTraceId(autoLog);
        return formatTraceId(id.id());
    }


    /**
     * 获取日志跟踪号策略
     * @param autoLog 注解
     * @return 没结果
     */
    protected Id getActualTraceId(AutoLog autoLog) {
        Class<? extends Id> idClass = autoLog.traceId();
        if(Id.class.equals(idClass)) {
            return Ids.uuid32();
        }
        return ClassUtil.newInstance(autoLog.traceId());
    }


    /**
     * 格式化日志跟踪号
     * @param id 跟踪号
     * @return 结果
     * @since 0.0.16
     */
    protected String formatTraceId(String id) {
        return String.format("[%s] ", id);
    }


    @Override
    public Result invoke(Invoker invoker, Invocation invocation) throws CommonFilterException {
        final IAutoLogContext autoLogContext = (IAutoLogContext) invocation.getAttachment(AutoLogAttachmentKeyConst.AUTO_LOG_CONTEXT);
        final AutoLog autoLog = autoLogContext.autoLog();
        final boolean enableAutoLog = enableAutoLog(autoLog);
        if(!enableAutoLog) {
            return invoker.invoke(invocation);
        }


        final String description = getMethodDescription(autoLogContext.method(), autoLog);
        // 默认从上下文中取一次
        String traceId = IdThreadLocalHelper.get();
        try {
            // 设置 traceId 策略
            if(autoLog.enableTraceId()) {
                Id id = getActualTraceId(autoLog);
                traceId = id.id();


                invocation.setAttachment(AutoLogAttachmentKeyConst.AUTO_LOG_TRACE_ID, traceId);
                IdThreadLocalHelper.put(traceId);
            }


            Result result = invoker.invoke(invocation);


            // 日志增强
            logForEnhance(autoLogContext, traceId, description, result.getValue(), invocation);


            return result;
        } catch (Exception e) {
            if (autoLog.exception()) {
                String message = String.format("[TID=%s][EXCEPTION=%s]", traceId, e.getMessage());
                LOG.error(message, e);
            }


            throw new RuntimeException(e);
        }
    }


    /**
     * 增强日志输出
     * @param autoLogContext 上下文
     * @param traceId 日志跟踪号
     * @param description 方法描述
     * @param resultValue 返回值
     * @param invocation 调用上下文
     */
    private void logForEnhance(final IAutoLogContext autoLogContext,
                               final String traceId,
                               final String description,
                               final Object resultValue,
                               Invocation invocation) {
        final AutoLog autoLog = autoLogContext.autoLog();


        StringBuilder logBuilder = new StringBuilder();
        logBuilder.Append(String.format("[TID=%s]", traceId));
        logBuilder.append(String.format("[METHOD=%s]", description));


        // 入参
        if(autoLog.param()) {
            Object[] params = (Object[]) invocation.getAttachment(AutoLogAttachmentKeyConst.AUTO_LOG_FILTER_PARAMS);
            logBuilder.append(String.format("[PARAM=%s]", JSON.toJSONString(params)));
        }
        // 出参
        if (autoLog.result()) {
            logBuilder.append(String.format("[RESULT=%s]", JSON.toJSONString(resultValue)));
        }
        // 耗时
        //3.1 耗时 & 慢日志
        if(autoLog.costTime()) {
            long startTime = (long) invocation.getAttachment(AutoLogAttachmentKeyConst.AUTO_LOG_START_TIME);
            long costTime = System.currentTimeMillis() - startTime;
            logBuilder.append(String.format("[COST=%d ms]", costTime));


            // 慢日志
            final long slowThreshold = autoLog.slowThresholdMills();
            if(slowThreshold > 0 && costTime > slowThreshold) {
                logBuilder.append(String.format("[SLOW-THRESHOLD=%s]", slowThreshold));
            }
        }


        // 输出日志
        LOG.info(logBuilder.toString());
    }


}

开源地址

为了便于大家学习,项目已开源。

Github: https://github.com/houbb/auto-log

Gitee: https://gitee.com/houbinbin/auto-log

小结

dubbo filter 模式非常的优雅,以前一直只是学习,没有将其应用到自己的项目中。

提供的便利性是非常强大的,值得学习运用。

参考资料

auto-log[7]

References

[1] java 注解结合 spring aop 实现自动输出日志: https://houbb.github.io/2023/08/06/auto-log-01-overview
[2] java 注解结合 spring aop 实现日志traceId唯一标识: https://houbb.github.io/2023/08/06/auto-log-02-trace-id
[3] java 注解结合 spring aop 自动输出日志新增拦截器与过滤器: https://houbb.github.io/2023/08/06/auto-log-03-filter
[4] 如何动态修改 spring aop 切面信息?让自动日志输出框架更好用: https://houbb.github.io/2023/08/06/auto-log-04-dynamic-aop
[5] 如何将 dubbo filter 拦截器原理运用到日志拦截器中?: https://houbb.github.io/2023/08/06/auto-log-05-dubbo-interceptor
[6] Dubbo-02-dubbo invoke filter 链式调用原理: https://houbb.github.io/2016/09/25/dubbo-02-invoke
[7] auto-log: https://github.com/houbb/auto-log



Tags:Dubbo Filter   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
如何将 Dubbo Filter 拦截器原理运用到日志拦截器中?
业务背景我们希望可以在使用日志拦截器时,定义属于自己的拦截器方法。实现的方式有很多种,我们分别来看一下。拓展阅读java 注解结合 spring aop 实现自动输出日志[1]java 注...【详细内容】
2023-08-06  Search: Dubbo Filter  点击:(225)  评论:(0)  加入收藏
▌简易百科推荐
即将过时的 5 种软件开发技能!
作者 | Eran Yahav编译 | 言征出品 | 51CTO技术栈(微信号:blog51cto) 时至今日,AI编码工具已经进化到足够强大了吗?这未必好回答,但从2023 年 Stack Overflow 上的调查数据来看,44%...【详细内容】
2024-04-03    51CTO  Tags:软件开发   点击:(5)  评论:(0)  加入收藏
跳转链接代码怎么写?
在网页开发中,跳转链接是一项常见的功能。然而,对于非技术人员来说,编写跳转链接代码可能会显得有些困难。不用担心!我们可以借助外链平台来简化操作,即使没有编程经验,也能轻松实...【详细内容】
2024-03-27  蓝色天纪    Tags:跳转链接   点击:(12)  评论:(0)  加入收藏
中台亡了,问题到底出在哪里?
曾几何时,中台一度被当做“变革灵药”,嫁接在“前台作战单元”和“后台资源部门”之间,实现企业各业务线的“打通”和全域业务能力集成,提高开发和服务效率。但在中台如火如荼之...【详细内容】
2024-03-27  dbaplus社群    Tags:中台   点击:(8)  评论:(0)  加入收藏
员工写了个比删库更可怕的Bug!
想必大家都听说过删库跑路吧,我之前一直把它当一个段子来看。可万万没想到,就在昨天,我们公司的某位员工,竟然写了一个比删库更可怕的 Bug!给大家分享一下(不是公开处刑),希望朋友们...【详细内容】
2024-03-26  dbaplus社群    Tags:Bug   点击:(5)  评论:(0)  加入收藏
我们一起聊聊什么是正向代理和反向代理
从字面意思上看,代理就是代替处理的意思,一个对象有能力代替另一个对象处理某一件事。代理,这个词在我们的日常生活中也不陌生,比如在购物、旅游等场景中,我们经常会委托别人代替...【详细内容】
2024-03-26  萤火架构  微信公众号  Tags:正向代理   点击:(10)  评论:(0)  加入收藏
看一遍就理解:IO模型详解
前言大家好,我是程序员田螺。今天我们一起来学习IO模型。在本文开始前呢,先问问大家几个问题哈~什么是IO呢?什么是阻塞非阻塞IO?什么是同步异步IO?什么是IO多路复用?select/epoll...【详细内容】
2024-03-26  捡田螺的小男孩  微信公众号  Tags:IO模型   点击:(8)  评论:(0)  加入收藏
为什么都说 HashMap 是线程不安全的?
做Java开发的人,应该都用过 HashMap 这种集合。今天就和大家来聊聊,为什么 HashMap 是线程不安全的。1.HashMap 数据结构简单来说,HashMap 基于哈希表实现。它使用键的哈希码来...【详细内容】
2024-03-22  Java技术指北  微信公众号  Tags:HashMap   点击:(11)  评论:(0)  加入收藏
如何从头开始编写LoRA代码,这有一份教程
选自 lightning.ai作者:Sebastian Raschka机器之心编译编辑:陈萍作者表示:在各种有效的 LLM 微调方法中,LoRA 仍然是他的首选。LoRA(Low-Rank Adaptation)作为一种用于微调 LLM(大...【详细内容】
2024-03-21  机器之心Pro    Tags:LoRA   点击:(12)  评论:(0)  加入收藏
这样搭建日志中心,传统的ELK就扔了吧!
最近客户有个新需求,就是想查看网站的访问情况。由于网站没有做google的统计和百度的统计,所以访问情况,只能通过日志查看,通过脚本的形式给客户导出也不太实际,给客户写个简单的...【详细内容】
2024-03-20  dbaplus社群    Tags:日志   点击:(4)  评论:(0)  加入收藏
Kubernetes 究竟有没有 LTS?
从一个有趣的问题引出很多人都在关注的 Kubernetes LTS 的问题。有趣的问题2019 年,一个名为 apiserver LoopbackClient Server cert expired after 1 year[1] 的 issue 中提...【详细内容】
2024-03-15  云原生散修  微信公众号  Tags:Kubernetes   点击:(6)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条