当前位置: 首页 > news >正文

05.Spring_AOP详解

Spring AOP 详解:原理与实现

一、利用加减乘除和日志理解 AOP 工作原理

1.1 不使用 AOP 的计算器

假设我们有一个简单的计算器类,提供加减乘除四种基本运算:

public class Calculator {public int add(int a, int b) {return a + b;}public int subtract(int a, int b) {return a - b;}public int multiply(int a, int b) {return a * b;}public int divide(int a, int b) {return a / b;}
}

这个计算器工作正常,但如果我们想在每次操作前后记录日志,传统做法需要修改每个方法:

public class Calculator {public int add(int a, int b) {System.out.println("开始计算: " + a + " + " + b);int result = a + b;System.out.println("计算结果: " + result);return result;}public int subtract(int a, int b) {System.out.println("开始计算: " + a + " - " + b);int result = a - b;System.out.println("计算结果: " + result);return result;}// 以此类推...
}

这种做法有明显缺点:

  • 代码重复,违反 DRY 原则
  • 业务逻辑(计算)与非业务逻辑(日志)混合,违反单一职责原则
  • 如果要修改日志逻辑,需要修改所有方法

1.2 使用 AOP 的计算器

使用 AOP,我们可以将核心业务逻辑和横切关注点(如日志)分离:

// 原始计算器类保持简洁
public class Calculator {public int add(int a, int b) {return a + b;}public int subtract(int a, int b) {return a - b;}public int multiply(int a, int b) {return a * b;}public int divide(int a, int b) {return a / b;}
}// 创建一个日志切面
@Aspect
public class LoggingAspect {@Before("execution(* Calculator.*(..))")public void beforeCalculation(JoinPoint joinPoint) {Object[] args = joinPoint.getArgs();System.out.println("开始计算: " + args[0] + " " +getOperator(joinPoint.getSignature().getName()) + " " + args[1]);}@AfterReturning(pointcut = "execution(* Calculator.*(..))", returning = "result")public void afterCalculation(JoinPoint joinPoint, Object result) {System.out.println("计算结果: " + result);}private String getOperator(String methodName) {switch (methodName) {case "add": return "+";case "subtract": return "-";case "multiply": return "*";case "divide": return "/";default: return "";}}
}

通过 AOP,我们实现了:

  • 核心业务逻辑与横切关注点的完全分离
  • 消除了重复代码
  • 可以轻松扩展和修改日志记录逻辑,而不影响业务代码

1.3 AOP 工作原理直观理解

使用 AOP 时,Spring 框架会在运行时动态地将我们的通知(如日志逻辑)织入到目标方法的执行过程中:

当我们调用calculator.add(1, 2)时,实际执行过程是:

  1. 执行@Before通知,记录"开始计算: 1 + 2"
  2. 执行原始方法add(1, 2),计算 1+2=3
  3. 执行@AfterReturning通知,记录"计算结果: 3"

通过这种方式,AOP 允许我们在不修改原始代码的情况下,对现有方法的行为进行增强。

二、Spring AOP 核心概念

2.1 AOP 关键术语

  • 切面(Aspect):横切关注点的模块化,如日志、事务等功能
  • 连接点(Join Point):程序执行过程中的某个特定点,如方法执行
  • 通知(Advice):切面在特定连接点执行的动作,如前置通知、后置通知等
  • 切点(Pointcut):指定通知应用的连接点集合,通常通过表达式定义
  • 目标对象(Target Object):被通知的对象,如我们的 Calculator 类
  • AOP 代理(AOP Proxy):AOP 框架创建的对象,实现了目标对象的增强
  • 织入(Weaving):将切面应用到目标对象并创建代理的过程

2.2 Spring AOP 提供的通知类型

  1. 前置通知(@Before):在目标方法执行前执行
  2. 后置通知(@After):在目标方法执行后执行(无论成功还是异常)
  3. 返回通知(@AfterReturning):在目标方法成功执行后执行
  4. 异常通知(@AfterThrowing):在目标方法抛出异常时执行
  5. 环绕通知(@Around):可以在目标方法执行前后自定义行为,甚至完全控制方法的执行

2.3 计算器示例的完整 AOP 实现

