Spring中基于注解的AOP,借用了AspectJ中的注解,将代理逻辑“织入”被代理的目标类编译得到的字节码文件。由于生成的的是字节码码,因此上效果也是动态的。
动态代理:JDK原生实现方式,需要代理的目标类必须实现接口,因为此技术要求代理类和目标类实现了相同的接口(兄弟拜把子)。
cglib:通过继承目标类(认干爹),实现代理,所以不需要实现接口。
AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
一、使用方法
需求:通过Spring中基于注解的AOP,来实现给计算器类中加减乘除方法增添日志。
step1:添加依赖
<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
step2:创建计算器类接口和实现类
package com.atguigu.spring.aop.annotation;
/**
* Date:2022/7/4
* Author:ybc
* Description:
*/
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
@Component
public class CalculatorImpl implements Calculator {
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部,result:"+result);
return result;
}
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部,result:"+result);
return result;
}
public int mul(int i, int j) {
int result = i * j;
System.out.println("方法内部,result:"+result);
return result;
}
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部,result:"+result);
return result;
}
}
step3:创建切面类
@Component
@Aspect //将当前组件标识为切面
public class LoggerAspect {
@Pointcut("execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..))")
public void pointCut(){}
//@Before("execution(public int com.atguigu.spring.aop.annotation.CalculatorImpl.add(int, int))")
//@Before("execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..))")
@Before("pointCut()")
public void beforeAdviceMethod(JoinPoint joinPoint) {
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
//获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect,方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
}
@After("pointCut()")
public void afterAdviceMethod(JoinPoint joinPoint){
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+",执行完毕");
}
/**
* 在返回通知中若要获取目标对象方法的返回值
* 只需要通过@AfterReturning注解的returning属性
* 就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
*/
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result){
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+",结果:"+result);
}
/**
* 在异常通知中若要获取目标对象方法的异常
* 只需要通过AfterThrowing注解的throwing属性
* 就可以将通知方法的某个参数指定为接收目标对象方法出现的异常的参数
*/
@AfterThrowing(value = "pointCut()", throwing = "ex")
public void afterThrowingAdviceMethod(JoinPoint joinPoint, Throwable ex){
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+",异常:"+ex);
}
@Around("pointCut()")
//环绕通知的方法的返回值一定要和目标对象方法的返回值一致
public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("环绕通知-->前置通知");
//表示目标对象方法的执行
result = joinPoint.proceed();
System.out.println("环绕通知-->返回通知");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知-->异常通知");
} finally {
System.out.println("环绕通知-->后置通知");
}
return result;
}
}
step4:将目标类和切面类交给Spring容器管理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--
AOP的注意事项:
切面类和目标类都需要交给IOC容器管理
切面类必须通过@Aspect注解标识为一个切面
在Spring的配置文件中设置<aop:aspectj-autoproxy />开启基于注解的AOP
-->
<context:component-scan base-package="com.atguigu.spring.aop.annotation"></context:component-scan>
<!--开启基于注解的AOP-->
<aop:aspectj-autoproxy />
</beans>
二、通知分类
类型 | 注解 | 执行位置 |
---|---|---|
前置通知 | @Before | 在被代理的目标方法前执行 |
返回通知 | @AfterReturning | 在被代理的目标方法成功结束后执行(寿终正寝) |
异常通知 | @AfterThrowing | 在被代理的目标方法异常结束后执行(死于非命) |
后置通知 | @After(finally) | 在被代理的目标方法最终结束后执行(盖棺定论) |
环绕通知 | @Around | 使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置 |
三、切入点表达式
切入点表达式的作用:找到要执行的位置。
比如:表示在CalculatorImpl类中所有方法执行。
@Pointcut("execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..))")
public void pointCut(){}
使用方法:直接在@Before等通注解后面使用即可。
@Before("pointCut()")
public void beforeAdviceMethod(JoinPoint joinPoint) {
四、获取通知相关信息
a. 连接点信息
获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参
@Before("pointCut()")
public void beforeAdviceMethod(JoinPoint joinPoint) {
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
//获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect,方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
}
如上述代码,通过joinPoint参数获取方法名称(joinPoint.getSignature().getName)和参数列表( joinPoint.getArgs())并打印
b.目标方法返回值
@AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值
/**
* 在返回通知中若要获取目标对象方法的返回值
* 只需要通过@AfterReturning注解的returning属性
* 就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
*/
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result){
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+",结果:"+result);
}
c.目标方法异常
/**
* 在异常通知中若要获取目标对象方法的异常
* 只需要通过AfterThrowing注解的throwing属性
* 就可以将通知方法的某个参数指定为接收目标对象方法出现的异常的参数
*/
@AfterThrowing(value = "pointCut()", throwing = "ex")
public void afterThrowingAdviceMethod(JoinPoint joinPoint, Throwable ex){
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+",异常:"+ex);
}
五、切面优先级
相同目标方法上同时存在多个切面
优先级高的切面:外面
优先级低的切面:里面
使用@Order注解可以控制切面的优先级:
@Order(较小的数):优先级高
@Order(较大的数):优先级低