欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 名人名企 > 静态代理 -> 动态代理 -> AOP

静态代理 -> 动态代理 -> AOP

2025/2/25 18:36:21 来源:https://blog.csdn.net/qq_23388169/article/details/145154082  浏览:    关键词:静态代理 -> 动态代理 -> AOP

1. 前言

        本文从 “在目标方法执行前后添加日志” 这一案例引出静态代理,进而引出动态代理,进而引出 AOP 技术。文章内容如有疏漏或不正确之处,欢迎评论区留言指正交流。

2. 需求

        有一个计算器类接口,其接口中定义了加减乘除四个方法及其实现类如下。想要在执行方法时,在方法前后添加日志。比如执行add方法时输入如下内容:

【日志】add开始,参数:1,2
目标方法开始执行...
【日志】add返回,结果:3

public interface MathCalculator {Integer add(int a, int b);Integer sub(int a, int b);Integer mul(int a, int b);Integer div(int a, int b);
}
public class MathCalculatorImpl implements MathCalculator {@Overridepublic Integer add(int a, int b) {System.out.println("目标方法开始执行...");int result = a + b;return result;}@Overridepublic Integer sub(int a, int b) {System.out.println("目标方法开始执行...");int result = a - b;return result;}@Overridepublic Integer mul(int a, int b) {System.out.println("目标方法开始执行...");int result = a * b;return result;}@Overridepublic Integer div(int a, int b) {System.out.println("目标方法开始执行...");int result = a / b;return result;}
}

3. 日志硬编码

        最原始的方式是在其实现类的每个方法中都加上日志,这种做法不推荐,因为代码复用性不好,如果后期要调整日志输出格式或内容,还需要重新修改代码。(下图只针对add方法进行硬编码日志演示,其他三个方法忽略)。

4. 静态代理

        静态代理的概念、实现步骤以及优缺点。这里引用雷丰阳老师的课件的内容。

        静态代理的实现步骤如下:

        (1)静态代理类实现和目标对象相同的接口(此处目标对象是 MathCalculatorImpl,接口是MathCalculator);

        (2)静态代理类中有一个成员变量是目标对象,此目标对象需要在构造静态代理类时传入进来;

        (3)对外暴露静态代理对象,并且是用代理对象进行方法调用(实际运行时,调用的是被代理对象的真实方法)。

public class CalculatorStaticProxy implements MathCalculator {// 要被代理的目标对象private MathCalculator target;public CalculatorStaticProxy(MathCalculator mathCalculator) {this.target = mathCalculator;}@Overridepublic Integer add(int a, int b) {System.out.println("【日志】add开始,参数:" + a + "," + b);int result = target.add(a, b); // 执行真正业务逻辑的是目标对象,此处的静态代理对象只是在目标对象执行前后做一些工作。System.out.println("【日志】add返回,结果:" + result);return result;}@Overridepublic Integer sub(int a, int b) {return 0;}@Overridepublic Integer mul(int a, int b) {return 0;}@Overridepublic Integer div(int a, int b) {return 0;}
}

        测试代码如下:

    // 测试静态代理@Testpublic void test01() {// 目标对象MathCalculator target = new MathCalculatorImpl();// 测试静态代理的打印日志功能MathCalculator calculatorStaticProxy = new CalculatorStaticProxy(target);calculatorStaticProxy.add(1, 2);}

        输出结果如下:

5. 动态代理

5.1. 动态代理概念

        针对上述静态代理在实际使用过程中的缺点,可以通过动态代理来解决。动态代理的概念,这里引入雷丰阳老师课件的内容。

5.2. 动态代理使用演示

        初步演示动态代理的使用方式,代码如下:

    // 测试动态代理@Testpublic void test02() {// 目标对象MathCalculator target = new MathCalculatorImpl();/*** Object proxy: 代理对象(明星的经纪人)* Method method:代理对象准备调用目标对象的方法(明星具有的演习,唱歌等功能)* Object[] args:方法调用传递的参数*/InvocationHandler handler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 目标方法执行前打印日志System.out.println("[动态代理]目标对象方法执行前的参数:" + Arrays.toString(args));// 目标方法执行Object result = method.invoke(target, args);// 目标方法执行后打印日志System.out.println("[动态代理]目标对象方法执行后结果:" + result);return result;}};/*** ClassLoader loader: 目标对象的类加载器* Class<?>[] interfaces:目标对象的接口* InvocationHandler h : 代理对象的处理器*/MathCalculator proxyInstance = (MathCalculator) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);proxyInstance.add(1, 2);}

        输出结果如下:

