springboot利用切面保存操作日志(支持Spring表达式语言(简称SpEL))

本文主要是介绍springboot利用切面保存操作日志(支持Spring表达式语言(简称SpEL)),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

springboot利用切面保存操作日志(支持Spring表达式语言(简称SpEL))


文章目录

  • springboot利用切面保存操作日志(支持Spring表达式语言(简称SpEL))
  • 前言
  • 一、Spring EL是什么?
  • 二、使用步骤
    • 1.定义日志实体类LogRecord
    • 2.定义日志记录注解LogSnipper
    • 3.定义上下文容器SnipperContext
    • 4.实现切面
    • 5.定义日志模板解析器LogTplParser
    • 6.定义业务代码


前言

在注解中使用Spring EL表达式,切面解析实现自定义操作日志。
在这里插入图片描述


一、Spring EL是什么?

Spring表达式语言(简称SpEL)是一个支持查询并在运行时操纵一个对象图的功能强大的表达式语言。SpEL语言的语法类似于统一EL,但提供了更多的功能,最主要的是显式方法调用和基本字符串模板函数。参考:SpringEL 表达式语言(Spring Expression Language)

二、使用步骤

1.定义日志实体类LogRecord

/*** 日志实体类,属性对应{@link LogSnipper}注解中的属性*/
@Data
@Builder
public class LogRecord {private String code;/*** 客户端ip*/private String ipAddr;/*** 业务编号(可以是任何标识)*/private String bizNo;/*** 对应{@link LogSnipper}注解中的success或者fail,当方法成功调用时,获取success中的值,反之则获取fail中的值*/private String content;/*** 日志类型*/private String category;/*** 操作者*/private String operator;/*** 备用字段,当其他字段不够用时可以使用该字段*/private String addition;/*** 操作时间*/private Date createTime;}

2.定义日志记录注解LogSnipper

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface LogSnipper {/*** 操作人* @return*/String operator() default "";/*** 操作成功时定义的模板* @return*/String success();/*** 操作失败时定义的模板* @return*/String fail() default "";/*** 业务主键* @return*/String bizNo() default "";/*** 日志类型* @return*/String category();/*** 其他信息,用于保存其他额外信息** @return*/String addition() default "";}

3.定义上下文容器SnipperContext

/*** 上下文容器,用来存放线程处理过程中的信息* <p>*/
public class SnipperContext {private static ThreadLocal<Map<String, Object>> context = new ThreadLocal<>();static {context.set(new HashMap<>());}public static Map<String, Object> getContext() {return context.get();}public static void set(String key, Object val) {Map<String, Object> map = new HashMap<>();map.put(key, val);context.set(map);}public static Object get(String key) {return context.get().get(key);}public static void clear() {context.remove();}}

4.实现切面

