一、环绕通知
1、环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。
2、对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。
3、在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。
4、注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。
环绕通知
@Aspect
@Component
public class LogUtils {
/**
* @throws Throwable
* @Around:环绕 :是Spring中强大的通知;顺序正常
* @Around:环绕:动态代理;
* try{
* //前置通知
* method.invoke(obj,args);
* //返回通知
* }catch(e){
* //异常通知
* }finally{
* //后置通知
* }
*
* 四合一通知就是环绕通知;
* 环绕通知中有一个参数: ProceedingJoinPoint pjp
*
*环绕通知:是优先于普通通知执行,执行顺序;
*/
@Around("hahaMyPoint()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjp.getArgs();
String name = pjp.getSignature().getName();
//args[0] = 100;
Object proceed = null;
try {
//@Before
System.out.println("【环绕前置通知】【"+name+"方法开始】");
//就是利用反射调用目标方法即可,就是method.invoke(obj,args)
proceed = pjp.proceed(args);
//@AfterReturing
System.out.println("【环绕返回通知】【"+name+"方法返回,返回值"+proceed+"】");
} catch (Exception e) {
//@AfterThrowing
System.out.println("【环绕异常通知】【"+name+"】方法出现异常,异常信息:"+e);
//为了让外界能知道这个异常,这个异常一定抛出去
throw new RuntimeException(e);
} finally{
//@After
System.out.println("【环绕后置通知】【"+name+"】方法结束");
}
//反射调用后的返回值也一定返回出去,若这里写死了,那么上面的try没有用了,很明显@Around是一个动态代理
return proceed;
}
}
二、 环绕通知加上普通通知执行顺序
/**
* 如何将这个类(切面类)中的这些方法(通知方法)动态的在目标方法运行的各个位置切入
* @author lfy
*
*/
@Aspect
@Component
public class LogUtils {
@Pointcut("execution(public int com.atguigu.impl.MyMathCalculator.*(..))")
public void hahaMyPoint(){};
//想在执行目标方法之前运行;写切入点表达式
//execution(访问权限符 返回值类型 方法签名)
@Before("hahaMyPoint()")
public static void logStart(JoinPoint joinPoint){
//获取到目标方法运行是使用的参数
Object[] args = joinPoint.getArgs();
//获取到方法签名
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[LogUtils-前置]【"+name+"】方法开始执行,用的参数列表【"+Arrays.asList(args)+"】");
}
//想在目标方法正常执行完成之后执行
@AfterReturning(value="hahaMyPoint()",returning="result")
public static void logReturn(JoinPoint joinPoint,Object result){
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[LogUtils-返回]【"+name+"】方法正常执行完成,计算结果是:"+result);
}
//想在目标方法出现异常的时候执行
@AfterThrowing(value="hahaMyPoint()",throwing="exception")
public static void logException(JoinPoint joinPoint,Exception exception) {
System.out.println("[LogUtils-异常]【"+joinPoint.getSignature().getName()+"】方法执行出现异常了,异常信息是【"+exception+"】:;这个异常已经通知测试小组进行排查");
}
//想在目标方法结束的时候执行
@After("hahaMyPoint()")
private int logEnd(JoinPoint joinPoint) {
System.out.println("[LogUtils-后置]【"+joinPoint.getSignature().getName()+"】方法最终结束了");
return 0;
}
/**
* @throws Throwable
* @Around:环绕 :是Spring中强大的通知;
* @Around:环绕:动态代理;
* try{
* //前置通知
* method.invoke(obj,args);
* //返回通知
* }catch(e){
* //异常通知
* }finally{
* //后置通知
* }
*
* 四合一通知就是环绕通知;
* 环绕通知中有一个参数: ProceedingJoinPoint pjp
*
*环绕通知:是优先于普通通知执行,执行顺序;
*
*[普通前置]
*{
* try{
* 环绕前置
* 环绕执行:目标方法执行
* 环绕返回
* }catch(){
* 环绕出现异常
* }finally{
* 环绕后置
* }
*}
*
*
*[普通后置]
*[普通方法返回/方法异常]
*新的顺序:
* (环绕前置---普通前置【这两个没有固定的前后顺序】)----目标方法执行----环绕正常返回/出现异常-----环绕后置----普通后置---普通返回或者异常
*注意: 为了让外界能知道这个异常,这个异常一定抛出去
* throw new RuntimeException(e);
*/
@Around("hahaMyPoint()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjp.getArgs();
String name = pjp.getSignature().getName();
//args[0] = 100;
Object proceed = null;
try {
//@Before
System.out.println("【环绕前置通知】【"+name+"方法开始】");
//就是利用反射调用目标方法即可,就是method.invoke(obj,args)
proceed = pjp.proceed(args);
//@AfterReturing
System.out.println("【环绕返回通知】【"+name+"方法返回,返回值"+proceed+"】");
} catch (Exception e) {
//@AfterThrowing
System.out.println("【环绕异常通知】【"+name+"】方法出现异常,异常信息:"+e);
//为了让外界能知道这个异常,这个异常一定抛出去
throw new RuntimeException(e);
} finally{
//@After
System.out.println("【环绕后置通知】【"+name+"】方法结束");
}
//反射调用后的返回值也一定返回出去
return proceed;
}
}
四、多切面运行顺序
切面一
@Aspect
@Component
@Order(2)//使用Order改变切面顺序;数值越小优先级越高,若存在多个切面,先入后出,后入先出
public class BValidateApsect {
@Before("com.czl.utils.LogUtils.hahaMyPoint()")
public void logStart(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[VaApsect-前置]【"+name+"】方法开始执行,用的参数列表【"+Arrays.asList(args)+"】");
}
@AfterReturning(value="com.czl.utils.LogUtils.hahaMyPoint()",returning="result")
public void logReturn(JoinPoint joinPoint,Object result){
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[VaApsect-返回]【"+name+"】方法正常执行完成,计算结果是:"+result);
}
@AfterThrowing(value="com.czl.utils.LogUtils.hahaMyPoint()",throwing="exception")
public void logException(JoinPoint joinPoint,Exception exception) {
System.out.println("[VaApsect-异常]【"+joinPoint.getSignature().getName()+"】方法执行出现异常了,异常信息是【"+exception+"】:;这个异常已经通知测试小组进行排查");
}
@After("com.czl.utils.LogUtils.hahaMyPoint()")
private int logEnd(JoinPoint joinPoint) {
System.out.println("[VaApsect-后置]【"+joinPoint.getSignature().getName()+"】方法最终结束了");
return 0;
}
}
切面二
@Order(1)//使用Order改变切面顺序;数值越小优先级越高,若存在多个切面,先入后出,后入先出
@Aspect
@Component
public class LogUtils {
/**
* 告诉Spring每个方法都什么时候运行;
* try{
* @Before
* method.invoke(obj,args);
* @AfterReturning
* }catch(e){
* @AfterThrowing
* }finally{
* @After
* }
*
* 5个通知注解
* @Before:在目标方法之前运行; 前置通知
* @After:在目标方法结束之后 后置通知
* @AfterReturning:在目标方法正常返回之后 返回通知
* @AfterThrowing:在目标方法抛出异常之后运行 异常通知
* @Around:环绕 环绕通知
*
*
* 抽取可重用的切入点表达式;
* 1、随便声明一个没有实现的返回void的空方法
* 2、给方法上标注@Pointcut注解
*/
@Pointcut("execution(public int com.czl.impl.MyMathCalculator.*(..))")
public void hahaMyPoint(){};
//想在执行目标方法之前运行;写切入点表达式
//execution(访问权限符 返回值类型 方法签名)
@Before("hahaMyPoint()")
public static void logStart(JoinPoint joinPoint){
//获取到目标方法运行是使用的参数
Object[] args = joinPoint.getArgs();
//获取到方法签名
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[LogUtils-前置]【"+name+"】方法开始执行,用的参数列表【"+Arrays.asList(args)+"】");
}
//想在目标方法正常执行完成之后执行
@AfterReturning(value="hahaMyPoint()",returning="result")
public static void logReturn(JoinPoint joinPoint,Object result){
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[LogUtils-返回]【"+name+"】方法正常执行完成,计算结果是:"+result);
}
//想在目标方法出现异常的时候执行
@AfterThrowing(value="hahaMyPoint()",throwing="exception")
public static void logException(JoinPoint joinPoint,Exception exception) {
System.out.println("[LogUtils-异常]【"+joinPoint.getSignature().getName()+"】方法执行出现异常了,异常信息是【"+exception+"】:;这个异常已经通知测试小组进行排查");
}
//想在目标方法结束的时候执行
@After("hahaMyPoint()")
private int logEnd(JoinPoint joinPoint) {
System.out.println("[LogUtils-后置]【"+joinPoint.getSignature().getName()+"】方法最终结束了");
return 0;
}
}
执行结果:看类首字母的大小写决定谁先执行,L在前面,所以LogUtils先执行。
使用Order改变切面顺序;数值越小优先级越高,若存在多个切面,先入后出,后入先出

