700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 自定义注解和SpEL表达式实现功能强大的无侵入式的日志功能

自定义注解和SpEL表达式实现功能强大的无侵入式的日志功能

时间:2021-08-10 20:57:32

相关推荐

自定义注解和SpEL表达式实现功能强大的无侵入式的日志功能

自定义注解和SpEL表达式实现功能强大的无侵入式的日志功能

需求:日志审计实现原则使用的技术代码实现自定义注解业务对象注解实现使用注解关键点总结

需求:日志审计

用户要求系统敏感操作添加日志审计功能,方便查看哪些用户做了敏感操作日志详情样例:用户[admin]新增角色id:[111]name:[testAddRole]结果:[成功]

实现原则

因为是后加的功能,所以原实现不能大面积修改;退一步讲,就算是新开发的项目,考虑添加日志审计功能时也应该尽可能的减少代码的耦合,减少代码侵入

原代码实现尽量不动尽量记录有用的信息使用时尽量方便

使用的技术

aspect切面(本章是基于切面功能实现,所以并不讲解关于切面的内容)自定义注解SpEl表达式

代码实现

自定义注解

package com.ultra.annotation;import java.lang.annotation.*;/*** 日志审计注解** @author admin*/@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface LogAudit {/*** 账号*/String account() default "";/*** 模块id对应模块名称(用户,角色,资源等)*/String moduleId() default "";/*** 操作id对应操作名称(新增、更新、删除等)*/String operateId() default "";/*** 对象id*/String id() default "";/*** 对象名称*/String name() default "";}

业务对象

日志详情对象

package com.ultra.bo;import lombok.Getter;import lombok.Setter;/*** 日志详情** @author fan*/@Setter@Getterpublic class LogDetails {/*** 账号*/private String account;/*** 操作*/private String operate;/*** 模块*/private String module;/*** id*/private String id;/*** 名称*/private String name;/*** 结果*/private String result;@Overridepublic String toString() {return "用户[" + account + "]" + operate + module + "id:[" + id + "]" + "name:[" + name + "]" + "结果:[" + result + "]";}}

操作枚举类

package com.ultra.constant;/*** 日志操作id与名称枚举关系** @author fan*/public enum LogOperateEnum {/*** id与操作对应关系*/ADD("01", "新增"),UPDATE("02", "更新"),DELETE("03", "删除");LogOperateEnum(String id, String name) {this.id = id;this.name = name;}private String id;private String name;public static String getValue(String id) {for (LogOperateEnum operateEnum : LogOperateEnum.values()) {if (operateEnum.id.equals(id)) {return operateEnum.name;}}return null;}}

模块枚举类

package com.ultra.constant;/*** 日志模块id与名称枚举关系** @author fan*/public enum LogModuleEnum {/*** id与操作对应关系*/ADD("01", "用户"),UPDATE("02", "角色"),DELETE("03", "资源");LogModuleEnum(String id, String name) {this.id = id;this.name = name;}private String id;private String name;public static String getValue(String id) {for (LogModuleEnum moduleEnum : LogModuleEnum.values()) {if (moduleEnum.id.equals(id)) {return moduleEnum.name;}}return null;}}

业务对象

package com.ultra.dao.entity;import java.io.Serializable;import lombok.ToString;import lombok.Getter;import lombok.Setter;/*** 角色** @author ${author}* @since -09-06*/@Setter@Getter@ToStringpublic class Role implements Serializable {private static final long serialVersionUID = 1L;private Long id;private String name;}

注解实现

package com.ultra.aspect;import com.ultra.annotation.LogAudit;import com.ultra.bo.LogDetails;import com.ultra.conditional.BeanRegisterConditional;import com.ultra.constant.LogModuleEnum;import com.ultra.constant.LogOperateEnum;import com.ultra.util.ArrayUtil;import com.ultra.util.StringUtil;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.reflect.MethodSignature;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.core.DefaultParameterNameDiscoverer;import org.springframework.expression.EvaluationContext;import org.springframework.expression.Expression;import org.springframework.expression.spel.standard.SpelExpressionParser;import org.springframework.expression.spel.support.StandardEvaluationContext;import org.ponent;import java.lang.reflect.Method;/*** 日志审计切面** @author fan*/@Aspect@Componentpublic class LogAuditAspect {private static final Logger logger = LoggerFactory.getLogger(LogAuditAspect.class);/*** 获取注解参数当做方法入参** @param joinPoint 切点方法* @param logAudit 注解参数* @return 方法执行的返回值* @throws Throwable 方法执行可能抛的异常*/@Around("@annotation(logAudit)")public Object doAround(ProceedingJoinPoint joinPoint, LogAudit logAudit) throws Throwable {Object proceed;LogDetails logDetails = new LogDetails();try {// 调度之类没有账号的可以手动指定accountString account = logAudit.account();if (StringUtil.isBlank(account)) {// 伪代码实现获取当前账号account = "admin";}String operateId = logAudit.operateId();String moduleId = logAudit.moduleId();String id = getElValue(logAudit.id(), joinPoint);String name = getElValue(logAudit.name(), joinPoint);logDetails.setAccount(account);logDetails.setOperate(LogOperateEnum.getValue(operateId));logDetails.setModule(LogModuleEnum.getValue(moduleId));logDetails.setId(id);logDetails.setName(name);proceed = joinPoint.proceed();// 这里假定认为没有异常是成功,有异常是失败;根据实际业务判断logDetails.setResult("成功");} catch (Throwable throwable) {logDetails.setResult("失败");throw throwable;} finally {//入库logger.info("logDetails:{}", logDetails);}return proceed;}/*** 用于SpEL表达式解析.*/private SpelExpressionParser parser = new SpelExpressionParser();/*** 用于获取方法参数定义名字.*/private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();private String getElValue(String elKey, ProceedingJoinPoint joinPoint) {// 通过joinPoint获取被注解方法MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();Method method = methodSignature.getMethod();// 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组String[] paramNames = nameDiscoverer.getParameterNames(method);if (paramNames != null && paramNames.length > 0) {// spring的表达式上下文对象EvaluationContext context = new StandardEvaluationContext();// 通过joinPoint获取被注解方法的形参Object[] args = joinPoint.getArgs();// 给上下文赋值for (int i = 0; i < args.length; i++) {context.setVariable(paramNames[i], args[i]);}// 解析过后的Spring表达式对象Expression expression = parser.parseExpression(elKey);Object expressionValue = expression.getValue(context);if (expressionValue == null) {return null;}return String.valueOf(expressionValue);}return null;}}

使用注解

package com.ultra.web;import com.ultra.annotation.LogAudit;import com.ultra.dao.entity.Role;import com.ultra.service.RoleService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import java.util.List;/*** 角色** @author fan* @since -09-06*/@RestController@RequestMapping("/role")public class RoleController {@Autowiredprivate RoleService roleService; @PostMapping@LogAudit(moduleId = "02", operateId = "01", id = "#entity.id", name = "#entity.name")public boolean save(@RequestBody Role entity) {return super.save(entity);}}

关键点总结

怎么获取注解参数:

@Around("@annotation(logAudit)")public Object doAround(ProceedingJoinPoint joinPoint, LogAudit logAudit) throws Throwable {}

方法参数怎么转化为日志参数:SpEl表达式,灵感来自Spring Cache中@CacheEvict注解中的keySpEl实现:使用方法的入参当做上下文,使用SpEl语法解析,所以对方法没有特殊要求,任何方法都可以,也可以获取到任意参数

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。