5.3. 动态代理实现记录日志功能 

        将动态代理封装成一个工具类,并辅助日志工具类,实现针对任意对象进行动态代理并记录日志的功能。

动态代理工具类:

import com.shg.spring.aop.log.LogUtils;import java.lang.reflect.Proxy;/*** @DESCRIPTION: 返回目标对象的动态代理对象* @USER: shg* @DATE: 2024/10/1 14:18*/
public class DynamicProxy {public static Object getProxyInstance(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),(proxy, method, args) -> {Object result = null;String methodName = target.getClass().getName() + "." + method.getName();// 1. 日志记录开始LogUtils.logStart(methodName, args);try {result = method.invoke(target, args);// 2. 日志记录返回结果LogUtils.logReturn(methodName, result);} catch (Exception e) {// 3. 日志记录异常LogUtils.logException(methodName, e);//throw new RuntimeException(e);} finally {// 4. 日志记录结束LogUtils.logEnd(methodName);}return result;});}
}

日志工具类:

import java.util.Arrays;/*** @DESCRIPTION: 日志工具类* @USER: shg* @DATE: 2024/10/1 16:57*/
public class LogUtils {public static void logStart(String methodName, Object... args) {System.out.println("方法【" + methodName + "】开始执行,参数:【" + Arrays.toString(args)+"】");}public static void logEnd(String methodName) {System.out.println("方法【" + methodName + "】执行结束");}public static void logException(String methodName, Throwable e) {System.out.println("方法【" + methodName + "】执行异常,异常信息:" + e.getCause());}public static void logReturn(String methodName, Object result) {System.out.println("方法【" + methodName + "】执行结束,返回结果:【" + result+"】");}
}

 测试程序:

    // 测试动态代理(自己针对JDK提供的动态代理进行一层封装后返回代理对象)@Testpublic void test03() {MathCalculator proxyInstance = (MathCalculator) DynamicProxy.getProxyInstance(new MathCalculatorImpl());proxyInstance.add(1, 2);System.out.println("===============================");proxyInstance.div(1, 0);}

输出结果:

5.4. 代理其他类

        有了上述的动态代理工具类和日志工具类后,我们就可以通过这个代理工具类代理任何想代理的目标对象(前提是这个目标对象有接口),比如现在有一个UserService接口及其实现类如下:

UserService接口:

import com.shg.spring.aop.domain.User;import java.util.List;public interface UserService {User getUserById(int id);List<User> getUserList();
}

User类:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private String name;
}

 UserService接口实现类UserServiceImpl:

import com.shg.spring.aop.domain.User;
import com.shg.spring.aop.service.UserService;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;@Service
public class UserServiceImpl implements UserService {@Overridepublic User getUserById(int id) {User user = new User();user.setName("shg");return user;}@Overridepublic List<User> getUserList() {List<User> userList = new ArrayList<>();userList.add(new User("zhangSan"));userList.add(new User("liSi"));return userList;}
}

使用上述动态代理工具,代理UserServiceImpl对象,实现在执行业务逻辑时,记录日志信息。

    @Testpublic void test04() {UserService userServiceProxy = (UserService) DynamicProxy.getProxyInstance(new UserServiceImpl());userServiceProxy.getUserById(1);System.out.println("===============================");userServiceProxy.getUserList();}

 输出结果:

5.5 动态代理总结

(1)JDK提供的动态代理工具位于反射包下面,显而易见,动态代理使用的是反射机制;

(2)JDK提供的动态代理,目标对象必须有接口;

(3)有了上述的动态代理类(配合日志工具类),我们就可以针对任何具有接口的目标对象进行代理了。这就是动态代理的好处。

6. AOP

6.1. AOP的专业术语

前面为了给业务执行前后添加日志,使用了如下三种方式:
   (1)日志硬编码到业务逻辑中;

   (2)静态代理;

   (3)手写动态代理。

        手写动态代理是其中最好的解决方案,但是实际用起来比较麻烦,Spring的AOP技术就是为了简化动态代理的。这里引用雷丰阳的课件解释下Spring AOP中的专业术语:

6.2. AOP的实现步骤

(1)导入aop依赖;

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

(2)编写切面Aspect(使用注解@Aspect指明某个类为切面类)

(3)编写通知方法(使用@Before、@AfterReturning、@AfterThrowing和@After注解指明不同类型的通知方法)

(4)指定切入点表达式(使用@Pointcut注解指定切入点表达式)

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import java.util.Arrays;// 日志切面类(切面类里面有各种通知方法)
@Component
@Aspect
public class LogAspect {/*** 告诉Spring,以下通知何时何地运行。* * 【何时:通过注解传达何时运行】* @Before:方法执行前运行。* @AfterReturning:方法正常执行后运行。* @AfterThrowing:方法抛出异常时运行。* @After:方法执行后运行,无论是否抛出异常。 * * 【何地:通过切入点表达式指定何地运行】* * 切入点表达式的写法:* execution(访问修饰符 返回值类型 方法名(参数列表))* (1)全写法:[public] int [com.shg.spring.aop.calculator.MathCalculator].add(int, int) [throws Exception]* (2)简写法:int add(int, int)* * 通配符:* * :表示任意字符串* ..* 1) 参数位置:表示多个参数,任意类型* 2) 类型位置(包位置):代表多个层级* * 最省略:* *(..)* */@Pointcut("execution(Integer com.shg.spring.aop.calculator.MathCalculator.*(int, int)))") // 切入点表达式推荐这种写法public void pointCut() {}@Before("pointCut()")public void logStart(JoinPoint joinPoint) {// 获取方法签名MethodSignature signature = (MethodSignature) joinPoint.getSignature();String shortMethodName = signature.toShortString();// 获取方法参数列表Object[] args = joinPoint.getArgs();System.out.println("【切面 - 日志】方法" + shortMethodName + "开始,参数列表:" + Arrays.toString(args));}@AfterReturning(value = "pointCut()", returning = "result")public void logReturn(JoinPoint joinPoint, Object result) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();String shortMethodName = signature.toShortString();System.out.println("【切面 - 日志】方法" + shortMethodName + "正常返回,结果是:" + result);}@After("pointCut()")public void logEnd(JoinPoint joinPoint) {// 获取方法签名MethodSignature signature = (MethodSignature) joinPoint.getSignature();String shortMethodName = signature.toShortString();System.out.println("【切面 - 日志】方法" + shortMethodName + "执行完毕,后置通知执行...");}@AfterThrowing(value = "pointCut()", throwing = "e")public void logException(JoinPoint joinPoint, Throwable e) {// 获取方法签名MethodSignature signature = (MethodSignature) joinPoint.getSignature();String shortMethodName = signature.toShortString();System.out.println("【切面 - 日志】方法" + shortMethodName + "执行出现异常...,异常信息:" + e.getMessage());}}

(5)测试AOP动态织入。

import com.shg.spring.aop.calculator.MathCalculator;
import com.shg.spring.aop.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;// 测试Spring提供的AOP
@SpringBootTest
public class AopTest {@Autowired private MathCalculator mathCalculator; // 如果被代理了,那么这个对象就是被代理后的对象@Testpublic void test01(){System.out.println(mathCalculator.getClass().getName());  // 被Spring的AOP代理了:com.shg.spring.aop.calculator.impl.MathCalculatorImpl$$SpringCGLIB$$0mathCalculator.div(1, 1);}
}

输出结果:

6.3. AOP的其他一些知识点

6.3.1. Spring AOP的底层原理

(1)Spring会为每个被切面类切入的组件创建代理对象(Spring底层默认是使用CGLIB创建的代理对象,被代理对象可以没有接口);

(2)代理对象中保存了切面类里面所有的通知方法,把这些通知方法组织成一个增强器链;

(3)目标对象执行时,会先去增强器链中拿到需要提前执行的通知方法并执行。

通过Debug,可以发现代理对象中有一个通知方法组成的增强器集合,如下图:

       

前面的6.1和6.2章节介绍了Spring中AOP的用法。除此之外,AOP还有其他一些知识点需要关注,比如(1)切入点表达式的不同写法;(2)通知方法的执行顺序;(3)如何获取连接点的信息;(4)同一个目标对象,存在多个切面时,如何控制多个切面的执行顺序,以及多个切面的执行流程是怎样的;(5)环绕通知的写法以及注意事项。

6.3.2. 切入点表达式的其他写法