// Spring配置类
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {@Beanpublic Calculator calculator() {return new Calculator();}@Beanpublic LoggingAspect loggingAspect() {return new LoggingAspect();}
}// 切面实现
@Aspect
public class LoggingAspect {// 定义一个切点,匹配Calculator类的所有方法@Pointcut("execution(* Calculator.*(..))")public void calculatorOperation() {}// 前置通知@Before("calculatorOperation()")public void beforeCalculation(JoinPoint joinPoint) {// 记录方法执行前的日志Object[] args = joinPoint.getArgs();String methodName = joinPoint.getSignature().getName();System.out.println("开始计算: " + args[0] + " " +getOperator(methodName) + " " + args[1]);}// 返回通知@AfterReturning(pointcut = "calculatorOperation()", returning = "result")public void afterCalculation(JoinPoint joinPoint, Object result) {// 记录方法执行结果的日志System.out.println("计算结果: " + result);}// 异常通知@AfterThrowing(pointcut = "calculatorOperation()", throwing = "ex")public void calculationError(JoinPoint joinPoint, Exception ex) {System.out.println("计算出错: " + ex.getMessage());}private String getOperator(String methodName) {switch (methodName) {case "add": return "+";case "subtract": return "-";case "multiply": return "*";case "divide": return "/";default: return "";}}
}// 测试类
public class Main {public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);Calculator calculator = context.getBean(Calculator.class);System.out.println("---加法运算---");calculator.add(5, 3);System.out.println("---减法运算---");calculator.subtract(8, 3);System.out.println("---乘法运算---");calculator.multiply(4, 2);System.out.println("---除法运算---");calculator.divide(10, 2);System.out.println("---异常测试---");try {calculator.divide(5, 0);} catch (Exception e) {// 异常已在切面中处理}}
}

执行结果:

---加法运算---
开始计算: 5 + 3
计算结果: 8
---减法运算---
开始计算: 8 - 3
计算结果: 5
---乘法运算---
开始计算: 4 * 2
计算结果: 8
---除法运算---
开始计算: 10 / 2
计算结果: 5
---异常测试---
开始计算: 5 / 0
计算出错: / by zero

三、Spring AOP 实现原理

Spring AOP 的实现基于代理模式,根据目标对象是否实现接口,Spring 会选择不同的代理实现方式。

3.1 两种代理方式

  1. JDK 动态代理

    • 目标类必须实现接口
    • 代理类和目标类实现相同接口
    • 通过接口方法调用目标类方法
  2. CGLIB 代理

    • 适用于目标类没有实现接口的情况
    • 代理类是目标类的子类
    • 通过继承和重写方法来实现增强

3.2 JDK 动态代理实现原理

JDK 动态代理通过java.lang.reflect.Proxy类和InvocationHandler接口实现:

// 计算器接口
public interface ICalculator {int add(int a, int b);int subtract(int a, int b);int multiply(int a, int b);int divide(int a, int b);
}// 计算器实现
public class CalculatorImpl implements ICalculator {@Overridepublic int add(int a, int b) {return a + b;}// 其他方法实现...
}// 代理工厂
public class JdkProxyCalculator {private ICalculator target;public JdkProxyCalculator(ICalculator target) {this.target = target;}public ICalculator getProxy() {// 获取类加载器ClassLoader classLoader = target.getClass().getClassLoader();// 获取接口数组Class<?>[] interfaces = target.getClass().getInterfaces();// 创建调用处理器InvocationHandler handler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("开始计算: " + args[0] + " " + getOperator(method.getName()) + " " + args[1]);Object result = null;try {// 调用目标方法result = method.invoke(target, args);System.out.println("计算结果: " + result);} catch (Exception e) {System.out.println("计算出错: " + e.getMessage());throw e;}return result;}private String getOperator(String methodName) {// 与前面相同...}};// 创建代理实例return (ICalculator) Proxy.newProxyInstance(classLoader, interfaces, handler);}
}// 使用示例
public class Main {public static void main(String[] args) {ICalculator target = new CalculatorImpl();ICalculator proxy = new JdkProxyCalculator(target).getProxy();proxy.add(5, 3);}
}

3.3 CGLIB 代理实现原理

CGLIB(Code Generation Library)通过继承目标类并重写方法来实现代理:

// 计算器类(无接口)
public class Calculator {public int add(int a, int b) {return a + b;}// 其他方法...
}// CGLIB代理
public class CglibProxyCalculator implements MethodInterceptor {private Calculator target;public CglibProxyCalculator(Calculator target) {this.target = target;}public Calculator getProxy() {// 创建增强器Enhancer enhancer = new Enhancer();// 设置父类enhancer.setSuperclass(target.getClass());// 设置回调enhancer.setCallback(this);// 创建代理对象return (Calculator) enhancer.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("开始计算: " + args[0] + " " + getOperator(method.getName()) + " " + args[1]);Object result = null;try {// 调用目标方法result = proxy.invoke(target, args);System.out.println("计算结果: " + result);} catch (Exception e) {System.out.println("计算出错: " + e.getMessage());throw e;}return result;}private String getOperator(String methodName) {// 与前面相同...}
}// 使用示例
public class Main {public static void main(String[] args) {Calculator target = new Calculator();Calculator proxy = new CglibProxyCalculator(target).getProxy();proxy.add(5, 3);}
}

3.4 Spring AOP 代理创建流程

Spring AOP 代理创建的主要流程:

