当前位置: 首页 > news >正文

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

业务背景

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

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

拓展阅读

java 注解结合 spring aop 实现自动输出日志

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

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

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

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

chain

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 拦截器原理运用到日志拦截器中?

业务背景 我们希望可以在使用日志拦截器时&#xff0c;定义属于自己的拦截器方法。 实现的方式有很多种&#xff0c;我们分别来看一下。 拓展阅读 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)指针数组的实质是一个数组&#xff0c;这个数组中存…...

【uniapp APP隐藏顶部的电量,无线,时间状态栏与导航栏】

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

微信小程序前后页面传值

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

没有jodatime,rust里怎么比较两个日期(时间)的大小呢?

关注我&#xff0c;学习Rust不迷路&#xff01;&#xff01; 在 Rust 中&#xff0c;比较两个日期的大小有多种方法。以下是列举的四种常见方法&#xff1a; 1. 使用 PartialOrd trait&#xff1a; use chrono::prelude::*;fn main() {let date1 NaiveDate::from_ymd(2022,…...

【雕爷学编程】Arduino动手做(186)---WeMos ESP32开发板18

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…...

C语言假期作业 DAY 14

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

Maven-生命周期及命令

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

常温超导:AI在研发中的潜力

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

【C++】开源:Eigen3矩阵与线性代数库配置使用

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

OpenMesh 网格简化算法(基于边长度)

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

FFmpeg解码详细流程

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

人工智能的缺陷

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

基于ASP.NET MVC开发的、开源的个人博客系统

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

【LeetCode】对称二叉树 平衡二叉树

对称二叉树 即先判断根节点的左右子树相不相同&#xff0c;相同时&#xff0c;再判断左孩子的左子树和右孩子的右子树比较&#xff0c;左孩子的右子树和右孩子的左子树&#xff08;当两个都相同时才是对称的&#xff09;.....依次递推&#xff0c;过程中并设置一些不满足相同的…...

区块链和WEB3.0有哪些基础知识呢

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

七、封装(1)

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

问题解决和批判性思维是软件工程的重要核心

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

【EI/SCOPUS征稿】2023年通信网络与机器学习国际学术会议(CNML 2023)

2023年通信网络与机器学习国际学术会议&#xff08;CNML 2023&#xff09; 2023 International Conference on Communication Networks and Machine Learning 随着数据流量的显著增长&#xff0c;新的通信应用程序不断出现&#xff0c;并产生更多的数据流量&#xff0c;这些数…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》

在注意力分散、内容高度同质化的时代&#xff0c;情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现&#xff0c;消费者对内容的“有感”程度&#xff0c;正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中&#xff0…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)

前言&#xff1a; 在Java编程中&#xff0c;类的生命周期是指类从被加载到内存中开始&#xff0c;到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期&#xff0c;让读者对此有深刻印象。 目录 ​…...