news 2026/10/20 15:38:49

代理模式核心解析:从静态代理到JDK动态代理与CGLIB实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
代理模式核心解析:从静态代理到JDK动态代理与CGLIB实战

你有没有遇到过这种场景:某个业务接口已经稳定运行了好几个月,突然要加操作日志、加参数校验、加缓存、加权限判断,而且这些逻辑几乎和核心业务毫无关系。我一开始的做法很朴素,直接打开实现类,在方法里插入一段代码。但改完就发现问题了——三个方法要加同样的日志,五个接口要加同样的校验,过两周需求又变了,改动面越来越大。真正的转折点是我后来彻底理解了代理模式(Proxy Pattern)这个东西:它最大的价值,就是让你在不修改源码的前提下,对已有对象进行功能增强。Spring AOP、声明式事务、各种ORM框架的Mapper调用,底层用的都是同一套思路。这篇文章我会从静态代理讲到JDK动态代理、CGLIB,再结合真实框架里的玩法、几个高频的坑和判断标准,把代理模式一次讲透。

1. 代理模式到底在解决什么问题:不修改源码的“包一层”艺术

1.1 一个印象很深的需求场景

之前接手过一个老项目,里面有个订单查询接口,线上跑了大半年一直很稳。某天产品提了一个需求:需要记录每一次查询的耗时,超过500ms的要单独打一条警告日志。当时摆在我面前的选择有三个:第一,直接改实现类,在方法入口和出口分别加计时逻辑——这个方法最快,但改完要重新回归整个类的所有调用方;第二,把日志逻辑抽成一个公共方法,在每个查询方法里手动调用——这比第一个干净,但本质上还是侵入业务代码,方法一多照样乱;第三,不动原始实现,在外面包一层“壳”,由壳负责计时,原类只负责业务逻辑。第三个方案就是代理模式的雏形。

这个场景特别典型:**你并不需要改变目标对象的业务行为,你只是要在它执行前后做一些额外的处理。**日志、事务、权限、缓存校验、异常兜底,这些都属于横切逻辑。它们散落在业务代码里会很难受,因为业务方法本该只表达“我做了什么”,结果还要夹杂一堆“我在什么条件下能做”“我做之前要记录什么”“我做之后要通知谁”。

1.2 代理模式的核心角色划分

代理模式的标准结构里有三个角色:

  • 抽象主题:定义业务方法的接口,客户端面向它编程,屏蔽真实对象和代理对象的差异。
  • 真实主题:真正处理业务逻辑的类,也就是被增强的目标。
  • 代理主题:持有真实主题的引用,对外暴露同样的接口,在调用真实主题之前或之后插入增强逻辑。

用生活里的事情去类比,就是明星本人(真实主题)和经纪人(代理主题)。你想找明星演出,不会直接联系明星,而是先找经纪人谈档期、谈价格、签合同。明星只需要上台表演,经纪人在上台前负责搞定所有附加事项。代理对象的本质就是一个“转发者 + 增强者”:它把调用请求转发给真实对象,同时在前后加上自己的处理逻辑。

这个模式解决的核心痛点,正是开闭原则(对扩展开放、对修改关闭)的体现——你想给一个类增加能力,却不希望改动它已有的代码。改别人写好的源码有风险,改依赖包里的类根本没有机会,代理模式在这种场景下几乎是唯一优雅的出路。

1.3 静态代理:最直观的“包一层”

先看静态代理。静态代理的意思是,代理类由程序员手写,一个接口对应一个代理类,编译期就确定下来。

假设有一个订单服务:

