Java 设计模式——代理模式:从静态代理到 Spring AOP 最优实现

Java 设计模式------代理模式:从静态代理到 Spring AOP 最优实现

代理模式是解决 "功能增强与业务解耦" 的核心设计模式,通过引入 "代理对象" 作为中间层,在不修改目标对象代码的前提下,为其附加日志记录、性能统计、事务管理等横切功能。本文将从原理拆解、3 种实战写法(静态代理、JDK 动态代理、CGLIB 动态代理)、Spring AOP 落地、场景对比四个维度,帮你掌握代理模式在实际项目中的灵活应用,尤其是在复杂业务中的解耦技巧。

文章目录

  • [Java 设计模式------代理模式:从静态代理到 Spring AOP 最优实现](#Java 设计模式——代理模式:从静态代理到 Spring AOP 最优实现)
    • [一、代理模式核心认知:看透 "中间层" 的价值](#一、代理模式核心认知:看透 “中间层” 的价值)
      • [1. 传统功能增强的痛点](#1. 传统功能增强的痛点)
      • [2. 代理模式的 "解耦" 逻辑](#2. 代理模式的 “解耦” 逻辑)
    • [二、3 种基础实战写法:从静态到动态](#二、3 种基础实战写法:从静态到动态)
      • [写法 1:静态代理(编译期生成代理类)](#写法 1:静态代理(编译期生成代理类))
        • 核心思路
        • 完整代码实现
          • [1. 定义抽象接口(统一行为)](#1. 定义抽象接口(统一行为))
          • [2. 实现目标对象(核心业务)](#2. 实现目标对象(核心业务))
          • [3. 实现代理对象(附加增强逻辑)](#3. 实现代理对象(附加增强逻辑))
          • [4. 测试类(客户端调用)](#4. 测试类(客户端调用))
          • [5. 测试结果(控制台输出)](#5. 测试结果(控制台输出))
        • 优缺点分析
        • 适用场景
      • [写法 2:JDK 动态代理(运行时生成代理类)](#写法 2:JDK 动态代理(运行时生成代理类))
        • 核心思路
        • 完整代码实现
          • [1. 复用抽象接口和目标对象(与静态代理一致)](#1. 复用抽象接口和目标对象(与静态代理一致))
          • [2. 实现调用处理器(增强逻辑封装)](#2. 实现调用处理器(增强逻辑封装))
          • [3. 实现代理工厂(动态生成代理对象)](#3. 实现代理工厂(动态生成代理对象))
          • [4. 测试结果(控制台输出)](#4. 测试结果(控制台输出))
        • 关键原理:动态生成的代理类
        • 优缺点分析
        • 适用场景
      • [写法 3:CGLIB 动态代理(运行时生成子类代理)](#写法 3:CGLIB 动态代理(运行时生成子类代理))
        • 核心思路
        • 完整代码实现
          • [1. 导入 CGLIB 依赖(Maven)](#1. 导入 CGLIB 依赖(Maven))
          • [2. 定义目标类(无需实现接口)](#2. 定义目标类(无需实现接口))
          • [3. 实现方法拦截器(增强逻辑封装)](#3. 实现方法拦截器(增强逻辑封装))
          • [4. 实现代理工厂(动态生成子类代理)](#4. 实现代理工厂(动态生成子类代理))
          • [5. 测试结果(控制台输出)](#5. 测试结果(控制台输出))
        • [关键原理:FastClass 机制](#关键原理:FastClass 机制)
        • 优缺点分析
        • 适用场景
    • [三、Spring AOP:代理模式的企业级落地](#三、Spring AOP:代理模式的企业级落地)
      • [1. Spring AOP 核心概念](#1. Spring AOP 核心概念)
      • [2. 实战:用 Spring AOP 实现性能统计](#2. 实战:用 Spring AOP 实现性能统计)
        • [步骤 1:导入 Spring AOP 依赖(Maven)](#步骤 1:导入 Spring AOP 依赖(Maven))
        • [步骤 2:定义目标服务类(业务逻辑)](#步骤 2:定义目标服务类(业务逻辑))
        • [步骤 3:定义切面类(增强逻辑)](#步骤 3:定义切面类(增强逻辑))
        • [步骤 4:定义 Spring Boot 启动类](#步骤 4:定义 Spring Boot 启动类)
        • [步骤 5:测试结果(控制台输出)](#步骤 5:测试结果(控制台输出))
      • [3. Spring AOP 的代理选择逻辑](#3. Spring AOP 的代理选择逻辑)
    • [四、关键对比:4 种代理方式的选择指南](#四、关键对比:4 种代理方式的选择指南)
      • [1. 代理方式核心差异对比](#1. 代理方式核心差异对比)
      • [2. 实际项目选择建议](#2. 实际项目选择建议)
    • [五、避坑指南:代理模式实战中的 5 个关键问题](#五、避坑指南:代理模式实战中的 5 个关键问题)
      • [1. 避免 "代理失效":Spring 环境下的常见陷阱](#1. 避免 “代理失效”:Spring 环境下的常见陷阱)
      • [2. 性能优化:减少代理带来的开销](#2. 性能优化:减少代理带来的开销)
      • [3. 异常处理:代理中的异常传递](#3. 异常处理:代理中的异常传递)
      • [4. 调试技巧:定位代理类逻辑](#4. 调试技巧:定位代理类逻辑)
      • [5. 安全性:避免代理滥用](#5. 安全性:避免代理滥用)
    • [六、总结:从 "理解" 到 "落地" 的代理模式](#六、总结:从 “理解” 到 “落地” 的代理模式)

一、代理模式核心认知:看透 "中间层" 的价值

在深入代码前,先明确代理模式的核心价值 ------ 它不是简单的 "功能封装",而是通过 "代理对象隔离直接访问",实现 "业务逻辑" 与 "非业务逻辑(如增强功能)" 的彻底解耦。

1. 传统功能增强的痛点

以 "统计方法执行时间" 为例,传统写法会将统计逻辑硬编码在业务方法中:

java 复制代码
public class TaskService {
   public void dealTask(String taskName) {
       // 非业务逻辑:记录开始时间
       long startTime = System.currentTimeMillis();
       
       // 核心业务逻辑
       System.out.println("执行任务:" + taskName);
       try {
           Thread.sleep(500);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       
       // 非业务逻辑:计算执行时间
       long cost = System.currentTimeMillis() - startTime;
       System.out.println("任务执行耗时:" + cost + "ms");
   }
}

这种写法的问题会随业务扩展暴露:

  • 耦合严重:统计逻辑与业务逻辑混杂,修改统计规则需改动业务代码,违反开闭原则;

  • 代码冗余:若多个方法需统计时间,需重复编写时间记录代码;

  • 职责混乱:一个方法同时负责 "业务执行" 和 "性能统计",不符合单一职责原则。

2. 代理模式的 "解耦" 逻辑

代理模式通过 "目标对象 + 代理对象 + 抽象接口" 三层结构,将耦合的逻辑拆分:

  • 抽象接口 :定义目标对象与代理对象的统一行为(如TaskService接口);

  • 目标对象 :实现抽象接口,专注于核心业务逻辑(如TaskServiceImpl);

  • 代理对象 :实现抽象接口,持有目标对象引用,在调用目标方法前后附加增强逻辑(如TaskProxy)。

最终实现 "增强功能不改业务代码、新增增强只需加代理、业务与增强完全解耦" 的理想状态。

二、3 种基础实战写法:从静态到动态

根据代理对象的创建时机,代理模式分为 "静态代理" 和 "动态代理",动态代理又细分为 JDK 动态代理和 CGLIB 动态代理,适用场景和灵活性各不相同。

写法 1:静态代理(编译期生成代理类)

核心思路

由程序员手动编写代理类,或通过工具自动生成代理类源代码,在编译期就确定代理类与目标类的关系。代理类与目标类实现同一接口,持有目标对象引用,在接口方法中调用目标方法并附加增强逻辑。

完整代码实现
1. 定义抽象接口(统一行为)
java 复制代码
package com.boke.proxy.staticproxy;

/**
 * 任务服务接口:定义目标对象与代理对象的统一行为
 */
public interface TaskService {
   /**
    * 执行任务
    * @param taskName 任务名称
    */
   void dealTask(String taskName);
}
2. 实现目标对象(核心业务)
java 复制代码
package com.boke.proxy.staticproxy.impl;

import com.boke.proxy.staticproxy.TaskService;

/**
 * 任务服务实现类:专注于核心业务逻辑
 */
public class TaskServiceImpl implements TaskService {
   @Override
   public void dealTask(String taskName) {
       // 仅包含核心业务逻辑,无任何增强代码
       System.out.println("开始执行任务:" + taskName);
       try {
           // 模拟任务执行耗时
           Thread.sleep(500);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("任务执行完成:" + taskName);
   }
}
3. 实现代理对象(附加增强逻辑)
java 复制代码
package com.boke.proxy.staticproxy.proxy;

import com.boke.proxy.staticproxy.TaskService;

/**
 * 任务服务代理类:附加性能统计增强逻辑
 */
public class TaskProxy implements TaskService {
   // 持有目标对象引用
   private final TaskService target;
   
   // 构造方法注入目标对象
   public TaskProxy(TaskService target) {
       this.target = target;
   }

   @Override
   public void dealTask(String taskName) {
       // 增强逻辑:执行前记录开始时间
       long startTime = System.currentTimeMillis();
       System.out.printf("任务[%s]开始执行,开始时间:%dms%n", taskName, startTime);
       
       // 调用目标对象的核心业务方法
       target.dealTask(taskName);

       // 增强逻辑:执行后计算耗时
       long costTime = System.currentTimeMillis() - startTime;
       System.out.printf("任务[%s]执行完成,总耗时:%dms%n", taskName, costTime);
   }
}
4. 测试类(客户端调用)
java 复制代码
package com.boke.proxy.staticproxy.test;

import com.boke.proxy.staticproxy.TaskService;
import com.boke.proxy.staticproxy.impl.TaskServiceImpl;
import com.boke.proxy.staticproxy.proxy.TaskProxy;

public class StaticProxyTest {
   public static void main(String[] args) {
       // 1. 创建目标对象
       TaskService target = new TaskServiceImpl();
       
       // 2. 创建代理对象,注入目标对象
       TaskService proxy = new TaskProxy(target);

       // 3. 调用代理对象方法(间接调用目标对象方法,附加增强逻辑)
       proxy.dealTask("生成月度报表");
   }
}
5. 测试结果(控制台输出)
text 复制代码
任务[生成月度报表]开始执行,开始时间:1718000000000ms
开始执行任务:生成月度报表
任务执行完成:生成月度报表
任务[生成月度报表]执行完成,总耗时:501ms
优缺点分析
优点 缺点
逻辑简单直观,易于理解和调试 代理类与目标类强绑定,一个目标类需对应一个代理类,类数量爆炸
无额外依赖,不依赖框架即可实现 接口新增方法时,目标类和所有代理类需同步实现,维护成本高
增强逻辑明确,可直接定位问题 仅支持接口代理,无法代理无接口的类
适用场景
  • 简单场景,目标类和方法数量少(如工具类的单一方法增强);

  • 需手动控制增强逻辑,且不依赖框架的场景;

  • 学习代理模式原理的入门案例。

写法 2:JDK 动态代理(运行时生成代理类)

核心思路

利用 JDK 自带的 java.lang.reflect.Proxy 类和 InvocationHandler 接口,在运行时动态生成代理类字节码,无需手动编写代理类。JDK 动态代理要求目标类实现接口,代理类通过反射调用目标方法,灵活性远高于静态代理。

完整代码实现
1. 复用抽象接口和目标对象(与静态代理一致)
java 复制代码
// 抽象接口:TaskService(同上)

// 目标对象:TaskServiceImpl(同上)
2. 实现调用处理器(增强逻辑封装)
java 复制代码
package com.boke.proxy.jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 任务服务调用处理器:实现InvocationHandler,封装增强逻辑
 */
public class TaskInvocationHandler implements InvocationHandler {
   // 持有目标对象引用(支持任意实现接口的目标类,通用性强)
   private final Object target;

   public TaskInvocationHandler(Object target) {
       this.target = target;
   }
   
   /**
    * 代理类的核心方法:所有代理对象的方法调用都会转发到这里
    * @param proxy 代理对象本身(一般不用)
    * @param method 被调用的目标方法
    * @param args 方法参数
    * @return 方法返回值
    * @throws Throwable 方法执行异常
    */
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       // 1. 增强逻辑:执行前记录开始时间
       String taskName = args[0].toString();
       long startTime = System.currentTimeMillis();
       System.out.printf("JDK动态代理:任务[%s]开始执行,开始时间:%dms%n", taskName, startTime);
       
       // 2. 反射调用目标对象的核心方法
       Object result = method.invoke(target, args);
       
       // 3. 增强逻辑:执行后计算耗时
       long costTime = System.currentTimeMillis() - startTime;
       System.out.printf("JDK动态代理:任务[%s]执行完成,总耗时:%dms%n", taskName, costTime);
       return result;
   }
}
3. 实现代理工厂(动态生成代理对象)
java 复制代码
package com.boke.proxy.jdk;

import com.boke.proxy.staticproxy.TaskService;
import java.lang.reflect.Proxy;

/**
 * 代理工厂:封装动态代理对象的创建逻辑
 */
public class JdkProxyFactory {
   /**
    * 生成目标对象的代理对象
    * @param target 目标对象(需实现接口)
    * @return 代理对象(实现与目标对象相同的接口)
    */
   @SuppressWarnings("unchecked")
   public static <T> T getProxy(Object target) {
       // 1. 获取目标对象的类加载器
       ClassLoader classLoader = target.getClass().getClassLoader();
       
       // 2. 获取目标对象实现的所有接口(JDK动态代理必须基于接口)
       Class<?>[] interfaces = target.getClass().getInterfaces();

       // 3. 创建调用处理器(封装增强逻辑)
       InvocationHandler handler = new TaskInvocationHandler(target);

       // 4. 动态生成代理对象并返回
       return (T) Proxy.newProxyInstance(classLoader, interfaces, handler);
   }

   // 测试方法
   public static void main(String[] args) {
       // 1. 创建目标对象
       TaskService target = new com.boke.proxy.staticproxy.impl.TaskServiceImpl();
       
       // 2. 动态生成代理对象
       TaskService proxy = JdkProxyFactory.getProxy(target);
       
       // 3. 调用代理对象方法
       proxy.dealTask("同步用户数据");
   }
}
4. 测试结果(控制台输出)
text 复制代码
JDK动态代理:任务[同步用户数据]开始执行,开始时间:1718001000000ms
开始执行任务:同步用户数据
任务执行完成:同步用户数据
JDK动态代理:任务[同步用户数据]执行完成,总耗时:502ms
关键原理:动态生成的代理类

JDK 动态代理在运行时会生成名为 com.sun.proxy.$Proxy0 的代理类字节码(可通过配置保存到本地),核心逻辑如下:

java 复制代码
// 动态生成的代理类示例(简化版)
public final class $Proxy0 extends Proxy implements TaskService {
   private static Method m1; // equals方法
   private static Method m2; // toString方法
   private static Method m3; // dealTask方法
   private static Method m0; // hashCode方法

   public $Proxy0(InvocationHandler var1) throws  {
       super(var1);
   }

   // 代理类的dealTask方法,最终调用InvocationHandler的invoke方法
   public final void dealTask(String var1) throws  {
       try {
           super.h.invoke(this, m3, new Object[]{var1});
       } catch (RuntimeException | Error var3) {
           throw var3;
       } catch (Throwable var4) {
           throw new UndeclaredThrowableException(var4);
       }
   }

   // 静态代码块:初始化方法对象
   static {
       try {
           m3 = Class.forName("com.boke.proxy.staticproxy.TaskService").getMethod("dealTask", Class.forName("java.lang.String"));
           m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
           m2 = Class.forName("java.lang.Object").getMethod("toString");
           m0 = Class.forName("java.lang.Object").getMethod("hashCode");
       } catch (NoSuchMethodException | ClassNotFoundException var2) {
           throw new NoSuchMethodError(var2.getMessage());
       }
   }
}
优缺点分析
优点 缺点
无需手动编写代理类,一个调用处理器可代理所有实现接口的目标类 仅支持接口代理,无法代理无接口的类(如未实现任何接口的普通类)
运行时动态生成代理类,灵活性高,新增目标类无需新增代理类 依赖反射调用,性能略低于 CGLIB 动态代理(JDK 8+ 反射优化后差距缩小)
无额外依赖,仅依赖 JDK 原生 API 无法直接代理目标类的私有方法和 final 方法
适用场景
  • 目标类已实现接口的场景(如基于接口开发的分层架构);

  • 不依赖第三方框架,希望使用 JDK 原生能力实现动态代理;

  • 代理逻辑通用,需覆盖多个不同接口的目标类。

写法 3:CGLIB 动态代理(运行时生成子类代理)

核心思路

CGLIB(Code Generation Library)是一个第三方字节码生成库,通过在运行时动态生成目标类的子类作为代理类,无需目标类实现接口。代理类重写目标类的非 final 方法,在方法中附加增强逻辑并调用父类(目标类)的方法。

完整代码实现
1. 导入 CGLIB 依赖(Maven)
xml 复制代码
<dependency>
   <groupId>cglib</groupId>
   <artifactId>cglib</artifactId>
   <version>3.3.0</version>
</dependency>
2. 定义目标类(无需实现接口)
java 复制代码
package com.boke.proxy.cglib;

/**
 * 任务服务类:无接口,直接通过CGLIB代理子类
 */
public class TaskService {
   // 非final方法,可被CGLIB重写
   public void dealTask(String taskName) {
       System.out.println("CGLIB目标类:开始执行任务" + taskName);
       try {
           Thread.sleep(500);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("CGLIB目标类:任务" + taskName + "执行完成");
   }

   // final方法,CGLIB无法重写(不会被代理)
   public final void finalMethod() {
       System.out.println("这是final方法,CGLIB无法代理");
   }
}
3. 实现方法拦截器(增强逻辑封装)
java 复制代码
package com.boke.proxy.cglib;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * CGLIB方法拦截器:实现MethodInterceptor,封装增强逻辑
 */
public class TaskMethodInterceptor implements MethodInterceptor {
   /**
    * 代理类的核心方法:所有代理对象的非final方法调用都会转发到这里
    * @param obj 代理对象(子类实例)
    * @param method 被调用的目标方法(父类方法)
    * @param args 方法参数
    * @param proxy 方法代理对象(用于调用父类方法,性能优于反射)
    * @return 方法返回值
    * @throws Throwable 方法执行异常
    */
   @Override
   public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
       // 1. 增强逻辑:执行前记录开始时间
       String taskName = args[0].toString();
       long startTime = System.currentTimeMillis();
       System.out.printf("CGLIB动态代理:任务[%s]开始执行,开始时间:%dms%n", taskName, startTime);
       
       // 2. 调用目标类(父类)的方法(使用MethodProxy,避免反射,性能更高)
       Object result = proxy.invokeSuper(obj, args);

       // 3. 增强逻辑:执行后计算耗时
       long costTime = System.currentTimeMillis() - startTime;
       System.out.printf("CGLIB动态代理:任务[%s]执行完成,总耗时:%dms%n", taskName, costTime);
       
       return result;
   }
}
4. 实现代理工厂(动态生成子类代理)
java 复制代码
package com.boke.proxy.cglib;

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;

/**
 * CGLIB代理工厂:封装子类代理对象的创建逻辑
 */

public class CglibProxyFactory {
   /**
    * 生成目标类的子类代理对象
    * @param targetClass 目标类的Class对象(无需实现接口)
    * @return 代理对象(目标类的子类实例)
    */
   @SuppressWarnings("unchecked")
   public static <T> T getProxy(Class<T> targetClass) {
       // 1. 开启CGLIB调试模式:将生成的代理类字节码保存到本地(可选,用于调试)
       System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:cglib_proxy_classes");
       
       // 2. 创建Enhancer对象(CGLIB的核心类,用于生成子类代理)
       Enhancer enhancer = new Enhancer();
       
       // 3. 设置父类(目标类):代理类将继承自该类
       enhancer.setSuperclass(targetClass);
       
       // 4. 设置方法拦截器:代理类的方法调用会转发到这里
       enhancer.setCallback(new TaskMethodInterceptor());
       
       // 5. 动态生成代理类并创建实例
       return (T) enhancer.create();
   }

   // 测试方法
   public static void main(String[] args) {
       // 1. 动态生成代理对象(目标类无需实现接口)
       TaskService proxy = CglibProxyFactory.getProxy(TaskService.class);
       
       // 2. 调用代理对象的非final方法(会被拦截并附加增强逻辑)
       System.out.println("=== 调用非final方法(dealTask)===");
       proxy.dealTask("处理订单数据");
       
       // 3. 调用代理对象的final方法(不会被拦截,直接执行目标类方法)
       System.out.println("n=== 调用final方法(finalMethod)===");
       proxy.finalMethod();
   }
}
5. 测试结果(控制台输出)
text 复制代码
=== 调用非final方法(dealTask)===
CGLIB动态代理:任务[处理订单数据]开始执行,开始时间:1718002000000ms
CGLIB目标类:开始执行任务处理订单数据
CGLIB目标类:任务处理订单数据执行完成
CGLIB动态代理:任务[处理订单数据]执行完成,总耗时:503ms
=== 调用final方法(finalMethod)===
这是final方法,CGLIB无法代理
关键原理:FastClass 机制

CGLIB 性能优于 JDK 动态代理的核心是 FastClass 机制

  1. 为目标类和代理类各生成一个 FastClass 类,该类会为每个方法分配一个唯一的索引(如 dealTask 对应索引 0,hashCode 对应索引 1);

  2. 调用方法时,通过索引直接定位方法,避免反射调用的性能损耗;

  3. FastClass 会缓存方法索引,后续调用无需重复计算,进一步提升性能。

以下是生成的 FastClass 类核心逻辑(简化版):

java 复制代码
public class TaskService$$FastClassByCGLIB$$xxx extends FastClass {
   @Override
   public int getIndex(String methodName, Class[] parameterTypes) {
       // 根据方法名和参数类型返回索引(如dealTask对应0)
       if ("dealTask".equals(methodName) && parameterTypes.length == 1 && parameterTypes[0] == String.class) {
           return 0;
       }
       
       // 其他方法的索引匹配...
       return -1;
   }

   @Override
   public Object invoke(int index, Object obj, Object[] args) throws InvocationTargetException {
       TaskService target = (TaskService) obj;
       switch (index) {
           case 0:
               target.dealTask((String) args[0]);
               return null;
           // 其他方法的调用逻辑...
           default:
               throw new IllegalArgumentException("无效的方法索引:" + index);
       }
   }
}
优缺点分析
优点 缺点
无需目标类实现接口,支持代理普通类 依赖第三方库(CGLIB),需额外导入依赖
基于 FastClass 机制,调用性能优于 JDK 动态代理 无法代理 final 类和 final 方法(子类无法重写)
支持代理私有方法(需通过特殊配置,不推荐) 生成的代理类字节码较复杂,调试难度略高
适用场景
  • 目标类未实现接口的场景(如遗留系统中的普通类);

  • 对代理性能要求较高,且目标类无 final 修饰的场景;

  • 需代理类的所有非 final 方法,而非特定接口方法的场景。

三、Spring AOP:代理模式的企业级落地

Spring AOP 是代理模式的 "终极形态",它基于 JDK 动态代理和 CGLIB 动态代理,通过 "切面编程" 实现横切功能的统一管理,无需手动创建代理对象,是 Spring 生态中最推荐的代理模式实现方式。

1. Spring AOP 核心概念

  • 切面(Aspect):封装横切功能的类(如日志切面、事务切面);

  • 切点(Pointcut) :定义哪些方法需要被增强(如所有 service 包下的方法);

  • 通知(Advice):切面在切点处执行的增强逻辑(如前置通知、环绕通知);

  • 连接点(JoinPoint):程序执行过程中可被增强的点(如方法调用、异常抛出);

  • 织入(Weaving):将切面逻辑融入目标对象的过程(Spring 采用运行时织入)。

2. 实战:用 Spring AOP 实现性能统计

步骤 1:导入 Spring AOP 依赖(Maven)
xml 复制代码
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
   <!-- Spring Boot 版本可根据项目需求指定,如 2.7.10 -->
</dependency>
步骤 2:定义目标服务类(业务逻辑)
java 复制代码
package com.boke.proxy.springaop.service;

import org.springframework.stereotype.Service;

/**
 * 订单服务类:需被 Spring 管理,才能被 AOP 代理
 */
@Service
public class OrderService {
   /**
    * 处理订单(核心业务方法)
    */
   public void processOrder(String orderNo) {
       System.out.println("开始处理订单:" + orderNo);
       try {
           // 模拟订单处理耗时
           Thread.sleep(600);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("订单处理完成:" + orderNo);
   }

   /**
    * 取消订单(核心业务方法)
    */
   public void cancelOrder(String orderNo) {
       System.out.println("开始取消订单:" + orderNo);
       try {
           Thread.sleep(400);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("订单取消完成:" + orderNo);
   }
}
步骤 3:定义切面类(增强逻辑)
java 复制代码
package com.boke.proxy.springaop.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 性能统计切面:封装横切增强逻辑
 */
@Aspect // 标识为切面类
@Component // 注册为 Spring Bean,才能被扫描到
public class PerformanceAspect {
   /**
    * 定义切点:匹配 com.boke.proxy.springaop.service 包下所有类的所有方法
    * 切点表达式语法:execution(访问修饰符 返回值 包名.类名.方法名(参数类型) 异常类型)
    */
   @Pointcut("execution(* com.boke.proxy.springaop.service.*.*(..))")
   public void performancePointcut() {}

   /**
    * 环绕通知:包围目标方法,可在执行前后自定义增强逻辑(最灵活的通知类型)
    * @param joinPoint 连接点对象,用于获取目标方法信息和执行目标方法
    * @return 目标方法的返回值
    * @throws Throwable 目标方法执行异常
    */
   @Around("performancePointcut()")
   public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
       // 1. 增强逻辑:执行前记录开始时间和方法信息
       String methodName = joinPoint.getSignature().getName(); // 获取目标方法名
       Object[] args = joinPoint.getArgs(); // 获取目标方法参数
       String orderNo = args.length > 0 ? args[0].toString() : "无参数";
       long startTime = System.currentTimeMillis();
       System.out.printf("Spring AOP 环绕通知:方法[%s]开始执行,订单号[%s],开始时间:%dms%n", methodName, orderNo, startTime);

       // 2. 执行目标方法(必须调用 proceed(),否则目标方法不会执行)
       Object result = joinPoint.proceed();
       
       // 3. 增强逻辑:执行后计算耗时
       long costTime = System.currentTimeMillis() - startTime;
       System.out.printf("Spring AOP 环绕通知:方法[%s]执行完成,总耗时:%dms%n", methodName, costTime);
       return result;
   }
}
步骤 4:定义 Spring Boot 启动类
java 复制代码
package com.boke.proxy.springaop;

import com.boke.proxy.springaop.service.OrderService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import javax.annotation.Resource;

@SpringBootApplication
public class SpringAopApplication {
   @Resource
   private OrderService orderService;

   public static void main(String[] args) {
       SpringApplication.run(SpringAopApplication.class, args);
   }

   /**
    * 项目启动后自动执行,测试 AOP 代理效果
    */
   @Bean
   public CommandLineRunner testAop() {
       return args -> {
           System.out.println("=== 测试处理订单 ===");
           orderService.processOrder("ORDER_20240601001");
           System.out.println("n=== 测试取消订单 ===");
           orderService.cancelOrder("ORDER_20240601002");
       };
   }
}
步骤 5:测试结果(控制台输出)
text 复制代码
=== 测试处理订单 ===
Spring AOP 环绕通知:方法[processOrder]开始执行,订单号[ORDER_20240601001],开始时间:1718003000000ms
开始处理订单:ORDER_20240601001
订单处理完成:ORDER_20240601001
Spring AOP 环绕通知:方法[processOrder]执行完成,总耗时:602ms
=== 测试取消订单 ===
Spring AOP 环绕通知:方法[cancelOrder]开始执行,订单号[ORDER_20240601002],开始时间:1718003000602ms
开始取消订单:ORDER_20240601002
订单取消完成:ORDER_20240601002
Spring AOP 环绕通知:方法[cancelOrder]执行完成,总耗时:401ms

3. Spring AOP 的代理选择逻辑

Spring AOP 会根据目标类自动选择代理方式:

  • 若目标类实现了接口:默认使用 JDK 动态代理

  • 若目标类未实现接口:默认使用 CGLIB 动态代理

  • 可通过配置强制使用 CGLIB 代理(适用于接口代理场景但需代理类的非接口方法):

yaml 复制代码
# application.yml 配置
spring:
  aop:
    proxy-target-class: true # true:强制使用 CGLIB 代理;false:默认逻辑

四、关键对比:4 种代理方式的选择指南

1. 代理方式核心差异对比

对比维度 静态代理 JDK 动态代理 CGLIB 动态代理 Spring AOP
代理基础 接口实现 接口实现 子类继承 接口实现 / 子类继承(自动选择)
代理类生成时机 编译期 运行时 运行时 运行时
依赖 JDK 原生 API CGLIB 库 Spring 核心 + AOP 模块
性能 高(直接调用) 中(反射) 高(FastClass) 中 / 高(随底层代理方式)
灵活性 低(一个目标类一个代理类) 中(接口通用) 高(类通用) 极高(切面配置)
适用场景 简单场景、学习原理 接口代理、无第三方依赖 无接口类、高性能需求 企业级项目、横切功能统一管理

2. 实际项目选择建议

  • 小工具 / 学习场景:用静态代理,直观理解代理模式原理;

  • 无框架依赖的接口代理:用 JDK 动态代理,避免第三方依赖;

  • 无接口类的高性能代理:用 CGLIB 动态代理,兼顾灵活性和性能;

  • 企业级 Spring 项目:用 Spring AOP,通过切面配置统一管理横切功能(如日志、事务、权限),无需手动处理代理逻辑。

五、避坑指南:代理模式实战中的 5 个关键问题

1. 避免 "代理失效":Spring 环境下的常见陷阱

  • 问题 1 :目标类未被 Spring 管理(未加 @Service/@Component),导致 AOP 无法生成代理;

    解决方案:确保目标类标注 Spring 组件注解,且被扫描到。

  • 问题 2 :在目标类内部调用自身方法(如 this.method()),绕过代理对象,导致增强逻辑不执行;

    解决方案 :通过 ApplicationContext 获取代理对象,再调用方法,或使用 @Autowired 注入自身代理对象。

  • 问题 3 :方法为 static/final 修饰,导致 CGLIB 无法重写;

    解决方案 :避免代理 static/final 方法,或改用 JDK 动态代理(基于接口)。

2. 性能优化:减少代理带来的开销

  • JDK 动态代理:避免在循环中频繁创建代理对象(可缓存代理对象);

  • CGLIB 动态代理:减少代理类生成次数(Enhancer 对象可复用);

  • Spring AOP :缩小切点范围(如精确匹配方法,避免 execution(* *.*(..))),减少不必要的代理。

3. 异常处理:代理中的异常传递

  • 静态代理 / 动态代理:目标方法抛出的异常会直接传递到代理调用处,需在调用层捕获;

  • Spring AOP:可通过 @AfterThrowing 通知统一捕获目标方法异常,便于日志记录和告警。

4. 调试技巧:定位代理类逻辑

  • 静态代理:直接断点调试代理类方法;

  • JDK 动态代理 :通过 System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true") 保存代理类字节码;

  • CGLIB 动态代理 :通过 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "路径") 保存代理类字节码;

  • Spring AOP:开启 Spring debug 日志,查看代理类生成过程:

yaml 复制代码
logging:
  level:
    org.springframework.aop: debug

5. 安全性:避免代理滥用

  • 不代理敏感方法(如密码加密、权限校验),防止增强逻辑泄露敏感信息;

  • 动态代理生成的类可能被反编译,需避免在代理逻辑中硬编码密钥等敏感数据。

六、总结:从 "理解" 到 "落地" 的代理模式

代理模式的核心不是 "生成代理类",而是 "通过中间层实现解耦"------ 将 "业务逻辑" 与 "非业务逻辑" 分离,让代码更易维护、扩展。从静态代理的 "手动编码" 到 Spring AOP 的 "切面配置",代理模式的演进本质是 "减少重复工作、提升灵活性"。

在实际项目中,无需纠结于 "哪种代理方式更高级",而是根据场景选择最合适的方案:简单场景用静态代理,无框架依赖用 JDK/CGLIB,企业级项目用 Spring AOP。掌握代理模式,不仅能优化代码结构,更是理解 Spring 事务、MyBatis mapper 代理等框架核心原理的基础。

相关推荐
梵得儿SHI3 小时前
Java 反射机制深度解析:从对象创建到私有成员操作
java·开发语言·class对象·java反射机制·操作类成员·三大典型·反射的核心api
JAVA学习通3 小时前
Spring AI 核心概念
java·人工智能·spring·springai
望获linux3 小时前
【实时Linux实战系列】实时 Linux 在边缘计算网关中的应用
java·linux·服务器·前端·数据库·操作系统
..Cherry..3 小时前
【java】jvm
java·开发语言·jvm
老K的Java兵器库3 小时前
并发集合踩坑现场:ConcurrentHashMap size() 阻塞、HashSet 并发 add 丢数据、Queue 伪共享
java·后端·spring
计算机毕业设计木哥4 小时前
计算机毕业设计选题推荐:基于SpringBoot和Vue的爱心公益网站
java·开发语言·vue.js·spring boot·后端·课程设计
ANnianStriver4 小时前
智谱大模型实现文生视频案例
java·aigc
普通网友4 小时前
KUD#73019
java·php·程序优化
番茄Salad4 小时前
自定义Spring Boot Starter项目并且在其他项目中通过pom引入使用
java·spring boot