如何将 dubbo filter 拦截器原理运用到日志拦截器中?
业务背景
我们希望可以在使用日志拦截器时,定义属于自己的拦截器方法。
实现的方式有很多种,我们分别来看一下。
拓展阅读
java 注解结合 spring aop 实现自动输出日志
java 注解结合 spring aop 实现日志traceId唯一标识
java 注解结合 spring aop 自动输出日志新增拦截器与过滤器
如何动态修改 spring aop 切面信息?让自动日志输出框架更好用
如何将 dubbo filter 拦截器原理运用到日志拦截器中?
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*/@Overridepublic Object autoLog(IAutoLogContext context) throws Throwable {//1. 日志唯一标识// ... 省略List<IAutoLogInterceptor> autoLogInterceptors = null;try {// ... 省略其他逻辑// 获取拦截器autoLogInterceptors = autoLogInterceptors(autoLog);//1.2 autoLogif(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 {@Overrideprotected void doBefore(AutoLog autoLog, IAutoLogInterceptorContext context) {System.out.println("自定义入参:" + Arrays.toString(context.filterParams()));}@Overrideprotected void doAfter(AutoLog autoLog, Object result, IAutoLogInterceptorContext context) {System.out.println("自定义出参:" + result);}@Overrideprotected void doException(AutoLog autoLog, Exception exception, IAutoLogInterceptorContext context) {System.out.println("自定义异常:");exception.printStackTrace();}}
方法的不足
这种方式可以实现常见的功能,但是依然不够优雅。
我们还是无法非常灵活的定义自己的拦截器实现,就像我们使用 aop 增强,或者 dubbo filter 一样。
感兴趣的小伙伴可以移步学习一下,此处不做展开。
Dubbo-02-dubbo invoke filter 链式调用原理
模拟 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);}@Overridepublic 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
相关文章:

如何将 dubbo filter 拦截器原理运用到日志拦截器中?
业务背景 我们希望可以在使用日志拦截器时,定义属于自己的拦截器方法。 实现的方式有很多种,我们分别来看一下。 拓展阅读 java 注解结合 spring aop 实现自动输出日志 java 注解结合 spring aop 实现日志traceId唯一标识 java 注解结合 spring ao…...

【java】【maven】【基础】MAVEN安装配置介绍
目录 1 下载 2 安装-windows为例 3 配置环境变量 3.1 JAVA_HOME 3.2 MAVEN_HOME 3.3 PATH 3.4 验证 4 MAVEN基础概念 4.1 仓库概念 4.2 坐标概念 4.2.1 打开网址 4.2.2 输入搜索内容junit 4.2.3 找到对应API名称点击 4.2.4 点击对应版本 4.2.5 复制MAVEN坐标 4.3 配置…...

【C语言进阶】指针的高级应用(下)
文章目录 一、指针数组与数组指针1.1 指针数组与数组指针的表达式 二、函数指针2.1 函数指针的书写方式 三、二重指针与一重指针3.1 二重指针的本质3.2 二重指针的用法3.3 二重指针与数组指针 总结 一、指针数组与数组指针 (1)指针数组的实质是一个数组,这个数组中存…...

