摘要:起因考虑如下一个例子定义在这个例子中我们定义了一个注解这个是一个方法注解我们的期望是当有此注解的方法被调用时需要执行指定的切面逻辑即执行方法在类中方法被所注解因此调用方法时应该会触发方法的调用不过有一点我
起因
考虑如下一个例子:
@Target(value = {ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyMonitor { }
@Component @Aspect public class MyAopAdviseDefine { private Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("@annotation(com.xys.demo4.MyMonitor)") public void pointcut() { } // 定义 advise @Before("pointcut()") public void logMethodInvokeParam(JoinPoint joinPoint) { logger.info("---Before method {} invoke, param: {}---", joinPoint.getSignature().toShortString(), joinPoint.getArgs()); } }
@Service public class SomeService { private Logger logger = LoggerFactory.getLogger(getClass()); public void hello(String someParam) { logger.info("---SomeService: hello invoked, param: {}---", someParam); test(); } @MyMonitor public void test() { logger.info("---SomeService: test invoked---"); } }
@EnableAspectJAutoProxy(proxyTargetClass = true) @SpringBootAppliMyion public class MyAopDemo { @Autowired SomeService someService; public static void main(String[] args) { SpringAppliMyion.run(MyAopDemo.class, args); } @PostConstruct public void aopTest() { someService.hello("abc"); } }
在这个例子中, 我们定义了一个注解 MyMonitor, 这个是一个方法注解, 我们的期望是当有此注解的方法被调用时, 需要执行指定的切面逻辑, 即执行 MyAopAdviseDefine.logMethodInvokeParam 方法.
在 SomeService 类中, 方法 test() 被 MyMonitor 所注解, 因此调用 test() 方法时, 应该会触发 logMethodInvokeParam 方法的调用. 不过有一点我们需要注意到, 我们在 MyAopDemo 测试例子中, 并没有直接调用 SomeService.test() 方法, 而是调用了 SomeService.hello() 方法, 在 hello 方法中, 调用了同一个类内部的 SomeService.test() 方法. 按理说, test() 方法被调用时, 会触发 AOP 逻辑, 但是在这个例子中, 我们并没有如愿地看到 MyAopAdviseDefine.logMethodInvokeParam 方法的调用, 这是为什么呢?
这是由于 Spring AOP (包括动态代理和 CGLIB 的 AOP) 的限制导致的. Spring AOP 并不是扩展了一个类(目标对象), 而是使用了一个代理对象来包装目标对象, 并拦截目标对象的方法调用. 这样的实现带来的影响是: 在目标对象中调用自己类内部实现的方法时, 这些调用并不会转发到代理对象中, 甚至代理对象都不知道有此调用的存在.
即考虑到上面的代码中, 我们在 MyAopDemo.aopTest() 中, 调用了 someService.hello("abc"), 这里的 someService bean 其实是 Spring AOP 所自动实例化的一个代理对象, 当调用 hello() 方法时, 先进入到此代理对象的同名方法中, 然后在代理对象中执行 AOP 逻辑(因为 hello 方法并没有注入 AOP 横切逻辑, 因此调用它不会有额外的事情发生), 当代理对象中执行完毕横切逻辑后, 才将调用请求转发到目标对象的 hello() 方法上. 因此当代码执行到 hello() 方法内部时, 此时的 this 其实就不是代理对象了, 而是目标对象, 因此再调用 SomeService.test() 自然就没有 AOP 效果了.
简单来说, 在 MyAopDemo 中所看到的 someService 这个 bean 和在 SomeService.hello() 方法内部上下文中的 this 其实代表的不是同一个对象(可以通过分别打印两者的 hashCode 以验证), 前者是 Spring AOP 所生成的代理对象, 而后者才是真正的目标对象(SomeService 实例).
解决弄懂了上面的分析, 那么解决这个问题就十分简单了. 既然 test() 方法调用没有触发 AOP 逻辑的原因是因为我们以目标对象的身份(target object) 来调用的, 那么解决的关键自然就是以代理对象(proxied object)的身份来调用 test() 方法.
因此针对于上面的例子, 我们进行如下修改即可:
@Service public class SomeService { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private SomeService self; public void hello(String someParam) { logger.info("---SomeService: hello invoked, param: {}---", someParam); self.test(); } @CatMonitor public void test() { logger.info("---SomeService: test invoked---"); } }
上面展示的代码中, 我们使用了一种很 subtle 的方式, 即将 SomeService bean 注入到 self 字段中(这里再次强调的是, SomeService bean 实际上是一个代理对象, 它和 this 引用所指向的对象并不是同一个对象), 因此我们在 hello 方法调用中, 使用 self.test() 的方式来调用 test() 方法, 这样就会触发 AOP 逻辑了.
Spring AOP 导致的 @Transactional 不生效的问题这个问题同样地会影响到 @Transactional 注解的使用, 因为 @Transactional 注解本质上也是由 AOP 所实现的.
例如我在 stackoverflow 上看到的一个类似的问题: Spring @Transaction method call by the method within the same class, does not work?
这里也记录下来以作参考.
那个哥们遇到的问题如下:
public class UserService { @Transactional public boolean addUser(String userName, String password) { try { // call DAO layer and adds to database. } catch (Throwable e) { TransactionAspectSupport.currentTransactionStatus() .setRollbackOnly(); } } public boolean addUsers(Listusers) { for (User user : users) { addUser(user.getUserName, user.getPassword); } } }
他在 addUser 方法上使用 @Transactional 来使用事务功能, 然后他在外部服务中, 通过调用 addUsers 方法批量添加用户. 经过了上面的分析后, 现在我们就可知道其实这里添加注解是不会启动事务功能的, 因为 AOP 逻辑整个都没生效嘛.
解决这个问题的方法有两个, 一个是使用 AspectJ 模式的事务实现:
另一个就是和我们刚才在上面的例子中的解决方式一样:
public class UserService { private UserService self; public void setSelf(UserService self) { this.self = self; } @Transactional public boolean addUser(String userName, String password) { try { // call DAO layer and adds to database. } catch (Throwable e) { TransactionAspectSupport.currentTransactionStatus() .setRollbackOnly(); } } public boolean addUsers(Listusers) { for (User user : users) { self.addUser(user.getUserName, user.getPassword); } } }
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/69876.html
摘要:总结动态代理的相关原理已经讲解完毕,接下来让我们回答以下几个思考题。 【干货点】 此处是【好好面试】系列文的第12篇文章。文章目标主要是通过原理剖析的方式解答Aop动态代理的面试热点问题,通过一步步提出问题和了解原理的方式,我们可以记得更深更牢,进而解决被面试官卡住喉咙的情况。问题如下 SpringBoot默认代理类型是什么 为什么不用静态代理 JDK动态代理原理 CGLIB动态代理...
摘要:又是什么其实就是一种实现动态代理的技术,利用了开源包,先将代理对象类的文件加载进来,之后通过修改其字节码并且生成子类。 在实际研发中,Spring是我们经常会使用的框架,毕竟它们太火了,也因此Spring相关的知识点也是面试必问点,今天我们就大话Aop。特地在周末推文,因为该篇文章阅读起来还是比较轻松诙谐的,当然了,更主要的是周末的我也在充电学习,希望有追求的朋友们也尽量不要放过周末时...
摘要:几乎每一个接口被调用后,都要记录一条跟这个参数挂钩的特定的日志到数据库。我最终采用了的方式,采取拦截的请求的方式,来记录日志。所有打上了这个注解的方法,将会记录日志。那么如何从众多可能的参数中,为当前的日志指定对应的参数呢。 前言 不久前,因为需求的原因,需要实现一个操作日志。几乎每一个接口被调用后,都要记录一条跟这个参数挂钩的特定的日志到数据库。举个例子,就比如禁言操作,日志中需要记...
摘要:会一直完善下去,欢迎建议和指导,同时也欢迎中用到了那些设计模式中用到了那些设计模式这两个问题,在面试中比较常见。工厂设计模式使用工厂模式可以通过或创建对象。 我自己总结的Java学习的系统知识点以及面试问题,已经开源,目前已经 41k+ Star。会一直完善下去,欢迎建议和指导,同时也欢迎Star: https://github.com/Snailclimb... JDK 中用到了那...
摘要:下图展示了这些概念的关联方式通知切面的工作被称为通知。切面在指定的连接点被织入到目标对象中。该注解表明不仅仅是一个,还是一个切面。 在软件开发中,散布于应用中多处的功能被称为横切关注点(crosscutting concern)。通常来讲,这些横切关注点从概念上是与应用的业务逻辑相分离的(但是往往会直接嵌入到应用的业务逻辑之中)。把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP...
阅读 667·2021-11-18 10:07
阅读 2863·2021-09-22 16:04
阅读 816·2021-08-16 10:50
阅读 3265·2019-08-30 15:56
阅读 1751·2019-08-29 13:22
阅读 2569·2019-08-26 17:15
阅读 1174·2019-08-26 10:57
阅读 1082·2019-08-23 15:23