java使用自定义注解实现业务操作日志保存
- 整体描述
- 具体实现
- 1. 创建自定义注解
- 2. 创建自定义注解解析器
- 2.1 方法说明:请求前
- 2.2 方法说明:请求中
- 2.3 方法说明:请求后
- 2.4 方法说明:拦截异常
- 2.5 注解解析器完整代码
- 3. 在controller层使用注解
- 结语
整体描述
使用Springboot框架,之前项目中有Log的注解,但是不满足项目需求,需要对指定的几个接口进行操作日志的保存,这里对Log注解进行改造。过程不算复杂,再次记录一下。
注:在此之前,需要创建数据库和相关Mapper和Service的基础类,这个也不是本文的重点,而且是非常基础的操作,就不在文章里写了。需要将操作日志存入数据库的话,此步骤是必须的。
具体实现
1. 创建自定义注解
创建一个自定义注解BUsinessLog,在里面定义注解所需要的参数。这里加了一个MethodName的参数,用于在处理注解的时候,根据MethodName进行不同的业务处理。
/**
* 自定义操作日志记录注解
*
* @author nhx
* @date 2022-10-14
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BusinessLog {
/**
* 操作人名称
*/
public String OperationName() default "";
/**
* 方法名称
*/
public String MethodName() default "";
/**
* 操作类别
*/
public BusinessType OperationType() default BusinessType.OTHER;
/**
* 操作内容
*/
public String OperationContent() default "";
/**
* 操作详情
*/
public String OperationDetail() default "";
}
2. 创建自定义注解解析器
注解创建完了,需要一个解析器去对注解进行操作,下面我们需要创建一个注解解析器。
这里重点讲一下解析器里的几个方法:
2.1 方法说明:请求前
这个方法在请求处理之前调用,用于处理一些删除操作,由于删除操作在接口请求处理之前,数据还是在的,可以进行一下数据的保存,在接口调用之后数据就没了。
/**
* 处理请求前执行
*
* @param joinPoint 切点
*/
@Before(value = "logPointCut()")
public void before(JoinPoint joinPoint) {
log.info("before");
}
2.2 方法说明:请求中
这个方法在请求过程中全程都覆盖,可以用来获得请求用时等操作
/**
* 处理请求中执行
*
* @param point 切点
*/
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) {
log.info("around");
}
2.3 方法说明:请求后
这个方法在接口请求完成调用,可以用来保存请求结果和请求信息等操作。
/**
* 处理请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
log.info("doAfterReturning");
}
2.4 方法说明:拦截异常
这个方法用于拦截请求过程中的异常
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "logPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
log.info("doAfterThrowing");
}
2.5 注解解析器完整代码
完整代码,里面有个获取请求参数的方法,可以获取请求的参数,和注解中的MethodName一起使用,可以进行一些业务逻辑的处理。
/**
* 注解解析
*
* @author nhx
* @date 2022-10-14
*/
@Aspect
@Component
public class MyLogAspect {
private static final Logger log = LoggerFactory.getLogger(MyLogAspect.class);
@Pointcut("@annotation(com.psim.framework.myannotation.BusinessLog)")
public void logPointCut() {
}
/**
* 处理完请求前执行
*
* @param joinPoint 切点
*/
@Before(value = "logPointCut()")
public void before(JoinPoint joinPoint) {
log.info("before");
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
log.info("doAfterReturning");
handleLog(joinPoint, null, jsonResult);
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "logPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
log.info("doAfterThrowing");
handleLog(joinPoint, e, null);
}
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
try {
// 获得注解
BusinessLog controllerLog = getAnnotationLog(joinPoint);
if (Objects.isNull(controllerLog)) {
return;
}
// 获取当前的用户
LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());
// 数据库日志
SysOperationLog sysOperationLog = new SysOperationLog();
sysOperationLog.setStatus(String.valueOf(BusinessStatus.SUCCESS.ordinal()));
if (Objects.nonNull(loginUser)) {
sysOperationLog.setOperationName(loginUser.getUsername());
}
if (Objects.nonNull(e)) {
sysOperationLog.setStatus(String.valueOf(BusinessStatus.FAIL.ordinal()));
}
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, sysOperationLog);
// 保存数据库
AsyncManager.me().execute(AsyncFactory.recordSysOperationLog(sysOperationLog));
} catch (Exception exp) {
// 记录本地异常日志
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param businessLog 日志
* @param sysOperationLog 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint joinPoint, BusinessLog businessLog, SysOperationLog sysOperationLog) throws Exception {
// 设置action动作
sysOperationLog.setOperationType(String.valueOf(businessLog.OperationType().ordinal()));
// 设置操作内容
sysOperationLog.setOperationContent(businessLog.OperationContent());
// TODO 获取MethodName,根据不同的方法获取不同的参数进行处理
String methodName = businessLog.MethodName();
}
/**
* 是否存在注解,如果存在就获取
*/
private BusinessLog getAnnotationLog(JoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(BusinessLog.class);
}
return null;
}
/**
* 获取参数Map集合
*
* @param joinPoint
* @return
*/
Map<String, Object> getNameAndValue(JoinPoint joinPoint) {
Map<String, Object> param = new HashMap<>();
Object[] paramValues = joinPoint.getArgs();
String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
for (int i = 0; i < paramNames.length; i++) {
param.put(paramNames[i], paramValues[i]);
}
return param;
}
/**
* 处理完请求中执行
*
* @param point 切点
*/
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) {
log.info("around");
//获取方法名称
Signature methodName = point.getSignature();
//日志输出
log.info(methodName + "进来了");
Long l1 = System.currentTimeMillis();
Object obj = null;
try {
obj = point.proceed(point.getArgs());
} catch (Throwable e) {
e.printStackTrace();
}
log.info(methodName + "bye" + "\t耗時 " + (System.currentTimeMillis() - l1));
//记录一个耗时时间,将证明日志通知
return obj;
}
3. 在controller层使用注解
调用就非常简单了,直接在controller的方法上面加上注解就可以了。
@BusinessLog(OperationContent = "这里是操作内容", OperationType = BusinessType.INSERT, MethodName = "这里是方法名称")
可以看一下debug日志,先log出了around方法,然后是before方法,最后是doAfterReturning方法,说明around是在before方法之前调用的。
结语
自定义注解保存日志功能就完成了,这里注意一下,如果需要将日志存入数据库,建议使用异步操作,这样不会拖慢接口调用的时间,具体可以参考我之前写的一篇文章:浅谈java开启异步线程的几种方法(@Async,AsyncManager,线程池),使用AsyncManager的方式就可以。