public interface OrderService { Long createOrder(String userId, List<Long> skuIds); void cancelOrder(Long orderId); } public class OrderServiceImpl implements OrderService { @Override public Long createOrder(String userId, List<Long> skuIds) { // 省略真实的下单逻辑 System.out.println("处理核心业务: 创建订单..."); return System.currentTimeMillis(); } @Override public void cancelOrder(Long orderId) { System.out.println("处理核心业务: 取消订单..."); } }

现在要给createOrder和cancelOrder都加上日志和耗时统计,在不改OrderServiceImpl的前提下,手写一个代理类:

public class OrderServiceProxy implements OrderService { private final OrderService target; public OrderServiceProxy(OrderService target) { this.target = target; } @Override public Long createOrder(String userId, List<Long> skuIds) { long start = System.currentTimeMillis(); System.out.println("Proxy: 开始创建订单, userId=" + userId); try { Long orderId = target.createOrder(userId, skuIds); System.out.println("Proxy: 创建订单成功, orderId=" + orderId); return orderId; } catch (Exception e) { System.out.println("Proxy: 创建订单失败, 记录日志"); throw e; } finally { System.out.println("Proxy: 创建订单耗时=" + (System.currentTimeMillis() - start) + "ms"); } } @Override public void cancelOrder(Long orderId) { long start = System.currentTimeMillis(); System.out.println("Proxy: 开始取消订单, orderId=" + orderId); try { target.cancelOrder(orderId); System.out.println("Proxy: 取消订单成功"); } finally { System.out.println("Proxy: 取消订单耗时=" + (System.currentTimeMillis() - start) + "ms"); } } }

使用的时候,把真实对象塞进代理对象里,所有调用方拿到的都是代理对象:

OrderService realService = new OrderServiceImpl(); OrderService proxy = new OrderServiceProxy(realService); proxy.createOrder("user123", Arrays.asList(1001L, 1002L));

这样做的好处是原始类干干净净,代理类和业务类各管各的。但我很快发现它的问题也很明显:**代理逻辑没法复用。**OrderServiceProxy里的耗时统计和日志打印,换到另外一个服务上就得重新写一遍。系统里如果有一百个Service,那不是要手工建一百个代理类?而且每次接口增加一个方法,代理类就要同步修改,反而变成了一种维护负担。于是就有了动态代理。

2. 从静态代理到动态代理:省下的代码都去了哪里

2.1 静态代理的痛点:代理类爆炸和重复代码

静态代理的缺点用一句话概括:**代理类与目标类一一绑定,增强逻辑散落在各个代理类中,无法统一抽取。**当时我那个项目里有十几个业务Service,按静态代理的思路,每个Service都要建一个代理类,而且每个代理类里几乎都是同样的日志、同样的计时、同样的异常捕获,区别只是调用的方法名不同。这种重复让我意识到,手写代理类的方案本质上只是把侵入从“业务实现类”转移到了“代理类”,没有真正解决复用问题。

动态代理的思路是:既然代理类的结构都差不多——取目标类的方法、在执行前做增强、调用目标方法、执行后做增强——那能不能把这个结构做成一个通用引擎,让代理类在运行时自动生成,我们只需要描述“增强逻辑”这一部分?

2.2 JDK动态代理的核心机制:InvocationHandler接管转发

JDK动态代理就是从Java 1.3开始内置的代理方案。它不再需要手写代理类,而是通过java.lang.reflect.Proxy这个工厂类,在运行时为指定的接口动态生成代理对象。

核心就两个东西:Proxy.newProxyInstance用来创建代理对象,InvocationHandler用来接收所有方法调用。

public interface MyInvocationHandler { Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }

InvocationHandler里那个invoke方法,会在代理对象上任何一个方法被调用时触发。也就是说,代理对象自己并不实现任何业务逻辑,它把所有调用都转交给InvocationHandler,由InvocationHandler决定“在转发给真实对象之前做什么、之后做什么”。

我写一个通用的耗时统计代理,它适用于任何接口:

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class TimeLoggingHandler implements InvocationHandler { private final Object target; public TimeLoggingHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = System.currentTimeMillis(); System.out.println("[" + method.getDeclaringClass().getSimpleName() + "." + method.getName() + "] 开始调用"); try { Object result = method.invoke(target, args); System.out.println("[" + method.getName() + "] 调用成功, 耗时=" + (System.currentTimeMillis() - start) + "ms"); return result; } catch (Exception e) { System.out.println("[" + method.getName() + "] 调用异常: " + e.getMessage()); throw e; } } @SuppressWarnings("unchecked") public static <T> T wrap(T target, Class<T> interfaceType) { return (T) Proxy.newProxyInstance( target.getClass().getClassLoader(), new Class<?>[]{interfaceType}, new TimeLoggingHandler(target) ); } }

调用方变得非常干净:

OrderService realService = new OrderServiceImpl(); OrderService proxy = TimeLoggingHandler.wrap(realService, OrderService.class); proxy.createOrder("user123", Arrays.asList(1001L, 1002L));

注意几个关键点:

