技术好文:Spring基础篇——AOP切面编程

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 技术好文:Spring基础篇——AOP切面编程

阅读目录


一 基本理解二 核心概念三 基础代码示例四 需求升级五 切点表达式常用图解


回到顶部一 基本理解


  AOP,面向切面编程,作为Spring的核心思想之一,度娘上有太多的教程啊、解释啊,但博主还是要自己按照自己的思路和理解再来阐释一下。原因很简单,别人的思想终究是别人的,自己的理解才是自己的,尤其当用文字、代码来阐述一遍过后,理解层面上又似乎变得不一样了。


  博主就不概念化解释AOP了,这里只简单说下为啥要使用这样一种编程思想和相关的AOP技术。其实很简单,就是为了业务模块间的解耦,尤其在现代的软件设计中强调高内聚、低耦合,要求我们的业务模块化,各个功能模块只关注自己的逻辑实现,而不用关注与主业务逻辑不相关的功能。然而,在面向对象的系统设计中,系统中不可或缺的一些功能如日志、事务是散布在应用各处与主逻辑代码高度耦合的,这让主业务代码变得相当冗余、难以复用。而在面向切面的编程思想中,我们是考虑将那些散布在应用多处的重复性代码抽离出来封装成模块化的功能类,一来让主业务逻辑更加专注、简单,二来模块化的日志、事务也便于复用和移植,这就是解耦的思想。但是,解耦并不等于断耦,抽离的功能最终还是要以某种方式"还"(qie)回去,否则应用的功能就不完善了。这里,"还"(qie)回去的技术就是AOP技术,而这种解耦的编程思想就是AOP的编程思想。在Java的生态中,提供AOP技术的框架也有不少,主要的运用就是Spring的AOP和Spring"借鉴"并包含进了自己的生态体系的 AspectJ的AOP。


回到顶部二 核心概念


  为便于理解阐述,博主先唠叨几句。上面的基本阐述中,我们知道,AOP要干的事情其实也很简单,就是要将对象编程中,抽离出来的模块代码(权限、日志、事务)还(qie)回去,但肯定不能是对象思维中的代码冗杂的组合,而是应该更加高明一些,最好能在原来的业务代码执行的过程中不知不觉的还(qie)回去——也就是说要在主业务逻辑执行的流程里,动态的添加(权限、日志、事务)代码抽离前干的那些事情。怎么能做到呢?用代理啊,亲!想想,我们对一个目标对象采用代理不就是为了在目标对象逻辑执行时候通过在代理对象中干点额外的事情吗?这样,虽然,原目标对象并没有增加任何额外的功能,通过代理的一番暗中骚操作,展示给调用者的就好像目标对象有了代理对象中的那些额外的功能一样。于是你也很好理解,为什么Spring的AOP中要用到动态代理了。好了,经过一番唠叨,我们再来看AOP的相关术语就要好理解得多——


  1、横切关注点


  如上描述,我们把日志、事务、权限等代码重复性极高却散布在应用程序各个地方的功能称为横切关注点。


  2、连接点(Join Point)


  被代理的目标对象在业务逻辑执行的过程中,可以被代理对象动态切入代理功能的一些时机节点,比如方法执行前、后,异常时,成功返回时等等。当然,这只是针对Spring来说的,因为Spring基于动态代理,只支持方法级别的AOP切入,实际上,AspectJ、JBoss等框架的AOP还能提供构造器以及更细粒度字段等的连接点支持。


  3、通知(Advice)


  如上描述,就是代理对象在什么时机要为目标对象额外增加的功能代码,因而很多教程资料上称之为 增强。请注意博主对通知的描述里有提到什么时机,这很好理解,你的代理对象要给目标对象增加额外功能,总得清楚要增加在哪些时机吧,所以,我们的通知按照功能切入的时机分为以下5个类型:


    前置通知(Before):被代理对象目标方法被调用之前执行通知代码;


    后置通知(After):被代理对象目标方法执行完成之后执行通知代码,不管方法是否成功执行(这相当于异常捕获中的finally块,总是会执行的意思,所以博主觉得如果将其命名为最终通知要更好理解些);


    异常通知(After-throwing):被代理对象目标方法抛出异常后执行通知代码;


    返回通知(After-returning):被代理对象目标方法成功执行后执行通知代码;


    环绕通知(Around) :包裹被代理对象的目标方法,相当于结合了以上的所有通知类型。


  4、切点(Pointcut)


  被代理对象目标方法执行过程中真正的要执行通知代码的一个或多个连接点,这会通过切点表达式语言进行匹配。


  6、切面(Aspect)


  通知和切点的结合,切面完整的包含了代理对象对目标对象进行通知的三个基本要素:何时(前、后、异常、环绕、返回等),何地(切点),干什么(通知切入的功能)。


  7、织入(Weaving)


  将切面应用到被代理对象并创建代理对象的的过程。切面会在指定的连接点(切点)被织入到被代理对象的执行方法中。其实,被代理对象的生命周期中有多个时机(编译、类加载、运行)都可以进行织入,就 Spring 而言,是在被代理对象运行期进行代理对象的创建,织入切面逻辑的。


