深入理解 Spring AOP:JDK 动态代理实现原理剖析

一、Spring AOP 是如何运作的?

Spring AOP(Aspect-Oriented Programming)是一种通过“横切逻辑”(如日志、安全、事务等)增强业务逻辑的技术。Spring 默认使用 JDK 动态代理CGLIB 来生成代理对象,并通过这些代理对象来织入增强逻辑。

JDK 动态代理的前提是目标类必须实现接口,否则使用 CGLIB。

演示代码:

service层:

@Service
public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello, " + name);
    }
}

controller层:

@RestController
public class TestController {

    @Autowired
    private HelloService helloService;

    @GetMapping("/hello")
    public String hello(@RequestParam String name) {
        helloService.sayHello(name);
        return "OK";
    }
}

切面类:

@Aspect
@Component
public class LogAspect {

    // 切点表达式:匹配 service 包下所有类的所有方法
    // 环绕通知:方法调用前后都能增强
    @Around("execution(* com.saul.service..*(..))")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取当前方法的签名信息,用于日志记录
        String method = joinPoint.getSignature().toShortString();
       // 记录方法开始执行的时间,用于计算执行耗时
        long start = System.currentTimeMillis();
        System.out.println("【AOP日志】开始调用方法:" + method);
        Object result = joinPoint.proceed(); // 执行原方法
        // 记录方法执行结束的时间,用于计算执行耗时
        long end = System.currentTimeMillis();

        System.out.println("【AOP日志】方法执行完毕:" + method + ",耗时:" + (end - start) + "ms");
        return result;
    }
}

在这段代码中,HelloServiceImpl implements HelloService,符合 JDK 动态代理的条件,所以 Spring AOP 会使用 java.lang.reflect.Proxy 来生成代理对象。


二、Spring AOP 执行流程详解(以 JDK 动态代理为例)

我们以以下切面代码为例,深入理解 Spring AOP 的完整执行流程:

@Aspect
@Component
public class LogAspect {

    @Around("execution(* com.saul.service..*(..))")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        String method = joinPoint.getSignature().toShortString();
        long start = System.currentTimeMillis();
        System.out.println("【AOP日志】开始调用方法:" + method);
        
        Object result = joinPoint.proceed(); // 执行目标方法
        
        long end = System.currentTimeMillis();
        System.out.println("【AOP日志】方法执行完毕:" + method + ",耗时:" + (end - start) + "ms");
        return result;
    }
}

步骤一:Spring 启动阶段,注册 AOP 代理机制

Spring 在启动过程中,会扫描到标注了 @Aspect 的切面类(如 LogAspect),并完成以下工作:

  • 注册 AOP 自动代理创建器(如 AnnotationAwareAspectJAutoProxyCreator)作为 BeanPostProcessor

  • 将切面类中带有增强注解(如 @Around)的方法封装为 Advisor(增强器);

  • 将这些增强器最终转化为 MethodInterceptor,并保存起来。

此时,Spring 会对所有 Bean 进行判断,一旦发现某个 Bean(如 HelloServiceImpl)的方法匹配切点表达式( @Around("execution(* com.saul.service..*(..))")),就会为它生成代理对象(使用 JDK 动态代理或 CGLIB)。

@Autowired
private HelloService helloService; // 注入的是代理对象,不是原始对象

步骤二:用户请求触发方法调用

用户访问 /hello?name=xxx

@GetMapping("/hello")
public String hello(@RequestParam String name) {
    helloService.sayHello(name); // 实际调用的是代理对象的方法
    return "OK";
}

此时,helloService 是一个 JDK 动态代理对象,调用方法会进入其内部的 InvocationHandler

步骤三:JDK 动态代理对象拦截方法调用

Spring 生成的代理对象内部结构可简化理解为:

HelloService proxy = (HelloService) Proxy.newProxyInstance(
    HelloService.class.getClassLoader(), //类加载器
    new Class[]{HelloService.class}, //代理对象需要实现的接口,和被代理对象保持一致
    new InvocationHandler() {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // ① 查找与方法匹配的增强器(Advisor)
            // ② 将增强器转化为拦截器链(List)
            // ③ 触发执行:ReflectiveMethodInvocation.proceed()

            return interceptorChain.proceed(); // 启动链式调用
        }
    });