  • method.invoke(target, args)是通过反射执行真实对象的方法,这就是转发动作。
  • 在invoke方法里围绕method.invoke前后写增强逻辑,这是对“执行前增强、执行后增强”的精确控制。
  • 那个proxy参数指向代理对象本身,一般不直接使用,因为如果在这个方法里调用代理对象的任何方法,都会再次进入invoke,容易造成无限递归。

从代码量上看,静态代理需要为每个业务接口写一个类,动态代理只需要写一个InvocationHandler,并且它对所有接口通用。这套改造一完成,新接入的服务只需要一行wrap(...),整个项目的横切逻辑就控制在一个类里了。

2.3 为什么JDK动态代理必须面向接口

用JDK动态代理时有一个硬性前提:目标对象必须实现至少一个接口。原因是Proxy.newProxyInstance生成的新类,在虚拟机里只能通过继承java.lang.reflect.Proxy来创建,而Java是单继承,它无法再继承任何业务类,所以只能通过实现接口来暴露方法。

Proxy的字节码是运行时动态生成的,JVM规定一个类只能有一个父类,既然父类已经被Proxy占用了,那方法定义就只能靠实现接口来补。这就是JDK动态代理的边界:它能代理的是“接口层面”的行为,而非某个具体类的行为。

这也引出了一个很实际的问题:很多老代码里的业务类是直接写死的一个Java类,没有抽象出接口,或者用了final修饰。这时候JDK动态代理就无能为力了,需要另一个方案登场,也就是CGLIB。

3. CGLIB:没有接口时的代理方案到底怎么选

3.1 CGLIB的底层思路:生成子类并重写方法

CGLIB(Code Generation Library)是一个第三方字节码生成库,它的思路和JDK动态代理完全不同。它不会去实现接口,而是在运行时生成目标类的子类,并在子类中重写父类的非final方法,然后在重写的方法里织入增强逻辑。

你可以这么理解:原本的业务类是父类,CGLIB动态地制造了一个“披着父类外衣的孩子”,这个孩子重写了父类的方法,在方法里先做增强,再调用父类的实现。外部使用时拿到的是这个子类实例,类型上依然是父类的样子,所以调用方完全无感知。

因为不依赖接口,所以CGLIB可以代理普通类和方法。代价也很明显:

  • final类无法被代理,因为final类无法被继承。
  • final方法无法被增强,因为子类无法重写final方法。
  • private方法无法被代理,因为子类看不到父类的私有方法。

3.2 Enhancer.MethodInterceptor的写法

CGLIB的使用很直接,核心类是Enhancer,核心接口是MethodInterceptor。还是用订单服务的例子,现在假设没有OrderService接口,只有一个OrderServiceImpl类:

public class OrderServiceImpl { public Long createOrder(String userId, List<Long> skuIds) { System.out.println("处理核心业务: 创建订单..."); return System.currentTimeMillis(); } public void cancelOrder(Long orderId) { System.out.println("处理核心业务: 取消订单..."); } }

给它生成代理对象:

import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CglibTimeProxy { public static <T> T create(T target) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> { long start = System.currentTimeMillis(); try { Object result = proxy.invokeSuper(obj, args); System.out.println("[" + method.getName() + "] 耗时=" + (System.currentTimeMillis() - start) + "ms"); return result; } catch (Throwable t) { System.out.println("[" + method.getName() + "] 异常: " + t.getMessage()); throw t; } }); return (T) enhancer.create(); } }

这里和JDK动态代理有一个显著区别:InvocationHandler调用目标方法用的是method.invoke(target, args),CGLIB的MethodInterceptor调用父类方法用的是proxy.invokeSuper(obj, args)。MethodProxy是CGLIB生成的一个高效调用类,它会直接生成字节码指令去调用父类的方法,而不是走反射,因此性能上优于JDK动态代理的反射调用。

3.3 JDK动态代理和CGLIB怎么选

这两者的区别,我整理了一个对照表:

