首页 » Java » Spring » 正文

Spring面向切面编程(AOP)的基本用法:基于注解的实现

一 简介

在软件开发中,分散于应用中多出的功能被称为横切关注点(如:事务、安全、缓存等)。通常来讲,这些横切关注点从概念上是与应用的业务逻辑相分离的(但是往往会直接嵌入到应用的业务逻辑之中)。因此,将这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题
关于AOP的几个基本概念分别如下:
(1)通知(Advice):
切面也有目标——它必须要完成的工作。在AOP术语中,切面的工作被称为通知。通知定义了切面是什么以及如何使用
Spring切面可以应用五种类型的通知:
  • 前置通知(Before):在目标方法被调用之前调用通知方法
  • 后置通知(After):在目标方法完成之后调用通知方法,此时不会关心目标方法是否执行成功或者抛出异常
  • 返回通知(After-returning):在目标方法成功执行之后调用通知方法
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知方法
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后分别执行自定义的行为

(2)连接点(Join point):

连接点是在应用执行过程中能够插入切面的一个点,这个点可以是调用方法时,抛出异常时甚至修改一个字段时,切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为

(3)切点(Pointcut):

如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”,切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点

(4)切面(Aspect):

切面是通知和切点的结合,通知和切点共同定义了切面的全部内容:它是什么,在何时和在何处完成其功能

(5)织入(Weaving):

织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入:

  • 编译期:切面在目标类编译时被织入,这种方式需要特殊的编译器。AspectJ的织入编译器就是就是以这种方式织入切面的
  • 类加载期:切面在目标类加载到JVM时被织入
  • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的

注:如果更习惯于使用XML文件来定义AOP的话,可以参考我的这篇文章:https://www.zifangsky.cn/805.html

二 基本用法示例

在正式开始介绍Spring AOP的基本用法之前,首先定义一个测试接口和它的实现类:

i)Performence.java:

ii)PianoPerformence.java:

(1)第一个实例:

i)定义一个切面:

在使用注解定义一个切面时,需要添加的注解是:@Aspect。同时下面方法上的几个注解的含义分别是:

  • @Before:定义一个前置通知
  • @AfterReturning:定义一个返回通知
  • @AfterThrowing:定义一个异常通知

当然,根据我前面的介绍内容,这里没有介绍到的通知还有:

  • @After:定义一个后置通知
  • @Around:定义一个环绕通知

最后,上面这些注解里面的参数的含义大致是这样的:

aop

ii)使用@EnableAspectJAutoProxy注解启用自动代理:

如果使用JavaConfig的话,可以这样配置:

可以看出,这个类首先使用了@Configuration注解,表明这个类属于一个配置类。然后使用@EnableAspectJAutoProxy注解启用了AspectJ自动代理。最后是使用了@ComponentScan注解指定需要扫描哪些包中的注解,这里配置的就是上面定义的Audience0类所在的包

如果不想使用JavaConfig的话,可以在Spring的配置文件中这样配置:

iii)基于JavaConfig的测试:

输出如下:

注:如果想要测试异常通知的效果的话,可以将我上面注释掉的 “throw new RuntimeException(“测试”);” 的注释去掉,再次测试即可

iv)基于xml配置的测试:

输出:略

(2)简化上面的切面写法:

在上面的切面定义的类中,可以看到每个具体的方法上面都有一个很长的“通知”表达式。其实,在这里是可以有简化写法的:

i)注释掉Audience0类的切面注解,即:

ii)定义一个新的切面:

从上面的代码可以看出,这里使用@Pointcut注解给被@Aspect注解标注的切面定义了一个可重用的切点。这样在使用其他通知时就可以直接在该切点商织入通知了,也就是使用被@Pointcut注解标注的 performance() 方法

注:这里的 performance() 方法的实际内容并不重要,在这里它实际上应该是空的。这个方法本身只是一个标志,供@Pointcut这个注解依附

iii)再次测试:

也就是再次运行TestAspect类的testPlay()方法。当然,输出结果跟上面一样,略

(3)环绕通知:

i)同样注释 Audience1 类的切面注解

ii)定义一个环绕通知:

从上面的代码可以看出,环绕通知跟前置通知和后置通知不同的是,它会在一个方法的执行之前和执行之后都会执行一些操作。关于这个通知方法,可以看到它接收了一个ProceedingJoinPoint对象作为参数。这个对象是必须要有的,因为我们要在通知中通过它来调用被通知的方法。通知方法中可以做任何的事情,当要将控制权交给被通知的方法时,它需要调用ProceedingJoinPoint 的 proceed()方法

iii)测试:

再次运行TestAspect类的testPlay()方法。最后输出如下:

(4)向通知方法中传递被通知方法的参数:

i)同样注释 Audience2 类的切面注解

ii)定义一个新的切面:

在定义切点时,指定了将有一个 String 类型的 play() 方法作为切点,同时其参数名是“pianist”

在下面定义具体的通知时,将获取到的被通知的方法中的“pianist”进行了计数处理。统计其演奏次数

iii)测试:

输出如下:

 

发表评论

*