        切入点表达式的更多写法,官网文档地址:Declaring a Pointcut :: Spring Framework 这里引用雷丰阳老师的课件(标黄的是实际开发中经常用到的)。

6.3.3. 通知方法的执行顺序

前置通知、返回通知、异常通知和后置通知的执行顺序是怎样的,如下图:

 6.3.4. JoinPoint连接点信息

        从下图解释连接点

        上图中每个圆圈就代表一个连接点,当切入点表达式指定的范围包含某个连接点时,此时连接点就是切入点了。在代理对象执行目标方法时,使用@Before、@AfterReturning、@AfterThrowing和@After标注的通知(也叫增强)就会 在合适的时机执行。那么如果想知道某个连接点的信息,该怎么办呢?

        在切面类里面的四种通知方法,其方法签名的 访问修饰符、返回值类型、方法名称都可以随意写(只要符合语法就行),但是其通知方法的参数列表有要求。可以写JoinPoint参数获取连接点信息。这个连接点参数里面包含了当前连接点的所有信息。

        四个通知方法都可以写 JoinPoint这个参数。其中返回通知(即:@AfterReturning)还可以写一个接收函数返回值的参数。异常通知(即:@AfterThrowing)还可以写一个接收异常的参数。【注意:JoinPoint参数只能写在第一个参数的位置】代码示例如下:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import java.util.Arrays;@Component
@Aspect
public class LogAspect {@Pointcut("execution(Integer com.shg.spring.aop.calculator.MathCalculator.*(int, int)))") // 切入点表达式推荐这种写法public void pointCut() {}@Before("pointCut()")public void logStart(JoinPoint joinPoint) {// 获取方法签名MethodSignature signature = (MethodSignature) joinPoint.getSignature();String shortMethodName = signature.toShortString();// 获取方法参数列表Object[] args = joinPoint.getArgs();System.out.println("【切面 - 日志】方法" + shortMethodName + "开始,参数列表:" + Arrays.toString(args));}@AfterReturning(value = "pointCut()", returning = "result")public void logReturn(JoinPoint joinPoint, Object result) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();String shortMethodName = signature.toShortString();System.out.println("【切面 - 日志】方法" + shortMethodName + "正常返回,结果是:" + result);}@After("pointCut()")public void logEnd(JoinPoint joinPoint) {// 获取方法签名MethodSignature signature = (MethodSignature) joinPoint.getSignature();String shortMethodName = signature.toShortString();System.out.println("【切面 - 日志】方法" + shortMethodName + "执行完毕,后置通知执行...");}@AfterThrowing(value = "pointCut()", throwing = "e")public void logException(JoinPoint joinPoint, Throwable e) {// 获取方法签名MethodSignature signature = (MethodSignature) joinPoint.getSignature();String shortMethodName = signature.toShortString();System.out.println("【切面 - 日志】方法" + shortMethodName + "执行出现异常...,异常信息:" + e.getMessage());}
}

6.3.4. 多切面存在时,如何控制多个切面的执行顺序,以及多个切面存在时各个切面的执行流程

        当同一个目标对象被多个切面切入时(大白话就是:一个目标对象,有多个切面类。比如一个目标对象是LoginService,其存在一个日志切面类,一个权限切面类。)这个时候我们需要指定多个切面的执行优先级(使用@Order注解进行指定,数值越小,优先级越高)如果没有使用@Order注解指明多个切面的执行优先级,Spring底层默认是按照切面类的首字母进行排序执行(这一点我没有验证过),比如两个切面类 LogAspect和AuthAspect,默认优先级较高的是AuthAspect。此处引用雷丰阳老师的课件。

