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

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

需求:日志审计

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

实现原则

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

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

使用的技术

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

代码实现

自定义注解

package com.ultra.annotation;import java.lang.annotation.*;/*** 日志审计注解** @author admin*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @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
@Getter
public 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 2019-09-06*/
@Setter
@Getter
@ToString
public 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.springframework.stereotype.Component;import java.lang.reflect.Method;/*** 日志审计切面** @author fan*/
@Aspect
@Component
public 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 2019-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注解中的key
  • SpEl实现:使用方法的入参当做上下文,使用SpEl语法解析,所以对方法没有特殊要求,任何方法都可以,也可以获取到任意参数

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

  1. Spring中自定义注解支持SpEl表达式(仅限在AOP中使用)

    大家平时在写代码的时候,安全方面一般都会考虑使用Shiro或者SpringSecurity,他们其中提供了很多注解可以直接使用,很方便,那么今天就来重复造个小轮子,如果不用他们的,自己在项目中如何基于 ...

  2. spring学习笔记(14)引介增强详解:定时器实例:无侵入式动态增强类功能

    引介增强实例需求 在前面我们已经提到了前置.后置.环绕.最终.异常等增强形式,它们的增强对象都是针对方法级别的,而引介增强,则是对类级别的增强,我们可以通过引介增强为目标类添加新的属性和方法,更为诱人 ...

  3. Spring Boot 自定义注解支持EL表达式(基于 MethodBasedEvaluationContext 实现)

    自定义注解 自定义 DistributeExceptionHandler 注解,该注解接收一个参数 attachmentId . 该注解用在方法上,使用该注解作为切点,实现标注该注解的方法抛异常后的统 ...

  4. spring aop拦截自定义注解的切入点表达式

    @within(com.cxh.study.aop.controller.UserAccessAnnotation) 表示拦截含有com.cxh.study.aop.controller.UserAc ...

  5. linux启用日志记录功能,Linux下启用Open vSwitch的日志功能以便调试和排障

    问题 我试着为我的Open vSwitch部署排障,鉴于此,我想要检查它的由内建日志机制生成的调试信息.我怎样才能启用Open vSwitch的日志功能,并且修改它的日志等级(如,修改成INFO/DE ...

  6. 妙用自定义注解,一行代码搞定大功能(文末赠书)

    以下内容来自公众号逆锋起笔,关注每日干货及时送达 1.简介 在使用spring完成项目的时候需要完成记录日志,开始以为Spring 的AOP功能,就可以轻松解决,半个小时都不用,可是经过一番了解过后, ...

  7. 自定义注解妙用,一行代码搞定用户操作日志记录

    1.简介 在使用spring完成项目的时候需要完成记录日志,开始以为Spring 的AOP功能,就可以轻松解决,半个小时都不用,可是经过一番了解过后,发现一般的日志记录,只能记录一些简单的操作,例如表 ...

  8. 功能强大特别的5款浏览器,简直好用极了

    很多小伙伴都用过QQ浏览器.谷歌浏览器以及360浏览器,除了这些主流的浏览器你还用过哪些呢?今天小编就给大家推荐几款的浏览器,小巧好用,功能强大,还具有很多实用的功能. 1.Microsoft Edg ...

  9. Java AOP自定义注解

    一.背景 在之前文章:Java注解详解中,主要介绍了注解的含义.作用.以及常用的各类注解.今天主要介绍在Springboot中如何实现一个自定义注解,通过自定义注解去实现一些定制化的需求. 二.了解元 ...

  10. java自定义注解实现

    引言 去杭州第一次面试的时候问及到自定义注解,那时候不清楚,现在简单写下,算是对过去的一个交代. 自定义注解 关于注解的定义这里就不解释了,自定义注解的场景有很多,比如登录.权限拦截.日志.以及各种框 ...

最新文章

  1. JSP笔记-页面重定向
  2. 历年计算机一级考试题库及答案,全国计算机一级考试试题库及答案
  3. golang 解决 TCP 粘包问题
  4. ubuntu上virsh+kvm安装虚拟机
  5. Ubuntu 16.04 安装 搜狗输入法 sogou input
  6. 自媒体运营,你要的小工具来了
  7. 彼得·林奇的成功投资
  8. 微信公众号(获取token 按钮生成 推送消息,微信授权)
  9. 计算机与音乐,计算机音乐与midi
  10. 树莓派4B EC20 查看4G信号强度
  11. python运维自动化脚本案例-python自动化运维脚本范例
  12. ros使用usb摄像头追踪ArUco markers
  13. 【论文阅读】Video Generation from Single Semantic Label Map-CVPR2019
  14. Ubuntu系统SSH免密登录,以及SSH免密登录原理
  15. 二手iPhone手机选购指南,花小钱办大事
  16. 百度地图开发之——百度地图鹰眼轨迹管理台DEMO-v3部署到服务器上
  17. 前端学习笔记(十五)
  18. 排班算法 java_【算法】基于优先级的排班算法实现
  19. 代码实践:MLP的反向传播算法
  20. CSS样式字体与文本相关属性

热门文章

  1. 32、[源码]-AOP原理-创建AOP代理
  2. linux基础第四天
  3. 小组成员的github地址
  4. eclipse 安装插件不生效
  5. Unity3d动画脚本 Animation Scripting(深入了解游戏引擎中的动画处理原理)
  6. Codeforces Round 1 - 10总结 【@Abandon】
  7. PX4 vision_to_mavros定位
  8. HW 基于接口/全局地址池的DHCP
  9. keepalived详解(二)——keepalived安装与配置文件
  10. Bugku 杂项刷题日常1--21: