1F什么是AOP
AOP(Aspect Oriented Programming),即面向切面编程,对于面向对象编程(OOP)来说,AOP是一种补充。面向对象编程是软件开发的最基本抽象思想,OOP使得代码脱离了面向过程这种高耦合的代码开发模式,同时OOP引入了封装、继承、多态等概念建立了一种纵向依赖关系,从代码复用的角度看,OOP复用了同一继承链上的代码,也就是说OOP是纵向代码复用,无法横向代码复用。OOP的缺陷导致了很多公共代码需要重复引用重复编写,这也是Java项目工程中常常需要封装很多静态类、公共类的原因,但对于Java程序员来说这不是一件很正常的事情吗?就像吃饭要用筷子夹,喝汤要用勺子舀一样。而AOP的思想却是关注横向切面,相当于在纵向关系链中横切一刀,将那些业务模块的公共调用代码封装起来,形成一个切面(Aspect),从而减少了公共代码的引用点,提高了可维护性。下图是小编绘制的一副AOP与OOP关系的小图:
很多小伙伴问AOP适合应用于什么样的场景?关于这个问题,网上有回答大部分都是权限认证、日志、事务、监控等场景,其实在小编看来,AOP是一种横向关注思想,可以说是一种基本的开发技巧,并不局限于某些特定的应用场景,只要在系统中,从横向角度可以看到有代码优化与简洁的空间,那就能用AOP技术来改善代码。这里小编始终在强调两个字:横向!的确,AOP开发最重要的指导思想就是角度,而最重要的观察角度就是横向(就像上图中小编画的程序员的眼睛那样观察)。关于AOP的理解,忆蓉之心说他一直把AOP看做”刀与豆腐的关系,哪里不爽,就切一刀“,的确这种横切一刀很形象的表达了AOP两个重要元素:横向与切面。其实过滤器技术(filter)即可看成面向切面的一种开发思想,只是过滤器一般应用于特定场景,不具备通用性。本片小编带大家一起探讨一下Spring 中对于AOP的通用支持 -- Spring AOP。
2FSpring AOP
Java中AOP的实现分为了基于AspectJ实现的静态织入与Spring支持的动态代理织入技术。ApectJ的原理主要是通过控制Java的编译时机,将切面增强代码直接编译入生成的class文件中,其效率较高,但入侵性强;而Spring AOP的动态织入主要依赖于JDK动态代理与CGLIB的动态代理来实现,相对于ApectJ绿色环保,但效率相对较低。(关于动态与静态的具体原理,以及一些剖析将在下一篇中详细讨论,本文主要让小伙伴们先初步认识一下Spring AOP的使用方式)。在项目中引用Spirng AOP也非常方便,通过Maven就可以简单的引入Spring AOP依赖的包了:
OK现在,我们就可以开始使用AOP来在项目中进行切面开发了。但是在开发前,需要理解一下AOP开发中的一些流程。其实引入Spring后,AOP开发非常简单,程序员只需要根据具体的业务逻辑定义切入点,然后程序员就只需要关注该切入点的增强处理逻辑(Advice)开发工作即可,然后剩余的工作就都交给AOP框架去自动实现了,Spring AOP框架会根据代理的切入点定义自动生成代理对象,而代理对象的方法便是增强处理加上被代理(目标)对象的方法的“总和”。AOP织入后,代理类与被代理类的关系可以抽象如下图所示的关系:
其实,AOP技术的思想与实现非常像作者之前从事C++开发中用到的Hook技术,其本质都通过对目标对象的代理,从而达到对目标对象调用的监控与扩展,只是实现的具体技术与方式有些区别罢了。所以在开发这个领域来说,很多技术都是相通的,多领会一门语言技巧,或许会为你多增加一个看待编程的角度。
3FSpring AOP的使用
好了,这里我们就可以开始使用Spring AOP来进行切面开发了,首先我们准备一个用于测试的服务类接口与服务实现,如下:
万事俱备,我们可以定义一个用于监控Service包下所有函数调用的监控切面,这里我们使用前置增强(Before)来实现对应的功能。代码如下:
好了,此时,我们就对 service包下的所有类的,所有函数调用进行了前置增强,在增强逻辑中我们只简单打印出增强的函数名称,运行结果如下:
说明Before增强逻辑被成功调用,也意味着我们使用的Spring AOP的增强处理生效了。
4FAOP切入时机之增强处理
上一节我们已经开发了前置增强处理,实际上Spring几乎支持了函数级别调用的所有环节的增强处理,常用的增强处理有 @Before、@After、@Around、@AfterReturning、@AfterThrowing 这5种增强,分别对应于被代理目标函数调用的 前置、后置、返回、异常返回、包含 这5种增强处理模式,这5种增强处理为AOP开发提供了全面的切入时机的支持,并且使用方式也非常统一,与上节Before增强的编写几乎相同:
这里我们再用一张更直观的图,来看看增强逻辑与代理目标函数的关系:
5FAOP的切入点
从上节开始,小伙伴基本上就能利用Spring AOP进行切面开发了。但是一定存在疑问,为何所有注解中都声明了这样的语句,如下:
execution(*com.znlover.spring.aop.sample.service.*.*(..))
这是什么鬼,这是AOP切入点的表达式语句,而Spring AOP表达式语句是从静态织入AOP框架ApectJ的表达式语法继承来的(完全相同)。具体全面的语法规则,小伙伴可以查阅相关资料,这里我们解读一下最常用的execution表达式语法,如下图所示:
此外,Spring AOP 还支持通过@Pointcut定义一个通用的切入点,这样就可以在其他增强注解中以名称的方式使用该切入点,从而避免了多出重复编写表达式语句:
6F增强处理连接点JoinPoint
通过上面的篇章,小伙伴应该对于切面有了一个初步的理解,但是还有一点这里需要说明一下,那就是增强处理的连接点JoinPoint,当该增强处理方法被调用时,JoinPoint 参数就代表了织入增强处理的连接点,通过JoinPoint增强处理可以获取到目标函数的相关信息,JoinPoint 里包含了如下几个常用方法。
- Object[] getArgs(): 返回执行目标方法时的参数。
- Signature getSignature(): 返回被增强的方法的相关信息。
- Object getTarget(): 返回被织入增强处理的目标对象。
- Object getThis(): 返回 AOP 框架为目标对象生成的代理对象。
注意:当时使用 Around 处理时,我们需要将第一个参数定义为ProceedingJoinPoint 类型,该类型是 JoinPoint 类型的子类。JoinPoint在AOP编程中基本是必要的一个参数,尤其在监控,参数过滤的使用场景下。
本节初步向小伙伴们展示了Java项目中集成Spring AOP的方法,可以发现,AOP开发中,自始至终对于被代理类都没有做出任何需要配合改变的地方,这就是AOP的魅力,一方面体现了AOP系统解耦构建的强大功力,另一方面也体现了AOP更适合于扩展与公共组件的开发,因此AOP已经是Java生态体系中的一项重要技术。当然依照本公众号的刨根问底的行文习惯,本片不会是结束,只是探讨AOP开发的开端,希望还没有了解过AOP相关知识的小伙伴,借此跟着我们一起踏上深挖AOP的探索之路!