Aop切面实现接口访问的动作日志记录
1.需求:接到一个新需求是这个样子的,要求对特定接口的访问进行日志记录,
实现:考虑是用到Aop切面环绕通知实现动态的切入,而尽量不对现有的代码进行侵入,并且后续的日志记录操作不敏感且尽可能不要阻塞接口的正常返回。后续操作尽量异步(实际上也可以使用消息中间件,kafka等实现解耦等,但是考虑到现有的系统实为单体架构,加入这些中间件反而累赘并且增加系统的复杂程度)
技术实现:aop+spring事件发布
代码:
1.为方便实现切入目标接口,自定义一个注解来实现
/** * 动态记录日志注解 * @author Administrator */ @Target(ElementType.METHOD) //注解可使用的范围:方法级别 @Retention(RetentionPolicy.RUNTIME) //注解生效的范围 运行时 public @interface ActionCapture { Type value(); //自定义注解的 可选值 这里type为一个枚举类 将可选值全部枚举限定范围 }
//type枚举类
public enum Type{
/**
*这里就简单的举个例子 CREATE_PROJECT_ITERATION_DTO 实际枚举值有很多g
*/
CREATE_PROJECT_ITERATION_DTO
;
}
2. 定义切面用来处理注解标记的方法
/** * 记录 操作切面处理类*/ @Aspect @Component @Slf4j @Lazy(false) public class ActionCaptureAspect { @Resource ApplicationContext applicationContext; //容器上下文对象 @Pointcut("@annotation(com.jn.annotation.ActionCapture)") //切点为自定义的注解 @ActionCapture public void actionCapture() { } /** * 环绕通知:灵活自由的在目标方法中切入代码 */ @Around("actionCapture()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { try {
//解析注解 Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; ActionCapture annotation = methodSignature.getMethod().getAnnotation(ActionCapture.class); Object[] args = joinPoint.getArgs(); //获取方法的请求参数列表 } catch (Exception e) { log.error("执行前置操作失败"); } //执行目标方法后,proceed为执行结果 Object proceed = joinPoint.proceed(); if (proceed instanceof ServerResponse<?>) { ServerResponse<?> response = (ServerResponse<?>) proceed; if (!response.getSuccess()) { return proceed; } } aspectDto.setResult(proceed); //发布一个事件 spring监听并处理这个事件 ActionCaptureEvent为自定义的事件类 this指代当前发布事件的对象 就是当前类对象 applicationContext.publishEvent(new ActionCaptureEvent(this)); return proceed; } }
3. 自定义处理事件类 ActionCaptureEvent
//如果没有要传递的参数 其实可以直接使用ApplicationEvent 就不用再自定以一个类继承ApplicationEvent 了 //我这里是需要传递一个对象 AspectDto @Getter public class ActionCaptureEvent extends ApplicationEvent { //private final AspectDto fields; //public ActionCaptureEvent(Object source, AspectDto args) { public ActionCaptureEvent(Object source) { super(source); //this.fields = args; } }
4. handler处理发布的事件
@Slf4j @Component public class ActionCaptureHandlerBetter { /** * 处理切面发布的日志记录事件 */ @Order @Async //异步处理 要在启动类开启异步支持 @EnableAsync @EventListener(ActionCaptureEvent.class) //事件监听 监听我们自定义的类ActionCaptureEvent public void handlerListener(ActionCaptureEvent event) { try { // 实际业务处理 。。。。。 } catch (Exception e) { log.error("记录日志操作异常", e); throw e; } }
4.接口方面:
@PostMapping("create") //使用我们的注解并且 加上不同的值 方便业务根据不同的取值进行不同的处理 @ActionCapture(Type.CREATE_PROJECT_ITERATION_DTO) @ApiOperation(value = "XXXXXX", notes = "XXXXXX") public ServerResponse<XXXXX> create(@Validated({Add.class}) @RequestBody CreateProjectIterationDTO createDto) { //被代理目标方法执行业务。。。。 return ServerResponse.createBySuccess(XXXXX); }
这样 就完成了aop+spring事件发布+异步 完成我们目前的需求了,spring事件发布和处理实际上是一个同步的过程所以要加上异步处理 ,这样才不会妨碍aop代理的接口正常返回