我是如何偶遇AOP并被爆锤了

需求背景

​ 我需要对业务层代码(比如add、delete)做操作日志记录并入库。

分析

​ 不用想肯定用aop

我是怎么做的

@Component
@Slf4j
@Aspect
public class GroundLockOptionLogAspect {
    @Resource
    private GroundLockOptionLogManager lockOptionLogManager;

    @Pointcut("execution(* com.xiaoju.automarket.doraemon.biz.manage.GroundLockManager.addGroundLock(..))")
    private void addGroundLock(){}

    @Pointcut("execution(* com.xiaoju.automarket.doraemon.biz.manage.GroundLockManager.updateGroundLock(..))")
    private void updateGroundLock(){}

    @AfterThrowing(pointcut = "addGroundLock()", throwing = "e")
    public void addGroundLockLog(JoinPoint point, Throwable e) {
        log.info("----------YYYYYY常写入日志----------");
        String methodName = point.getTarget().getClass().getName();
        System.out.println(point.getSignature().getName());
        System.out.println(methodName);
        System.out.println(e.getMessage());
        e.printStackTrace();
        LockManagerLog record = new LockManagerLog();
        record.setOpType(DeviceOpTypeEnum.ADD_DEVICE.getCode().byteValue());
        record.setOpContent(DeviceOpTypeEnum.ADD_DEVICE.getDesc());
        record.setOpName("嘿嘿嘿"//todo
                            /*Optional.ofNullable(BossUserHolder.getSSOUserFromThreadLocal())
                            .orElse(new BossUserHolder())
                            .getUserName()*/);
        lockOptionLogManager.insert(record);
    }

    @AfterThrowing(pointcut = "updateGroundLock()", throwing = "e")
    public void updateGroundLockLog(Throwable e) {
        log.info("-----------------update a lock---------------");
        LockManagerLog record = new LockManagerLog();
        record.setOpType(DeviceOpTypeEnum.EDIT_DEVICE.getCode().byteValue());
        record.setOpContent(DeviceOpTypeEnum.EDIT_DEVICE.getDesc());
        record.setOpName("哈哈哈2"//todo
                            /*Optional.ofNullable(BossUserHolder.getSSOUserFromThreadLocal())
                            .orElse(new BossUserHolder())
                            .getUserName()*/);
        lockOptionLogManager.insert(record);
    }
}

直觉告诉我应在AddGroundLock()执行完毕后再日志记录,所以我机智的选了@After,但是实际测试后我发现,@After对于正常执行的add功能可以记录入库,但是对执行错误的add也会记录入库,(起初我的想法是既然出错了就不要入库了啊,所以才有了后续的一阵折腾,后来细想了下,异常操作其实也该记录的,毕竟要记录下是谁操作的,但这里其实还有问题,异常操作其实应该和正常操作的日志不一样才对。。。)

​ 于是我又换成了@AfterThrowing且传入了throwing = "e"这个参数,天真的我以为这样就可以通过e来判断切点方法是否执行正常了,然鹅...这样的确能判断,but,正常的业务操作就不会记录日志了,因为正常操作不会抛异常,也就不会被@AfterThrowing捕捉。

​ 也考虑过事务传播啥啥的,网上搜到类似的功能,他提到了声明式事务编程式事务,还有待研究。

​ ----------------------------------等等,你以为这就结束了?

​ 在google搜索切面如何判断切点函数内部是否有异常时偶然看到几个字:

​ 切点Pointcut,切点代表了一个关于目标函数的过滤规则,后续的通知是基于切点来跟目标函数关联起来的。

​ 然后要围绕该切点定义一系列的通知Advice,如@Before、@After、@AfterReturning、@AfterThrowing、@Around等等定义的方法都是通知。其含义是在切点定义的函数执行之前、完成之后、正常返回之后、抛出异常之后以及环绕前后执行对应的切面逻辑。

​ 这不就是我想要的么...于是我赶紧实验了一番,结果就ok了。这里其实并没有ok,看logger打印了,其实没入库。😮‍💨

--------------------------------------------又来补充后续了

现在的情况呢是正常新增的情况下可以同时插入业务数据和日志记录,业务逻辑抛异常的时候日志的失败记录也没插进去,似乎。。。

和事务有关系?现在两个Service我都用的

默认事务REQUIRED

业务方法需要在一个事务中运行,如果方法运行时,已处在一个事务中,那么就加入该事务,否则自己创建一个新的事务.

​ 所以两个Service一定是使用了同一个事务,在业务代码抛异常的时候一起会滚了。

那,该怎么改?

很简单,建立一个新事务就好了,各自安好,互不打扰

REQUIRES_NEW

业务方法总是会为自己发起一个新的事务,如果方法已运行在一个事务中,则原有事务被挂起,新的事务被创建,直到方法结束,新事务才结束,原先的事务才会恢复执行.

猜猜我在哪个Service上加了REQUIRES_NEW?

当然是日志的Service,这样后续即便业务代码被修改了引入了其他的业务逻辑,也不会出现事务失效的问题。

后记

为啥写个切面日志落库对我来说这么难呢?

  1. AOP
    1. 不知道有五种通知方式,只知道三种的含义。
    2. 走了点弯路,想在方法内部接收异常然后判断处理,其实用@AfterThrowing就可以。
  2. 事务
    1. 目前只是了解声明式事务,除此之外还有编程式事务
    2. 虽然冥冥中能感受到是事务影响的,但是反应不够快,若不是以前为了面试看过事务相关的东西,那可就头疼了。
posted @ 2021-06-05 11:08  夜旦  阅读(108)  评论(0编辑  收藏  举报