Spring框架代理模式深度解析:从静态代理到动态代理与AOP实战
1. 项目概述为什么Spring框架绕不开代理模式如果你正在深入学习Spring5或者任何现代Spring版本你会发现一个绕不开的核心概念代理模式。这不仅仅是设计模式课程里的一个知识点而是Spring框架实现其核心功能——如声明式事务管理、AOP切面编程、Async异步调用、Cacheable缓存甚至是Spring Security的权限控制——的基石。很多朋友在初学Spring时可能会觉得这些功能“魔法”般地生效了背后的原理却模糊不清。今天我们就来彻底拆解这个“魔法”的发动机代理模式。简单来说代理模式就是在不修改原始对象代码的前提下为其提供额外的功能。想象一下你是一家公司的核心业务部门目标对象你只负责处理核心业务。但公司运营还需要法务审核、财务结算、日志记录等公共事务。这时公司为你配备了一个“秘书”或“代理”代理对象。所有外部请求都先经过这个代理由它来处理那些公共事务增强逻辑然后再把核心业务转交给你执行最后可能还需要代理来“善后”。这样你的部门目标对象就能保持纯粹而整个系统的能力却得到了增强。Spring框架大量使用了这种“秘书”机制来为我们的Bean添加各种企业级能力。理解代理模式尤其是Java中静态代理与动态代理的区别与实现是理解Spring AOP、理解Transactional为何能生效、乃至后续理解Spring Boot自动配置中很多“魔法”的关键一步。这不仅是面试的高频考点更是你从“会用Spring”到“懂Spring”的必经之路。接下来我将结合代码示例和Spring内部的真实应用场景带你从静态代理入门再到动态代理的核心最后落地到Spring AOP的源码级理解。2. 核心原理静态代理与动态代理的深度对比在深入Spring之前我们必须先打好基础清晰理解两种代理模式的实现机制、优缺点以及适用场景。这是后续所有内容的地基。2.1 静态代理手工作坊式的增强静态代理顾名思义代理类在编译期就已经确定并生成好了.class文件。它的实现需要三个角色抽象主题Subject通常是接口定义了业务方法。真实主题Real Subject实现接口的具体业务类。代理类Proxy同样实现接口内部持有真实主题的引用并在调用真实主题方法前后添加增强逻辑。让我们用一个最经典的“用户服务”添加日志功能的例子来演示// 1. 抽象主题 - 用户服务接口 public interface UserService { void addUser(String username); void deleteUser(String id); } // 2. 真实主题 - 核心业务实现 public class UserServiceImpl implements UserService { Override public void addUser(String username) { System.out.println(核心业务添加用户 username); // 模拟业务操作... } Override public void deleteUser(String id) { System.out.println(核心业务删除用户 id); // 模拟业务操作... } } // 3. 静态代理类 - 手工编写的“秘书” 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 start System.currentTimeMillis(); // 转发给真实对象执行核心业务 target.addUser(username); long end System.currentTimeMillis(); System.out.println([静态代理] 日志记录addUser 执行完毕耗时: (end - start) ms); } Override public void deleteUser(String id) { System.out.println([静态代理] 日志记录开始执行 deleteUser, 参数: id); // 这里还可以加入权限校验等逻辑 if (!admin.equals(getCurrentUserRole())) { throw new SecurityException(权限不足); } target.deleteUser(id); System.out.println([静态代理] 日志记录deleteUser 执行完毕); } private String getCurrentUserRole() { // 模拟获取当前用户角色 return user; } } // 测试类 public class StaticProxyTest { public static void main(String[] args) { // 创建真实对象 UserService realService new UserServiceImpl(); // 创建代理对象将真实对象传入 UserService proxy new UserServiceStaticProxy(realService); // 客户端调用的是代理对象 proxy.addUser(张三); System.out.println(---); proxy.deleteUser(1001); } }运行上述代码你会看到在核心业务逻辑执行前后都打印了我们添加的日志信息。这就是静态代理的工作方式。静态代理的优缺点分析优点结构清晰易于理解和实现。因为代理类是手动编写的所以增强逻辑可以非常定制化。缺点冗余与僵化每个需要代理的真实类都需要手动编写一个对应的代理类。如果系统中有成百上千个Service这个工作量是灾难性的会产生大量重复的样板代码。紧耦合代理类和真实类实现了相同的接口一旦接口方法发生变动如增加、删除或修改方法所有对应的代理类都必须同步修改违反了开闭原则。功能单一一个静态代理类通常只为一种增强逻辑如日志服务。如果想同时为同一个类添加日志、事务、缓存等多种增强要么写一个包含所有逻辑的“巨无霸”代理类违反单一职责要么嵌套多层代理代码复杂。注意静态代理在实际大型企业项目中很少直接用于业务增强因为它难以维护。但它非常适合用于理解代理模式的基本思想以及在一些非常明确、固定的场景下如为一个特定的第三方库类做一层固定的封装使用。2.2 动态代理Spring的“魔法”引擎动态代理解决了静态代理的核心痛点。它的代理类不是在编译期生成的而是在程序运行时动态地在内存中创建的。Java提供了两种主流的动态代理机制JDK动态代理和CGLIB动态代理。Spring AOP默认根据目标对象是否实现接口来智能选择使用哪一种。2.2.1 JDK动态代理基于接口的代理JDK动态代理是Java标准库的一部分java.lang.reflect.Proxy它要求目标对象必须至少实现一个接口。其核心是InvocationHandler接口。import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // 增强逻辑处理器 - 这是动态代理的核心 public class LogInvocationHandler implements InvocationHandler { // 持有真实目标对象 private final 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()); long start System.currentTimeMillis(); // 利用反射调用真实目标对象的方法 Object result method.invoke(target, args); // 方法执行后增强 long end System.currentTimeMillis(); System.out.println([JDK动态代理] 方法执行完毕: method.getName() , 耗时: (end - start) ms); return result; // 返回真实方法执行的结果 } } // 测试JDK动态代理 public class JdkDynamicProxyTest { public static void main(String[] args) { // 1. 创建真实对象必须实现接口 UserService realService new UserServiceImpl(); // 2. 创建InvocationHandler传入真实对象 InvocationHandler handler new LogInvocationHandler(realService); // 3. 使用Proxy类创建代理对象 // 参数类加载器、接口数组、InvocationHandler实例 UserService proxy (UserService) Proxy.newProxyInstance( realService.getClass().getClassLoader(), // 加载代理类的类加载器 realService.getClass().getInterfaces(), // 真实对象实现的所有接口 handler // 增强逻辑处理器 ); // 4. 调用代理对象的方法 System.out.println(代理对象类型: proxy.getClass().getName()); // 输出代理对象类型: com.sun.proxy.$Proxy0 // 这是一个运行时生成的类类名以$Proxy开头 proxy.addUser(李四); System.out.println(---); proxy.deleteUser(1002); } }JDK动态代理的核心机制Proxy.newProxyInstance方法在运行时动态生成了一个实现了指定接口列表的代理类如$Proxy0。这个生成的代理类内部所有接口方法的实现都简单地委托给了我们传入的InvocationHandler.invoke方法。当我们调用proxy.addUser()时实际上调用的是$Proxy0.addUser()而它内部调用了handler.invoke(...)从而执行了我们定义的增强逻辑并通过反射调用真实对象的方法。实操心得你可以将生成的代理类字节码保存到磁盘查看这能极大地帮助你理解其原理。在main方法开始前添加这行代码System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);。运行后会在项目根目录生成一个com/sun/proxy/$Proxy0.class文件用反编译工具如JD-GUI打开你会看到它确实实现了UserService接口并且每个方法都调用了super.h.invoke(...)这个h就是我们传入的InvocationHandler。2.2.2 CGLIB动态代理基于子类继承的代理如果目标对象没有实现任何接口JDK动态代理就无能为力了。这时Spring会退而求其次使用CGLIBCode Generation Library来创建代理。CGLIB的原理是通过字节码技术生成目标类的一个子类并重写其中的方法来实现增强。因为这个子类继承了目标类所以它自然就拥有了和目标类相同的方法。首先需要引入CGLIB依赖Spring核心包已包含!-- 如果单独使用CGLIB -- dependency groupIdcglib/groupId artifactIdcglib/artifactId version3.3.0/version /dependencyimport net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; // 一个没有实现接口的类 public class UserServiceWithoutInterface { public void addUser(String username) { System.out.println(核心业务无接口添加用户 username); } public void deleteUser(String id) { System.out.println(核心业务无接口删除用户 id); } } // CGLIB的方法拦截器类似于InvocationHandler public class CglibMethodInterceptor implements MethodInterceptor { /** * param obj 代理对象CGLIB生成的子类实例 * param method 被拦截的方法 * param args 方法参数 * param proxy 用于调用父类原始方法的代理 */ Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println([CGLIB代理] 开始执行方法: method.getName()); // 注意这里调用父类方法用的是 proxy.invokeSuper而不是 method.invoke Object result proxy.invokeSuper(obj, args); System.out.println([CGLIB代理] 方法执行完毕: method.getName()); return result; } } // 测试CGLIB动态代理 public class CglibDynamicProxyTest { public static void main(String[] args) { // 1. 创建Enhancer对象CGLIB的字节码增强器 Enhancer enhancer new Enhancer(); // 2. 设置父类即要被代理的目标类 enhancer.setSuperclass(UserServiceWithoutInterface.class); // 3. 设置回调即我们的增强逻辑拦截器 enhancer.setCallback(new CglibMethodInterceptor()); // 4. 创建代理对象 UserServiceWithoutInterface proxy (UserServiceWithoutInterface) enhancer.create(); // 5. 调用方法 System.out.println(代理对象类型: proxy.getClass().getName()); // 输出代理对象类型: com.example.UserServiceWithoutInterface$$EnhancerByCGLIB$$xxxxxx // 这是一个继承了UserServiceWithoutInterface的子类 proxy.addUser(王五); proxy.deleteUser(1003); } }CGLIB与JDK动态代理的关键区别特性JDK动态代理CGLIB动态代理机制基于接口实现使用Proxy和InvocationHandler基于继承使用Enhancer和MethodInterceptor生成子类目标要求必须实现至少一个接口可以代理没有实现接口的普通类性能早期版本反射调用稍慢现代JVM优化后差距不大早期版本生成子类调用更快现代JVM下差异很小限制只能代理接口中声明的方法无法代理final类或final、private方法因为无法继承或重写生成类com.sun.proxy.$Proxy0TargetClass$$EnhancerByCGLIB$$...注意事项由于CGLIB通过生成子类来代理所以需要特别注意构造函数的调用。目标类的默认无参构造函数会被调用两次一次是创建真实对象不CGLIB生成子类实例时会先调用父类构造器这算一次。但Spring通常会控制目标Bean本身只初始化一次。另外被代理的类和方法不能是final的否则无法继承或重写。3. Spring AOP如何运用动态代理从配置到织入理解了动态代理我们终于可以揭开Spring AOP的神秘面纱。AOP面向切面编程的核心就是将分散在各个方法中的横切关注点如日志、事务、安全抽取出来形成独立的“切面”然后通过动态代理技术在运行时将这些切面逻辑“织入”到目标方法中。3.1 Spring AOP的代理创建决策逻辑Spring的DefaultAopProxyFactory是决定使用JDK代理还是CGLIB代理的工厂类。其核心逻辑如下简化版检查目标对象是否实现了接口AdvisedSupport配置中optimize、proxyTargetClass等属性也会影响决策。如果目标对象没有实现接口则强制使用CGLIB。如果目标对象实现了接口默认情况下Spring AOP默认使用JDK动态代理。但是如果我们在配置中显式设置了proxyTargetClass true在XML中是aop:config proxy-target-class”true”在Spring Boot的EnableAspectJAutoProxy注解中设置proxyTargetClass true那么Spring会强制使用CGLIB代理即使目标类实现了接口。为什么有时要强制使用CGLIB类型转换问题如果你需要将代理对象强制转换为具体的实现类而不是接口JDK代理基于接口会抛出ClassCastException而CGLIB代理基于子类则可以成功。代理更多方法JDK代理只代理接口方法。如果目标类有自身的方法非接口方法这些方法不会被代理。CGLIB代理了整个类除final方法外。3.2 一个完整的Spring AOP声明式事务示例让我们通过最经典的Transactional声明式事务来看Spring AOP和动态代理是如何协作的。步骤1定义业务组件和接口// 业务接口 public interface OrderService { void createOrder(Order order); void updateOrderStatus(Long orderId, String status); } // 业务实现类 - 这是我们的目标对象 Service public class OrderServiceImpl implements OrderService { Autowired private OrderMapper orderMapper; Override Transactional // 关键注解声明此方法需要事务管理 public void createOrder(Order order) { // 1. 插入订单主表 orderMapper.insertOrder(order); // 2. 插入订单明细 for (OrderItem item : order.getItems()) { orderMapper.insertOrderItem(item); } // 模拟一个可能失败的业务测试事务回滚 // if (someCondition) throw new RuntimeException(业务异常); } Override public void updateOrderStatus(Long orderId, String status) { orderMapper.updateStatus(orderId, status); } }步骤2Spring容器的幕后工作Bean创建当Spring容器启动扫描到OrderServiceImpl并创建其实例我们称之为target。代理决策Spring发现createOrder方法上有Transactional注解。它知道这个Bean需要被增强添加事务管理能力。创建代理因为OrderServiceImpl实现了OrderService接口默认情况下Spring的AOP基础设施具体是AbstractAutoProxyCreator会使用JDK动态代理。Spring会创建一个InvocationHandler在Spring中通常是JdkDynamicAopProxy这个Handler知道如何协调多个“增强器”Advisor其中就包含了事务增强器TransactionInterceptor。调用Proxy.newProxyInstance(...)生成代理对象proxy。Bean替换Spring容器最终放入单例池BeanFactory的不是原始的OrderServiceImpl实例target而是它的代理对象proxy。步骤3方法调用时的织入过程当其他Bean比如一个Controller通过Autowired注入OrderService时它拿到的是代理对象proxy。RestController public class OrderController { Autowired private OrderService orderService; // 这里注入的是代理对象 PostMapping(/order) public String create(RequestBody Order order) { orderService.createOrder(order); // 调用的是代理对象的方法 return success; } }调用orderService.createOrder(order)时发生了什么请求进入代理对象proxy的createOrder方法。JDK动态代理的InvocationHandler.invoke被触发即JdkDynamicAopProxy.invoke。Spring AOP的调用链开始工作。它会根据方法匹配规则找到适用于createOrder方法的“拦截器链”Interceptor Chain。在这个链里就包含了TransactionInterceptor。TransactionInterceptor开始执行前置增强获取数据库连接关闭自动提交开启事务connection.setAutoCommit(false)。执行目标方法通过反射调用原始OrderServiceImpl实例target的createOrder方法。后置增强如果目标方法执行成功则提交事务connection.commit()。如果目标方法抛出RuntimeException或Error则回滚事务connection.rollback()。调用链执行完毕结果返回给代理对象最终返回给Controller。核心要点整个过程中我们的业务类OrderServiceImpl对事务的存在一无所知。它只关心核心的业务逻辑插入订单。事务的开启、提交、回滚这些横切关注点完全由Spring AOP通过动态代理机制在外部“套”了上去。这就是AOP“解耦”的精髓。4. 动态代理在Spring中的高级应用与深度剖析掌握了基础原理我们来看几个更深入、更“实战”的场景和问题这能帮你更好地应对复杂情况。4.1 自调用失效问题代理对象的陷阱这是Spring AOP一个非常经典的坑。考虑下面的代码Service public class UserServiceImpl implements UserService { Override Transactional public void createUser(User user) { userDao.insert(user); // 自调用在同一个类中调用另一个被AOP代理的方法 this.updateUserLog(user.getId()); // 问题在这里这里的this是原始对象不是代理对象。 } Override Transactional(propagation Propagation.REQUIRES_NEW) // 希望创建一个新事务 public void updateUserLog(Long userId) { logDao.insertLog(userId, CREATE); } }你期望updateUserLog方法会在一个独立的新事务中运行。但实际上它的Transactional注解会失效日志插入操作可能不会回滚或者根本不在你期望的事务中。原因深度解析Spring AOP以及绝大多数AOP实现是基于代理的。Autowired注入的UserServicebean是代理对象proxy。当外部调用proxy.createUser()时事务生效。但在createUser方法内部this.updateUserLog()中的this指向的是目标对象本身UserServiceImpl实例而不是它的代理对象。因此这次调用绕过了代理直接进入了原始对象的方法自然也就没有事务拦截器参与。解决方案最佳实践重构代码。将updateUserLog方法抽取到另一个Service中如UserLogService然后通过Autowired注入进来。这样调用的是另一个Bean的代理对象。Service public class UserServiceImpl implements UserService { Autowired private UserLogService userLogService; // 注入另一个Service的代理 Override Transactional public void createUser(User user) { userDao.insert(user); userLogService.updateUserLog(user.getId()); // 调用代理对象事务生效 } }使用AopContext暴露代理不推荐用于生产。需要先开启exposeProxy。EnableAspectJAutoProxy(exposeProxy true) // 在配置类上开启public void createUser(User user) { userDao.insert(user); // 获取当前线程的代理对象 UserService proxy (UserService) AopContext.currentProxy(); proxy.updateUserLog(user.getId()); // 通过代理调用 }这种方法有性能开销且使代码与Spring框架强耦合一般仅用于调试或遗留代码改造。4.2 多种增强的执行顺序拦截器链如何工作一个方法可能被多个切面增强比如同时有日志切面、事务切面、缓存切面。它们的执行顺序是怎样的Spring AOP将多个增强组织成一个拦截器链MethodInterceptor Chain。你可以通过Order注解或实现Ordered接口来控制顺序。数值越小优先级越高越在外层。Aspect Component Order(1) // 顺序1最外层 public class LoggingAspect { Before(execution(* com.example.service.*.*(..))) public void logBefore(JoinPoint joinPoint) { System.out.println([Log-1] 方法开始: joinPoint.getSignature().getName()); } AfterReturning(execution(* com.example.service.*.*(..))) public void logAfter(JoinPoint joinPoint) { System.out.println([Log-1] 方法正常返回); } } Aspect Component Order(2) // 顺序2在日志里面一层 public class TransactionAspect { Around(execution(* com.example.service.*.*(..))) public Object manageTransaction(ProceedingJoinPoint pjp) throws Throwable { System.out.println([Tx-2] 开启事务); try { Object result pjp.proceed(); // 执行目标方法并进入链中的下一个拦截器 System.out.println([Tx-2] 提交事务); return result; } catch (Exception e) { System.out.println([Tx-2] 回滚事务); throw e; } } }执行someService.method()时输出顺序将是[Log-1] 方法开始: method [Tx-2] 开启事务 ... (执行目标方法) ... [Tx-2] 提交事务 [Log-1] 方法正常返回可以看到Order(1)的日志切面像“洋葱”的最外层最先执行Before最后执行AfterReturning。而Order(2)的事务切面Around在中间它调用pjp.proceed()时才去执行目标方法或下一个切面。理解这个“洋葱模型”对于调试复杂的切面交互至关重要。4.3 性能考量与代理选择策略虽然动态代理非常强大但它并非没有代价。创建开销动态代理类是在运行时生成的首次创建代理对象时会有生成字节码和加载类的开销。不过Spring容器在启动时创建所有单例Bean这个开销主要影响启动时间对运行时影响不大。调用开销每次通过代理调用方法都会经过拦截器链。相比于直接调用这多了一层或多层反射或方法调用。但在绝大多数业务应用中这个开销与网络IO、数据库操作相比微乎其微完全可以接受。Spring Boot中的默认策略与优化在Spring Boot 2.x 之后默认情况下proxyTargetClass被设置为true即默认使用CGLIB代理。这是因为避免开发者因类型转换注入实现类而非接口而踩坑。可以代理类自身的所有方法非接口方法。现代JVM对动态生成的字节码优化很好CGLIB的性能损失很小。排查技巧如果你不确定一个Bean是否被代理、是JDK代理还是CGLIB代理可以在调试时查看其类名或者通过以下代码简单判断Autowired private SomeService someService; PostConstruct public void checkProxy() { System.out.println(Bean class: someService.getClass().getName()); System.out.println(Is JDK Proxy? Proxy.isProxyClass(someService.getClass())); // 如果是CGLIB代理类名会包含$$EnhancerBySpringCGLIB$$ }5. 从原理到实战模拟实现一个简易的AOP框架为了彻底吃透动态代理和AOP最好的方式就是自己动手模拟一个。我们来实现一个超简易版的“Spring AOP”它只支持方法执行时间的日志记录。目标通过注解ExecTime标记需要记录耗时的方法框架自动为其创建代理并输出耗时。步骤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 ExecTime { }步骤2创建Bean后处理器模拟Spring的BeanPostProcessor这是核心它负责在Bean初始化后检查其方法如果有ExecTime注解就为其创建代理。import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; Component public class ExecTimeBeanPostProcessor implements BeanPostProcessor { Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // 1. 获取Bean的所有方法 Class? beanClass bean.getClass(); Method[] methods beanClass.getDeclaredMethods(); // 2. 检查是否有方法被ExecTime注解标记 boolean needProxy false; SetString annotatedMethods new HashSet(); for (Method method : methods) { if (method.isAnnotationPresent(ExecTime.class)) { needProxy true; annotatedMethods.add(method.getName()); } } // 3. 如果至少有一个方法需要增强则创建代理 if (needProxy) { System.out.println(为Bean [ beanName ] 创建ExecTime代理增强方法: annotatedMethods); // 使用CGLIB创建代理简单起见这里不判断接口 Enhancer enhancer new Enhancer(); enhancer.setSuperclass(beanClass); // 设置父类 enhancer.setCallback(new MethodInterceptor() { Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // 判断当前调用的方法是否需要增强 if (annotatedMethods.contains(method.getName())) { long start System.nanoTime(); Object result proxy.invokeSuper(obj, args); // 调用父类原始方法 long end System.nanoTime(); System.out.println([简易AOP] 方法 method.getName() 执行耗时: (end - start) / 1000000.0 ms); return result; } else { // 不需要增强的方法直接调用 return proxy.invokeSuper(obj, args); } } }); // 4. 返回代理对象替换原来的Bean return enhancer.create(); } // 5. 不需要代理返回原Bean return bean; } }步骤3在业务类中使用注解Service public class HeavyTaskService { ExecTime // 标记需要记录耗时的方法 public void doHeavyTask() { try { Thread.sleep(1500); // 模拟耗时操作 System.out.println(重型任务执行完成。); } catch (InterruptedException e) { e.printStackTrace(); } } public void doLightTask() { // 这个方法没有注解不会被增强 System.out.println(轻型任务执行完成。); } }步骤4测试SpringBootTest public class ExecTimeAopTest { Autowired private HeavyTaskService heavyTaskService; // 这里注入的已经是代理对象了 Test public void testAop() { System.out.println(Service类: heavyTaskService.getClass().getName()); // 输出: Service类: com.example.service.HeavyTaskService$$EnhancerBySpringCGLIB$$... heavyTaskService.doHeavyTask(); // 会输出耗时日志 heavyTaskService.doLightTask(); // 不会输出耗时日志 } }运行测试你会看到doHeavyTask方法执行前后自动打印了耗时信息而doLightTask则没有。这就是一个最简易的AOP框架雏形它清晰地展示了Spring AOP利用BeanPostProcessor和动态代理实现“无侵入增强”的核心流程。通过这个实战模拟你应该对Spring中“注解驱动”的魔法有了更本质的认识注解只是元数据真正起作用的是在容器生命周期中扫描这些注解并创建代理的后处理器。