1206 aop学习使用有感

我觉得学习是一个反复的过程,隔了一段时间再回过头来看之前学的东西,会有新的感悟。

在以前学习动态代理的时候,虽然明白了如何实现动态代理,但是会觉得很麻烦,因为为了实现动态代理,我们需要实现InvocationHandler接口,通过在invoke方法中去增加无关业务的代码,另外还需要去使用Proxy类构造一个代理类。会觉得这样用起来更加麻烦,倒还不如直接重写类来得方便。

也了解过aop,看过一些文章,说通过aop我们可以把一些无关业务的代码横向切入到原有的方法中,许多文章以日志的记录为例子来介绍。可是我却觉得类似这样的操作并不能说服自己aop是真的有用的:

//在代理真实对象前我们可以添加一些自己的操作
System.out.println("before rent house");

//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
method.invoke(subject, args);

//在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after rent house");

上个月,在项目中总算是把aop给用上了,也在使用的过程中确切的感受到了使用aop的好处。
在这个模块中,主要是封装请求参数,再去调第三方的接口,然后返回数据给前端。在这个过程中,当出现了各种异常,需要将异常反馈到前端页面,并且对于一些意料之外的异常,需要通过钉钉机器人将错误信息报到相关的钉钉群中。
在以前的模块中,我是通过在controller层使用try catch来进行处理,在catch到异常的时候,将错误信息组装好,然后调用钉钉发送通知工具方法。如此,来实现异常通知。
我写接口的时候,习惯把service层、dao层都给写好,并且通过单元测试,最后再来实现controller层。而在实现controller层的时候,我发现我不得不在每个方法都去写try catch,重复率非常高。于是想到了aop,于是改装了一下service层的方法,使其抛出的全部转化成一个实现了RuntimeException的Exception。这样,controller层就无需显示的使用try catch去处理异常,然后通过环绕通知,来实现异常时的catch。
这样,controller层的代码就会简化很多,去掉了重复的try catch操作。后面我又顺便把controller层返回数据的格式统一了,统一返回一个ResultBean,通过statusCode来判断接口调用是否出现异常,当statusCode不为0000即接口出现异常的时候,会把异常信息封装到errMsg中。也正是因为使用了aop,可以统一处理,因此才改得这么快。
上面是aop的使用。

而最近在测试一个service层方法的时候,遇到了一个问题:预期的事务回滚没有发生。一开始很懵逼,后面理解了spring的事务是跟aop有关的,就好理解很多了。
场景是这样的,在一个定时器类中,我写了一个定时器方法,在这个定时器方法中,会去调用同一个类中的业务逻辑方法,像这样:

    @Scheduled(cron = "0 0 19 * * ?") //每天下午7点钟执行一次
    public void gogogo(){
        try {
            core();
        }catch (Exception e){
            logger.error("出现异常,回滚。");
            SendMessageUtils.sendTextMessage("7774","获取圈存账单出现异常:" + ExceptionRecordUtils.getExceptionDetailInfo(e));
        }
    }
        @Transactional(rollbackFor = Exception.class)
        public void core(){
        //省略了一些代码
        QueryStoreRecordInput input = new QueryStoreRecordInput();
            List<StoreRecordBean> beanList = storeRecordService.queryStoreRecord(input);
            //插入数据库
            for (StoreRecordBean bean : beanList){
                int result = storeDao.insertStoreRecords(bean);
                logger.info("********【获取圈存账单】入库结果:" + (result==1?"success":"fail"));
            }
        }
    }

在单元测试的时候,我故意让主键重复,结果竟然没有回滚。主键重复前的记录都成功插入了。更让我觉得奇怪的时候,在单元测试中直接调用gogogo方法出现异常时不会回滚,而单独调用core方法时,却会回滚。
于是上网找了一些文章来看,spring事务、aop、动态代理等知识串在一起,才算是想懂了为什么会出现这些奇怪的现象。
是这样的,spring事务的原理是动态代理,在开启了事务注解扫描后,在启动项目的时候,spring在实例化bean的时候,在扫描到@Transactional注解的时候,会为该类生成一个代理类,在这个代理类中,对带有@Transactional注解的类进行了改进,增加了开启事务,以及出现异常时自动回滚的代码,如此实现@Transactional事务回滚的功能。
而在上面的代码中,新生成的代理类中,较原本的类而言,多了一个新的core方法,但是原本的core方法依然是存在的。而当我们在单元测试的时候,直接调用gogogo方法,由于在gogogo方法中我们调用的是core()方法,因此实际上调用的还是原本的core方法,即它就是原本的样子,没有开启事务功能的。而当我们在单元测试的时候直接调用core方法,由于我们使用的代理类的实例对象,因此我们调用了是代理类生成的新的core方法,即增加了事务功能的core方法,因此在这个测试中,当出现了异常,在代理类生成的新的core方法中的catch中,会通过rollback方法来实现事务回滚。
明白这个原理后,就知道要怎么改进了:

    @Transactional(rollbackFor = Exception.class)
    @Scheduled(cron = "0 0 19 * * ?") //每天下午7点钟执行一次
    public void gogogo(){
        try {
            core();
        }catch (Exception e){
            logger.error("出现异常,回滚。");
            SendMessageUtils.sendTextMessage("7774","获取圈存账单出现异常:" + ExceptionRecordUtils.getExceptionDetailInfo(e));
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }
        
        public void core(){
        //省略了一些代码
        QueryStoreRecordInput input = new QueryStoreRecordInput();
            List<StoreRecordBean> beanList = storeRecordService.queryStoreRecord(input);
            //插入数据库
            for (StoreRecordBean bean : beanList){
                int result = storeDao.insertStoreRecords(bean);
                logger.info("********【获取圈存账单】入库结果:" + (result==1?"success":"fail"));
            }
        }
    }

我们把@Transactional注解放到gogogo方法中,当我们启动项目的时候,spring会为该类生成一个代理类,并且生成了一个新的gogogo方法,这个gogogo方法是被带有事务开启与回滚的try catch包围的gogogo方法。但是由于原本的gogogo方法中,我使用了try catch,因此必须得在原本的catch的时候显示的声明需要回滚,否则就会相当于没有出现异常,就不会出现事务回滚了。
所以说,理解了aop、动态代理,会对spring的事务有更好的理解。

我之前觉得学习动态代理,了解InvocationHandler、Proxy等类来实现动态代理,只能写个demo来玩玩,没什么意思。但后面发现,当把知识贯穿起来的时候,才发现学习基础知识是多么的有必要。spring的事务管理,就是利用的动态代理。我们直接把bean交由spring管理,spring根据注解去生成新的代理类,这些对我们都是透明的,我们都是直接拿来用来。

以上,是对aop的一些学习与使用的感悟~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值