/*** 日志记录切面**/
@Aspect
public class LogAspect {@Autowiredprivate LogTplParser logTplParser;@Autowired(required = false)private LogPersistent logPersistent;@Pointcut("@annotation(com.sztech.logsnipper.LogSnipper)")public void snip(){}/*** 正常执行方法,执行success模板*/@AfterReturning(pointcut = "snip()", returning = "returnValue")public void doAfter(JoinPoint joinPoint, Object returnValue) {//获取注解的实例LogSnipper logSnipper = getLogSnipper(joinPoint);String success = logSnipper.success();//表达式上下文,将方法中的参数设置到spel容器中EvaluationContext evaluationContext = generateEvaluationContext(joinPoint);/*** 将方法的返回结果设置进内置变量中* 其中_rs_对应为返回值* _ex_对应异常原因* _context_对应上下文内容,为map类型**/evaluationContext.setVariable(LogTplParser._RS_, returnValue);evaluationContext.setVariable(LogTplParser._CONTEXT, SnipperContext.getContext());saveLog(logSnipper, success, evaluationContext);}/*** 非正常执行,执行fail模板*/@AfterThrowing(pointcut = "snip()", throwing = "exception")public void doFail(JoinPoint joinPoint, Exception exception) {LogSnipper logSnipper = getLogSnipper(joinPoint);String fail = logSnipper.fail();/*** 将方法的返回结果设置进内置变量中* 其中_rs_对应为返回值* _ex_对应异常原因* _context_对应上下文内容,为map类型**/EvaluationContext evaluationContext = generateEvaluationContext(joinPoint);evaluationContext.setVariable(LogTplParser._EX_, exception);evaluationContext.setVariable(LogTplParser._CONTEXT, SnipperContext.getContext());saveLog(logSnipper, fail, evaluationContext);}/*** 保存日志* @param logSnipper* @param tpl* @param evaluationContext*/private void saveLog(LogSnipper logSnipper, String tpl, EvaluationContext evaluationContext) {String content = logTplParser.parseTpl(tpl, evaluationContext);String operator = logTplParser.parseTpl(logSnipper.operator(), evaluationContext);String bizNo = logTplParser.parseTpl(logSnipper.bizNo(), evaluationContext);String category = logTplParser.parseTpl(logSnipper.category(), evaluationContext);String addition = logTplParser.parseTpl(logSnipper.addition(), evaluationContext);LogRecord logRecord =LogRecord.builder().content(content).code(IdUtil.simpleUUID()).operator(operator).bizNo(bizNo).category(category).addition(addition).createTime(new Date()).build();if(null != logPersistent) {//实现日志存储logPersistent.save(logRecord);}}/*** 将方法中的参数设置到spel容器中** @param joinPoint* @return*/private EvaluationContext generateEvaluationContext(JoinPoint joinPoint) {EvaluationContext evaluationContext = new StandardEvaluationContext();//获取方法参数值Object[] args = joinPoint.getArgs();//获取方法参数名String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();int i = 0;for(Object arg : args) {evaluationContext.setVariable(parameterNames[i++], arg);}return evaluationContext;}/*** 获取方法{@link LogSnipper}的注解实例* @param joinPoint* @return*/private LogSnipper getLogSnipper(JoinPoint joinPoint) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();LogSnipper logSnipper = signature.getMethod().getAnnotation(LogSnipper.class);return logSnipper;}/*** 最后执行,用于释放资源*/@After("snip()")public void finish() {//释放上下文SnipperContext.clear();}
}

5.定义日志模板解析器LogTplParser

/*** 日志模板解析器**/
@Component
public class LogTplParser {private ExpressionParser expressionParser = new SpelExpressionParser();/*** 通配符正则表达式:{#user.name},user为实例对象,name为对象属性,也可以使用getName()方法*/private static final String expressionRegex = "\\{#.*?}";public static final String _CONTEXT = "_context_";public static final String _EX_ = "_ex_";public static final String _RS_ = "_rs_";private static final Pattern pattern = Pattern.compile(expressionRegex);/*** 解析Spel模板字符串** @param tpl 模板字符串,例如:"操作人:{#username}, 操作模块:{#module.name}"等,使用了spring的spel表达式* @param context 对象容器,将spel表达式对应的值或者对象呢set进容器中* @return*/public String parseTpl(String tpl, EvaluationContext context) {Matcher matcher = pattern.matcher(tpl);List<String> sPelList = new ArrayList<>();//匹配正则表达式,可能会出现匹配到多个while (matcher.find()) {sPelList.add(matcher.group());}//如果没有匹配到通配符,直接返回原字符串if(sPelList.size() == 0) {return tpl;}String[] datas = new String[sPelList.size()];int i = 0;//从context中取出通配符对应的值for(String sp : sPelList) {Expression expression = expressionParser.parseExpression(sp);datas[i++] = expression.getValue(context, String.class);}//格式化字符串,并将通配符的值填充到字符串中String formatStr = tpl.replaceAll(expressionRegex, "%s");return String.format(formatStr, datas);}}

6.定义业务代码

@RestController
public class AController {@Autowiredprivate AService aService;@GetMapping("/sayBye")public String sayBye(String msg) {User user = new User();user.setName("李白");user.setAge(12);user.setDepartment("001");SnipperContext.set("currentUser", user);SnipperContext.set("nickName", "汪沦");return aService.sayBye(msg, user);}}@Component
public class aService {@LogSnipper(category = "打招呼行为",success = "{#_context_.get(\"nickName\")}向{#user.name}说:{#msg}",fail = "{#user.name}有事,请下次再来",bizNo = "{#user.department}",operator = "{#_context_.get(\"nickName\")}")public String sayBye(String msg, User user) {return "sayBye";}
}

这篇关于springboot利用切面保存操作日志(支持Spring表达式语言(简称SpEL))的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/973255

相关文章

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

Spring Security方法级安全控制@PreAuthorize注解的灵活运用小结

《SpringSecurity方法级安全控制@PreAuthorize注解的灵活运用小结》本文将带着大家讲解@PreAuthorize注解的核心原理、SpEL表达式机制,并通过的示例代码演示如... 目录1. 前言2. @PreAuthorize 注解简介3. @PreAuthorize 核心原理解析拦截与

一文详解JavaScript中的fetch方法

《一文详解JavaScript中的fetch方法》fetch函数是一个用于在JavaScript中执行HTTP请求的现代API,它提供了一种更简洁、更强大的方式来处理网络请求,:本文主要介绍Jav... 目录前言什么是 fetch 方法基本语法简单的 GET 请求示例代码解释发送 POST 请求示例代码解释

Java图片压缩三种高效压缩方案详细解析

《Java图片压缩三种高效压缩方案详细解析》图片压缩通常涉及减少图片的尺寸缩放、调整图片的质量(针对JPEG、PNG等)、使用特定的算法来减少图片的数据量等,:本文主要介绍Java图片压缩三种高效... 目录一、基于OpenCV的智能尺寸压缩技术亮点:适用场景:二、JPEG质量参数压缩关键技术:压缩效果对比

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

Java利用docx4j+Freemarker生成word文档

《Java利用docx4j+Freemarker生成word文档》这篇文章主要为大家详细介绍了Java如何利用docx4j+Freemarker生成word文档,文中的示例代码讲解详细,感兴趣的小伙伴... 目录技术方案maven依赖创建模板文件实现代码技术方案Java 1.8 + docx4j + Fr

SpringBoot首笔交易慢问题排查与优化方案

《SpringBoot首笔交易慢问题排查与优化方案》在我们的微服务项目中,遇到这样的问题:应用启动后,第一笔交易响应耗时高达4、5秒,而后续请求均能在毫秒级完成,这不仅触发监控告警,也极大影响了用户体... 目录问题背景排查步骤1. 日志分析2. 性能工具定位优化方案:提前预热各种资源1. Flowable

基于SpringBoot+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、