Spring AOP失效之谜

什么是AOP

AOP(Aspect Oriented Programming),即面向切面编程,其是OOP(Object Oriented Programming,面向对象编程)的补充和完善。在面向对象编程的世界中,我们很容易理解OOP的思想,简单来说,OOP引入封装、继承、多态等概念来建立一种对象层次结构,这种层次结构是纵向的。虽然OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能关系不大,对于其他类型的代码,如安全性检查、异常处理、事务处理等也都是如此,这种散布在各处的重复的代码被称为横切逻辑,在OOP设计中,它导致了大量代码的重复,不利于各个功能模块的重用。

AOP技术则恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块之中,并将其命名为"Aspect",即切面。所谓"切面",简单说就是将那些被多个业务模块所共同调用的逻辑封装起来,以达到减少重复代码,降低模块之间的耦合度,并提高系统的可维护性的目的。

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与业务逻辑关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事务管理等。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

AOP示例

前面说了这么多,接下来我们就用Spring AOP来实现简单的日志记录功能吧。假如我们已经有了一个功能完善的用户登陆接口,现在我们需要在用户调用登陆接口的前后记录下用户的登录行为日志。要实现该功能,最简单的方法就是在原有的登录逻辑里加入日志记录代码,但是这样一来势必需要对原有的登录逻辑进行修改,容易引入新的bug,因此我们决定使用AOP来实现日志记录的功能。在本实例中,我们搭建一个Spring Boot工程,并引入Spring AOP依赖,pom文件依赖关系如下:

接着我们实现最原始的登录逻辑:

登录逻辑十分简单,首先判断用户是否为合法用户,如果是则可以正常登录,如果不是则禁止登录。

由于我们要在登录逻辑前后加入日志功能,所以我们需要编写一个环绕通知:

可以看到,我们的环绕增强针对login方法进行横切逻辑的织入,在调用目标对象的前后,分别对用户登录日志进行记录。

接下来写一个测试类看一下效果:

结果输出如下:

可以看到,我们通过AOP很方便地实现了日志记录功能。

AOP失效的情形

接下来假如我们又有了一个新需求,就是要对不合法用户做些特殊的处理,比如说统计下不合法用户调用登陆接口的次数。由于直接修改原有的登录逻辑有很多弊端,所以我们还是选择通过AOP来实现该功能。这可以通过编写一个返回增强来实现:

我们对isLegal方法进行增强,先拿到isLegal方法的返回值,再根据该返回值决定是否需要累加登录次数。

接下来我们还是用上一节的测试类来测试一下,我们直接看结果:

这个时候诡异的事情发生了。明明user_1为非法用户,但是为何没有对其登录次数进行累加呢?AOP为何会失效呢?下文将为你解开谜团。

AOP为何失效

之所以会出现上述AOP失效的现象,归根到底是由于AOP的实现机制导致的。Spring AOP采用代理的方式实现AOP,我们编写的横切逻辑被添加到动态生成的代理对象中,只要我们调用的是代理对象,则可以保证调用的是被增强的代理方法。而在代理对象中,不管你的横切逻辑是怎样的,也不管你增加了多少层的横切逻辑,有一点可以确定的是,你终归会调用目标对象的同一方法来调用原始的业务逻辑。

如果目标对象中的原始方法依赖于其他对象,那么Spring会注入所依赖对象的代理对象,从而保证依赖的对象的横切逻辑能够被正常织入。而一旦目标对象调用的是自身的其他方法时,问题就来了,这种情况下,目标对象调用的并不是代理对象的方法,故被调用的方法无法织入横切逻辑。

如上图所示,method1和method2方法是同个类中的方法,当外部通过代理对象调用method1时,最终会调用目标对象的method1方法,而在目标对象的method1方法中调用method2方法时,最终调用的是目标对象的method2方法,而不是代理对象的method2方法,故而针对method2的AOP增强失效了。

如何避免AOP失效

要解决上述Spring AOP失效的问题,有两个方法,一个是将isLegal方法跟login方法写在不同的类里,这样一来,当login方法调用isLegal方法时,Spring会注入相应的代理对象,从而可以调用到isLegal方法的代理逻辑。另一个方法是在调用isLegal方法时先获取当前上下文的代理对象,再通过该代理对象调用被增强了的isLegal方法,这样一来也能解决AOP失效的问题。实际上Spring AOP为我们提供了获取当前上下文代理对象的方法,使用起来非常方便,首先需要在AOP配置里暴露代理对象,在Spring Boot中可以通过注解@EnableAspectJAutoProxy(exposeProxy = true)进行配置:

然后修改login方法,通过AopContext获取当前上下文代理对象,再通过该代理对象调用isLegal方法:

最后我们运行测试类看下效果:

可以看到,现在已经可以实现对非法用户的登录次数进行累加了,这样就解决了上述AOP失效的问题。

更多精彩,请关注微信公众号:编程沉思录。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏weixuqin 的专栏

spring 学习(三):aop 学习

3 aop底层使用动态代理实现 (1)第一种情况,有接口情况,使用动态代理创建接口实现类代理对象 (2)第二种情况,没有接口情况,使用动态代理创建类的子类代...

11520
来自专栏IT可乐

Spring详解(七)------AOP 注解

  上一篇博客我们讲解了 AspectJ 框架如何实现 AOP,然后具体的实现方式我们是通过 xml 来进行配置的。xml 方式思路清晰,便于理解,但是书写过于...

20370
来自专栏coderhuo

虚拟内存探究 -- 第四篇:malloc, heap & the program break

这是虚拟内存系列文章的第四篇,也是最后一篇。 本文主要介绍malloc和heap相关知识,以便回答上一篇文章结尾提出的一些问题:

20010
来自专栏Seebug漏洞平台

Exim Off-by-one(CVE-2018-6789)漏洞复现分析

前段时间meh又挖了一个Exim的RCE漏洞[1],而且这次RCE的漏洞的约束更少了,就算开启了PIE仍然能被利用。虽然去年我研究过Exim,但是时间过去这么久...

55470
来自专栏钟绍威的专栏

spring整合hibernate

spring整合hibernate包括三部分:hibernate的配置、hibernate核心对象交给spring管理、事务由AOP控制 好处: 由java代码...

206100
来自专栏Hongten

java开发_模仿百度文库_OpenOffice2PDF_源码下载

Txt/Word/Excel/PPT=>PDF(OpenOffice+JodConverter)=>SWF(pdf2swf)=>FlexPaper浏览

12810
来自专栏xingoo, 一个梦想做发明家的程序员

基于配置的Spring AOP

前面几篇学习了Spring的依赖注入,这篇开始学习另一个核心功能——面向切面编程AOP。 通过本文,你可以了解到:  1 Spring xml规范 ...

23150
来自专栏用户画像

Spring AOP和IOC

Spring的核心机制是依赖注入(Dependency Inversion),也称为控制反转(IOC)。所谓依赖注入,就是指运行工程中,如果需要调用另一个对象协...

7110
来自专栏java思维导图

Spring思维导图,让Spring不再难懂(aop篇)

什么是aop AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing...

37370
来自专栏林德熙的博客

win10 uwp 读写XML xml 语法XmlDocumentLinq 读写 XMLWPF 读XMLWPF 读写 xaml

UWP 对 读写 XML做了一些修改,但和之前 WPF 的方法没有大的区别。 我们先来说下什么是 XML , XML 其实是 树结构,可以表达复杂的结构,所以在...

18410

扫码关注云+社区

领取腾讯云代金券