news 2026/8/28 6:22:28

Spring代理模式深度解析:从AOP原理到事务模拟实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring代理模式深度解析:从AOP原理到事务模拟实战

1. 项目概述:为什么Spring开发者绕不开代理模式

如果你正在使用Spring框架,尤其是Spring 5,那么“代理模式”这四个字你一定不陌生。它就像空气一样,无处不在,却又常常被我们忽略其存在。从@Transactional注解让方法自动拥有事务能力,到@Cacheable实现缓存逻辑的无缝嵌入,再到AOP(面向切面编程)实现日志、权限校验等横切关注点,其底层基石正是代理模式。很多开发者在使用Spring时感觉很“魔法”——加个注解,功能就自动实现了。这层“魔法”的面纱,很大程度上就是由代理模式织就的。

我刚开始接触Spring时,也曾对@Autowired进来的Bean为什么能执行额外的逻辑感到困惑。直到深入理解了代理模式,尤其是Spring中动态代理的实现,很多问题才豁然开朗。比如,为什么this调用同一个类的方法会导致@Transactional失效?为什么有些Bean需要接口而有些不需要?这些看似琐碎的“坑”,其根源都在于对代理机制的理解不透彻。

本次分享,我将结合Spring 5的常见应用场景,带你从静态代理到动态代理,彻底搞懂这个支撑起Spring AOP半边天的核心设计模式。我们不止于理论,更会通过模拟Spring中常见的场景(比如模拟一个简易的声明式事务管理器)来动手实践,让你真正理解代理是如何被创建、如何介入方法调用,以及在实际开发中需要注意哪些关键点。无论你是想更深入地理解Spring原理以优化应用性能,还是想解决因代理引发的诡异Bug,这篇文章都将为你提供清晰的路径。

2. 核心概念与模式解析:代理的本质是什么

2.1 代理模式的设计思想与价值

代理模式,顾名思义,就是为一个对象提供一个替身或占位符,以控制对这个对象的访问。它的核心价值在于“控制”二字。在生活中,明星的经纪人就扮演着代理的角色。导演想找明星拍戏,不会直接联系明星本人,而是先联系经纪人。经纪人会帮明星处理琐事(如筛选剧本、洽谈片酬),只有在必要的时候,才会让明星本人出面完成核心工作(拍戏)。在这个过程中,经纪人增强了明星的功能(多了筛选和洽谈的能力),也可能对访问进行了限制(不接某些类型的戏)。

映射到软件设计中,代理模式的核心角色通常有三个:

  1. 抽象主题(Subject):定义了真实主题和代理主题的共同接口。这样,客户端就可以面向接口编程,无需关心自己拿到的是真实对象还是代理对象。在Java中,这通常是一个接口。
  2. 真实主题(Real Subject):真正执行业务逻辑的核心对象,是代理最终要委托的对象。
  3. 代理主题(Proxy):持有对真实主题的引用,客户端直接与之交互。它可以在调用真实主题的方法前后,添加额外的处理逻辑。

这种模式带来的好处非常明显:

  • 职责清晰,符合单一职责原则:真实对象只需关注核心业务逻辑(如“保存用户”),而像日志记录、事务管理、安全检查等“增强”逻辑,可以交给代理对象来完成。
  • 增强对象功能,符合开闭原则:想要给一个已有的类增加新功能(如监控方法耗时),无需修改原有类的代码,只需创建一个代理类即可。这极大地提高了系统的可扩展性和可维护性。
  • 控制访问:代理可以在调用真实方法前进行权限校验,如果校验不通过,则直接拒绝访问,保护了真实对象。

在Spring框架中,AOP正是代理模式最经典的应用。Spring AOP的目标就是将那些分散在各个业务方法中的公共行为(如事务、日志)抽取出来,形成独立的“切面”,然后通过代理机制,动态地将这些切面织入到目标方法中。

2.2 静态代理:简单直接的增强方式

静态代理,顾名思义,代理类在编译期就已经确定并创建好了。我们需要手动编写代理类,并实现与真实主题相同的接口。

让我们通过一个最简单的例子来理解。假设我们有一个用户服务接口和实现类:

