有个同步实时数据的任务需求,要求先清空表再将新的同步数据入库,并保证原子性。所以一开始想到的是继承原有的定时器框架基类,并将数据层的Mapper引入,直接将业务逻辑写入到定时器中,然后在业务逻辑的方法上加上spring的Transational注解。
这个方案直接踩了两个坑:
1、定时器的入口是拿到容器的bean,然后调用一个final实现的schedule方法。因为在实现类里面搞了@Transational注解,所以schedulerTask对象实际上是spring 利用cglib生成的动态代理对象。debug到schedule方法的时候发现进来的对象是动态代理对象,由于spring对动态代理对象的master字段做注入操作,所以master字段为空,直接报了空指针异常。
原理:
之前正常写@Transational注解一般都没啥问题,正常调用方法后deubg时进来的都是被代理的对象。这次怎么进来的对象直接是动态代理对象呢?
为了一探究竟,查看debug调用栈,发现直接就到schedule函数了,没有经历过cglib的任何interceptor,所以难怪直接进来的就是就是动态代理对象。
这里又进入疑惑了,为啥这个schedule函数不经历任何的interceptor然后在调用被代理对象的schedule函数呢?我们知道,其实动态代理无非就是在运行时动态生成动态代理类,所以想办法将动态代理类下载到本地,看下生成的源码到底是咋样的。cglib下载类到本地,只需要增加代码:System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, “D:\test”); 在这里顺便补充jdk动态代理的:System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, “D:\test”);
将生成的class拖进idea就可以反编译了查看源码了。观察一段时间源码之后发现,源码里面没有schedule函数,所以这里也证实了为啥debug直接进入动态代理对象的schedule函数了,直接继承了基类的schedule函数,没有做任何处理。再观察源码,发现基类和实现类的其他函数都有,唯独就没有schedule,直接麻了。再对比下schedule函数和其他函数的区别,发现schedule函数有final修饰,联想到cglib动态代理是继承实现,jdk动态代理是基于接口实现,会不会是final函数不能被子类复写,所以没有在动态代理类里面生成schedule函数。然后百度一搜,果然,cglib动态代理,无法实现对final方法的代理。
2、第二个坑有时间再更新了,大概是被代理对象的A函数直接调用被代理对象的@Transational注解的函数的时候,事务是失效的。可以搜索sprig事务失效的常见场景。记住一个原理就行:要想事务有效,必须进入动态代理对象的函数,不然执行不到切面的东西。
省流:cglib动态代理,无法实现对final方法的代理