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代理的接口正常返回

 

posted @ 2022-07-13 14:48  少侠砍人不用刀  阅读(283)  评论(0编辑  收藏  举报