
Java 23 种设计模式从踩坑到精通 | 代理模式 —— 你的 AOP 就是用代理实现的摘要当需要在不修改源码的情况下控制对象访问、添加横切逻辑日志、事务、缓存、权限时代理模式是最优雅的解决方案。本文从仓储库存分配的性能优化场景出发完整讲解静态代理、JDK 动态代理、CGLIB 动态代理三种实现方式深入剖析 Spring AOP 的代理选择机制含 Spring Boot 默认策略纠正与自调用失效陷阱结合 MyBatis Mapper、RPC 框架、Mockito 等经典应用帮你彻底掌握“面向切面编程”的基石与避坑指南。《Java 23 种设计模式从踩坑到精通》开篇系列介绍与目录 上一篇享元模式 当前代理模式 下一篇责任链模式 返回系列总目录关键知识点场景驱动通过“报表缓存”案例展示如何在不修改源码的情况下控制对象访问。源码级解析分析 SpringDefaultAopProxyFactory澄清 Spring Boot 默认代理策略proxyTargetClass的常见误区。避坑指南深度讲解 Spring AOP 自调用Self-Invocation导致事务失效的原因及三种解决方案推荐拆分 Service。模式辨析清晰区分代理模式控制访问与装饰器模式增强功能的意图差异。实战应用涵盖 Spring 事务、MyBatis Mapper 代理、RPC 远程调用等真实框架底层原理。1. 从“每次查询都查全库”的性能灾难说起假设你在开发一个仓储管理系统InventoryService负责分配库存。每次调用allocateStock()系统都要走完整的库存查询、锁定、更新流程高并发下数据库压力巨大。你想加缓存、延迟加载、权限校验……但如果直接在InventoryService中写这些逻辑会导致职责混乱InventoryService既要处理分配逻辑又要管理缓存和权限违反开闭原则切换优化策略本地缓存 → Redis必须改核心代码代码冗余10 个 Service 都要缓存就得改 10 次。代理模式Proxy Pattern的解决思路是创建一个“替身”对象代理对真实对象的访问。替身在调用前后做额外操作客户端和真实对象都无需感知。1.1 三种代理速览代理类型特点创建时机需要接口性能代表框架静态代理手动编写代理类编译期是无损耗早期设计模式教学JDK 动态代理反射动态生成运行时是反射开销约10~20xMyBatis Mapper、Spring AOPCGLIB 动态代理字节码生成子类运行时否接近原生约1.2~2xSpring AOP、Hibernate 代理模式的核心在于控制访问权限、延迟加载、日志而非单纯的“功能增强”。这一设计天然符合开闭原则——新增横切逻辑无需修改原类。2. 模式定义与 UML 结构代理模式为其他对象提供一种代理以控制对这个对象的访问。它属于结构型设计模式。三个角色Subject抽象主题定义RealSubject和Proxy的共同接口体现依赖倒置原则——客户端只依赖抽象RealSubject真实主题业务逻辑的真正执行者Proxy代理持有RealSubject引用在调用前后添加控制逻辑。代理与真实对象实现同一接口体现了里氏替换原则。3. 代码实现静态代理3.1 抽象主题publicinterfaceInventoryService{booleanallocateStock(StringproductId,intquantity);}3.2 真实主题publicclassInventoryServiceImplimplementsInventoryService{OverridepublicbooleanallocateStock(StringproductId,intquantity){System.out.println(【库存分配】商品 productId 分配数量 quantity);try{Thread.sleep(500);}catch(InterruptedExceptione){}returntrue;}}3.3 代理类publicclassInventoryServiceProxyimplementsInventoryService{privateInventoryServicerealService;privateMapString,BooleancachenewHashMap();publicInventoryServiceProxy(InventoryServicerealService){this.realServicerealService;}OverridepublicbooleanallocateStock(StringproductId,intquantity){StringkeyproductId_quantity;if(cache.containsKey(key)){System.out.println(【静态代理】缓存命中: key);returncache.get(key);}System.out.println(【静态代理】缓存未命中调用真实服务...);booleanresultrealService.allocateStock(productId,quantity);cache.put(key,result);returnresult;}}代理类只负责缓存InventoryServiceImpl只负责业务单一职责分离清晰。3.4 客户端InventoryServiceservicenewInventoryServiceProxy(newInventoryServiceImpl());service.allocateStock(P1001,5);// 缓存未命中service.allocateStock(P1001,5);// 缓存命中⚠️致命缺点每代理一个类就要手写一个代理类违反开闭原则对扩展的约束。这就是动态代理要解决的问题。4. 代码实现JDK 动态代理JDK 动态代理无需手写代理类只需实现InvocationHandler接口。4.1 通用代理工厂publicclassCacheProxyFactory{SuppressWarnings(unchecked)publicstaticTTcreateProxy(Ttarget,ClassTinterfaceType){return(T)Proxy.newProxyInstance(interfaceType.getClassLoader(),newClass[]{interfaceType},newCacheInvocationHandler(target));}privatestaticclassCacheInvocationHandlerimplementsInvocationHandler{privateObjecttarget;privateMapString,ObjectcachenewConcurrentHashMap();publicCacheInvocationHandler(Objecttarget){this.targettarget;}OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{Stringkeymethod.getName()Arrays.toString(args);returncache.computeIfAbsent(key,k-{try{System.out.println(【JDK动态代理】缓存未命中调用真实方法...);returnmethod.invoke(target,args);}catch(Exceptione){thrownewRuntimeException(e);}});}}}invoke()三个参数proxy是生成的代理对象本身method是被调用的方法args是方法参数。ConcurrentHashMap.computeIfAbsent保证线程安全的原子操作。4.2 客户端InventoryServicerealServicenewInventoryServiceImpl();InventoryServiceproxyCacheProxyFactory.createProxy(realService,InventoryService.class);proxy.allocateStock(P1001,5);// 缓存未命中proxy.allocateStock(P1001,5);// 缓存命中5. CGLIB 动态代理当目标类没有实现任何接口时使用 CGLIB 生成子类代理。Maven 依赖!-- Spring Boot 3.x / Spring 6.x 已内置 CGLIB无需额外引入 --!-- 若独立使用需添加 --dependencygroupIdcglib/groupIdartifactIdcglib/artifactIdversion3.3.0/version/dependency5.1 CGLIB 代理工厂importnet.sf.cglib.proxy.Enhancer;importnet.sf.cglib.proxy.MethodInterceptor;publicclassCglibProxyFactory{SuppressWarnings(unchecked)publicstaticTTcreateProxy(Ttarget){EnhancerenhancernewEnhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback((MethodInterceptor)(obj,method,args,proxy)-{System.out.println(【CGLIB代理】方法执行前);Objectresultproxy.invokeSuper(obj,args);System.out.println(【CGLIB代理】方法执行后);returnresult;});return(T)enhancer.create();}}⚠️限制final类和方法无法代理。Java 14 的record类隐式final同样无法被 CGLIB 代理。Lombok 的Data需避免将代理目标方法标记为final。6. 进阶Spring AOP 源码中的代理机制Transactional、Cacheable、PreAuthorize的背后都是 Spring 通过代理织入横切逻辑。6.1 Spring 如何选择代理方式// Spring 源码简化版DefaultAopProxyFactorypublicAopProxycreateAopProxy(AdvisedSupportconfig){if(config.isOptimize()||config.isProxyTargetClass()||hasNoUserSuppliedProxyInterfaces(config)){Class?targetClassconfig.getTargetClass();if(targetClass.isInterface()||Proxy.isProxyClass(targetClass)){returnnewJdkDynamicAopProxy(config);// JDK 动态代理}returnnewObjenesisCglibAopProxy(config);// CGLIB}returnnewJdkDynamicAopProxy(config);}选择规则环境默认策略说明Spring Framework原生JDK 动态代理优先proxyTargetClassfalse目标类实现接口 → JDK无接口 → CGLIBSpring Boot 2.x / 3.x默认 CGLIBspring.aop.proxy-target-classtrue自动配置强制开启 CGLIB避免强制转类型时的ClassCastException# application.yml 中关闭 CGLIB 强制回退到 JDK 优先spring:aop:proxy-target-class:false Spring AOP 代理模式 责任链模式。代理负责拦截方法调用责任链按顺序执行拦截器事务 → 缓存 → 权限最后调用目标方法。7. 实战陷阱Spring 自调用失效7.1 失效场景ServicepublicclassOrderService{TransactionalpublicvoidcreateOrder(Orderorder){this.sendNotification(order);// ⚠️ 自调用事务失效}Transactional(propagationPropagation.REQUIRES_NEW)publicvoidsendNotification(Orderorder){}}失效原因this指向原始对象绕过 Spring 容器中的代理对象AOP 逻辑全部不生效。7.2 解决方案方案写法优缺点自我注入Autowired private OrderService self;简单存在循环依赖风险AopContext.currentProxy()配置exposeProxytrue需显式配置侵入性强拆分 Service推荐将sendNotification抽到NotificationService符合单一职责根本解决// 推荐方案拆分 ServiceServicepublicclassOrderService{AutowiredprivateNotificationServicenotificationService;TransactionalpublicvoidcreateOrder(Orderorder){notificationService.sendNotification(order);// 通过代理调用事务生效}}8. 代理模式 vs 装饰器模式对比维度代理模式装饰器模式目的控制访问权限、延迟加载、日志增强功能添加职责与被包装对象关系一对一可层层包装任意组合典型应用Spring AOP、MyBatis Mapper、RPCJava I/O 流简单记忆代理是“中介”控访问装饰器是“加料师”加功能。核心区别在于意图——代理要“管控”装饰器要“增强”。9. 框架与实践中的应用Spring AOPTransactional、Cacheable的底层机制MyBatis Mapper只定义接口JDK 动态代理生成实现RPC 框架Dubbo、gRPC 通过代理将本地调用转网络请求单元测试 MockMockito 使用 Byte Buddy 生成代理对象。10. 高频面试与避坑指南 面试必背JDK vs CGLIB→ JDK 基于接口反射CGLIB 基于继承字节码。Spring 选择规则→ 原生 Spring 优先 JDKSpring Boot 默认 CGLIB。自调用失效→this.xxx()绕过代理。推荐拆分 Service 解决。MyBatis Mapper 是什么模式→ 代理模式JDK 动态代理生成接口实现。代理 vs 装饰器→ 代理控访问装饰器加功能。Spring AOP 是代理Java I/O 是装饰器。❌ 常见误区动态代理一定比静态好 ❌ → 静态代理无反射损耗简单场景更优。Spring Boot 默认 JDK 代理 ❌ → Boot 2.x 默认 CGLIB。CGLIB 无法代理record❌ → 正确record隐式final必须用 JDK 代理。11. 总结✅最终建议日常优先用框架代理Spring AOP手动控制用 JDK 动态代理无接口用 CGLIB。注意 Spring Boot 默认 CGLIB 的策略差异以及自调用陷阱——拆分 Service 是根本解法。面试回答“Spring AOP 代理 责任链 避坑自调用”瞬间脱颖而出。 《Java 23 种设计模式从踩坑到精通》快速导航开篇系列介绍与目录上一篇享元模式当前代理模式你在这里下一篇责任链模式 即将发布创建型模式汇总结构型模式汇总行为型模式汇总 关注《Java 23 种设计模式从踩坑到精通》用 25 篇文章彻底吃透设计模式。福利预告全系列代码及 UML 源码将在完结时统一打包开放点击「关注」「收藏」第一时间获取。下一篇责任链模式 —— 请求流转审批流程的本质 即将发布敬请关注 除了设计模式我也在深挖智能物流实战WMS、托盘调度、机器学习落地。欢迎点击头像看看专栏 《出版社物流WMS智能调度实战》。技术相通思路可鉴。