各位小猿,程序员小猿开发笔记,希望大家共同进步。
使用注解来创建切面是AspectJ 5所引入的关键特性。AspectJ 5之前, 编写AspectJ切面需要学习一种Java语言的扩展,但是AspectJ面向注解 的模型可以非常简便地通过少量注解把任意类转变为切面。
我们已经定义了Performance接口,它是切面中切点的目标对象。 现在,让我们使用AspecJ注解来定义切面。
1.5.1 定义切面
流程图:
如果一场演出没有观众的话,那不能称之为演出。对不对?从演出的 角度来看,观众是非常重要的,但是对演出本身的功能来讲,它并不 是核心,这是一个单独的关注点。因此,将观众定义为一个切面,并将其应用到演出上就是较为明智的做法。
程序清单4. 1展现了Audience类,它定义了我们所需的一个切面。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
@Aspect
public class Audience {
@Before("execution(**concert.Performance.perform(..))")
public void silenceCellPhones() {
System.out.println("Silencing cell phones");
}
@Before("execution(**concert.Performance.perform(..))")
public void takeSeats() {
System.out.println("Taking seats");
}
@AfterReturning("execution(**concert.Performance.perform(..))")
public void applause() {
System.out.println("CLAP CLAP CLAP!!!");
}
@AfterThrowing("execution(**concert.Performance.perform(..))")
public void demandRefund() {
System.out.println("CLAP CLAP CLAP!!!");
}
}
程序清单4.1 Audience类:观看演出的切面
1. @AspectJ注解的作用是什么?
Audience类使用@AspectJ注解进行了标注。
该注解表明Audience不仅仅是一个POJO,还是一个切面。
Audience类中的方法都使用注解来定义切面的具体行为。
可以看到,这些方法都使用了通知注解来表明它们应该在什么时候调用。AspectJ提供了五个注解来定义通知,如表4.2所示。
表4.2 Spring使用AspectJ注解来声明通知方法
Audience使用到了前面五个注解中的三个。
takeSeats ()和 silence CellPhones ()方法都用到了@Before注解,表明它们应 该在演出开始之前调用。
applause ()方法使用了@AfterReturning注解,它会在演出成功返回后调用。
demandRefund()方法上添加了@AfterThrowing注解,这表 明它会在抛出异常以后执行。
暂时无法在飞书文档外展示此内容
2.我们这时候会发现,这些注解都有一个切点表达式作为值?
所有的这些注解都给定了一个切点表达式作为它的值,同时,这四个方法的切点表达式都是相同的。反之,它们可以设置成不同的切点表达式,但是在这里,这个切点表达式就能满足所有通知方法的需求。
3.切点表达式用了四次,存在一定的问题?
触发时间:在Performance的perform()方法执行时触发。
缺点:在任务执行前后,触发了四次,虽然能达到预期功能,但是给人怪怪的感觉。是不是可以定义一次呢,在每次需要的时候引用它,是一个很好的方案。
这时候,我们可以使用@PointCut注解,在@Aspect内定义可重用的切点。
package com.spring.cut;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
@Aspect
public class Audience {
/**
* 定义命名的切点
*/
@Pointcut("execution(**concert.Performance.perform(..))")
public void performace() {
}
/**
* 表演之前
*/
@Before("performace() ")
public void silenceCellPhones() {
System.out.println("Silencing cell phones");
}
@Before("performace() ")
public void takeSeats() {
System.out.println("Taking seats");
}
/**
* 表演之后
*/
@AfterReturning("performace() ")
public void applause() {
System.out.println("CLAP CLAP CLAP!!!");
}
/**
* 表演失败之后
*/
@AfterThrowing("performace() ")
public void demandRefund() {
System.out.println("CLAP CLAP CLAP!!!");
}
}
程序清单4.2 通过@Pointcut注解声明频繁使用的切点表达式
4. 使用@Pointcut注解代替切面表达式,应该怎么做?
1.在Audience类中,我们使用了@Pointcut注解来定义performance()方法。
2.@Pointcut注解接受一个切点表达式作为参数,就像我们之前在通知注解中所使用的那样。
5.这样做的意义在哪里?
通过在performance()方法上添加@Pointcut注解,我们扩展了切点表达式语言的能力,这样我们可以在任何需要使用performance()的地方使用它,而不必再使用更长的切点表达式。我们将所有通知注解中的长表达式都替换成了performance()。
实际上,performance()方法的具体内容并不重要,在这里它是空的。该方法只是一个标识,供@Pointcut注解使用。
6.Audience的意义何在?
Audience只是一个通过注解表明将用作切面的Java类。
像其他的Java类一样,它可以装配为Spring中的bean:
@Bean
public Audience. audience(){
return new Audience();
}
7.Audience自动代理会怎么样?
如果你就此止步的话,Audience只会是Spring容器中的一个bean。 即便使用了AspectJ注解,但它并不会被视为切面,这些注解不会解析,也不会创建将其转换为切面的代理。
8.如何启动自动代理功能?
如果你使用JavaConfig的话,可以在配置类的类级别上通过使用EnableAspectJ-AutoProxy注解启用自动代理功能。
程序清单 4.3展现了如何在JavaConfig中启用自动代理。
@Configuration
@EnableAspectJAutoproxy//启用AspectJ自动代理
@ComponentScan
public class ConcertConfig{
@Bean//声明Audience bean
public Audience audience(){
return new Audience();
}
}
程序清单4.3 在JavaConfig中启用AspectJ注解的自动代理
假如你在Spring中要使用XML来装配bean的话,那么需要使用Spring aop命名空间中的<aop:aspectj-autoproxy>元素。下面的XML 配置展现了如何完成该功能。
程序清单4.4 在XML中,通过Spring的aop命名空间启用AspectJ自 动代理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/c"
xmlns:aop="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<context:component-scan base-package="concert"/>
<aop:aspectj-autoproxy/>
<!--声明Audience bean-->
<bean class="com.spring.cut.Audience"/>
</beans>
不管你是使用JavaConfig还是XML,AspectJ自动代理都会为使用@Aspect注解的bean创建一个代理,这个代理会围绕着所有该切面 的切点所匹配的bean。在这种情况下,将会为Concertbean创建一个 代理,Audience类中的通知方法将会在perform()调用前后执行。
9.Spring的Aspect自动代理特性?
Spring的AspectJ自动代理仅仅使用@AspectJ作为创建切面的指导,切面依然是基于代理的。
在本质上,它依然是 Spring基于代理的切面。换句话说虽然使用的 是@AspectJ注解,仍然限于代理方法的调用。
10.如何充分利用Aspect的能力?
如果想利用 AspectJ的所有能力,我们必须在运行时使用AspectJ并且不依赖Spring 来创建基于代理的切面。
到现在为止,我们的切面在定义时,使用了不同的通知方法来实现前 置通知和后置通知。但是表4.2还提到了另外的一种通知:环绕通知 (around advice ) 。环绕通知与其他类型的通知有所不同,因此值得 花点时间来介绍如何进行编写。
1.5.2 创建环绕通知
环绕通知是最为强大的通知类型。它能够让你所编写的逻辑将被通知Aui的目标方法完全包装起来。实际上就像在一个通知方法中同时编写前置通知和后置通知。
为了阐述环绕通知,我们重写Audience切面。这次,我们使用一个 环绕通知来代替之前多个不同的前置通知和后置通知。
程序清单4.5 使用环绕通知重新实现Audience切面
package com.spring.cut;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class Audience {
/**
* 定义命名的切点
*/
@Pointcut("execution(**concert.Performance.perform(..))")
public void performace(){}
/**
* 环绕通知方法
*/
@Around("performace()")
public void watchperformance(ProceedingJoinPoint jp){
try{
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
//通知控制权转移
jp.proceed();
System.out.println("CLAP CLAP CLAP!!!");
}catch (Throwable e){
System.out.println("Demanding a refund");
}
}
}
1.@ around注解在上述方法的意义何在?代表了什么?
在这里,@Around注解表明watchPerformance ()方法会作为performance ()切点的环绕通知。
2.在这程序中,执行流程是怎么样的?
暂时无法在飞书文档外展示此内容
在这个通知中,观众在演出之 前会将手机调至静音并就坐,演出结束后会鼓掌喝彩。像前面一样, 如果演出失败的话,观众会要求退款。
3.通知的执行效果与前置通知和后置通知效果怎么样?
这个通知所达到的效果与之前的前置通知和后置通知是一样的。但是,现在它们位于同一个方法中,不像之前那样分散在四个不同的通知方法里面。
4.关于这个新的通知方法,参数是怎么样的?
这个通知方法,它接受ProceedingJoinPoint作为参数。这个对象是必须要有的,因为你要在通知中通过它来调用被通知的方法。
5.通知方法中控制权转移?
通知方法中可以做任何的事情,当要将控制权交给被通知的方法时,它需要调用ProceedingJoinPoint的proceed()方法。
6.为什么需要调用proceed()方法?
需要注意的是,别忘记调用proceed()方法。如果不调这个方法的 话,那么你的通知实际上会阻塞对被通知方法的调用。有可能这就是你想要的效果,但更多的情况是你希望在某个点上执行被通知的方 法。
7.如果不调用proceed方法会怎么样?
有意思的是,你可以不调用proceed()方法,从而阻塞对被通知方法的访问,与之类似,你也可以在通知中对它进行多次调用。要这样做的一个场景就是实现重试逻辑,也就是在被通知方法失败后,进行重复尝试。
欢迎程序员关注程序员小猿小胡工作之家,大家一起交流进步。
本文由博客一文多发平台 OpenWrite 发布!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。