零基础入门:动态代理与 Spring AOP 核心知识点总结


一、什么是代理模式?

在深入动态代理之前,我们需要先理解代理模式(Proxy Pattern)的基本思想。

代理模式是为其他对象提供一种代理以控制对这个对象的访问。简单来说,我们使用代理对象来代替对真实对象的访问,这样就可以在不修改原目标对象的前提下,扩展目标对象的功能。

举个例子:你想租房子,不会直接找房东,而是找中介。中介帮你找房、谈价格,还会额外帮你验房、签合同,你只需要最终和房东签合同就行。在这个场景中:

  • 房东 = 目标对象(Real Object)
  • 中介 = 代理对象(Proxy)
  • 租房行为 = 需要被增强的方法

代理模式的核心价值有两个:

  1. 控制访问:不让调用者直接接触目标对象,由代理统一管理
  2. 增强功能:在不修改目标对象代码的前提下,给方法加前置/后置逻辑

代理模式分为静态代理动态代理两种实现方式。


二、静态代理:一个"写死"的中间人

静态代理需要提前编写一个代理类,它实现与目标对象相同的接口,并在方法中调用目标对象的方法,同时添加额外逻辑。

复制代码
// 1. 定义接口
public interface RentHouse {
    void rent();
}

// 2. 目标对象(房东)
public class Landlord implements RentHouse {
    @Override
    public void rent() {
        System.out.println("房东:房子租给你");
    }
}

// 3. 代理对象(中介)
public class HouseProxy implements RentHouse {
    private RentHouse landlord;

    public HouseProxy(RentHouse landlord) {
        this.landlord = landlord;
    }

    @Override
    public void rent() {
        // 前置增强
        System.out.println("中介:帮你验房,确认水电没问题");
        // 调用目标方法
        landlord.rent();
        // 后置增强
        System.out.println("中介:帮你签租房合同");
    }
}

静态代理的致命缺陷:一个接口对应一个代理类,接口方法一变,代理类也要改。当需要代理的对象数量增多时,代理类会爆炸式增长,代码重复、维护成本高。正因为如此,日常开发中几乎看不到使用静态代理的场景。


三、动态代理:运行时生成的"影分身"

动态代理在程序运行时自动生成代理类,无需手动编写。它真正实现了"一次编写,处处生效"。

想象一下,明星(真实对象)很忙,不能亲自处理所有事。于是他找了个经纪人(代理),对外说:"有事找我!"粉丝以为在跟明星打交道,其实是经纪人在接电话、谈合同、安排行程。

Java 中有两种主流动态代理方式:

3.1 JDK 动态代理(基于接口)

JDK 动态代理是Java原生支持的代理方式,核心三件套:

  • 接口:规定代理对象的行为

  • 真实类:实现接口的目标对象

  • InvocationHandler:定义代理逻辑的处理者

    // 1. 定义接口
    public interface UserService {
    void saveUser(String name);
    String getUser(int id);
    }

    // 2. 目标类实现接口
    public class UserServiceImpl implements UserService {
    @Override
    public void saveUser(String name) {
    System.out.println("正在保存用户:" + name);
    }

    复制代码
      @Override
      public String getUser(int id) {
          System.out.println("正在查询用户:" + id);
          return "用户" + id;
      }

    }

    // 3. InvocationHandler 拦截所有方法调用
    public class LoggingHandler implements InvocationHandler {
    private Object target;

    复制代码
      public LoggingHandler(Object target) {
          this.target = target;
      }
    
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          // 前置增强
          System.out.println("【代理拦截】开始执行:" + method.getName());
          long start = System.currentTimeMillis();
    
          // 反射调用真实方法
          Object result = method.invoke(target, args);
    
          // 后置增强
          long cost = System.currentTimeMillis() - start;
          System.out.println("【代理拦截】执行完成,耗时:" + cost + "ms");
          return result;
      }

    }

    // 4. 创建代理对象
    UserService target = new UserServiceImpl();
    UserService proxy = (UserService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new LoggingHandler(target)
    );
    proxy.saveUser("张三"); // 自动被拦截增强

关键限制 :JDK 动态代理只能代理实现了接口的类。原因是代理对象需要实现与目标对象相同的接口,而 Java 不支持多继承。

3.2 CGLIB 动态代理(基于继承)

CGLIB(Code Generation Library)是一个高性能的字节码生成库,它通过继承目标类来创建代理,无需目标类实现接口。

复制代码
// 1. 目标类(可以不实现接口)
public class ProductService {
    public void addProduct(String name) {
        System.out.println("添加商品:" + name);
    }
}