  1. 配置解析:解析@Aspect注解和切面定义
  2. 找出通知方法:解析切面类中的通知方法
  3. 创建代理工厂:根据切面信息创建 AOP 代理工厂
  4. 获取增强器:将通知方法转换为 Advisor 增强器
  5. 创建 AOP 代理:根据目标类是否实现接口选择 JDK 动态代理或 CGLIB 代理
  6. 代理方法调用:当调用目标方法时,代理会执行相应的通知方法

四、Spring AOP 源码分析

4.1 AOP 核心组件

Spring AOP 的关键组件及其作用:

组件作用
ProxyFactory代理工厂,用于创建 AOP 代理
AdvisorAdapter适配器,将通知转换为 Advisor 增强器
PointcutAdvisor切点增强器,持有切点和通知
MethodInterceptor方法拦截器,织入通知逻辑
AopProxy代理接口,有 JdkDynamicAopProxy 和 CglibAopProxy 两种实现

4.2 创建代理的关键源码

Spring AOP 创建代理的核心源码在DefaultAopProxyFactory类中:

// DefaultAopProxyFactory类的createAopProxy方法
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {// 判断是否满足以下任一条件:// 1. 优化标志设置为true// 2. 代理目标类标志设置为true// 3. 目标类没有实现接口,或者只实现了SpringProxy接口if (config.isOptimize() || config.isProxyTargetClass() ||hasNoUserSuppliedProxyInterfaces(config)) {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");}// 如果目标类是接口或者是Proxy的子类,使用JDK动态代理if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {return new JdkDynamicAopProxy(config);}// 否则使用CGLIB代理return new ObjenesisCglibAopProxy(config);} else {// 默认使用JDK动态代理return new JdkDynamicAopProxy(config);}
}

4.3 JDK 动态代理的实现

JdkDynamicAopProxy类的关键源码:

// JdkDynamicAopProxy类的invoke方法(简化版)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MethodInvocation invocation;Object oldProxy = null;boolean setProxyContext = false;// 获取目标对象TargetSource targetSource = this.advised.getTargetSource();Object target = null;try {// 处理Object类的方法,如equals()、hashCode()等if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {// 返回代理配置的服务方法return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);}// 获取适用于当前方法的拦截器链List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);// 如果没有拦截器,直接调用目标方法if (chain.isEmpty()) {Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);return AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);}// 创建方法调用对象invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);// 执行拦截器链return invocation.proceed();}finally {// 清理资源}
}

4.4 CGLIB 代理的实现

CglibAopProxy类的关键源码:

// CglibMethodInvocation类的intercept方法(简化版)
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;Object target = null;try {// 处理Object类的方法if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {// 返回代理配置的服务方法return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);}// 获取适用于当前方法的拦截器链List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);// 如果没有拦截器,直接调用目标方法if (chain.isEmpty()) {Object retVal = methodProxy.invoke(target, args);return retVal;}// 创建方法调用对象CglibMethodInvocation invocation = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy);// 执行拦截器链return invocation.proceed();}finally {// 清理资源}
}

4.5 拦截器链的执行过程

ReflectiveMethodInvocation类的关键源码:

// ReflectiveMethodInvocation类的proceed方法
public Object proceed() throws Throwable {// 当所有拦截器都执行完毕后,调用目标方法if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {return invokeJoinpoint();}// 获取下一个拦截器Object interceptorOrInterceptionAdvice =this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);// 执行拦截器if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {// 动态匹配拦截器InterceptorAndDynamicMethodMatcher dm =(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {return dm.interceptor.invoke(this);}else {// 不匹配,跳过此拦截器return proceed();}}else {// 普通拦截器,直接调用return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);}
}

五、Spring AOP 常见应用场景

5.1 事务管理

使用@Transactional注解实现声明式事务管理:

@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Transactionalpublic void updateUser(User user) {userRepository.save(user);// 如果后续发生异常,事务会自动回滚}
}

5.2 日志记录

使用 AOP 实现统一的日志记录:

@Aspect
@Component
public class LoggingAspect {private final Logger logger = LoggerFactory.getLogger(this.getClass());@Around("execution(* com.example.service.*.*(..))")public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {long start = System.currentTimeMillis();String methodName = joinPoint.getSignature().getName();logger.info("开始执行方法: {}", methodName);Object result = joinPoint.proceed();long executionTime = System.currentTimeMillis() - start;logger.info("方法: {} 执行完成,耗时: {}ms", methodName, executionTime);return result;}
}

5.3 权限控制

使用 AOP 实现方法级别的权限控制:

@Aspect
@Component
public class SecurityAspect {@Autowiredprivate SecurityService securityService;@Before("@annotation(requiresAdmin)")public void checkAdminAccess(JoinPoint joinPoint, RequiresAdmin requiresAdmin) {if (!securityService.isCurrentUserAdmin()) {throw new AccessDeniedException("需要管理员权限");}}
}// 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiresAdmin {
}// 使用示例
@Service
public class AdminService {@RequiresAdminpublic void deleteUser(Long userId) {// 只有管理员才能执行的操作}
}

5.4 缓存管理

使用 AOP 实现方法缓存:

@Aspect
@Component
public class CachingAspect {private Map<String, Object> cache = new ConcurrentHashMap<>();@Around("@annotation(cacheable)")public Object cacheMethod(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {String methodName = joinPoint.getSignature().getName();String cacheKey = methodName + Arrays.toString(joinPoint.getArgs());if (cache.containsKey(cacheKey)) {return cache.get(cacheKey);}Object result = joinPoint.proceed();cache.put(cacheKey, result);return result;}
}// 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Cacheable {
}// 使用示例
@Service
public class ProductService {@Cacheablepublic Product getProductById(Long id) {// 耗时的数据库查询操作return productRepository.findById(id);}
}

六、参考资料

