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执行流程

  1. 解析配置:Spring容器启动时,解析AOP相关配置(如@Aspect@Before等注解或XML配置),识别切面、通知和切入点。
  2. 创建代理:对符合切入点匹配的目标类,Spring自动为其创建代理对象(JDK或CGLIB代理)。
  3. 拦截调用:当调用目标方法时,实际执行的是代理对象的方法。
  4. 执行通知:代理对象在目标方法执行前后(或异常时)插入通知逻辑。
  5. 执行目标方法:通知逻辑执行完毕后,代理对象调用原始目标类的方法。

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通过动态代理技术,在不侵入业务代码的前提下,实现了横切关注点的模块化,降低了代码耦合度。其核心是通过代理对象拦截目标方法调用,并在调用过程中织入通知逻辑,从而实现日志记录、事务管理等通用功能的复用。

posted @ 2025-10-15 16:49  向着朝阳  阅读(30)  评论(0)    收藏  举报