通知:通知定义了切面是什么以及何时使用,除了描述切面要完成的工作,还解决了何时执行这个工作的问题,Spring切面定义了5中通知类型:
前置通知(Before):在目标方法被调用之前调用通知功能
后置通知(After):在目标方法完成之后调用通知功能,此时不会关心方法的输出是什么
返回通知(AfterReturning):在目标方法成功执行之后调用通知
异常通知(AfterThrowing):在目标方法抛出异常后调用通知
环绕通知(Around):在被通知的方法调用之前和调用之后执行自定义的行为
连接点:在应用执行过程中能够插入切面的一个点
切点:定义何处调用通知,切点的定义会匹配通知所有织入的一个或多个连接点,通常使用明确的类、方法名称,或利用正则表达式定义所要匹配的类和方法名来指定这些切点,Spring Aop所支持的AspectJ切点指示器有:
execution():用于匹配是连接点的执行方法
arg():限制连接点匹配参数为指定类型的执行方法
@args():限定连接点匹配参数由指定注解标注的执行方法
this():限定连接点匹配AOP代理的bean引用为指定类型的类
target:限定连接点匹配目标对象为指定类型的类
@target:限定连接点匹配特定的执行对象,这些对象对应的类要具有特定类型的注解
within():限制连接点匹配指定的类型
@within():限制连接点匹配指定注解所标注的类型
@annotation:限制匹配带有指定注解的连接点
在切点定义中支持以下运算符进行复合运算:
运算符 | 说明 |
---|---|
&& | 与运算。 |
! | 非运算。 |
|| | 或运算。 |
在xml中定义切点时运算符“&&”也可以用“and”代替,“!”可以用“not”代替,“||”可以用“or”代替
切点表达式定义常用的可以参考:https://blog.csdn.net/w605283073/article/details/82999927
切面:切面是通知和切点的结合,通知和切面共同定义了切面的全部内容:它是什么,在何时何处完成其功能。
引入:
织入:织入是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中,一个连接点可以同时匹配多个切点,且切点所对应的增强在连接点上织入顺序的规则为:
1、如果在同一个切面类中声明的增强,则按照增强在切面类中定义的顺序织入;
2、如果是不同切面类中声明的增强,如果切面类都实现了org.springframework.core.Ordered 接口,则按照order方法的顺序号织入;
3、如果是不同切面类中声明的增强,切面类没有实现org.springframework.core.Ordered 接口,则织入顺序是不确定的。
因为Spring基于动态代理,所以Spring只支持方法连接点,不支持字段和构造器接入点,所以我们也无法在bean创建时应用通知
下面写一个简单的切面类:
public interface DanceService {
public void dance();
public void danceCount(int count);
}
@Service
public class DanceServiceImpl implements DanceService {
public void dance() {
System.out.println("dance");
}
public void danceCount(int count) {
System.out.println("count:" + count);
}
}
public interface SingService {
public void sing();
}
@Service
public class SingServiceImpl implements SingService {
public void sing() {
System.out.println("sing");
}
}
Aspect类:
@Aspect
public class AudienceAspect {
@Before("execution(public * com.mybatis.service.*.*(..))")
public void beforePerformance(){
System.out.println("方法调用前:");
System.out.println("Silencing cell phones");
}
@After("execution(public * com.mybatis.service.*.*(..))")
public void afterPerformance(){
System.out.println("方法调用后:");
System.out.println("谢幕");
}
}
applicationContext.xml配置文件:
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AspectTest {
@Autowired
private SingService singService;
@Autowired
private DanceService danceService;
@org.junit.Test
public void testAspect(){
singService.sing();
danceService.dance();
}
}
执行结果:
方法调用前:
Silencing cell phones
sing
方法调用后:
谢幕
方法调用前:
Silencing cell phones
dance
方法调用后:
谢幕
上述是使用xml配置文件,如果使用javaConfig配置类启用自动代理功能,则通过加@EnableAspectJAutoProxy注解来实现
如果一个切面类中有多个相同的切点表达式,如上面的例子,两个切点的表达式是一样的,我们可以用@Pointcut注解定义一个可重用的切点,如下:
@Aspect
public class AudienceAspect {
@Pointcut("execution(public * com.mybatis.service.*.*(..))")
public void performance(){
}
@Before("performance()")
public void beforePerformance(){
System.out.println("方法调用前:");
System.out.println("Silencing cell phones");
}
@After("performance()")
public void afterPerformance(){
System.out.println("方法调用后:");
System.out.println("谢幕");
}
}
上面的例子也可以通过@Around实现:
@Aspect
public class AudienceAspect {
@Pointcut("execution(public * com.mybatis.service.*.*(..))")
public void performance(){
}
@Around("performance()")
public void aroundPerformance(ProceedingJoinPoint point){
System.out.println("方法调用前:");
System.out.println("Silencing cell phones");
try {
point.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("方法调用后:");
System.out.println("谢幕");
}
}
@Around 被通知的方法调用之前和调用之后执行自定义的行为,ProceedingJoinPoint是使用@Around必须接受的参数,因为通知中通过它调用被通知的方法,通知方法中可以做任何的事情,当将控制权交给被通知的方法时,需要调用proceed()方法。ProceedingJoinPoint接口继承JoinPoint接口,这两个接口主要有哪些方法呢:
org.aspectj.lang.JoinPoint 接口表示目标类连接点对象,它定义以下这些主要方法:
Object getTarget():获取连接点所在的目标对象
Object getThis():获取代理对象
Signature getSignature():获取连接点的方法签名对象
Object[] getArgs():获取连接点方法运行时的入参列表
org.aspectj.lang.ProceedingJoinPoint 继承了 JoinPoint 接口,它新增了两个方法(它们用于执行连接点方法)
Object proceed() : 通过反射执行目标对象连接点处的方法
Object proceed():使用新的入参,通过反射执行目标对象连接点处的方法
通过它我们可以清楚的了解到连接点的目标对象,方法以及入参等信息,如下所示:
@Aspect
public class AudienceAspect {
@Pointcut("execution(public * com.mybatis.service.*.*(..))")
public void performance(){
}
@Around("performance()")
public void aroundPerformance(ProceedingJoinPoint point){
System.out.println("方法调用前:");
System.out.println("Silencing cell phones");
try {
System.out.println("point.getTarget() 目标对象为:" + point.getTarget());
System.out.println("point.getThis() 代理对象为:" + point.getThis());
System.out.println("point.getSignature() 方法签名对象为:" + point.getSignature());
Object[] objects = point.getArgs();
if (objects.length > 0){
for (int i = 0; i < objects.length; i++){
System.out.println("该方法第" + (i+1) + "个参数值为:" + objects[i]);
}
}
point.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("方法调用后:");
System.out.println("谢幕");
}
}
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AspectTest {
@Autowired
private SingService singService;
@Autowired
private DanceService danceService;
@org.junit.Test
public void testAspect(){
singService.sing();
danceService.dance();
danceService.danceCount(1);
}
}
测试结果:
方法调用前:
Silencing cell phones
point.getTarget() 目标对象为:com.mybatis.service.impl.SingServiceImpl@1e04fa0a
point.getThis() 代理对象为:com.mybatis.service.impl.SingServiceImpl@1e04fa0a
point.getSignature() 方法签名对象为:void com.mybatis.service.SingService.sing()
sing
方法调用后:
谢幕
方法调用前:
Silencing cell phones
point.getTarget() 目标对象为:com.mybatis.service.impl.DanceServiceImpl@25fb8912
point.getThis() 代理对象为:com.mybatis.service.impl.DanceServiceImpl@25fb8912
point.getSignature() 方法签名对象为:void com.mybatis.service.DanceService.dance()
dance
方法调用后:
谢幕
方法调用前:
Silencing cell phones
point.getTarget() 目标对象为:com.mybatis.service.impl.DanceServiceImpl@25fb8912
point.getThis() 代理对象为:com.mybatis.service.impl.DanceServiceImpl@25fb8912
point.getSignature() 方法签名对象为:void com.mybatis.service.DanceService.danceCount(int)
该方法第1个参数值为:1
count:1
方法调用后:
谢幕
上面的例子是使用注解实现的,但面向注解的切面声明有一个劣势,就是必须能够为通知类添加注解,为了做到这一点就必须有源码,如果没有源码或者不想将AspectJ注解放到代码中,则可以使用另一种实现方式:在SpringXML配置文件中声明切面
AOP配置元素 | 用途 |
顶层的AOP配置元素,大多数的 |
|
定义一个切面 | |
定义AOP通知器 | |
定义一个AOP前置通知 | |
定义一个AOP后置通知(不管被通知的方法是否执行成功) | |
定义一个AOP返回通知(只有被通知的方法执行成功时才执行) | |
定义一个AOP异常通知(捕获被通知的方法的异常情况) | |
定义AOP环绕通知 | |
定义一个切点 | |
启用@Aspect注解驱动的切面 | |
以透明的方式为被通知的对象引入额外的接口 |
那如何使用这些元素实现上述例子实现的结果呢?
//定义切面的类
public class AudienceXmlAspect {
public void beforePerformance(){
System.out.println("方法调用前:");
System.out.println("Silencing cell phones");
}
public void afterPerformance(){
System.out.println("方法调用后:");
System.out.println("谢幕");
}
}
//applicationContext.xml