        具体示例场景:以上面的MathCalculator类的实例为目标对象,现在有一个日志切面类和权限切面类都作用于此目标对象。具体代码如下:

目标对象类:

public interface MathCalculator {Integer add(int a, int b);Integer sub(int a, int b);Integer mul(int a, int b);Integer div(int a, int b);
}
@Component
public class MathCalculatorImpl implements MathCalculator {@Overridepublic Integer add(int a, int b) {System.out.println("目标方法开始执行...");int result = a + b;return result;}@Overridepublic Integer sub(int a, int b) {System.out.println("目标方法开始执行...");int result = a - b;return result;}@Overridepublic Integer mul(int a, int b) {System.out.println("目标方法开始执行...");int result = a * b;return result;}@Overridepublic Integer div(int a, int b) {System.out.println("目标方法开始执行...");int result = a / b;return result;}
}

 日志切面类:

@Order(2) // 使用@Order注解设置优先级
@Component
@Aspect
public class LogAspect {@Pointcut("execution(Integer com.shg.spring.aop.calculator.MathCalculator.*(int, int)))") // 切入点表达式推荐这种写法public void pointCut() {}@Before("pointCut()")public void logStart(JoinPoint joinPoint) {// 获取方法签名MethodSignature signature = (MethodSignature) joinPoint.getSignature();String shortMethodName = signature.toShortString();// 获取方法参数列表Object[] args = joinPoint.getArgs();System.out.println("【切面 - 日志】方法" + shortMethodName + "开始,参数列表:" + Arrays.toString(args));}@AfterReturning(value = "pointCut()", returning = "result")public void logReturn(JoinPoint joinPoint, Object result) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();String shortMethodName = signature.toShortString();System.out.println("【切面 - 日志】方法" + shortMethodName + "正常返回,结果是:" + result);}@After("pointCut()")public void logEnd(JoinPoint joinPoint) {// 获取方法签名MethodSignature signature = (MethodSignature) joinPoint.getSignature();String shortMethodName = signature.toShortString();System.out.println("【切面 - 日志】方法" + shortMethodName + "执行完毕,后置通知执行...");}@AfterThrowing(value = "pointCut()", throwing = "e")public void logException(JoinPoint joinPoint, Throwable e) {// 获取方法签名MethodSignature signature = (MethodSignature) joinPoint.getSignature();String shortMethodName = signature.toShortString();System.out.println("【切面 - 日志】方法" + shortMethodName + "执行出现异常...,异常信息:" + e.getMessage());}
}

权限切面类:

@Order(1) // 多个切面切入点时,优先级高的先执行(数字越小,优先级越高,越先执行)
@Component
@Aspect
public class AuthAspect {@Pointcut("execution(Integer com.shg.spring.aop.calculator.MathCalculator.*(int, int)))")public void pointCut() {}@Before("pointCut()")public void before(JoinPoint joinPoint) {Signature signature = joinPoint.getSignature();String shortMethodName = signature.toShortString();Object[] args = joinPoint.getArgs();System.out.println("【切面 - 权限】方法" + shortMethodName + "开始,参数列表:" + Arrays.toString(args));}@AfterReturning("pointCut()")public void afterReturning() {System.out.println("【切面 - 权限】正常返回");}@After("pointCut()")public void after() {System.out.println("【切面 - 权限】后置");}@AfterThrowing("pointCut()")public void afterThrowing() {System.out.println("【切面 - 权限】异常");}}

 测试程序:

@SpringBootTest
public class AopTest {@Autowired private MathCalculator mathCalculator;@Testpublic void test01(){mathCalculator.div(1, 1);}
}

同一个目标对象,存在多个切面时的输出结果:

具体执行流程如下图:

6.3.5.环绕通知如何实现

        前面我们在实现动态代理时,可以做到修改目标方法的参数和执行结果。在Spring AOP技术中,环绕通知可以实现和动态代理相同的功能。环绕通知可以控制目标方法是否执行,修改目标方法参数和执行结果等功能。

(1)环绕通知的案例

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;import java.util.Arrays;@Component
@Aspect
public class AroundAspect {@Pointcut("execution(Integer com.shg.spring.aop.calculator.MathCalculator.*(int, int)))")public void pointCut() {}// 环绕通知的参数必须是 ProceedingJoinPoint,意思是可以继续推进的切点@Around("pointCut()")public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {String shortMethodName = pjp.getSignature().toShortString();Object[] args = pjp.getArgs();// 环绕通知 - 前置通知System.out.println("【切面 - 环绕前置通知】方法" + shortMethodName + "开始,参数列表:" + Arrays.toString(args));Object result;try {result = pjp.proceed(args);System.out.println("【切面 - 环绕正常返回通知】方法" + shortMethodName + "正常返回,结果是:" + result);} catch (Throwable e) {System.out.println("【切面 - 环绕异常通知】方法执行出现异常...,异常信息:" + e.getMessage());throw e;} finally {System.out.println("【切面 - 环绕后置通知】方法" + shortMethodName + "执行完毕,后置通知执行...");}return result;}
}

(2)测试程序

import com.shg.spring.aop.calculator.MathCalculator;
import com.shg.spring.aop.calculator.impl.MathCalculatorImpl;
import com.shg.spring.aop.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class AopTest {@Autowired private MathCalculator mathCalculator; // 如果被代理了,那么这个对象就是被代理后的对象@Testpublic void test01(){mathCalculator.div(10, 2);}
}

(3)输出结果