对比维度JDK动态代理CGLIB
代理生成机制运行时生成接口实现类运行时生成目标类的子类
前提要求目标必须实现接口目标类可继承,方法非final
底层调用方式反射调用字节码直接调用父类方法
创建代理对象的开销相对低相对高(需要生成字节码)
方法调用性能反射有额外开销忽略反射开销,效率更高
依赖JDK内置,无额外依赖需要引入CGLIB库
典型使用接口明确的RPC、Mapper、AOP切面无接口的业务类、Spring默认场景

说一个很反直觉的点:**性能好不等于应该优先选。**CGLIB创建目标子类时,需要生成并加载新的类,如果目标类非常多,启动期会有一波开销;而且它生成的类在调试时更容易让人困惑——堆栈里出现一堆类似OrderServiceImpl$$EnhancerByCGLIB$$xxx的怪异类名。很多时候,优先用JDK动态代理反而是更稳妥的选择。框架层面的默认策略也在变化:早期Spring AOP默认用JDK动态代理,后来某个版本开始对无接口的类自动切到CGLIB,再后来在Spring Boot 2.x里,AOP的默认实现改成了CGLIB,官方给出的理由是:接口变化时更稳定、支持自调用更友好。

实际工程里我的选择逻辑很简单:**目标类有接口,能用JDK动态代理就用JDK;目标类没有接口,或者需要特殊优化高频调用路径,再考虑CGLIB。**不要让“哪个性能高”成为唯一判断标准,先看代码收口形态,再看性能验证数据。

4. 框架里的代理模式:从AOP到Mapper和延迟加载的典型玩法

4.1 AOP切面:代理模式最典型的工程化落地

前面讲了这么多,如果你还没把代理模式和日常开发联系起来,看一下Spring AOP就懂了。Spring AOP的底层封装的就是代理模式:你写了一个@Aspect切面类,定义了@Before、@After、@Around等通知方法,Spring在容器启动时会把所有匹配的Bean包装成代理对象。客户端注入到业务代码里的,根本不是原始的Bean,而是那个代理对象。

流程大概是这样:

  1. Spring解析切面定义,找出所有匹配的Bean。
  2. 根据目标Bean是否实现接口,决定使用JDK动态代理还是CGLIB。
  3. 生成代理对象,把切面逻辑封装成Advice挂在调用链上。
  4. 客户端从容器里拿到的Bean已经是代理对象,调用方法时先走增强逻辑,再走真实业务方法。

最常见的例子就是@Transactional。你写一个Service方法,加一个@Transactional注解,Spring容器给你的Bean被代理,方法执行前开启事务,业务方法执行完后提交事务,发生异常则回滚。你自己在方法里完全看不到任何事务代码,这就是代理模式“不修改源码却能增强功能”的极致体现。

4.2 Mapper接口的代理玩法:没有实现类也能有完整行为

代理模式还有一种更“野”的玩法,不是增强已有实现,而是为“从未写过实现类”的接口提供完整行为。这一点在数据访问框架的Mapper接口上体现得最明显。

Mapper接口通常只定义方法签名,没有实现类。框架的启动阶段会为每个Mapper接口生成一个代理对象,接口方法的每次调用都会进入同一个InvocationHandler。处理器内部是怎么做的呢?它会解析当前方法上的注解或者映射配置,拼装SQL,执行查询,再把结果集映射为实体对象返回。这些逻辑全部封装在一起,对上层调用者来说,接口是黑盒,但只要注入就能用,方法的执行细节完全由代理对象控制。

这个场景给了另一个启发:**代理模式不仅可以“拦截和包装”,还可以“凭空赋值行为”。**代理对象是接口的实现,它完全可以自己决定每个方法该干什么,而不只是转发给真实对象。很多框架在做的“接口即服务”玩法,本质都是这么回事。

4.3 虚拟代理、保护代理、远程代理:几种常见的变体