注:以上描述都是基于Spring 方法级别的AOP 来进行阐述


回到顶部三 基础代码示例


  说了那么多,还是上代码最简单直接。准备工作:


  ① 测试依赖的包及其版本(注:很多教程中都提到需要 aopalliance包,但是博主测试过程中并没有确认此包存在的必要性)


    aspectjweaver-1.9.2.jar


    commons-logging-1.2.jar


    spring-aop-4.3.18.RELEASE.jar


    spring-beans-4.3.18.RELEASE.jar


    spring-context-4.3.18.RELEASE.jar


    spring-core-4.3.18.RELEASE.jar


    spring-expression-4.3.18.RELEASE.jar


    spring-test-4.3.18.RELEASE.jar


//主业务功能


public class HuaWeiPhone {


public void ring() {


System.out.println("华为手机,产销第一");


}


}


//额外添加的功能


public class Photograph {


public void takePictures(){


System.out.println("华为手机,拍照牛批");


}


public void playGames(){


System.out.println("华为手机,游戏玩得也这么畅快");


}


}


  1、XML配置的方式


   根据以上Java代码,进行非常简单的配置,就能看到动态的为手机增加了拍照功能的效果了——


[/span>bean class="main.java.model.HuaWeiPhone"/>


[/span>bean id="photograph" class="main.model.Photograph"/>


[/span>aop:config

[/span>aop:pointcut id="ring" expression="execution( main.model.HuaWeiPhone.ring(..))"/>


[/span>aop:aspect ref="photograph"

[/span>aop:before method="takePictures" pointcut-ref="ring"/>


[/span>aop:after method="playGames" pointcut-ref="ring"/>




  在Spring环境下测试类XML配置——


@RunWith(SpringJUnit4ClassRunner.class)


@ContextConfiguration(locations = "classpath:main/resource/applicationContext.xml")


public class SpringTest {


@Autowired


HuaWeiPhone huaWeiPhone;


@Test


public void testXml(){


huaWeiPhone.ring();


}


}


  输出结果


  2、Java注解的方式


  需要先说明的是,Spring的基于注解的 AOP 实际上是借鉴吸收了AspectJ的功能,所以你会看到很多类似 AspectJ 框架的注解。在之前的模型类上通过添加相应的注解改造成一个切面——


@Aspect //将该类标注为一个AOP切面


@Component


public class Photograph {


@Pointcut("execution( main.model.HuaWeiPhone.ring(..))")


public void chenbenbuyi (){}


@Before("chenbenbuyi()")


public void takePictures(){


System.out.println("华为手机,拍照牛批");


}


@After("chenbenbuyi()")


public void playGames(){


System.out.println("华为手机,游戏玩得也这么畅快");


}


}


  同样的,目标类(HuaWeiPhone)上也要添加@Componet注解将其交给Spring 容器管理。然后,如果是纯注解的话,还要一个配置类——


//配置注解扫描


@ComponentScan(basePackages = "main")


//启用AspectJ的自动代理功能


@EnableAspectJAutoProxy


public class JavaConfig {


}


  最后,在Spring的环境下测试——


@RunWith(SpringJUnit4ClassRunner.class)


//@ContextConfiguration(locations = "classpath:main/resource/applicationContext.xml")


@ContextConfiguration(classes = JavaConfig.class)


public class SpringTest {


@Autowired


HuaWeiPhone huaWeiPhone;


@Test


public void testAnno(){


huaWeiPhone.ring();


}


}


  结果同上,这里就不展示了。不过需要注意的是,不管什么配置方式,基于Spring 的AOP编程实现的前提都是要将通知对象和被通知方法交给Spring IOC容器管理,也就是要声明为Spring 容器中的Bean。


回到顶部四 需求升级


  在第三部分中,博主只是展示了最最简单的AOP功能实现,还有稍微复杂的技能点没有列出。比如,5种通知类型中的环绕通知呢?再比如,我的切面代码如果要传参数怎么办呢?接下来博主依次讲解。


  ① 关于环绕通知的运用


  基于 二 中的阐述,5 种通知类型中 环绕通知 是功能最为强大,实际上,我们可以在环绕通知中个性化的定制出前置 、后置、异常和返回的通知类型,而如果单独的采用前置、后置等通知类型,如果业务涉及多线程对成员变量的修改,可能出现并发问题,所以环绕要比单独的使用另外的几种通知类型更加的安全。我们对上面的切面基于环绕通知进行修改,使之包含所有的通知类型的功能——


@Aspect


@Component


public class Photograph {


@Pointcut("execution( main.model.HuaWeiPhone.ring(..))")


public void chenbenbuyi (){}


@Around("chenbenbuyi()")


//代码效果参考:http://hnjlyzjd.com/xl/wz_25084.html

public void surround(ProceedingJoinPoint joinPoint){

try {


System.out.println("目标方法执行前执行,我就是前置通知");


joinPoint.proceed();// ①


// int i =1/0; // ② 制造异常


System.out.println("正常返回,我就是返回通知");


} catch (Throwable e) {


System.out.println("出异常了,我就是异常通知");


}finally {


System.out.println("后置通知,我就是最终要执行的通知");


}


}


}


  XML的配置和上面的其它通知类型一样,只不过元素标签为 [/span>aop:around />而已。上面的打印语句的位置就对应了其它几种通知类型执行切面逻辑的时机。这里注意,环绕通知方法体中需要有 ProceedingJoinPoint 接口作为参数,在环绕通知中,通过执行该参数的 proceed() 方法来调用通知需要切入的目标方法。如果不执行 ① 处的调用,//代码效果参考:http://hnjlyzjd.com/xl/wz_25082.html

被通知方法实际上会被阻塞掉,所以你会看到,明明测试中执行了被通知的方法,实际却没有执行。该参数对象还可以获取方法签名、代理对象、目标对象等信息,可以自己测试着玩。

  ② 关于通知的传参问题


  切面虽然是通用逻辑,但实际在切入不同的目标方的时候,可能还是希望通知方法根据被通知方法的不同(比如参数不同)而执行不一样的逻辑,这就要求我们的通知也能获取到被通知方法传入的参数。通过切点表达式,这也很容易办到。首先我们修改被通知的方法可以传参:


public void ring(String str) {


System.out.println("华为手机,产销第一");


int i =1/0;


}


  然后切面中切点表达式和切面方法也做对应的修改——


@Aspect


@Component


public class Photograph {


/**


Spring 借助于 AspectJ的切点表达式语言中的arg()表达式执行参数的传递工作


/


@Pointcut("execution( main.model.HuaWeiPhone.ring(String))&&args(name)")


public void chenbenbuyi (String name){}


/


① 在引用空标方法的切点表达式时同时也就要传入相应的参数


② 传入的参数形参名字必须和切点表达式中的相同


*/


@Before("chenbenbuyi(name)")


public void takePictures(String name){


System.out.println("喂喂,你好我是 "+ name);


}


/


对于异常通知,有专门的异常参数可以直接获取到被通知方法出现异常后信息的


/


@AfterThrowing(pointcut = "chenbenbuyi(name)",throwing = "e")


public void excep(String name,Throwable e){


System.out.println("出异常了,异常信息是:"+e.getMessage());


}


}


  XML中配置参数传递




  测试代码——


@RunWith(SpringJUnit4ClassRunner.class)


//@ContextConfiguration(locations = "classpath:main/resource/applicationContext.xml")


@ContextConfiguration(classes = JavaConfig.class)


public class SpringTest {


@Autowired


HuaWeiPhone huaWeiPhone;


@Test


public void testAnno(){


huaWeiPhone.ring("博客园 陈本布衣");


}


}


  最终测试的执行结果——


  注意点:


    ① XML配置中由于 &符号有特殊含义,所以 切点表达式中 连接形参名的时候就不能再使用注解中的 && ,而应该使用 and 代替,同样的如果有 或(|| )非 (!)操作,分别使用 or 和 not 代替。


    ② 注解和XML配置中切点表达式描述形参类型的地方博主采用了不同的方式,因为 .. 就表示任意类型,可以不用指明。


回到顶部五 切点表达式常用图解


作者:陈本布衣


    出处:


    本文版权归作者和博客园共有,欢迎转载分享,但必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
3月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
AOP(面向切面编程)能够帮助我们在不修改现有代码的前提下,为应用程序添加新的功能或行为。Micronaut框架中的AOP模块通过动态代理机制实现了这一目标。AOP将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高模块化程度。在Micronaut中,带有特定注解的类会在启动时生成代理对象,在运行时拦截方法调用并执行额外逻辑。例如,可以通过创建切面类并在目标类上添加注解来记录方法调用信息,从而在不侵入原有代码的情况下增强应用功能,提高代码的可维护性和可扩展性。
82 1
|
16天前
|
Java Spring
一键注入 Spring 成员变量,顺序编程
介绍了一款针对Spring框架开发的插件,旨在解决开发中频繁滚动查找成员变量注入位置的问题。通过一键操作(如Ctrl+1),该插件可自动在类顶部添加`@Autowired`注解及其成员变量声明,同时保持光标位置不变,有效提升开发效率和代码编写流畅度。适用于IntelliJ IDEA 2023及以上版本。
一键注入 Spring 成员变量,顺序编程
|
1月前
|
XML Java 数据库连接
Spring高手之路25——深入解析事务管理的切面本质
本篇文章将带你深入解析Spring事务管理的切面本质,通过AOP手动实现 @Transactional 基本功能,并探讨PlatformTransactionManager的设计和事务拦截器TransactionInterceptor的工作原理,结合时序图详细展示事务管理流程,最后引导分析 @Transactional 的代理机制源码,帮助你全面掌握Spring事务管理。
37 2
Spring高手之路25——深入解析事务管理的切面本质
|
1月前
|
安全 Java 编译器
什么是AOP面向切面编程?怎么简单理解?
本文介绍了面向切面编程(AOP)的基本概念和原理,解释了如何通过分离横切关注点(如日志、事务管理等)来增强代码的模块化和可维护性。AOP的核心概念包括切面、连接点、切入点、通知和织入。文章还提供了一个使用Spring AOP的简单示例,展示了如何定义和应用切面。
146 1
什么是AOP面向切面编程?怎么简单理解?
|
1月前
|
XML Java 开发者
论面向方面的编程技术及其应用(AOP)
【11月更文挑战第2天】随着软件系统的规模和复杂度不断增加,传统的面向过程编程和面向对象编程(OOP)在应对横切关注点(如日志记录、事务管理、安全性检查等)时显得力不从心。面向方面的编程(Aspect-Oriented Programming,简称AOP)作为一种新的编程范式,通过将横切关注点与业务逻辑分离,提高了代码的可维护性、可重用性和可读性。本文首先概述了AOP的基本概念和技术原理,然后结合一个实际项目,详细阐述了在项目实践中使用AOP技术开发的具体步骤,最后分析了使用AOP的原因、开发过程中存在的问题及所使用的技术带来的实际应用效果。
70 5
|
2月前
|
存储 Java API
简单两步,Spring Boot 写死的定时任务也能动态设置:技术干货分享
【10月更文挑战第4天】在Spring Boot开发中,定时任务通常通过@Scheduled注解来实现,这种方式简单直接,但存在一个显著的限制:任务的执行时间或频率在编译时就已经确定,无法在运行时动态调整。然而,在实际工作中,我们往往需要根据业务需求或外部条件的变化来动态调整定时任务的执行计划。本文将分享一个简单两步的解决方案,让你的Spring Boot应用中的定时任务也能动态设置,从而满足更灵活的业务需求。
182 4
|
2月前
|
Java 数据库连接 Spring
【2021Spring编程实战笔记】Spring开发分享~(下)
【2021Spring编程实战笔记】Spring开发分享~(下)
36 1
|
3月前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
49 1
|
3月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
【9月更文挑战第9天】AOP(面向切面编程)通过分离横切关注点提高模块化程度,如日志记录、事务管理等。Micronaut AOP基于动态代理机制,在应用启动时为带有特定注解的类生成代理对象,实现在运行时拦截方法调用并执行额外逻辑。通过简单示例展示了如何在不修改 `CalculatorService` 类的情况下记录 `add` 方法的参数和结果,仅需添加 `@Loggable` 注解即可。这不仅提高了代码的可维护性和可扩展性,还降低了引入新错误的风险。
54 13
|
2月前
|
Java 容器
AOP面向切面编程
AOP面向切面编程
48 0