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)
时,实际执行过程是:
- 执行
@Before
通知,记录"开始计算: 1 + 2" - 执行原始方法
add(1, 2)
,计算 1+2=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 提供的通知类型
- 前置通知(@Before):在目标方法执行前执行
- 后置通知(@After):在目标方法执行后执行(无论成功还是异常)
- 返回通知(@AfterReturning):在目标方法成功执行后执行
- 异常通知(@AfterThrowing):在目标方法抛出异常时执行
- 环绕通知(@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 两种代理方式
-
JDK 动态代理:
- 目标类必须实现接口
- 代理类和目标类实现相同接口
- 通过接口方法调用目标类方法
-
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 代理创建的主要流程:
- 配置解析:解析
@Aspect
注解和切面定义 - 找出通知方法:解析切面类中的通知方法
- 创建代理工厂:根据切面信息创建 AOP 代理工厂
- 获取增强器:将通知方法转换为 Advisor 增强器
- 创建 AOP 代理:根据目标类是否实现接口选择 JDK 动态代理或 CGLIB 代理
- 代理方法调用:当调用目标方法时,代理会执行相应的通知方法
四、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)