除了切面和Mapper,代理模式在架构层面还有一些经典变体,我挑三个比较有代表性的展开:

  • 远程代理:客户端只想调用一个普通方法,但目标服务在另一台机器上。客户端本地持有一个代理对象,这个代理对象负责把方法调用信息序列化,通过网络发送到远端服务,远端执行完再返回结果。RPC框架的核心就是这个思路,调用方以为自己调的是本地接口,其实横跨了整个网络。这里面把“访问控制”和“位置透明”做到了极致。

  • 虚拟代理:当对象创建成本非常高时,可以先创建一个轻量级的代理对象放在那里,真正要使用数据的那一刻才去初始化真实对象。典型的场景是加载大图和初始化重型资源。虚拟代理的核心价值是延迟,降低启动开销,让系统快速响应。

  • 保护代理:代理对象在转发调用之前,先检查调用方是否有权限。比如管理员才能删除数据,普通用户只能查询。权限校验逻辑集中在代理层,而不是散落在每个业务方法里,这样天然形成了安全边界。

这三种变体提醒我们,遇到代理模式不要只想到“加日志加事务”,它的适用面比很多人想的宽得多。

5. 代理模式实操中的高频坑点与排查记录

5.1 内部方法自调用导致增强逻辑失效

这是代理模式最经典的坑,没有之一。假设有一个订单服务:

@Service public class OrderService { @Transactional public void createOrder(OrderDTO order) { saveOrder(order); deductStock(order.getSkuId(), order.getQuantity()); } @Transactional public void deductStock(Long skuId, Integer quantity) { // 扣减库存 } }

你看,createOrder方法内部调用了this.deductStock(),deductStock上明明加了@Transactional,但实际运行时这个事务不会生效。因为createOrder调用this.deductStock()时,调用的是当前真实对象的方法,没有经过代理对象。代理对象只在容器外部包裹了一层,可一旦进入了真实对象的内部,从内部发起的自调用就不会再经过代理。

这个问题的排查链路很值得记录。我先是在deductStock里打了日志,发现SQL正常执行了,但事务日志里没有出现事务开启记录。接着我怀疑是不是事务管理器配置出问题,排查了一圈没发现异常。最后在调试器里观察当前对象,发现方法的调用者是原始类而非代理类,才意识到是自调用绕过了代理。

解决思路有几种:

  1. 把被调用的子逻辑单独放进另一个类,通过注入外部对象来调用,而不是直接this调用,这是最干净的做法。
  2. 注入自身代理:把自己代理后的对象注进来,用这个代理对象调用内部方法,保证每次都经过增强链路。
  3. 通过特定上下文获取当前代理对象:比如一些框架提供AopContext.currentProxy()这类工具,但需要额外配置线程绑定,有一定的使用条件。
  4. 把事务边界上移:直接在createOrder方法上开事务,避免内部方法依赖独立事务。

5.2 final方法和final类的限制

只要用了代理生成子类的方案,final就是个绕不开的坎。CGLIB生成子类时,如果父类方法用final修饰,子类无法重写,增强逻辑就不生效,调用会直接落到原方法上。如果整个类是final的,代理对象压根生成不了,项目启动时可能直接抛异常。

这个坑我曾经在排查一个“为什么缓存一直没生效”的时候遇到过。某个查询方法被调用次数特别多,我想给它加个缓存代理,但方法被final修饰了,结果代理静默失效,我盯着代码看了半天,死活想不通为什么增强逻辑没有被触发。后来用javap看了一下类的结构才恍然大悟,CGLIB生成了子类,但由于父类方法final无法覆盖,增强逻辑根本找不到切入点。

排查经验:**遇到代理不生效或生效了一半,第一反应是看目标类和方法的修饰符,再用反射打印方法的modifiers,确认是否有final。**如果在业务代码里发现某个方法经常要加代理增强,最好不要用final修饰它。

5.3 代理对象在集合与判等场景中的类型陷阱

代理对象的类不是原始类。这个事实在日常开发中很容易被忽略,但它会在最不经意的地方咬你一口。

场景一:if (obj instanceof OrderServiceImpl)。当obj是一个CGLIB代理对象时,这个判断会是false。因为代理生成的类虽然继承了OrderServiceImpl,但instanceof判断的是“是不是该类或其子类”,原始类当然不是代理生成的子类。如果代码里有针对具体实现类的类型判断,往往会走到错误分支。

场景二:(OrderServiceImpl) proxy强转。如果proxy是JDK动态代理生成的接口实现类,它和OrderServiceImpl之间没有继承关系,强转一定抛ClassCastException。如果是CGLIB代理,因为代理类继承了OrderServiceImpl,强转反而可能成功——这种不确定性让问题更加隐蔽。

场景三:equals和hashCode的处理。有些代理框架会拦截这两个方法,导致两个业务上相等的对象在哈希集合中的表现不一致。你往Map里放了一个代理对象,用另一个等价的代理对象去取,可能取不到。我处理这类问题时通常的做法是:在业务对象的equals/hashCode实现里,显式使用一个固定的业务标识字段(比如ID),并且明确排除代理类本身对等值判断的影响。

5.4 多层代理下的性能损耗与定位困难

当一个对象被多个增强逻辑代理时,调用链会变得很长。比如日志代理包着缓存代理,缓存代理包着事务代理,事务代理包着真正业务对象。调用一次方法,要把这层包装逐层穿透,反射和字节码调用带来的额外开销会被放大,尤其在高频调用和嵌套调用场景下不容忽视。

这就是过度设计的代价。定位问题时,堆栈会很长,充满了各种EnhancerByCGLIB、Proxy$$类名。如果连接下发就出错,你根本看不出来是自己的业务代码有问题还是代理链上的某个增强逻辑出了问题。

碰到这种情况我的一般排查顺序是:

  1. 先看当前Bean的类名,确认它被哪些代理层包裹。
  2. 看增强器/过滤器的执行顺序,手动算一遍从入口到目标方法的层数。
  3. 必要时临时去掉某些代理层,看问题是否消失,快速收敛范围。
  4. 确认是性能问题,就用真机Profile抓CPU热点,而不是凭感觉猜哪层慢。

6. 要不要用代理、什么时候别用:我的判断标准

6.1 代理模式和装饰器、适配器的边界

很多人分不清代理模式和装饰器模式,因为它们看起来很像,都是“包一层”。它们最大的区别在于意图:

  • 装饰器模式的重点是给对象增加新的职责。比如InputStream外面包一层BufferedInputStream,是为了增加缓冲功能。装饰器对客户端是透明的,客户端知道自己拿到的是一个被增强后的对象,它强调的是增强。
  • 代理模式的重点是控制访问或者代为处理。比如你调代理对象的方法,代理决定要不要转发给真实对象、转发前后做什么。代理更强调保护、隔离、延迟、转发。

适配器模式则完全不一样,它解决的是接口不兼容的问题——把一个接口转换成客户端期望的另一个接口。它不增加逻辑,不控制访问,重点是翻译和转换。

工程中这三种模式可能出现同一种代码结构,都是包一层类实现同一个接口,但背后的意图完全不同。这种意图差异会影响代码的组织方式:你是在包装内部对象暴露自己的行为,还是在控制对内部对象的访问,想清楚这个,代码就自然分开了。

6.2 代理模式的风险点:不要为了用而用

代理模式不是银弹,我见过不少业务代码把代理用得很过火,导致系统难调试、难维护。什么时候不该用,我总结了几条经验:

  • **增强逻辑只针对一个类的一个方法时,不要去搞动态代理。**直接在那个方法里写逻辑,或者用装饰器包一层,简单直接,不引入运行时机制。
  • **调用频率极高、并发量很大的方法,要小心代理的反射和字节码开销。**如果确实需要代理增强,优先选CGLIB的MethodProxy,或者考虑在启动期做代理预热,避免第一次调用时触发类生成和反射调用的额外损耗。
  • **团队对代理机制不了解时,代理会给排查问题增加认知负担。**如果项目里没人见过$$EnhancerByCGLIB$$这种类名,出问题时大家会不知所措。这时候需要先在团队里普及一下代理原理,或者干脆把增强逻辑收敛到极少数地方。
  • **增强逻辑本身不稳定时,不要放进代理。**代理层相当于所有调用方的必经之路,一旦代理里的代码抛了异常,所有业务都会被影响。代理里只放相对稳定的横切逻辑,比如日志、权限、事务,而不是临时性的实验代码。

6.3 我对代理模式的实际体会与选择建议

代理模式是我认为设计模式里“杠杆率”最高的几个之一。它不复杂,三句话说清楚:包一层、转发、增强。但真正理解它,需要你在AOP失效、Mapper代理、自调用事务断裂这些问题里摸爬滚打过一遍才体会得到。

我现在的选择逻辑一般长这样。第一,先判断增强逻辑是不是横切的,也就是多个不相关的地方都需要同样的处理。如果是,代理模式值得考虑;如果只是单个方法,直接写在方法里更实在。第二,判断我能不能改目标源码。能改,但改起来波及面很大,用代理;根本不能改,那没得选,用代理。第三,判断性能和代调试成本能不能接受。项目启动慢、调用链深的场景就需要冷静一点,必要时把代理收敛到核心边界,而不是所有类都套一层。

最后分享一个小技巧。写代理相关的代码时,我习惯在增强逻辑里输出当前代理对象的真实类名,比如this.getClass().getName(),开发阶段这样能快速确认容器里到底注入的是原始Bean还是代理Bean,也能立刻发现是不是有自调用绕过代理的问题。这个习惯帮我排查过好几次类型陷阱,写代码和调试的时候都踏实很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 15:38:10

Java老系统注释重构:业务语义注释与状态机还原实践

简介&#xff1a;本资源是一份面向Java初学者与Web开发入门者的轻量级移动应用大厅学习案例&#xff0c;聚焦ServletJSP技术栈的MVC实践。项目以完整可运行的HTML静态页面为核心&#xff0c;辅以详尽中文注释&#xff0c;清晰呈现用户登录、服务浏览等典型移动端大厅交互逻辑的…

作者头像 李华
网站建设 2026/10/20 15:37:22

个人Agent崛起:企业与消费者的博弈规则正在被改写

近一年多&#xff0c;"个人Agent"从一个概念名词慢慢变成了真实可用的工具形态。国外一位科技企业家最近撰文聊到一个有意思的切入点&#xff1a;当每个消费者都拥有了自己的智能代理&#xff0c;企业和消费者之间的博弈方式会完全改写。这篇文章在行业里讨论度不低&…

作者头像 李华
网站建设 2026/10/20 15:37:09

AI智能体沙箱失控排查与治理:从限额熔断到网络边界

上周凌晨两点&#xff0c;我盯着监控面板上一排排“活着”的沙箱&#xff0c;CPU 曲线齐刷刷顶满 100%&#xff0c;队列里的任务全部卡在原地。这种场景做 AI 智能体工程的人应该都不陌生&#xff1a;你把一个看似安全的隔离环境交给模型生成的代码&#xff0c;它却成了失控的温…

作者头像 李华
网站建设 2026/10/20 15:36:43

AI Agent落地选型指南:从市场版图到靠谱度评估全解析

这几年 AI Agent 的声浪一路狂飙&#xff0c;打开任何一个技术社区或行业群&#xff0c;满屏都是“智能体”“自主决策”“多步骤任务”这些词。但作为在这条赛道踩过不少坑、也真金白银落过几套系统的从业者&#xff0c;我的体感是&#xff1a;市场版图看着热闹&#xff0c;真…

作者头像 李华
网站建设 2026/10/20 15:36:38

低功耗电源设计:电容如何影响纹波噪声?从机理到布局实测

纹波噪声这事儿&#xff0c;做硬件的迟早都得面对。尤其这几年低功耗设备越做越多&#xff0c;一块电池恨不得用半年&#xff0c;MCU大部分时间在睡觉&#xff0c;偶尔醒过来干点活又睡回去。这种工作模式对电源的要求跟传统设备完全不一样&#xff0c;纹波、瞬态、噪声全都变成…

作者头像 李华
网站建设 2026/10/20 15:36:27

路-网耦合下电动汽车充电负荷时空分布预测的Matlab实现

做充电负荷预测这几年&#xff0c;我越来越觉得“按历史负荷曲线外推”的老办法快到头了。电动车渗透率上来之后&#xff0c;充电负荷不再是一根均匀上涨的曲线&#xff0c;而是跟着人流车流走的&#xff1a;早高峰的办公园区、晚高峰的居民小区、节假日的商圈快充站&#xff0…

作者头像 李华