// 2. MethodInterceptor 定义拦截逻辑
public class LoggingInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, 
                            MethodProxy proxy) throws Throwable {
        System.out.println("【CGLIB拦截】开始执行:" + method.getName());
        long start = System.currentTimeMillis();

        // 调用父类方法
        Object result = proxy.invokeSuper(obj, args);

        long cost = System.currentTimeMillis() - start;
        System.out.println("【CGLIB拦截】执行完成,耗时:" + cost + "ms");
        return result;
    }
}

// 3. 创建代理对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ProductService.class);
enhancer.setCallback(new LoggingInterceptor());
ProductService proxy = (ProductService) enhancer.create();
proxy.addProduct("iPhone");  // 自动被拦截增强

CGLIB 通过字节码技术(ASM框架)为目标类创建子类,在子类中重写目标方法并在调用前后插入增强逻辑。

CGLIB 的限制

  • 无法代理 final 修饰的方法(无法被子类重写)
  • 创建代理对象的时间比 JDK 方式长,但运行性能更高

3.3 JDK 动态代理 vs CGLIB 对比

对比维度 JDK 动态代理 CGLIB 动态代理
实现原理 基于接口,通过反射调用目标方法 基于继承,通过字节码生成子类
前置条件 目标类必须实现至少一个接口 目标类无需实现接口
核心API Proxy + InvocationHandler Enhancer + MethodInterceptor
创建速度 慢(需要生成字节码)
运行性能 较慢(反射调用) 快(直接调用,约10倍性能差)
final 方法 可以代理(接口方法不能是 final) 无法代理
依赖 JDK 内置 第三方库

选择建议

  • 对于单例对象实例池中的对象,优先用 CGLIB(运行性能更好)
  • 对于频繁创建代理对象的场景,优先用 JDK 动态代理(创建速度更快)
  • Spring Boot 2.x 之后,默认使用 CGLIB 代理

四、AOP 核心概念详解

AOP(Aspect Oriented Programming,面向切面编程)是 Spring 核心两大思想之一(另一个是 IoC)。它允许在不修改原有业务代码的前提下,对方法进行增强,统一处理日志、事务、权限、监控等横切逻辑。

4.1 为什么需要 AOP?

在传统 OOP(面向对象编程)中,处理日志、权限、事务这类横切关注点(即多个模块都需要的功能,但与业务逻辑本身不直接相关)时,往往需要在每个方法中重复编写相同的代码,导致:

  • 代码重复率高(可达 60% 以上)
  • 维护成本极高(一个改动要改几十个地方)
  • 业务代码与通用逻辑耦合严重

AOP 正是为了解决这个问题而诞生------将这些重复逻辑抽离出来,做成一个"切面",自动织入到目标方法中。

4.2 AOP 核心概念(一图胜千言)

AOP 有 7 个核心概念,理解它们是掌握 AOP 的关键:

概念 英文 说明 生活类比
切面 Aspect 横切关注点的模块化,把通用功能抽离成一个类 小区的保安系统
连接点 Join Point 程序中所有可以被拦截的方法执行点 所有人进出大门的时刻
切入点 Pointcut 通过表达式匹配到的真正需要被增强的方法 只检查没有门禁卡的外卖员
通知/增强 Advice 在切入点执行的逻辑(前置/后置/环绕等) 登记信息、联系业主、决定是否放行
目标对象 Target 被代理的业务对象 小区里的住户
织入 Weaving 将切面应用到目标对象的过程 保安检查并放行的过程
引入 Introduction 动态为目标类添加方法或字段(不常用) ---

通俗理解:切面 = 切入点 + 通知。切面类里定义了什么方法需要被增强(切入点表达式),以及增强的具体逻辑是什么(通知方法)。

4.3 五种通知类型

通知类型 注解 执行时机
前置通知 @Before 目标方法执行前
后置通知 @After 目标方法执行后(无论是否异常)
返回通知 @AfterReturning 目标方法正常返回后
异常通知 @AfterThrowing 目标方法抛出异常后
环绕通知 @Around 包裹目标方法,可控制执行前后

其中 @Around 最强大,可以完全控制目标方法的执行流程,但需要手动调用 joinPoint.proceed() 来触发原始方法。

4.4 切入点表达式

最常用的 execution 表达式语法:

复制代码
execution(修饰符? 返回值类型 包名.类名.方法名(参数列表))

示例:

复制代码
// 匹配 service 包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")

// 匹配 Controller 包下所有 public 方法
@Pointcut("execution(public * com.example.controller.*.*(..))")

// 匹配带有 @Log 注解的方法
@Pointcut("@annotation(com.example.annotation.Log)")

五、动态代理与 AOP 的关系

一句话概括:AOP 是思想,动态代理是实现这个思想的底层技术。

5.1 AOP 底层原理就是动态代理

Spring AOP 的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。

Spring AOP 使用了两种动态代理技术:

  1. JDK 动态代理:当目标对象实现了接口时使用
  2. CGLIB 动态代理:当目标对象没有实现接口时使用