步骤四:执行拦截器链,触发切面逻辑

当执行 interceptorChain.proceed() 时,会依次执行链中的所有增强方法(即切面逻辑)。每个拦截器一般结构如下:

public Object invoke(MethodInvocation invocation) throws Throwable {
    // 前置逻辑
    Object result = invocation.proceed(); // 调用下一个拦截器或目标方法
    // 后置逻辑
    return result;
}

如果方法匹配 @Around("execution(* com.saul.service..*(..))"),就会进入 logExecutionTime() 方法。

logExecutionTime 中再次调用 joinPoint.proceed(),这就是执行目标方法(即原始 sayHello())的入口。

步骤五:调用目标方法 + 收尾逻辑

Object result = joinPoint.proceed(); // 执行目标方法

这一行才真正进入 HelloServiceImpl.sayHello(),完成业务逻辑。

执行完毕后,切面方法会记录日志、统计耗时等,然后将结果返回,完成整个调用流程。

可视化流程总结:

阶段 执行环节 说明
1️⃣ 请求发起 用户访问 /hello 浏览器发送请求到控制器
2️⃣ 控制器调用 TestController.hello()helloService.sayHello() 实际调用的是 JDK 动态代理对象的方法
3️⃣ 方法拦截 JDK 动态代理对象执行 InvocationHandler.invoke() 进入代理逻辑,准备增强处理
4️⃣ 构建增强链 查找匹配的切面表达式,如 @Around("execution(...)") 匹配到切面方法,封装成 MethodInterceptor
5️⃣ 执行切面 logExecutionTime(joinPoint) 方法执行 记录开始时间、打印前置日志
6️⃣ 调用原方法 joinPoint.proceed() 实际调用目标方法,如 HelloServiceImpl.sayHello()
7️⃣ 后置增强 logExecutionTime() 后续逻辑 打印耗时日志、收尾工作
8️⃣ 返回结果 返回 sayHello() 结果 → 控制器返回响应 响应回客户端,流程结束

 三、深入解析:JDK 动态代理原理

JDK Proxy 的核心类

java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler

当你调用某个代理对象的方法时,其实是调用了:

InvocationHandler.invoke(proxy, method, args)

Spring 中的 AOP 代理类实现了 InvocationHandler 接口。这个实现内部会:

  1. 找到与当前方法匹配的增强(Advice)

  2. 将这些增强按顺序封装成 Interceptor Chain(拦截器链)

  3. 使用 ReflectiveMethodInvocation 对象执行这些增强,并最终调用目标方法


四、实际案例分析

切面:

@Around("execution(* com.saul.service..*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint)

Spring 会解析这个表达式,匹配 HelloServiceImpl.sayHello(),并将其织入 logExecutionTime() 中,组成增强链,最终通过动态代理执行。


 五、验证代理对象类型(可选测试)

你可以在 TestController 中打印出 helloService 的真实类型:

System.out.println(helloService.getClass());

你会看到类似这样的输出:

class com.sun.proxy.$Proxy23

这表示使用的是 JDK 动态代理(如果是 CGLIB 会看到 HelloServiceImpl$$EnhancerBySpringCGLIB


 六、总结执行流程

请求 /hello
   ↓
TestController.hello(name)
   ↓
helloService.sayHello(name)
   ↓
JDK 代理对象 intercept
   ↓
匹配切面表达式?
   ↓
Yes → 执行切面逻辑(如 @Around)
   ↓
调用目标方法 sayHello
   ↓
执行完成 → 后置增强 → 返回结果

七、执行结果:

前端发送请求参数:

深入理解 Spring AOP:JDK 动态代理实现原理剖析_第1张图片

执行结果:

深入理解 Spring AOP:JDK 动态代理实现原理剖析_第2张图片 

你可能感兴趣的:(spring,java,spring,后端)