【uniapp APP隐藏顶部的电量,无线,时间状态栏与导航栏】
uniapp APP隐藏顶部的电量,无线,时间状态栏 如下代码配置(在一个页面设置这个段代码,所有页面都会消失) onShow() {// #ifdef APP-PLUS// 隐藏顶部电池,时间等信息 plus.navigator.setFullscreen(true);//隐藏虚拟按…...

微信小程序前后页面传值
微信小程序前后页面传值 从前一个页面跳转到下一个页面,如何传递参数?从后一个页面返回前一个页面,如何回调参数? 向后传值 从前一个页面跳转到下一个页面并传值。 前页面:在跳转链接中添加参数并传递 wx.navigat…...

没有jodatime,rust里怎么比较两个日期(时间)的大小呢?
关注我,学习Rust不迷路!! 在 Rust 中,比较两个日期的大小有多种方法。以下是列举的四种常见方法: 1. 使用 PartialOrd trait: use chrono::prelude::*;fn main() {let date1 NaiveDate::from_ymd(2022,…...

【雕爷学编程】Arduino动手做(186)---WeMos ESP32开发板18
37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的&#x…...

C语言假期作业 DAY 14
一、选择题 1、有以下函数,该函数的功能是( ) int fun(char *s) {char *t s;while(*t);return(t-s); } A: 比较两个字符的大小 B: 计算s所指字符串占用内存字节的个数 C: 计算s所指字符串的长度 D: 将s所指字符串复制到字符串t中 答案解析 …...

Maven-生命周期及命令
关于本文 ✍写作原因 之前在学校学习的时候,编写代码使用的项目都是单体架构,导入开源框架依赖时只需要在pom.xml里面添加依赖,点一下reload按钮即可解决大部分需求;但是在公司使用了dubbo微服务架构之后发现只知道使用reload不足…...

常温超导:AI在研发中的潜力
目录 引言:1. 常温超导的挑战:2. AI在材料研究中的作用:3. AI在理论模型中的作用:4. AI与实验室合作的潜力:结论: 引言: 常温超导一直被认为是科学界的“圣杯”,可以在室温条件下实…...

【C++】开源:Eigen3矩阵与线性代数库配置使用
😏★,:.☆( ̄▽ ̄)/$:.★ 😏 这篇文章主要介绍Eigen3矩阵与线性代数库配置使用。 无专精则不能成,无涉猎则不能通。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下&…...

OpenMesh 网格简化算法(基于边长度)
文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 网格简化的算法有很多种,基于边结构进行简化的方法便是其中一种方式。此类算法主要关注于它们如何选择要收缩的边,并且似乎都是为流形表面设计的,尽管边缘收缩也可以用于非流形表面,但往往会存在变形较大的问题…...

FFmpeg解码详细流程
介绍 FFmpeg的 libavcodec 模块完成音视频多媒体的编解码模块。老版本的 FFmpeg 将avcodec_decode_video2()作为视频的解码函数 API,将avcodec_decode_audio4()作为音频的解码函数 API;从 3.4版本开始已经将二者标记为废弃过时 API(attribut…...

人工智能的缺陷
首先从应用层面理解什么是人工智能,目前人工智能主流应用面包括:自然语言处理领域,代表为chatgpt,我们能用其进行日常交流,问题答疑,论文书写等。计算机视觉领域,代表为人脸识别,现在…...

基于ASP.NET MVC开发的、开源的个人博客系统
推荐一个功能丰富、易于使用和扩展的开源博客,可以轻松地创建和管理自己的博客。 项目简介 基于.Net Framework 4.5开发的、开源博客系统,具有丰富的功能,包括文章发布、分类、标签、评论、订阅、统计等功能,同时也可以根据需要…...

【LeetCode】对称二叉树 平衡二叉树
对称二叉树 即先判断根节点的左右子树相不相同,相同时,再判断左孩子的左子树和右孩子的右子树比较,左孩子的右子树和右孩子的左子树(当两个都相同时才是对称的).....依次递推,过程中并设置一些不满足相同的…...

区块链和WEB3.0有哪些基础知识呢
区块链基础知识 常用区块链基础知识包括: (1)区块链概念:区块链是一种去中心化的分布式账本技术,它通过加密算法和共识机制保证了数据的安全性和不可篡改性。区块链中的每一个区块都包含了前一个区块的哈希值&#x…...

七、封装(1)
本章概要 包的概念 代码组织创建独一无二的包名冲突定制工具库使用 import 改变行为使用包的忠告 访问控制(Access control)(或者_隐藏实现(implementation hiding)_)与“最初的实现不恰当”有关。 所有优…...

问题解决和批判性思维是软件工程的重要核心
软件工程的重心在于问题解决和批判性思维(合理设计和架构降低复杂度),而非仅局限于编程。 许多人误以为软件工程就只是编程,即用编程语言编写指令,让计算机按照这些指令行事。但实际上,软件工程的内涵远超…...

【EI/SCOPUS征稿】2023年通信网络与机器学习国际学术会议(CNML 2023)
2023年通信网络与机器学习国际学术会议(CNML 2023) 2023 International Conference on Communication Networks and Machine Learning 随着数据流量的显著增长,新的通信应用程序不断出现,并产生更多的数据流量,这些数…...

算法-岛屿数量
给你一个由 1(陆地)和 0(水)组成的的二维网格,请你计算网格中岛屿的数量。 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此外,你可以假设该网格的四条边…...

Crescent QuickPak Crack
Crescent QuickPak Crack Crescent QuickPak是一个32位ActiveX组件的综合集合,用于使用Visual Basic开发应用程序,这将减少开发时间并提高生产力。Crescent QuickPak包含Internet功能,用于打开、读取和解析IIS日志文件,将日志文件…...

六、ESP32数码管显示数字
1. 本节课的成功 2. 数码管 为什么会亮呢? 答:里面就是LED灯...

【Kubernetes】当K8s出现问题时,从哪些方面可以排查
前言 kubernetes,简称K8s,是用8代替名字中间的8个字符“ubernete”而成的缩写。是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单并且高效(powerful),Kub…...

[ MySQL ] — 库和表的操作
目录 库的操作 创建数据库 语法: 使用: 字符集和校验规则 查看系统默认字符集以及校验规则 查看数据库支持的字符集 查看数据库支持的字符集校验规则 校验规则对数据库的影响 操纵数据库 查看数据库 显示创建语句 修改数据库 删除数据库 备…...

Hive常见面试题
Hive的基本概念 什么是Hive?它的主要作用是什么? Hive是一个基于Hadoop生态系统的数据仓库和数据处理工具。 它提供了类似于SQL的查询语言(HiveQL),使用户能够使用SQL语句来查询和分析 大规模存储在Hadoop集群上的数…...

【单片机】晨启科技,酷黑版,密码锁
密码锁 任务要求: 当输入密码(至少6位密码)时,OLED显示屏显示输入的数字(或者字符),当密码位数输入完毕按下确认键时,对输入的密码与设定的密码进行比较(可使用外设键盘&…...

常见监控网络链路和网络设备的方法
网络监控主要包括网络链路监控和网络设备监控,通常系统运维人员会比较关注。 一、网络链路监控 网络链路监控主要包含三个部分,网络连通性、网络质量、网络流量。 连通性和质量的监控手段非常简单,就是在链路一侧部署探针,去探…...

C#控制台程序+Window增加右键菜单
有时候我们可能会想定制一些自己的右键菜单功能,帮我们减少重复的操作。那么使用控制台程序加自定义右键菜单,就可以很好地满足我们的需求。 1 编写控制台程序 因为我只用到了在文件夹中空白处的右键菜单,所以这里提供了一个对应的模板&…...

【Docker】Docker+Zipkin+Elasticsearch+Kibana部署分布式链路追踪
文章目录 1. 组件介绍2. 服务整合2.1. 前提:安装好Elaticsearch和Kibana2.2. 再整合Zipkin 点击跳转:Docker安装MySQL、Redis、RabbitMQ、Elasticsearch、Nacos等常见服务全套(质量有保证,内容详情) 本文主要讨论在Ela…...