
摘要代理模式是Java开发中最核心的设计模式之一也是Spring AOP的底层基石。但很多同学写了三年Java对静态代理、JDK动态代理、CGLIB代理的区别还是一知半解。这篇文章从源码级别拆解三种代理的实现原理对比它们的性能差异和适用场景最后结合Spring AOP的源码看看Spring到底是怎么选代理的。看完这篇面试问代理模式你可以直接和他聊源码。一、为什么会写这篇最近在review项目代码时发现组里一位同学在写一个日志切面用的是Spring AOP的Around注解。一切看起来很正常直到他问了我一个问题“门主为什么我加了Transactional的Service方法事务有时候生效有时候不生效”我让他把代码发过来看了一眼发现他Service实现了一个接口但是在Transactional注解加在了实现类的方法上而不是接口方法上。深入了解后才发现他对Spring AOP底层用的哪种代理方式完全不清楚。这其实不是个例。很多Java开发用了几年Spring每天在用Transactional、Cacheable但对底层的代理机制一知半解。一旦遇到代理失效、AOP不生效的问题就开始玄学排错。所以今天干脆把代理模式这件事彻底讲清楚。二、什么是代理模式2.1 定义代理模式Proxy Pattern属于结构型设计模式。它的核心思想是不直接访问目标对象而是通过一个代理对象来间接访问。听起来很简单对吧但就是这个简单的思想撑起了Spring AOP、MyBatis Mapper接口、RPC远程调用等一大片技术体系。2.2 核心角色代理模式中有三个核心角色角色说明类比Subject抽象主题定义真实对象和代理对象的公共接口合同RealSubject真实主题真正执行业务逻辑的对象房东Proxy代理持有RealSubject的引用在调用前后做增强处理中介2.3 UML类图2.4 为什么需要代理举个例子。假设你有一个UserService里面有个saveUser()方法。现在上线要求所有Service方法调用都要记录日志。最简单的做法是什么在每个方法里手动加日志public void saveUser(User user) { log.info(开始保存用户: {}, user.getId()); // 业务逻辑... log.info(保存用户完成); }一个方法还行但如果有100个方法呢这就是典型的横切关注点问题。日志、事务、权限这些逻辑和业务逻辑混在一起代码越来越臃肿。代理模式就是来解决这个问题的把增强逻辑抽离出来通过代理对象统一织入。三、静态代理3.1 实现方式静态代理是最原始的代理方式。代理类和目标类实现同一个接口在代理类中持有目标类的引用在调用目标方法前后做增强处理。3.2 代码示例先定义一个接口/** * 用户服务接口 * 不管是真实对象还是代理对象都实现这个接口 */ public interface UserService { /** * 保存用户 */ void saveUser(String userId, String userName); /** * 查询用户 */ String getUser(String userId); }真实业务类/** * 真实的用户服务实现类 * 这里只关心业务逻辑不关心日志、事务等横切关注点 */ public class UserServiceImpl implements UserService { Override public void saveUser(String userId, String userName) { // 模拟业务处理 System.out.println(【业务逻辑】保存用户: id userId , name userName); } Override public String getUser(String userId) { // 模拟业务处理 System.out.println(【业务逻辑】查询用户: id userId); return User{id userId , name张三}; } }静态代理类import java.time.LocalDateTime; /** * 静态代理类 * * 核心思路 * 1. 实现和目标类相同的接口 * 2. 持有目标类的引用 * 3. 在调用目标方法前后做增强处理 */ public class UserServiceStaticProxy implements UserService { // 持有目标对象的引用 private final UserService target; /** * 通过构造方法传入目标对象 */ public UserServiceStaticProxy(UserService target) { this.target target; } Override public void saveUser(String userId, String userName) { // 前置增强记录日志 System.out.println(【代理】前置日志 - 开始执行 saveUser时间: LocalDateTime.now()); // 调用目标对象的实际方法 target.saveUser(userId, userName); // 后置增强记录日志 System.out.println(【代理】后置日志 - saveUser 执行完成时间: LocalDateTime.now()); } Override public String getUser(String userId) { // 前置增强 System.out.println(【代理】前置日志 - 开始执行 getUser时间: LocalDateTime.now()); // 调用目标方法 String result target.getUser(userId); // 后置增强 System.out.println(【代理】后置日志 - getUser 执行完成时间: LocalDateTime.now()); return result; } }客户端调用/** * 客户端调用 * 对调用方来说完全感知不到代理的存在 * 它只知道自己拿到的是一个 UserService 接口 */ public class Client { public static void main(String[] args) { // 1. 创建真实对象 UserService realService new UserServiceImpl(); // 2. 创建代理对象把真实对象传进去 UserService proxy new UserServiceStaticProxy(realService); // 3. 调用代理对象的方法对调用方透明 proxy.saveUser(U001, 张三); System.out.println(--- 分割线 ---); String user proxy.getUser(U001); System.out.println(查询结果: user); } }运行结果【代理】前置日志 - 开始执行 saveUser时间: 2025-06-15T10:30:15.123 【业务逻辑】保存用户: idU001, name张三 【代理】后置日志 - saveUser 执行完成时间: 2025-06-15T10:30:15.125 --- 分割线 --- 【代理】前置日志 - 开始执行 getUser时间: 2025-06-15T10:30:15.125 【业务逻辑】查询用户: idU001 【代理】后置日志 - getUser 执行完成时间: 2025-06-15T10:30:15.125 查询结果: User{idU001, name张三}3.3 静态代理的优缺点优点实现简单容易理解代码直观编译期就能发现类型错误缺点代码冗余严重每个方法都要写一遍增强逻辑如果有100个方法代理类就有100份重复代码类爆炸每个目标类都需要一个对应的代理类。如果有10个Service就需要10个代理类维护成本高新增方法时接口、目标类、代理类都要改只能代理特定接口代理类是为特定接口服务的不具备通用性总结一句话静态代理只适合接口方法极少、代理类数量极少的场景。企业项目中基本不会用。四、JDK动态代理4.1 为什么需要动态代理静态代理的问题在于代理类是写死的每个目标类都要手写一个代理类。能不能在运行时动态生成代理类答案是JDK动态代理。4.2 核心原理JDK动态代理的核心是两个类java.lang.reflect.Proxy负责生成代理对象java.lang.reflect.InvocationHandler负责处理代理逻辑核心流程调用Proxy.newProxyInstance()方法JDK在运行时动态生成一个代理类字节码这个类实现了你指定的接口所有接口方法的调用都会被转发到InvocationHandler.invoke()方法在invoke()方法中你可以自由决定是否调用目标方法以及在调用前后做什么4.3 代码示例import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.time.LocalDateTime; /** * JDK动态代理的调用处理器 * * 核心思想 * 不再为每个接口手写代理类而是通过一个通用的 InvocationHandler * 来处理所有接口方法的调用 */ public class LogInvocationHandler implements InvocationHandler { // 持有目标对象 private final Object target; public LogInvocationHandler(Object target) { this.target target; } /** * 代理对象的所有方法调用都会走到这里 * * param proxy 代理对象本身注意不要在这个方法里调用proxy的方法会死循环 * param method 当前被调用的方法 * param args 方法参数 * return 方法执行结果 */ Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 方法名方便日志输出 String methodName method.getName(); // 前置增强 System.out.println(【JDK动态代理】前置日志 - 开始执行 methodName 时间: LocalDateTime.now()); Object result null; try { // 调用目标对象的实际方法 // 这里用了反射method.invoke(target, args) result method.invoke(target, args); // 后置增强 System.out.println(【JDK动态代理】后置日志 - methodName 执行成功时间: LocalDateTime.now()); } catch (Exception e) { // 异常增强 System.out.println(【JDK动态代理】异常日志 - methodName 执行失败异常: e.getCause().getMessage()); throw e; } return result; } /** * 创建代理对象的工厂方法 * * param target 目标对象 * return 代理对象 */ SuppressWarnings(unchecked) public static T T createProxy(T target) { // 三个参数 // 1. 类加载器用来加载动态生成的代理类 // 2. 接口数组代理类需要实现的接口 // 3. InvocationHandler方法调用处理器 return (T) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new LogInvocationHandler(target) ); } }客户端调用/** * 客户端调用JDK动态代理 */ public class JdkProxyClient { public static void main(String[] args) { // 1. 创建真实对象 UserService realService new UserServiceImpl(); // 2. 通过工厂方法创建代理对象 UserService proxy LogInvocationHandler.createProxy(realService); // 3. 调用代理对象的方法 proxy.saveUser(U002, 李四); System.out.println(--- 分割线 ---); String user proxy.getUser(U002); System.out.println(查询结果: user); // 验证代理对象的类型 System.out.println(--- 类型信息 ---); System.out.println(代理对象类型: proxy.getClass().getName()); System.out.println(是否是代理对象: Proxy.isProxyClass(proxy.getClass())); } }运行结果【JDK动态代理】前置日志 - 开始执行 saveUser时间: 2025-06-15T10:35:22.456 【业务逻辑】保存用户: idU002, name李四 【JDK动态代理】后置日志 - saveUser 执行成功时间: 2025-06-15T10:35:22.458 --- 分割线 --- 【JDK动态代理】前置日志 - 开始执行 getUser时间: 2025-06-15T10:35:22.458 【业务逻辑】查询用户: idU002 【JDK动态代理】后置日志 - getUser 执行成功时间: 2025-06-15T10:35:22.458 查询结果: User{idU002, name李四} --- 类型信息 --- 代理对象类型: com.sun.proxy.$Proxy0 是否是代理对象: true4.4 源码级分析JDK动态代理到底做了什么关键问题来了Proxy.newProxyInstance()到底生成了什么我们可以通过设置系统属性来保存生成的代理类字节码然后反编译看看// 在main方法开头设置保存JDK动态代理生成的类到磁盘 System . setProperty ( sun.misc.ProxyGenerator.saveGeneratedFiles , true ) ; // JDK 8 使用 // System.setProperty(jdk.proxy.ProxyGenerator.saveGeneratedFiles, true);反编译后看到的代理类大致长这样简化版/** * 这是JDK在运行时动态生成的代理类 * 注意这个类在编译期不存在是在运行时由ProxyGenerator生成的字节码 */ public final class $Proxy0 extends Proxy implements UserService { // 缓存Method对象避免每次调用都通过反射获取 private static Method m1; // equals private static Method m2; // toString private static Method m3; // saveUser private static Method m4; // getUser private static Method m0; // hashCode // 构造方法接收InvocationHandler public $Proxy0(InvocationHandler h) { super(h); // 调用Proxy的构造方法保存h } Override public final void saveUser(String userId, String userName) { try { // 核心逻辑调用InvocationHandler的invoke方法 // 把当前方法(m3)和参数传进去 super.h.invoke(this, m3, new Object[]{userId, userName}); } catch (RuntimeException | Error e) { throw e; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } Override public final String getUser(String userId) { try { return (String) super.h.invoke(this, m4, new Object[]{userId}); } catch (RuntimeException | Error e) { throw e; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } }关键点就一个代理类$Proxy0继承自java.lang.reflect.Proxy实现了UserService接口。所有接口方法的实现都是调用super.h.invoke()也就是InvocationHandler.invoke()。4.5 JDK动态代理的优缺点优点无需手写代理类运行时动态生成一个InvocationHandler可以代理任意接口代码复用性高增强逻辑集中在invoke()方法中接口驱动符合面向接口编程的思想缺点必须实现接口JDK动态代理要求目标类必须实现至少一个接口。如果目标类没有实现任何接口就无法使用性能开销反射调用有一定的性能损耗虽然JDK 8已经做了大量优化五、CGLIB代理5.1 为什么需要CGLIBJDK动态代理有一个硬伤目标类必须实现接口。但在实际项目中并不是所有类都实现了接口。比如// 没有实现任何接口的类 public class UserService { public void saveUser(String userId, String userName) { System.out.println(保存用户: userId); } }这种类怎么办用不了JDK动态代理。这时候CGLIB就派上用场了。5.2 核心原理CGLIBCode Generation Library的代理原理和JDK动态代理完全不同JDK动态代理基于接口生成一个实现了该接口的代理类CGLIB代理基于继承生成目标类的子类作为代理类CGLIB的核心机制通过ASM字节码框架动态生成目标类的子类子类重写所有非final的方法在方法中通过MethodInterceptor来拦截调用调用父类目标类的方法并在前后做增强5.3 代码示例首先添加Maven依赖!-- CGLIB 依赖 -- dependency groupIdcglib/groupId artifactIdcglib/artifactId version3.3.0/version /dependency目标类没有实现接口/** * 目标类没有实现任何接口 * 这就是JDK动态代理无法处理的情况 */ public class UserServiceNoInterface { public void saveUser(String userId, String userName) { System.out.println(【业务逻辑】保存用户: id userId , name userName); } public String getUser(String userId) { System.out.println(【业务逻辑】查询用户: id userId); return User{id userId , name王五}; } // final方法CGLIB无法重写也就无法代理 public final void cannotProxy() { System.out.println(这个方法是final的CGLIB无法代理); } }CGLIB代理实现import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; import java.time.LocalDateTime; /** * CGLIB代理实现 * * 核心思路 * 1. 通过Enhancer创建目标类的子类 * 2. 子类重写目标类的所有非final方法 * 3. 通过MethodInterceptor拦截方法调用 * 4. 调用父类方法目标类方法前后做增强 */ public class LogMethodInterceptor implements MethodInterceptor { /** * 拦截目标类的方法调用 * * param obj 代理对象本身目标类的子类 * param method 被拦截的方法父类的方法 * param args 方法参数 * param proxy 方法代理对象用于调用父类的方法 * return 方法执行结果 */ Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { String methodName method.getName(); // 前置增强 System.out.println(【CGLIB代理】前置日志 - 开始执行 methodName 时间: LocalDateTime.now()); Object result null; try { // 注意这里调用的是 proxy.invokeSuper()而不是 method.invoke() // invokeSuper 会调用父类目标类的方法而不是代理类的方法 // 如果这里用 method.invoke(obj, args)obj是代理对象会死循环 result proxy.invokeSuper(obj, args); // 后置增强 System.out.println(【CGLIB代理】后置日志 - methodName 执行成功时间: LocalDateTime.now()); } catch (Exception e) { System.out.println(【CGLIB代理】异常日志 - methodName 执行失败异常: e.getCause().getMessage()); throw e; } return result; } /** * 创建CGLIB代理对象 */ SuppressWarnings(unchecked) public static T T createProxy(ClassT targetClass) { // 1. 创建Enhancer对象CGLIB的核心类 Enhancer enhancer new Enhancer(); // 2. 设置父类目标类 enhancer.setSuperclass(targetClass); // 3. 设置方法拦截器 enhancer.setCallback(new LogMethodInterceptor()); // 4. 创建代理对象目标类的子类 return (T) enhancer.create(); } }客户端调用/** * 客户端调用CGLIB代理 */ public class CglibProxyClient { public static void main(String[] args) { // 1. 创建CGLIB代理对象 UserServiceNoInterface proxy LogMethodInterceptor.createProxy( UserServiceNoInterface.class); // 2. 调用代理对象的方法 proxy.saveUser(U003, 王五); System.out.println(--- 分割线 ---); String user proxy.getUser(U003); System.out.println(查询结果: user); System.out.println(--- 类型信息 ---); System.out.println(代理对象类型: proxy.getClass().getName()); System.out.println(代理对象的父类: proxy.getClass().getSuperclass().getName()); // 验证代理对象是目标类的子类 System.out.println(是否是目标类的实例: (proxy instanceof UserServiceNoInterface)); } }运行结果【CGLIB代理】前置日志 - 开始执行 saveUser时间: 2025-06-15T10:40:33.789 【业务逻辑】保存用户: idU003, name王五 【CGLIB代理】后置日志 - saveUser 执行成功时间: 2025-06-15T10:40:33.791 --- 分割线 --- 【CGLIB代理】前置日志 - 开始执行 getUser时间: 2025-06-15T10:40:33.791 【业务逻辑】查询用户: idU003 【CGLIB代理】后置日志 - getUser 执行成功时间: 2025-06-15T10:40:33.791 查询结果: User{idU003, name王五} --- 类型信息 --- 代理对象类型: com.example.proxy.UserServiceNoInterface$$EnhancerByCGLIB$$a1b2c3d4 代理对象的父类: com.example.proxy.UserServiceNoInterface 是否是目标类的实例: true5.4 CGLIB的注意事项第一final方法无法被代理CGLIB基于继承所以凡是不能被继承、不能被重写的方法都无法被代理。包括final方法static方法private方法第二invokeSuper和invoke的区别这是最容易踩坑的地方// 正确用法调用父类目标类的方法 proxy.invokeSuper(obj, args); // 错误用法在代理对象上调用方法会导致死循环 // method.invoke(obj, args); // obj是代理对象会再次进入intercept()方法第三CGLIB生成的是子类从输出可以看到代理类名是UserServiceNoInterface$$EnhancerByCGLIB$$a1b2c3d4继承了UserServiceNoInterface。这意味着proxy instanceof UserServiceNoInterface为true目标类如果有public字段子类可以直接访问5.5 JDK 8 对CGLIB的替代从JDK 16开始Java提供了java.lang.reflect.InvocationHandler的一个变体方案但真正值得注意的是Spring已经可以不依赖CGLIB了。Spring 4.0 自带了org.springframework.cglib包把CGLIB的核心代码直接内嵌了所以使用Spring的项目不需要额外引入CGLIB依赖。六、三种代理方式对比6.1 核心差异对比对比维度静态代理JDK动态代理CGLIB代理实现原理手写代理类实现相同接口运行时生成代理类实现目标接口运行时生成目标类的子类是否需要接口是是否代理类生成时机编译期运行时运行时代理类与目标类关系平级实现相同接口平级实现相同接口父子关系继承能否代理final方法能代理类本身有该方法能接口方法没有final限制不能final方法不可重写调用方式直接调用目标对象方法通过反射调用通过FastClass机制调用代码维护困难类爆炸容易一个Handler代理所有容易一个Interceptor代理所有性能首次最快无额外开销较慢需要生成代理类较慢需要生成字节码性能调用最快较慢反射调用快FastClass接近原生6.2 性能说明很多人以为CGLIB比JDK动态代理慢其实恰恰相反JDK动态代理方法调用走的是反射Method.invoke()有额外的开销CGLIB代理使用了FastClass机制通过索引直接调用方法性能接近原生调用JDK 8 对反射做了大量优化但即便如此CGLIB的调用性能仍然优于JDK动态代理。不过在实际项目中这个性能差异通常可以忽略不计。真正影响性能的是代理对象的创建过程而不是方法调用过程。6.3 深入理解CGLIB的FastClass机制CGLIB之所以调用性能好是因为它内部使用了FastClass机制。简单来说FastClass为一个类生成两个索引表一个索引表根据方法签名给方法编号另一个索引表根据方法编号直接跳转到方法实现调用时先通过方法签名找到编号然后通过编号直接调用。本质上是一个空间换时间的策略避免了反射调用。七、Spring AOP中的代理机制7.1 Spring AOP的代理选择策略这是面试高频题也是很多人在实际开发中踩坑的地方。Spring AOP的代理选择逻辑在DefaultAopProxyFactory中源码如下// 来自 Spring 源码org.springframework.aop.framework.DefaultAopProxyFactory public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { // 核心判断逻辑 if (!NativeDetector.inNativeImage() (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) { Class? targetClass config.getTargetClass(); if (targetClass null) { throw new AopConfigException(...); } // 如果目标类本身是接口或者目标类是JDK代理类则使用JDK动态代理 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } // 否则使用CGLIB代理 return new ObjenesisCglibAopProxy(config); } else { // 默认使用JDK动态代理 return new JdkDynamicAopProxy(config); } } /** * 判断是否没有用户自定义的代理接口 */ private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) { Class?[] interfaces config.getProxiedInterfaces(); return interfaces.length 0 || (interfaces.length 1 SpringProxy.class.isAssignableFrom(interfaces[0])); } }7.2 选择逻辑总结Spring选择JDK代理还是CGLIB代理的逻辑如下是否满足以下任一条件 1. config.isOptimize() true 优化标志 2. config.isProxyTargetClass() true proxyTargetClasstrue 3. 没有用户自定义的代理接口 ┌───────── 是 ─────────┐ ┌──── 否 ────┐ ↓ ↓ ↓ 目标类是否是接口或 ↓ 使用JDK动态代理 JDK代理类 ↓ ┌─ 是 → JDK动态代理 ↓ └─ 否 → CGLIB代理 ↓7.3 如何在Spring中配置方式一全局配置XML方式!-- 强制使用CGLIB代理 -- aop:config proxy-target-classtrue aop:aspect reflogAspect aop:pointcut idservicePointcut expressionexecution(* com.example.service.*.*(..))/ aop:around pointcut-refservicePointcut methodlogAround/ /aop:aspect /aop:config方式二全局配置注解方式Configuration EnableAspectJAutoProxy(proxyTargetClass true) // 强制使用CGLIB public class AppConfig { // ... }方式三Spring Boot中的配置# application.properties spring.aop.proxy-target-classtrue7.4 经典踩坑事务注解失效这是我在项目中被问得最多的问题。来看一个典型的场景// 接口 public interface UserService { void saveUser(User user); } // 实现类 Service public class UserServiceImpl implements UserService { Override Transactional // 事务注解加在实现类方法上 public void saveUser(User user) { // 保存用户... saveLog(user); // 调用本类的另一个方法 } Transactional(propagation Propagation.REQUIRES_NEW) public void saveLog(User user) { // 保存日志... } }问题saveLog()的事务不会生效。原因分析Spring默认配置下如果UserServiceImpl实现了接口优先使用JDK动态代理JDK动态代理生成的代理对象只代理接口中声明的方法saveLog()不是接口方法所以不会被代理更关键的是saveUser()内部调用saveLog()是直接调用this.saveLog()这里的this是目标对象本身不是代理对象解决方案// 方案一将事务方法提取到接口中 public interface UserService { void saveUser(User user); void saveLog(User user); // 在接口中声明 } // 方案二强制使用CGLIB代理 SpringBootApplication EnableAspectJAutoProxy(proxyTargetClass true, exposeProxy true) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } // 方案三通过AopContext获取当前代理对象 Service public class UserServiceImpl implements UserService { Autowired private UserService self; // 注入自己代理对象 Override Transactional public void saveUser(User user) { // ... self.saveLog(user); // 通过代理对象调用 } Transactional(propagation Propagation.REQUIRES_NEW) public void saveLog(User user) { // ... } }八、实际应用场景8.1 声明式事务管理这是代理模式在企业应用中最经典的应用。Spring的Transactional注解底层就是通过AOP代理来实现的。Service public class OrderService { Transactional(rollbackFor Exception.class) public void createOrder(OrderDTO order) { // 1. 保存订单代理对象会在方法执行前开启事务 orderDao.insert(order); // 2. 扣减库存 stockService.deduct(order.getProductId(), order.getQuantity()); // 3. 发送消息代理对象会在方法执行后提交事务 // 如果上面任何一步报错代理对象会自动回滚事务 mqService.sendOrderMessage(order); } }8.2 日志记录统一的日志记录不用在每个方法里手动写log.info()。Aspect Component public class LogAspect { Around(execution(* com.example.service.*.*(..))) public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { String methodName joinPoint.getSignature().getName(); Object[] args joinPoint.getArgs(); // 前置日志 log.info(调用方法: {}参数: {}, methodName, args); long start System.currentTimeMillis(); try { Object result joinPoint.proceed(); // 后置日志 long elapsed System.currentTimeMillis() - start; log.info(方法执行成功: {}耗时: {}ms结果: {}, methodName, elapsed, result); return result; } catch (Exception e) { log.error(方法执行异常: {}异常: {}, methodName, e.getMessage()); throw e; } } }8.3 权限控制在方法执行前检查当前用户是否有权限。Aspect Component public class PermissionAspect { Around(annotation(permission)) public Object checkPermission(ProceedingJoinPoint joinPoint, Permission permission) throws Throwable { String requiredRole permission.value(); String currentUserRole SecurityContextHolder.getContext() .getAuthentication().getAuthorities().iterator().next().getAuthority(); // 检查权限 if (!currentUserRole.equals(requiredRole)) { throw new AccessDeniedException(权限不足需要: requiredRole); } return joinPoint.proceed(); } } // 自定义注解 Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface Permission { String value(); // 需要的角色 } // 使用 RestController public class UserController { Permission(ADMIN) GetMapping(/admin/users) public ListUser listUsers() { return userService.listAll(); } }8.4 缓存通过代理在方法执行前检查缓存如果缓存命中直接返回避免执行方法。Aspect Component public class CacheAspect { private final CacheManager cacheManager; Around(annotation(cacheable)) public Object aroundCache(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable { String cacheName cacheable.value(); String key generateKey(joinPoint); // 先查缓存 Cache cache cacheManager.getCache(cacheName); Cache.ValueWrapper wrapper cache.get(key); if (wrapper ! null) { log.info(缓存命中: {}:{}, cacheName, key); return wrapper.get(); } // 缓存未命中执行方法 Object result joinPoint.proceed(); // 放入缓存 cache.put(key, result); log.info(缓存写入: {}:{}, cacheName, key); return result; } }8.5 性能监控统计每个方法的调用次数和耗时用于监控告警。Aspect Component public class MonitorAspect { private final MeterRegistry meterRegistry; Around(annotation(monitored)) public Object monitor(ProceedingJoinPoint joinPoint, Monitored monitored) throws Throwable { String methodName joinPoint.getSignature().toShortString(); // 调用次数计数 Counter.builder(method.calls) .tag(method, methodName) .register(meterRegistry) .increment(); long start System.nanoTime(); try { return joinPoint.proceed(); } finally { long duration System.nanoTime() - start; // 记录耗时 Timer.builder(method.duration) .tag(method, methodName) .register(meterRegistry) .record(duration, TimeUnit.NANOSECONDS); // 如果耗时超过阈值告警 if (duration monitored.threshold().toNanos()) { log.warn(方法执行超时: {}耗时: {}ms, methodName, duration / 1_000_000); } } } }九、总结代理模式从设计模式上来说不难但它在Java生态中的地位极其重要。Spring AOP、事务管理、MyBatis Mapper接口、RPC框架底层全部依赖代理机制。三种代理方式的选择建议静态代理基本不用。除非你的接口极其稳定且方法极少否则别碰。JDK动态代理优先选择。如果你的代码遵循面向接口编程的原则JDK动态代理足够用而且不需要额外依赖。CGLIB代理当目标类没有实现接口或者需要代理非接口方法时使用。Spring Boot 2.0 默认使用CGLIB因为大部分场景下我们不需要额外的接口层。门主踩坑总结事务注解加在private方法上不生效。因为代理子类无法重写private方法。同一个类中方法A调用方法B方法B的Transactional不生效因为this指向的是目标对象不是代理对象。接口方法上加了Transactional但实现类实现了接口如果Spring发现接口上有事务注解会优先使用JDK动态代理。如果实现类方法上也有Transactional事务注解会被覆盖。三连支持动力源泉如果这篇文章帮你省下了踩坑的时间欢迎点赞—— 让更多人看到这篇干货在看—— 你的认可是我持续输出的动力转发—— 分享给身边正在做AI Agent的朋友你的每一个小动作对我都很重要 ❤️关于作者你好我是空门技术栈一个常年和Bug战斗、持续填坑的Java开发者。专注分享✅ Java / Spring Boot / Spring AI Alibaba 企业级实战✅ RAG知识库、AI Agent、多智能体协作落地经验✅ Docker部署、微服务架构、线上问题排查✅ 偶尔聊聊「如何保住头发」这类程序员终极话题不搞水文不贩卖焦虑只写能跑通、能落地、能帮你少加班的实战内容。关注我咱们一起少踩坑多写优雅代码。更多干货推荐告别手动复制接口文档Apifox MCP AI 自动测试让开发效率起飞MySQL MCP Server 从零安装到使用实战AI 直接查询数据库Spring Event 用了三年同事一句话把我问懵了Java 抽象类Abstract Class彻底讲透从基础到多态实战Spring AI Alibaba 多智能体Multi-agent实战6 大协作模式 完整代码Spring AI Alibaba 智能体作为工具实战别再让主 Agent 当人肉路由器了一文搞懂 Spring AI Alibaba Workflow10 个实战案例带你彻底掌握 AI 工作流编排AI 工程进入第四时代Loop Engineering 正在淘汰 Prompt EngineeringRAG 知识库为什么越更新越乱一文讲透生产级文档更新方案GPT-5.6 三档模型Sol、Terra、Luna 怎么选一篇文章讲透Transformers VS vLLM大模型部署到底该选谁从本地运行到生产上线完整解析粉丝福利本文涉及的完整源码 可运行Demo 配置示例已打包整理好。免费获取方式私信空门技术栈领取源码包。有任何问题也欢迎后台私信门主看到都会回复~对文章内容有疑问想看下一篇写什么主题直接在评论区留言你的每一条评论我都会看。说不定下一篇文章的主题就来自你的问题项目合作 / 技术咨询平时工作之余也会接一些技术项目和咨询主要方向⚔️企业级开发Java / Spring Boot 项目开发与重构微服务架构设计与落地系统性能调优、线上问题排查AI 应用落地这是我最近的主力方向Spring AI Alibaba / RAG / Agent 应用开发企业私有知识库搭建AI能力接入现有业务系统大模型本地化部署与调优️技术顾问 / 疑难Bug排查项目架构评审与方案设计线上疑难问题定位解决技术选型与团队培训如果你正遇到以下情况欢迎找我聊聊✅ 想做AI项目但技术方案拿不准✅ 项目卡在某个Bug上很久团队搞不定✅ 想把AI接入现有业务不知道从哪下手✅ 需要靠谱的开发外包或长期技术顾问