// 1. 抽象主题 - UserService接口 public interface UserService { void addUser(String username); } // 2. 真实主题 - UserServiceImpl实现类 public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("保存用户: " + username + " 到数据库..."); // 模拟耗时 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }

现在,我们想在不修改UserServiceImpl代码的前提下,为addUser方法添加日志记录和耗时监控的功能。这时,就可以创建一个静态代理类:

// 3. 静态代理类 - UserServiceStaticProxy public class UserServiceStaticProxy implements UserService { // 持有真实主题的引用 private UserService target; public UserServiceStaticProxy(UserService target) { this.target = target; } @Override public void addUser(String username) { // 前置增强:记录日志 System.out.println("[静态代理] 开始执行 addUser, 参数: " + username); long startTime = System.currentTimeMillis(); // 调用真实对象的方法 target.addUser(username); // 后置增强:记录耗时 long endTime = System.currentTimeMillis(); System.out.println("[静态代理] 执行结束,耗时: " + (endTime - startTime) + "ms"); } }

客户端这样使用:

public class Client { public static void main(String[] args) { // 创建真实对象 UserService realService = new UserServiceImpl(); // 创建代理对象,将真实对象传入 UserService proxy = new UserServiceStaticProxy(realService); // 客户端与代理对象交互 proxy.addUser("张三"); } }

运行后,你会看到除了核心的保存用户逻辑,还打印了日志和耗时信息。

静态代理的优缺点与实操心得:

  • 优点:非常直观,易于理解和实现。它能很好地完成增强功能的任务。
  • 缺点冗余和僵化。这是静态代理最致命的问题。如果UserService接口有10个方法,我们需要增强其中8个,那么代理类就必须手动实现这8个方法,并在每个方法里编写重复的增强代码(如日志、耗时)。一旦接口新增方法,代理类和所有实现类都需要同步修改,违反了开闭原则。
  • 注意事项:静态代理在小型项目或方法数量极少时可以作为快速解决方案。但在Spring这种大型框架中,需要代理的Bean成千上万,方法更是数不胜数,手动编写静态代理类是完全不现实的。因此,Spring选择了更强大的动态代理。

3. 动态代理深度剖析:Spring AOP的引擎

正是因为静态代理的局限性,动态代理才成为框架级应用的必然选择。动态代理的核心在于:代理类不是在编译期生成的,而是在程序运行时,根据需要动态地在内存中创建。在Java中,主要有两种实现动态代理的方式:JDK动态代理和CGLIB(Code Generation Library)动态代理。Spring默认会根据目标类的情况智能选择使用哪一种。

3.1 JDK动态代理:基于接口的代理

JDK动态代理是Java标准库java.lang.reflect包自带的特性。它有一个关键限制:只能为接口创建代理。其核心是java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。

我们来用JDK动态代理实现上面同样的增强功能:

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // 1. 实现InvocationHandler接口,定义增强逻辑 public class LogInvocationHandler implements InvocationHandler { // 持有真实目标对象 private Object target; public LogInvocationHandler(Object target) { this.target = target; } /** * 代理对象任何方法被调用时,都会走到这个invoke方法 * @param proxy 代理对象本身(通常很少直接用) * @param method 被调用的方法对象 * @param args 方法参数 * @return 方法执行结果 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前置增强 System.out.println("[JDK动态代理] 开始执行 " + method.getName() + ", 参数: " + Arrays.toString(args)); long startTime = System.currentTimeMillis(); // 利用反射,调用真实目标对象的方法 Object result = method.invoke(target, args); // 后置增强 long endTime = System.currentTimeMillis(); System.out.println("[JDK动态代理] 执行结束,耗时: " + (endTime - startTime) + "ms"); return result; } }

客户端使用方式:

public class Client { public static void main(String[] args) { // 1. 创建真实对象 UserService realService = new UserServiceImpl(); // 2. 创建InvocationHandler,传入真实对象 InvocationHandler handler = new LogInvocationHandler(realService); // 3. 使用Proxy类动态创建代理对象 UserService proxy = (UserService) Proxy.newProxyInstance( realService.getClass().getClassLoader(), // 类加载器 realService.getClass().getInterfaces(), // 目标对象实现的接口数组 handler // 增强逻辑处理器 ); // 4. 使用代理对象 proxy.addUser("李四"); } }

JDK动态代理的核心机制与注意事项:

  • Proxy.newProxyInstance:这个静态方法是创建代理对象的工厂。它需要三个参数:类加载器、接口数组和InvocationHandler。生成的代理类实现了指定的所有接口。
  • InvocationHandler.invoke:这是增强逻辑的“总闸”。所有对代理对象方法的调用,都会被路由到这个invoke方法。你可以在这里自由地添加前置、后置、环绕甚至异常处理逻辑。
  • 生成的代理类:运行时生成的代理类,其类名通常类似于$Proxy0。你可以通过设置系统属性jdk.proxy.ProxyGenerator.saveGeneratedFilestrue,将其保存到磁盘查看。你会发现它继承了Proxy类并实现了我们指定的接口。
  • 重要限制:因为它基于接口,所以无法代理没有实现任何接口的普通类。这是JDK动态代理与CGLIB最根本的区别。
  • 性能考量:JDK动态代理使用反射调用目标方法(method.invoke),在早期版本中性能开销较大。但在现代JVM(尤其是JDK 8以后)中,由于反射调用的优化(如MethodHandle),其性能已经非常可观,与CGLIB的差距在大多数场景下可以忽略不计。

3.2 CGLIB动态代理:基于继承的代理

CGLIB是一个强大的、高性能的代码生成库,它通过继承目标类的方式创建子类,并在子类中重写父类方法来实现代理。因此,它可以代理没有实现接口的类

Spring如果发现目标类没有实现接口,就会自动使用CGLIB来创建代理。我们需要引入CGLIB的依赖(Spring Core已经包含)。

<!-- 如果单独使用,需要引入 --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>

使用CGLIB实现代理:

import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; // 1. 实现MethodInterceptor接口,定义增强逻辑 public class LogMethodInterceptor implements MethodInterceptor { /** * @param obj 代理对象(CGLIB生成的子类实例) * @param method 被拦截的方法(父类方法) * @param args 方法参数 * @param proxy 用于调用父类(未被拦截的原始)方法的代理 * @return 方法执行结果 */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // 前置增强 System.out.println("[CGLIB动态代理] 开始执行 " + method.getName() + ", 参数: " + Arrays.toString(args)); long startTime = System.currentTimeMillis(); // 调用父类(原始目标类)的方法。注意这里是invokeSuper,不是invoke。 Object result = proxy.invokeSuper(obj, args); // 后置增强 long endTime = System.currentTimeMillis(); System.out.println("[CGLIB动态代理] 执行结束,耗时: " + (endTime - startTime) + "ms"); return result; } }

客户端使用方式:

public class Client { public static void main(String[] args) { // 1. 创建Enhancer对象(CGLIB的核心类) Enhancer enhancer = new Enhancer(); // 2. 设置父类(即要被代理的目标类) enhancer.setSuperclass(UserServiceImpl.class); // 3. 设置回调(即我们的增强逻辑拦截器) enhancer.setCallback(new LogMethodInterceptor()); // 4. 创建代理对象(注意:这里创建的是目标类的子类对象) UserServiceImpl proxy = (UserServiceImpl) enhancer.create(); // 5. 使用代理对象 proxy.addUser("王五"); } }

CGLIB动态代理的核心机制与避坑指南:

  • 基于继承:生成的代理类是目标类的子类。这意味着,如果目标类的方法是final的,则无法被重写,也就无法被代理。同样,如果目标类本身是final的,也无法生成子类代理。
  • 构造方法:CGLIB代理不会代理目标类的构造方法。创建代理对象时,默认调用的是目标类的无参构造。如果目标类没有无参构造,需要额外处理。
  • MethodProxy.invokeSuper:这是关键。它调用的是父类(即原始目标类)的方法,而不是当前代理对象的方法。如果错误地使用method.invoke(target, args),并且target是代理对象本身,会导致递归调用和栈溢出。
  • 性能对比:在早期,CGLIB因为直接生成字节码并使用方法索引调用,性能优于基于反射的JDK代理。但随着JVM优化,差距已不明显。选择哪种更多是基于“是否有接口”这个条件,而非纯粹性能。

3.3 Spring中代理的选择策略与配置

Spring的ProxyFactory是创建AOP代理的核心工厂类,它内部封装了JDK动态代理和CGLIB的选择逻辑。其默认策略可以概括为:

