Spring AOP原理和实现方式
目录
Spring AOP(面向切面编程)是Spring框架的核心特性之一,它基于动态代理和字节码增强技术实现,能够在不修改原有代码的情况下,为程序添加横切关注点(如日志、事务、安全等)。其核心原理可以从以下几个方面理解:
原理
1. AOP核心概念
- 切面(Aspect):封装横切关注点的类,包含通知和切入点。
- 通知(Advice):切面的具体实现(如前置通知、后置通知等)。
- 切入点(Pointcut):定义通知作用的目标方法(通过表达式匹配)。
- 连接点(JoinPoint):程序执行过程中可插入切面的点(如方法调用、异常抛出等)。
- 代理(Proxy):AOP通过代理对象执行目标方法,并在执行前后插入通知逻辑。
2. 动态代理机制
Spring AOP的核心实现依赖动态代理,根据目标类是否实现接口,自动选择两种代理方式:
(1)JDK动态代理
- 适用场景:目标类实现了接口。
- 原理:通过
java.lang.reflect.Proxy
类在运行时动态生成代理类,代理类实现目标接口,并在接口方法中嵌入通知逻辑。 - 特点:只代理接口中的方法,不代理类中的非接口方法。
// JDK动态代理示例(简化版)
public class JdkProxy implements InvocationHandler {
private Object target; // 目标对象
public JdkProxy(Object target) {
this.target = target;
}
// 生成代理对象
public Object getProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
// 代理逻辑(调用目标方法时执行)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置通知(如日志)
System.out.println("方法执行前...");
// 执行目标方法
Object result = method.invoke(target, args);
// 后置通知
System.out.println("方法执行后...");
return result;
}
}
(2)CGLIB动态代理
- 适用场景:目标类未实现接口。
- 原理:通过CGLIB(Code Generation Library)在运行时动态生成目标类的子类,并重写目标方法,在子类中嵌入通知逻辑。
- 特点:可代理类中的所有方法(需注意final方法无法被重写,因此不能被代理)。
3. AOP执行流程
- 解析配置:Spring容器启动时,解析AOP相关配置(如
@Aspect
、@Before
等注解或XML配置),识别切面、通知和切入点。 - 创建代理:对符合切入点匹配的目标类,Spring自动为其创建代理对象(JDK或CGLIB代理)。
- 拦截调用:当调用目标方法时,实际执行的是代理对象的方法。
- 执行通知:代理对象在目标方法执行前后(或异常时)插入通知逻辑。
- 执行目标方法:通知逻辑执行完毕后,代理对象调用原始目标类的方法。
4. 与AspectJ的关系
- Spring AOP使用了AspectJ的切入点表达式语法(如
execution(* com.example.service.*.*(..))
),但实现原理不同。 - AspectJ是基于编译期或类加载期的字节码增强,而Spring AOP是基于运行时的动态代理,更轻量且与Spring容器深度集成。
实现方案1:实现切面类(LogAspect)
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
// 声明这是一个切面类
@Aspect
// 让Spring容器管理这个切面
@Component
public class LogAspect {
// 定义切入点:匹配com.example.service包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethodPointcut() {}
// 前置通知:在目标方法执行前执行
@Before("serviceMethodPointcut()")
public void beforeAdvice(JoinPoint joinPoint) {
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("【前置通知】" + className + "." + methodName + " 方法开始执行,参数:" + Arrays.toString(args));
}
// 后置通知:在目标方法执行后执行(无论是否发生异常)
@After("serviceMethodPointcut()")
public void afterAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("【后置通知】" + methodName + " 方法执行结束");
}
// 返回通知:在目标方法正常返回后执行
@AfterReturning(pointcut = "serviceMethodPointcut()", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("【返回通知】" + methodName + " 方法返回结果:" + result);
}
// 异常通知:在目标方法抛出异常时执行
@AfterThrowing(pointcut = "serviceMethodPointcut()", throwing = "ex")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
System.out.println("【异常通知】" + methodName + " 方法抛出异常:" + ex.getMessage());
}
// 环绕通知:包围目标方法执行,可在方法执行前后、异常时执行操作
@Around("serviceMethodPointcut()")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
String methodName = proceedingJoinPoint.getSignature().getName();
long startTime = System.currentTimeMillis();
try {
// 执行目标方法
Object result = proceedingJoinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println("【环绕通知】" + methodName + " 方法执行耗时:" + (endTime - startTime) + "ms");
return result;
} catch (Exception e) {
System.out.println("【环绕通知】" + methodName + " 方法执行异常:" + e.getMessage());
throw e; // 继续抛出异常,让其他异常通知可以捕获
}
}
}
实现方案2:更灵活,代码可读性更好,通过自定义注解+ MethodInterceptor
public class LoggingInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 1. 方法执行前逻辑
System.out.println("调用方法: " + invocation.getMethod().getName());
long start = System.currentTimeMillis();
try {
// 2. 执行目标方法(或下一个拦截器)
Object result = invocation.proceed();
// 3. 方法执行后逻辑(正常返回时)
System.out.println("方法执行耗时: " + (System.currentTimeMillis() - start) + "ms");
return result;
} catch (Exception e) {
// 4. 异常处理逻辑
System.out.println("方法执行异常: " + e.getMessage());
throw e; // 可以选择抛出或处理异常
}
}
}
总结
Spring AOP通过动态代理技术,在不侵入业务代码的前提下,实现了横切关注点的模块化,降低了代码耦合度。其核心是通过代理对象拦截目标方法调用,并在调用过程中织入通知逻辑,从而实现日志记录、事务管理等通用功能的复用。