切面二:加环绕
>切面二
```java
@Order(1)//使用Order改变切面顺序;数值越小优先级越高,若存在多个切面,先入后出,后入先出
@Aspect
@Component
public class LogUtils {
@Pointcut("execution(public int com.czl.impl.MyMathCalculator.*(..))")
public void hahaMyPoint(){};
//想在执行目标方法之前运行;写切入点表达式
//execution(访问权限符 返回值类型 方法签名)
@Before("hahaMyPoint()")
public static void logStart(JoinPoint joinPoint){
//获取到目标方法运行是使用的参数
Object[] args = joinPoint.getArgs();
//获取到方法签名
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[LogUtils-前置]【"+name+"】方法开始执行,用的参数列表【"+Arrays.asList(args)+"】");
}
//想在目标方法正常执行完成之后执行
@AfterReturning(value="hahaMyPoint()",returning="result")
public static void logReturn(JoinPoint joinPoint,Object result){
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[LogUtils-返回]【"+name+"】方法正常执行完成,计算结果是:"+result);
}
//想在目标方法出现异常的时候执行
@AfterThrowing(value="hahaMyPoint()",throwing="exception")
public static void logException(JoinPoint joinPoint,Exception exception) {
System.out.println("[LogUtils-异常]【"+joinPoint.getSignature().getName()+"】方法执行出现异常了,异常信息是【"+exception+"】:;这个异常已经通知测试小组进行排查");
}
//想在目标方法结束的时候执行
@After("hahaMyPoint()")
private int logEnd(JoinPoint joinPoint) {
System.out.println("[LogUtils-后置]【"+joinPoint.getSignature().getName()+"】方法最终结束了");
return 0;
}
//环绕方法
@Around("hahaMyPoint()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjp.getArgs();
String name = pjp.getSignature().getName();
//args[0] = 100;
Object proceed = null;
try {
//@Before
System.out.println("【环绕前置通知】【"+name+"方法开始】");
//就是利用反射调用目标方法即可,就是method.invoke(obj,args)
proceed = pjp.proceed(args);
//@AfterReturing
System.out.println("【环绕返回通知】【"+name+"方法返回,返回值"+proceed+"】");
} catch (Exception e) {
//@AfterThrowing
System.out.println("【环绕异常通知】【"+name+"】方法出现异常,异常信息:"+e);
//为了让外界能知道这个异常,这个异常一定抛出去
throw new RuntimeException(e);
} finally{
//@After
System.out.println("【环绕后置通知】【"+name+"】方法结束");
}
//反射调用后的返回值也一定返回出去
return proceed;
}
}
执行结果

五、AOP使用场景:
1、AOP加日志保存到数据库;
2、AOP做权限验证;
3、AOP做安全检查;
4、AOP做事务控制;
本文详细介绍了Spring AOP中的环绕通知,强调其功能强大并能完全控制连接点,包括何时及是否执行。环绕通知必须使用ProceedingJoinPoint作为参数,并通过调用proceed()执行原方法,否则会导致目标方法未执行。还讨论了环绕通知与其他通知的执行顺序,以及如何通过Order调整切面优先级。最后,列举了AOP的常见应用场景,如日志记录、权限验证、安全检查和事务控制。
984

被折叠的 条评论
为什么被折叠?