5.2 Spring 如何选择代理方式?

Spring 代理选择的默认逻辑:

复制代码
// Spring 内部 DefaultAopProxyFactory 的简化逻辑
public AopProxy createAopProxy(AdvisedSupport config) {
    if (config.isProxyTargetClass() || !hasInterfaces(targetClass)) {
        // 强制使用 CGLIB 或没有接口时
        return new CglibAopProxy(config);
    } else {
        // 有接口时默认使用 JDK 动态代理
        return new JdkDynamicAopProxy(config);
    }
}
  • Spring MVC:默认使用 JDK 动态代理,只有目标类没有实现接口时才使用 CGLIB
  • Spring Boot:2.x 版本后默认使用 CGLIB

注意@Transactional@Async@Cacheable 等注解也都属于 AOP 的一种应用。

5.3 从动态代理到 AOP 的层次关系

复制代码
┌─────────────────────────────────────────────┐
│              AOP(面向切面编程)               │
│    @Aspect、@Before、@Around、@Pointcut...  │
├─────────────────────────────────────────────┤
│            Spring AOP 框架层                  │
│    ProxyFactory、Advisor、AdvisedSupport    │
├─────────────────────────────────────────────┤
│              动态代理技术层                    │
│     JDK 动态代理(接口)  CGLIB(继承)         │
├─────────────────────────────────────────────┤
│                 Java 反射 / ASM               │
│                 底层字节码操作                  │
└─────────────────────────────────────────────┘
  • 动态代理负责在运行时生成代理对象,拦截方法调用
  • AOP 基于动态代理,将切面逻辑(通知)织入到目标方法的执行流程中
  • 你写的 @Aspect 注解,最终都会被 Spring 翻译成动态代理对象

六、常见问题与避坑指南

6.1 AOP 注解失效问题

当同一个类内部,一个方法(不带注解)调用另一个带 AOP 注解的方法时,AOP 注解会失效。

原因 :内部调用直接通过 this.method() 执行,绕过了代理对象。

解决方案

复制代码
// 1. 通过 @Autowired 注入自己(Spring 4.3+)
@Autowired
private UserService self;
public void methodA() {
    self.methodB();  // 走代理
}

// 2. 使用 AopContext.currentProxy()
((UserService) AopContext.currentProxy()).methodB();

// 3. 将 methodB 抽取到另一个 Bean 中

6.2 final 方法无法被代理

  • JDK 动态代理:可以代理 final 方法(因为接口方法不能是 final
  • CGLIB 动态代理:无法代理 final 方法(因为不能被子类重写)

建议在设计需要被增强的方法时,避免使用 final 修饰。

6.3 性能考量

  • CGLIB 创建代理对象的时间比 JDK 方式约多 8 倍,但运行性能高约 10 倍
  • 对于单例 Bean,CGLIB 更合适(一次创建,多次使用)
  • 对于频繁创建的场景,JDK 方式更合适

Spring Boot 2.x 之后默认使用 CGLIB,实际上对日常开发影响不大,无需刻意调整。


七、总结

核心知识点回顾

知识点 核心要点
代理模式 为对象提供替身,控制访问并增强功能
静态代理 提前编写代理类,灵活性和扩展性差
JDK 动态代理 基于接口 + 反射,要求目标类实现接口
CGLIB 动态代理 基于继承 + 字节码,目标类无需接口
AOP 概念 切面、连接点、切入点、通知、目标对象、织入
二者关系 动态代理是 AOP 的底层实现技术

一条清晰的理解链

代理模式动态代理 (解决静态代理"类爆炸"问题)→ AOP (基于动态代理的编程思想)→ Spring AOP(开箱即用的 AOP 框架)

相关推荐
算.子2 小时前
【Spring AI 实战】五、RAG 核心原理:为什么需要检索增强生成?
java·人工智能·spring
Java面试题总结2 小时前
Spring AI 核心架构、抽象模型与四大核心组件设计精髓
人工智能·spring·架构
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【20】MessagesAgentHook 、MessagesModelHook 相关实现类
java·人工智能·spring
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【15】工具执行拦截器(ToolInterceptor)
java·人工智能·spring
希望永不加班4 小时前
Spring AOP 核心概念:切面、通知、切点、织入
java·数据库·后端·mysql·spring
云烟成雨TD4 小时前
Spring AI Alibaba 1.x 系列【17】模型拦截器(ModelInterceptor)
java·人工智能·spring
Flittly4 小时前
【SpringSecurity新手村系列】(1)初识安全框架
java·spring boot·安全·spring·安全架构
Predestination王瀞潞4 小时前
Java EE3-我独自整合(第五章:Spring AOP 介绍与入门案例)
java·后端·spring·java-ee
それども4 小时前
Spring Boot 异常拦截处理机制
java·spring