  1. 如果目标对象实现了至少一个接口,则默认使用JDK动态代理
  2. 如果目标对象没有实现任何接口,则使用CGLIB

但是,这个策略可以通过配置来改变。在Spring AOP(使用@AspectJ注解或XML配置)或Spring Boot中,常见的配置方式如下:

  • 强制使用CGLIB代理:有时,即使有接口,我们也希望使用CGLIB,例如为了代理this调用(后面会详述)或代理非接口方法。
    • XML配置<aop:config proxy-target-class="true">
    • 注解配置(Java Config)@EnableAspectJAutoProxy(proxyTargetClass = true)
    • Spring Boot:在application.properties中设置spring.aop.proxy-target-class=true

注意proxy-target-class设置为true后,Spring会强制对所有需要代理的Bean使用CGLIB。这可能会带来一些细微的影响,比如Bean必须能被继承(即不能是final类),并且代理对象的类型是目标类的子类,而不是接口类型。

4. 模拟Spring声明式事务:从理论到实践

理解了动态代理的原理后,我们可以尝试模拟一个Spring中非常经典的功能:声明式事务管理。我们将创建一个极简版的@MyTransactional注解,并通过动态代理实现其功能,这能让你深刻体会到Spring@Transactional注解背后的工作原理。

4.1 定义注解与数据源工具类

首先,定义一个我们自己的事务注解:

import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) // 该注解可以标注在方法上 @Retention(RetentionPolicy.RUNTIME) // 注解信息在运行时保留,这是动态代理能获取到的关键 public @interface MyTransactional { // 可以扩展属性,比如rollbackFor,这里先做一个最简单的标记注解 }

然后,模拟一个极简的数据源和连接管理工具。在真实Spring中,这是由DataSourceTransactionManagerTransactionSynchronizationManager等复杂组件完成的。

import java.sql.Connection; import java.sql.SQLException; /** * 模拟数据库连接工具类,管理线程本地(ThreadLocal)的连接和事务状态。 * 这是实现声明式事务的“基础设施”。 */ public class ConnectionUtils { // 使用ThreadLocal为每个线程绑定独立的数据库连接,保证事务的线程隔离性 private static final ThreadLocal<Connection> CONNECTION_HOLDER = new ThreadLocal<>(); // 模拟从数据源获取连接 public static Connection getConnection() throws SQLException { Connection conn = CONNECTION_HOLDER.get(); if (conn == null) { // 这里应该从真实的数据源获取,我们模拟一个 conn = MockDriver.getConnection(); // 假设的模拟驱动 CONNECTION_HOLDER.set(conn); } return conn; } // 开启事务 public static void beginTransaction() throws SQLException { Connection conn = getConnection(); if (conn != null) { conn.setAutoCommit(false); // 关闭自动提交,即开启事务 System.out.println("[事务管理器] 开启事务..."); } } // 提交事务 public static void commitTransaction() throws SQLException { Connection conn = getConnection(); if (conn != null) { conn.commit(); System.out.println("[事务管理器] 提交事务..."); } // 提交后,关闭连接并移除ThreadLocal中的引用 closeAndRemove(); } // 回滚事务 public static void rollbackTransaction() { Connection conn = CONNECTION_HOLDER.get(); if (conn != null) { try { conn.rollback(); System.out.println("[事务管理器] 回滚事务..."); } catch (SQLException e) { e.printStackTrace(); } finally { closeAndRemove(); } } } // 关闭连接并清理ThreadLocal private static void closeAndRemove() { Connection conn = CONNECTION_HOLDER.get(); if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } finally { CONNECTION_HOLDER.remove(); // 必须移除,防止内存泄漏 } } } }

4.2 创建事务增强的InvocationHandler

接下来,创建一个InvocationHandler,它负责拦截被@MyTransactional注解的方法,并在方法执行前后管理事务。

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.sql.SQLException; public class TransactionalInvocationHandler implements InvocationHandler { private Object target; // 真实的目标对象(例如UserServiceImpl) public TransactionalInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1. 检查当前方法是否被@MyTransactional注解标注 Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes()); if (targetMethod.isAnnotationPresent(MyTransactional.class)) { // 2. 如果标注了,则进行事务增强 System.out.println("[事务代理] 拦截到事务方法: " + method.getName()); Object result = null; try { // 2.1 前置增强:开启事务 ConnectionUtils.beginTransaction(); // 2.2 执行原方法(核心业务逻辑) result = method.invoke(target, args); // 2.3 后置增强:提交事务 ConnectionUtils.commitTransaction(); } catch (Exception e) { // 2.4 异常增强:回滚事务 System.out.println("[事务代理] 方法执行异常,触发回滚: " + e.getMessage()); ConnectionUtils.rollbackTransaction(); throw e; // 异常继续向上抛出 } return result; } else { // 3. 如果没有标注注解,则直接执行原方法,不进行事务管理 return method.invoke(target, args); } } }

4.3 组装与测试:体验声明式事务的魔力

现在,我们创建一个业务服务,并使用我们的代理工厂来生成具有事务能力的代理对象。

// 业务服务接口和实现 public interface OrderService { @MyTransactional void createOrder(String orderId) throws SQLException; void updateOrder(String orderId); // 这个方法没有事务 } public class OrderServiceImpl implements OrderService { @Override public void createOrder(String orderId) throws SQLException { System.out.println(" 业务逻辑:创建订单 " + orderId + " ..."); // 模拟数据库操作 ConnectionUtils.getConnection().createStatement().executeUpdate("INSERT INTO orders ..."); // 模拟一个可能失败的操作 if (orderId.equals("error-order")) { throw new SQLException("模拟业务异常:库存不足!"); } } @Override public void updateOrder(String orderId) { System.out.println(" 业务逻辑:更新订单 " + orderId + " (无事务)..."); } } // 代理工厂 public class ProxyFactory { public static Object getProxy(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TransactionalInvocationHandler(target) ); } } // 测试类 public class TransactionTest { public static void main(String[] args) throws SQLException { OrderService realService = new OrderServiceImpl(); OrderService proxyService = (OrderService) ProxyFactory.getProxy(realService); System.out.println("=== 测试1:正常事务方法 ==="); proxyService.createOrder("order-001"); System.out.println("\n=== 测试2:非事务方法 ==="); proxyService.updateOrder("order-002"); System.out.println("\n=== 测试3:事务方法抛出异常 ==="); try { proxyService.createOrder("error-order"); } catch (Exception e) { System.out.println("捕获到异常: " + e.getCause().getMessage()); } } }

运行测试,你会看到类似以下输出:

=== 测试1:正常事务方法 === [事务代理] 拦截到事务方法: createOrder [事务管理器] 开启事务... 业务逻辑:创建订单 order-001 ... [事务管理器] 提交事务... === 测试2:非事务方法 === 业务逻辑:更新订单 order-002 (无事务)... === 测试3:事务方法抛出异常 === [事务代理] 拦截到事务方法: createOrder [事务管理器] 开启事务... 业务逻辑:创建订单 error-order ... [事务代理] 方法执行异常,触发回滚: 模拟业务异常:库存不足! [事务管理器] 回滚事务... 捕获到异常: 模拟业务异常:库存不足!

通过这个简单的模拟,你可以清晰地看到:

  1. 声明式:我们只需要在方法上加一个@MyTransactional注解,事务的开启、提交、回滚逻辑就自动附加上了,业务代码非常干净。
  2. 代理的介入:是动态代理在幕后拦截了方法调用,并植入了事务管理代码。
  3. AOP思想:事务管理这个“横切关注点”被从业务代码中剥离出来,通过代理进行统一管理。

这正是Spring声明式事务管理的核心原理。当然,真实的Spring事务管理器要复杂得多,它需要处理传播行为、隔离级别、超时设置、只读事务等众多特性,并且与Spring的Bean生命周期、数据库连接池等深度集成,但其根本的驱动机制,就是我们所演示的动态代理。

5. Spring AOP中代理相关的典型问题与排查

理解了代理机制后,很多Spring开发中的“诡异”问题就变得容易理解和排查了。下面列举几个最常见的问题及其根源。

5.1 “this”调用导致AOP失效问题

这是最经典的一个坑。考虑以下代码:

@Service public class UserService { public void methodA() { System.out.println("执行methodA"); this.methodB(); // 注意这里用的是 this.methodB() } @Transactional public void methodB() { System.out.println("执行methodB(期望有事务)"); // 数据库操作... } }

当你从外部调用userService.methodA()时,methodB()上的@Transactional会生效吗?答案是:不会。

原因分析:Spring的AOP(无论是JDK代理还是CGLIB代理)是基于代理的。当Spring容器启动后,注入到其他Bean中的UserService实例,实际上是它的代理对象(比如UserService$$EnhancerBySpringCGLIB)。当我们调用proxy.methodA()时,代理会拦截这次调用,但methodA内部通过this调用的methodB,这个this指向的是目标对象本身(即UserService的原始实例),而不是代理对象。因此,这次调用绕过了代理,自然也就没有事务管理、日志等AOP增强功能。

解决方案:

  1. (推荐)自我注入:将代理对象注入到自己的一个字段中,通过这个字段调用。
    @Service public class UserService { @Autowired private UserService self; // 注入代理对象本身 public void methodA() { System.out.println("执行methodA"); self.methodB(); // 通过代理对象调用 } // ... methodB }
    注意:需要确保启用了CGLIB代理(proxyTargetClass=true)或UserService实现了接口,否则自我注入可能会遇到类型问题。
  2. 使用AopContext获取当前代理(不推荐,需暴露代理):
    // 首先在配置中暴露代理:@EnableAspectJAutoProxy(exposeProxy = true) public void methodA() { System.out.println("执行methodA"); ((UserService) AopContext.currentProxy()).methodB(); }
    这种方法有性能开销,且将框架细节暴露在业务代码中,破坏了整洁性。
  3. 重构代码:将methodB抽离到另一个Service中,通过Service间调用来触发代理。这是最符合设计原则的方式。

5.2 代理对象的类型识别问题

由于代理对象的存在,直接使用getClass()instanceof或类型转换时可能会得到意想不到的结果。

@Service public class MyService implements ServiceInterface { // ... } // 在某个地方 @Autowired private ServiceInterface myService; System.out.println(myService.getClass().getName()); // 输出可能是:com.sun.proxy.$Proxy123 (JDK代理) 或 MyService$$EnhancerBySpringCGLIB... (CGLIB代理) // 而不是 com.example.MyService // 以下判断可能为false if (myService instanceof MyService) { // ... }

排查技巧:

  • 如果需要获取原始目标类,可以使用Aop工具类:
    import org.springframework.aop.framework.AopProxyUtils; import org.springframework.aop.support.AopUtils; // 判断是否是代理对象 boolean isProxy = AopUtils.isAopProxy(myService); // 获取原始目标类 Class<?> targetClass = AopProxyUtils.ultimateTargetClass(myService); if (targetClass.isAssignableFrom(MyService.class)) { // 处理原始类型逻辑 }

5.3 final方法与类导致CGLIB代理失败

如果你强制使用了CGLIB代理(proxy-target-class=true),但你的Bean类或其中需要被增强的方法是final的,那么Spring将无法为其创建代理,通常会在启动时抛出异常。

错误示例:

@Service public final class FinalService { // final类,无法被CGLIB继承 @Transactional public final void finalMethod() { // final方法,无法被重写 // ... } }

解决方案:

  • 移除类或方法的final修饰符。
  • 或者,如果该类实现了接口,可以考虑不使用CGLIB(即不设置proxy-target-class=true),让Spring使用JDK动态代理(但这样只能代理接口方法)。

5.4 常见问题速查表

问题现象可能原因排查方向与解决方案
@Transactional@Cacheable等注解不生效1. 方法非public
2. 方法被类内部调用(this调用)。
3. 异常类型未被默认回滚策略捕获(如捕获了Exception但未抛出)。
4. 数据库引擎不支持事务(如MyISAM)。
1. 确保方法为public
2. 检查是否为自调用,改用自我注入或重构。
3. 检查异常处理逻辑,确保异常能传播出去,或配置rollbackFor
4. 检查@EnableTransactionManagement是否配置。
注入的Bean类型转换失败(ClassCastException代理对象类型与期望的原始类型不匹配。例如,期望MyServiceImpl但注入的是CGLIB代理对象MyServiceImpl$$Enhancer...1. 面向接口编程,注入接口类型。
2. 如果必须注入实现类,确保启用了CGLIB并正确配置。
3. 使用AopProxyUtils.ultimateTargetClass()获取原始类。
Bean循环依赖,且涉及AOP代理时启动报错在Bean创建过程中,如果存在循环依赖,并且其中一个Bean需要被代理(如@Async),在早期暴露原始对象可能导致问题。1. 优先通过设计打破循环依赖。
2. 使用@Lazy注解延迟注入。
3. 使用setter注入而非字段注入。
CGLIB代理创建失败,报IllegalArgumentException1. 目标类是final的。
2. 目标类没有默认无参构造。
3. 需要增强的方法是final的。
1. 移除final修饰符。
2. 提供无参构造,或检查Spring是否能够实例化Bean。
3. 检查方法是否为final

理解代理模式,尤其是动态代理在Spring中的应用,是深入掌握Spring框架的关键一步。它不仅仅是AOP的基础,更是理解Spring Bean生命周期、解决一系列疑难杂症的核心钥匙。当你再遇到注解不生效、类型转换出错、自调用失效等问题时,不妨先从“代理”这个角度去思考,往往能更快地定位到问题的根源。

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

从零构建LLM:打通训练与推理全流程的工程实践

很多人学深度学习&#xff0c;前半段是“舒服”的。卷积、循环网络、注意力机制&#xff0c;每章讲一个模块&#xff0c;跟着代码敲一遍&#xff0c;跑个小案例&#xff0c;能出一个结果&#xff0c;就觉得自己懂了。等课程进入后半段&#xff0c;尤其是类似“从零构建 LLM”的…

作者头像 李华
网站建设 2026/8/28 6:16:39

effective modern C++- item 1: 理解模版类型推导

一、问题的核心模型先记住这个"框架"&#xff0c;后面所有讨论都套用它&#xff1a;template<typename T> void f(ParamType param); // ParamType 是 T 加上某种修饰&#xff0c;如 T、T&、const T&、T&&、T* f(expr); /…

作者头像 李华
网站建设 2026/8/28 6:14:43

零基础也能吃透!Python自动化办公全实操教程,告别加班效率翻倍

各位好&#xff01;我乃热衷于分享见闻的老王&#xff01;我将会每日于此处给诸位呈上最新的消息, 并在所对应的各篇之中都倾囊提供有价值的关键信息, 希望这些可以帮到大家&#xff1b;要是你认为这些告知你的内容能对你的实际生活提供积极有效的作用, 那就赶紧点个关注吧&…

作者头像 李华
网站建设 2026/8/28 6:13:46

学习Python图像处理库Pillow

1.背景介绍图像的存储、处理、分析以及识别等方面, 都被图像处理所涉及, 而图像处理属于计算机视觉领域里一个相当重要的分支。有一种流行的编程语言, 其图像处理库&#xff08;PIL Fork&#xff09;是个极为强大能够帮助我们把图像处理以及操作得轻轻松松的图像处理工具。在本…

作者头像 李华
网站建设 2026/8/28 6:12:24

【29册即拍即发】折纸侦探团全系列PDF合集(1-29卷)|高清步骤图+动物/昆虫/人物全覆盖|折纸入门与进阶必备收藏版

温馨提示&#xff1a;文末有联系方式 **&#x1f525; 全网稀缺29册一次配齐** 折纸侦探团系列电子重磅整合&#xff01;本合集完整收录第1至第29册全部内容&#xff0c;共29本独立PDF文件&#xff0c;全部为高清可缩放电子版&#xff0c;专为折纸爱好者系统学习与长期收藏打造…

作者头像 李华
网站建设 2026/8/28 6:10:53

14.什么时候用pgvector什么时候单独部署Milvus

什么时候用 pgvector&#xff0c;什么时候单独部署 Milvus&#xff1f; 码海寻道 大模型、智能体与 RAG 工程组件系列第 14 篇 PostgreSQL 加 pgvector&#xff0c;还是 PostgreSQL 加 Milvus&#xff1f; 这是 RAG 项目非常常见的架构选择题。很多团队一开始想要“最专业”的…

作者头像 李华