        注意这里针对MathCalculator类又增加了一个环绕通知切面,此时针对目标对象MathCalculator就有三个切面类了。所以输出结果中,先执行最外层的权限切面,然后执行日志切面,最后执行环绕通知切面。示意图如下:

        输出结果如下: 

(2)环绕通知注意事项

        环绕通知需要把异常抛出去,让外层感知异常,否则在某些场景下会出问题。比如在事务切面要做异常回滚时,如果事务切面是外层的切面,而里面嵌套了一个其他切面通知,如果里面的切面通知把异常吞了,外面的切面感知不到异常就不能进行回滚了。代码及注释如下:

@Component
@Aspect
public class AroundAspect {@Pointcut("execution(Integer com.shg.spring.aop.calculator.MathCalculator.*(int, int)))")public void pointCut() {}// 环绕通知的参数必须是 ProceedingJoinPoint,意思是可以继续推进的切点@Around("pointCut()")public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable { // 方法签名处声明编译时检异常String shortMethodName = pjp.getSignature().toShortString();Object[] args = pjp.getArgs();// 环绕通知 - 前置通知System.out.println("【切面 - 环绕前置通知】方法" + shortMethodName + "开始,参数列表:" + Arrays.toString(args));Object result;try {result = pjp.proceed(args);System.out.println("【切面 - 环绕正常返回通知】方法" + shortMethodName + "正常返回,结果是:" + result);} catch (Throwable e) {System.out.println("【切面 - 环绕异常通知】方法执行出现异常...,异常信息:" + e.getMessage());throw e;  // 注意!!!这里要把异常抛出去,让外层感知异常} finally {System.out.println("【切面 - 环绕后置通知】方法" + shortMethodName + "执行完毕,后置通知执行...");}return result;}
}

        再次运行上述测试程序,故意制造一个数学运算除以0异常,如下:

@SpringBootTest
public class AopTest {@Autowired private MathCalculator mathCalculator;@Testpublic void test01(){mathCalculator.div(10, 0);}
}

        运行结果如下:

 6.4. AOP的使用场景

        AOP技术可以在目标方法执行前后及异常发生进行拦截,做一些额外的业务逻辑。所以模板化的业务逻辑都可以使用AOP技术来实现。

比如以事务处理场景为例,只有执行SQL和封装返回值是每个业务方法不一样的,其他步骤都是模板化的。

(1)获取数据库连接;

(2)设置非自动提交(开启事务);

(3)执行SQL;

(4)封装返回值;

(5)正常:提交事务;

(6)异常:回滚事务;

(7)关闭连接,释放资源。

比如权限检查场景,假设目标方法上标注了自定义的注解 @Role("admin")

(1)接收请求,拿到用户身份;

(2)拿到目标方法上标注的所有注解;

(3)判断目标方法是否有标注@Role注解,如果标注了,则判断(1)中的用户身份是否和@Role注解里面指定的用户角色相同;

(4)如果相同,放行,执行业务逻辑;

(5)如果不相同:不执行业务逻辑,抛出异常并记录非法请求。 

其他的一些场景:

(1)日志记录;

(2)事务控制;

(3)权限检查;

(4)统一异常处理(SpringMVC里面的统一异常处理);

(5)缓存管理 等场景。 

8. 针对Spring AOP的应用场景之 Spring声明式事务,会在接下来的《Spring声明式事务中介绍》

9.如果此篇文章对你有帮助,感谢关注并点个赞~ 后续持续输出高质量的原创技术文章~

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词