  • Spring AOP 源码分析(一):整体设计
  • Spring AOP 源码分析(二):创建代理对象
  • Spring AOP 源码分析(三):拦截器链的执行
  • Spring AOP 源码分析(四):融合设计模式
  • Spring 官方文档

// 使用示例
@Service
public class ProductService {
@Cacheable
public Product getProductById(Long id) {
// 耗时的数据库查询操作
return productRepository.findById(id);
}
}


## 六、参考资料- [Spring AOP 源码分析(一):整体设计](https://www.pdai.tech/md/spring/spring-x-framework-aop-source-1.html)
- [Spring AOP 源码分析(二):创建代理对象](https://www.pdai.tech/md/spring/spring-x-framework-aop-source-2.html)
- [Spring AOP 源码分析(三):拦截器链的执行](https://www.pdai.tech/md/spring/spring-x-framework-aop-source-3.html)
- [Spring AOP 源码分析(四):融合设计模式](https://www.pdai.tech/md/spring/spring-x-framework-aop-source-4.html)
- [Spring 官方文档](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop)

相关文章:

  • MDG 实现后端主数据变更后快照自动刷新的相关设置
  • 【k8s系列4】工具介绍
  • 【网工第6版】第3章 局域网②
  • 天梯赛DP汇总
  • 前端资源加载失败后重试加载(CSS,JS等引用资源)
  • Linux 内核开发/测试工具对比 Windows 驱动验证工具 (Driver Verifier)
  • 【CPP】死锁产生、排查、避免
  • leetcode0146. LRU 缓存-medium
  • 服务器的算力已经被被人占用了,我如何能“无缝衔接”?
  • Kaamel隐私与安全分析报告:Apple Intelligence隐私保护机制
  • 使用Hypre BoomerAMG求解大规模泊松方程示例
  • 图像预处理-图像噪点消除
  • 星智CASE|拆解行业工具类智能体搭建方法论!
  • 12.FFN基于位置的前馈网络
  • 中华传承-医山命相卜-易经
  • Linux MySQL版本升级(rpm安装方式)
  • 嵌入式开发--STM32G4系列硬件CRC支持MODBUS和CRC32
  • mybatisFlex各种链式sql写法
  • 深度比较Gemini 2.5两款最新模型差异
  • Python基础知识语法归纳总结(数据类型-1)
  • 贵州赤水“整改复耕”:为何竹林砍了,地却荒了?
  • 延安市委副书记马月逢已任榆林市委副书记、市政府党组书记
  • 花卉引流+商场促销,上海浦东用“花经济”带动“消费热”
  • 对话地铁读书人|来自法学副教授的科普:读书日也是版权日
  • 世卫成员国就《大流行病协议》达成一致,首次演练应对气候诱发的病毒危机
  • 牛市早报|李强:在一些关键的时间窗口,推动各方面政策措施早出手、快出手