Spring IoC、AOP 与 Bean 全面详解

发布时间:2026-03-16 21:14

本文深入讲解 Spring 框架的核心机制:IoC 容器、AOP 面向切面编程、Spring Bean 生命周期,并重点总结 AOP 代理失效场景与代理原理。

文章目录 1. 什么是代理(Proxy) 1.1 代理模式概念 1.2 静态代理 1.3 动态代理 JDK 动态代理(基于接口) CGLIB 动态代理(基于继承) 1.4 JDK 动态代理 vs CGLIB 对比 2. Spring IoC 详解 2.1 什么是 IoC 2.2 IoC 容器体系 2.3 依赖注入三种方式 构造器注入(推荐) Setter 注入 字段注入(不推荐) 2.4 IoC 容器启动流程 2.5 @Autowired 注入原理 3. Spring Bean 详解 3.1 Bean 的定义 3.2 Bean 的作用域(Scope) 3.3 Bean 的生命周期 生命周期代码示例 3.4 Bean 的注册方式 3.5 循环依赖问题 4. Spring AOP 详解 4.1 什么是 AOP 4.2 AOP 核心概念 4.3 通知类型 4.4 切点表达式语法 4.5 Spring AOP 的实现原理 5. AOP 代理失效场景总结 核心原因 5.1 类内部方法自调用(最常见) 5.2 `final` 方法 5.3 `private` 方法 5.4 `static` 方法 5.5 目标对象未被 Spring 管理 代理失效场景速查表 总结

1. 什么是代理(Proxy)

1.1 代理模式概念

代理模式(Proxy Pattern)是一种结构型设计模式,其核心思想是:为某个对象提供一个代理对象,由代理对象控制对原始对象的访问,从而在不修改原始类的前提下,增强其功能。
把代理对象当作经纪人,把原始对象当明星,明星有完成演出的功能,经纪人负责在明星演出的前后做各种增强准备。

客户端 ──► 代理对象(Proxy)──► 目标对象(Target) │ 增强逻辑(前置/后置/异常处理) 1.2 静态代理

静态代理由开发者手动编写代理类,代理类与目标类实现同一接口。

// 目标接口 public interface UserService { void save(String name); } // 目标实现 public class UserServiceImpl implements UserService { @Override public void save(String name) { System.out.println("保存用户: " + name); } } // 静态代理类 public class UserServiceProxy implements UserService { private final UserService target; public UserServiceProxy(UserService target) { this.target = target; } @Override public void save(String name) { System.out.println("方法执行前"); target.save(name); System.out.println("方法执行后"); } }

缺点:每个类都需要手写代理,代码冗余,扩展性差。

1.3 动态代理

动态代理在运行时自动生成代理类,Spring AOP 使用了两种动态代理:

JDK 动态代理(基于接口) 要求目标类实现接口 通过 java.lang.reflect.Proxy 和 InvocationHandler 实现 生成的代理类与目标类实现同一接口

public class JdkProxyDemo { public static void main(String[] args) { // 1. 明星本人:创建真实的目标对象(具体的业务实现) UserService target = new UserServiceImpl(); // 2. 造星机器:使用 Proxy 动态创建代理对象(即经纪人) // Proxy.newProxyInstance 返回的是 Object要强转 UserService proxy = (UserService) Proxy.newProxyInstance( // 参数 A:类加载器。告诉 JVM 用哪个加载器把这个新生成的“代理类”加载进内存 target.getClass().getClassLoader(), // 参数 B:接口列表。明星要做什么,经纪人就得表现出懂什么业务(save方法) target.getClass().getInterfaces(), // 参数 C:经纪人的大脑 (InvocationHandler)。 // 当代理对象的方法被调用时,所有的请求都会转发到这个 Lambda 表达式中 (proxyObj, method, methodArgs) -> { // 前置:明星上台前的动作(如:检查权限、打印日志、开启事务) System.out.println("[JDK代理] 前置增强:收尾款、调音响"); // 核心:反射调用。 // method:当前被调用的方法(save) // target:真正干活的明星本人 // methodArgs:调用时传的参数("张三") // 这行代码的意思是:调用【target】对象的【method】方法,传入【methodArgs】参数 Object result = method.invoke(target, methodArgs); // 后置:明星下台后的动作(如:释放连接、记录耗时、提交事务) System.out.println("[JDK代理] 后置增强:送明星回酒店"); // 返回明星演出的结果(如果有返回值的话) return result; } ); // 3. 【正式演出】:客户通过代理对象发起调用 // 此时,程序并不会直接跳到 UserServiceImpl,而是先进入上面的 Lambda 逻辑 proxy.save("张三"); } } CGLIB 动态代理(基于继承) 不需要目标类实现接口 通过继承目标类,重写方法来实现代理 Spring Boot 2.x 起默认使用 CGLIB

public class CglibProxyDemo { public static void main(String[] args) { // 1. 造星工厂:Enhancer 是 CGLIB 的核心类,用于生成代理对象 Enhancer enhancer = new Enhancer(); // 2. 确定血缘:设置父类。 // CGLIB 是通过“继承”目标类来工作的。 // 这里的代理对象会变成 UserServiceImpl 的一个“儿子”。 enhancer.setSuperclass(UserServiceImpl.class); // 3. 设置拦截器:定义经纪人的逻辑。 // MethodInterceptor 接口相当于 JDK 中的 InvocationHandler enhancer.setCallback((MethodInterceptor) (obj, method, methodArgs, proxy) -> { // 前置:明星上台前干的事 System.out.println("[CGLIB代理] 前置增强:我是子类代理,我先接活"); // 核心:调用父类的方法。 // 注意:这里用的是 proxy.invokeSuper,而不是 method.invoke。 // 它的意思是:去执行我“爸爸”(UserServiceImpl)里的那个真实方法。 Object result = proxy.invokeSuper(obj, methodArgs); // 后置:明星下台后干的事 System.out.println("[CGLIB代理] 后置增强:演出结束,清理现场"); return result; }); // 4. 创建代理对象:在内存中生成子类并实例化 // 因为它是 UserServiceImpl 的子类,所以可以直接强转成 UserServiceImpl UserServiceImpl proxy = (UserServiceImpl) enhancer.create(); // 5. 执行:调用代理对象的方法 proxy.save("李四"); } } 1.4 JDK 动态代理 vs CGLIB 对比 特性 JDK 动态代理 CGLIB 动态代理 代理方式 基于接口 基于继承(子类) 要求 目标类必须实现接口 目标类不能是 final 性能 反射调用,略慢 字节码操作,略快 Spring 默认 Spring 4.x 及以前 Spring Boot 2.x 起默认 代理 final 方法 不支持 不支持

2. Spring IoC 详解

2.1 什么是 IoC

IoC(Inversion of Control,控制反转) 是一种设计思想,核心是将对象的创建权依赖管理权从开发者手中转交给 Spring 容器。

传统方式:对象自己 new 依赖对象,主动控制 IoC 方式:对象只声明依赖,由容器负责注入,被动接受

DI(Dependency Injection,依赖注入) 是 IoC 的具体实现方式。

传统:A 类 ──new──► B 类 (A 控制 B 的创建) IoC :容器创建 A 和 B,将 B 注入 A (容器控制一切) 2.2 IoC 容器体系

BeanFactory(根接口) └── ApplicationContext(扩展接口) ├── ClassPathXmlApplicationContext (从类路径加载 XML) ├── FileSystemXmlApplicationContext (从文件系统加载 XML) ├── AnnotationConfigApplicationContext(注解配置) └── WebApplicationContext (Web 环境) 对比项 BeanFactory ApplicationContext Bean 实例化时机 第一次使用时(懒加载) 容器启动时(预实例化) 功能 基础 IoC 功能 IoC + AOP + 事件 + 国际化等 适合场景 资源受限环境 绝大多数场景 2.3 依赖注入三种方式 构造器注入(推荐)

@Service public class OrderService { private final UserRepository userRepository; private final PaymentService paymentService; public OrderService(UserRepository userRepository, PaymentService paymentService) { this.userRepository = userRepository; this.paymentService = paymentService; } }

优点:依赖不可变(final)、对象完整性保证、便于单元测试

Setter 注入

@Service public class OrderService { private UserRepository userRepository; @Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } }

适用:可选依赖、需要支持后期重新注入的场景

字段注入(不推荐)

@Service public class OrderService { @Autowired private UserRepository userRepository; // 不推荐:隐藏依赖、不易测试 }

缺点:依赖关系隐蔽、无法注入 final 字段、不利于单元测试

2.4 IoC 容器启动流程

1. 读取配置(XML / 注解 / JavaConfig) ↓ 2. 解析 BeanDefinition(Bean 的元数据) ↓ 3. 注册 BeanDefinition 到 BeanDefinitionRegistry ↓ 4. BeanFactoryPostProcessor 扩展点(修改 BeanDefinition) ↓ 5. 实例化非懒加载的单例 Bean ↓ 6. 依赖注入(填充属性) ↓ 7. BeanPostProcessor 扩展点(初始化前后增强) ↓ 8. 容器就绪,对外提供服务 2.5 @Autowired 注入原理

@Autowired 由 AutowiredAnnotationBeanPostProcessor 处理:

按类型(byType) 查找匹配的 Bean 找到多个时,按名称(byName) 精确匹配 仍有歧义时,结合 @Qualifier 指定或 @Primary 优先

// 有多个实现时,使用 @Qualifier 指定 @Autowired @Qualifier("mysqlUserRepository") private UserRepository userRepository; // 或用 @Primary 标注优先使用的实现 @Primary @Repository public class MysqlUserRepository implements UserRepository { ... }

3. Spring Bean 详解

3.1 Bean 的定义

Spring Bean 是由 Spring IoC 容器管理的对象。容器负责 Bean 的创建、装配、初始化和销毁全过程。

3.2 Bean 的作用域(Scope) 作用域 说明 适用场景 singleton(默认) 整个容器中只有一个实例 无状态服务类 prototype 每次请求创建新实例 有状态对象 request 每个 HTTP 请求创建一个实例 Web 请求相关数据 session 每个 HTTP 会话创建一个实例 用户会话数据 application 整个 Web 应用一个实例 全局共享数据

@Component @Scope("prototype") public class ShoppingCart { private List<Item> items = new ArrayList<>(); // ... } 3.3 Bean 的生命周期

在这里插入图片描述

生命周期代码示例

@Component public class MyBean implements InitializingBean, DisposableBean, BeanNameAware, ApplicationContextAware { private String beanName; // 1. 构造器 - 实例化 public MyBean() { System.out.println("1. 实例化:构造函数执行"); } // 2. Aware 回调 @Override public void setBeanName(String name) { this.beanName = name; System.out.println("3. BeanNameAware:beanName = " + name); } @Override public void setApplicationContext(ApplicationContext ctx) { System.out.println("3. ApplicationContextAware 回调"); } // 3. @PostConstruct 初始化 @PostConstruct public void postConstruct() { System.out.println("5. @PostConstruct 初始化方法 启动自检"); } // 4. InitializingBean 初始化 @Override public void afterPropertiesSet() { System.out.println("5. InitializingBean.afterPropertiesSet()"); } // 5. @PreDestroy 销毁 @PreDestroy public void preDestroy() { System.out.println("8. @PreDestroy 销毁前"); } // 6. DisposableBean 销毁 @Override public void destroy() { System.out.println("8. DisposableBean.destroy()"); } } 3.4 Bean 的注册方式

// 方式1:注解(最常用) @Component / @Service / @Repository / @Controller // 方式2:@Bean 方法 @Configuration public class AppConfig { @Bean public DataSource dataSource() { return new HikariDataSource(); } } // 方式3:XML 配置(传统) // <bean id="userService" class="com.example.UserServiceImpl"/> // 方式4:编程式注册 GenericBeanDefinition beanDef = new GenericBeanDefinition(); beanDef.setBeanClass(UserServiceImpl.class); registry.registerBeanDefinition("userService", beanDef); 3.5 循环依赖问题

Spring 通过三级缓存解决单例 Bean 的循环依赖:

一级缓存(singletonObjects) :存放完整初始化的 Bean 二级缓存(earlySingletonObjects):存放提前暴露的早期 Bean(未完成初始化) 三级缓存(singletonFactories) :存放 ObjectFactory(用于生成早期引用)

解决流程(A 依赖 B,B 依赖 A):

1. 创建 A → 将 A 的 ObjectFactory 放入三级缓存 2. A 注入属性,发现需要 B 3. 创建 B → 将 B 的 ObjectFactory 放入三级缓存 4. B 注入属性,发现需要 A → 从三级缓存获取 A 的早期引用 5. B 初始化完成,放入一级缓存 6. A 拿到 B,完成初始化,放入一级缓存

⚠️ 构造器循环依赖无法解决(因为无法提前暴露未完成的对象),Spring 会抛出 BeanCurrentlyInCreationException。

4. Spring AOP 详解

4.1 什么是 AOP

AOP(Aspect-Oriented Programming,面向切面编程) 是对 OOP 的补充,用于处理横切关注点(Cross-Cutting Concerns),如日志、事务、安全、缓存等。

OrderService UserService PaymentService │ │ │ 日志 ────────┼────────────────┼───────────────┼────── 横切关注点 事务 ────────┼────────────────┼───────────────┼────── 横切关注点 鉴权 ────────┼────────────────┼───────────────┼────── 横切关注点 4.2 AOP 核心概念 概念 英文 说明 切面 Aspect 封装横切逻辑的模块(切点 + 通知) 切点 Pointcut 定义在哪些方法上织入逻辑(匹配规则) 通知 Advice 具体的增强逻辑(前置、后置、环绕等) 连接点 JoinPoint 可被拦截的程序执行点(方法调用) 织入 Weaving 将切面逻辑插入目标对象的过程 目标对象 Target 被代理的原始对象 代理对象 Proxy 包含增强逻辑的代理对象 4.3 通知类型

@Aspect @Component public class LogAspect { // 切点表达式(可复用) @Pointcut("execution(* com.example.service.*.*(..))") public void serviceLayer() {} // 前置通知:在目标方法执行前 @Before("serviceLayer()") public void before(JoinPoint jp) { System.out.println("前置通知:" + jp.getSignature().getName()); } // 后置通知:目标方法执行后(无论是否异常) @After("serviceLayer()") public void after(JoinPoint jp) { System.out.println("后置通知(Finally)"); } // 返回通知:目标方法正常返回后 @AfterReturning(pointcut = "serviceLayer()", returning = "result") public void afterReturning(Object result) { System.out.println("返回通知,返回值:" + result); } // 异常通知:目标方法抛出异常后 @AfterThrowing(pointcut = "serviceLayer()", throwing = "ex") public void afterThrowing(Exception ex) { System.out.println("异常通知:" + ex.getMessage()); } // 环绕通知:最强大,包围目标方法执行 @Around("serviceLayer()") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕通知 - 前"); long start = System.currentTimeMillis(); try { Object result = pjp.proceed(); // 执行目标方法 System.out.println("环绕通知 - 后,耗时:" + (System.currentTimeMillis() - start) + "ms"); return result; } catch (Exception e) { System.out.println("环绕通知 - 异常"); throw e; } } }

执行顺序(正常情况):

环绕前 → 前置通知 → 目标方法 → 返回通知 → 后置通知 → 环绕后

执行顺序(异常情况):

环绕前 → 前置通知 → 目标方法抛异常 → 异常通知 → 后置通知 → 环绕异常处理 4.4 切点表达式语法

// execution 表达式语法 execution([修饰符] 返回类型 [类路径].方法名(参数) [异常]) // 示例 execution(* com.example.service.*.*(..)) // service 包下所有类所有方法 execution(public String com.example.UserService.*(..)) // UserService 的所有 public String 方法 execution(* *..UserService.save(..)) // 任意包下 UserService 的 save 方法 // @annotation 表达式:拦截带有特定注解的方法 @Pointcut("@annotation(com.example.annotation.Log)") public void logMethods() {} // within 表达式:拦截特定类内的所有方法 @Pointcut("within(com.example.service.*)") public void inServiceLayer() {} // args 表达式:拦截参数类型匹配的方法 @Pointcut("args(java.lang.String, ..)") public void stringFirstArg() {} 4.5 Spring AOP 的实现原理

AOP 代理由 AbstractAutoProxyCreator(BeanPostProcessor的子类)在 Bean 初始化后创建:

Bean 初始化完成 ↓ postProcessAfterInitialization() ↓ 判断是否需要代理(是否有匹配的 Advisor) ↓ ┌──────────────────────────────┐ │ 目标类实现了接口? │ │ 是 → JDK 动态代理(默认) │ │ 否 → CGLIB 代理 │ └──────────────────────────────┘ ↓ 返回代理对象(替换原 Bean)

5. AOP 代理失效场景总结

这是面试和实际开发中的高频问题,理解代理失效的根本原因是关键。

核心原因

AOP 代理失效的根本原因:方法调用绕过了代理对象,直接调用了目标对象的方法。

5.1 类内部方法自调用(最常见)

@Service public class OrderService { @Transactional public void createOrder(OrderDTO dto) { // ❌ 失效!this 指向的是目标对象,不是代理对象 this.validateAndSave(dto); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void validateAndSave(OrderDTO dto) { // 事务不会开启新事务,因为代理被绕过 } }

原因:this.validateAndSave() 调用的是原始对象的方法,没有经过 Spring 代理。

解决方案 1:注入自身代理

@Service public class OrderService { @Autowired private OrderService self; // 注入代理对象 @Transactional public void createOrder(OrderDTO dto) { self.validateAndSave(dto); // ✅ 通过代理调用 } }

解决方案 2:从 ApplicationContext 获取代理

@Service public class OrderService implements ApplicationContextAware { private ApplicationContext context; @Override public void setApplicationContext(ApplicationContext ctx) { this.context = ctx; } @Transactional public void createOrder(OrderDTO dto) { OrderService proxy = context.getBean(OrderService.class); proxy.validateAndSave(dto); // ✅ 通过代理调用 } }

解决方案 3:使用 AopContext.currentProxy()(推荐)

// 需要开启 exposeProxy @EnableAspectJAutoProxy(exposeProxy = true) @SpringBootApplication public class App { ... } @Service public class OrderService { @Transactional public void createOrder(OrderDTO dto) { ((OrderService) AopContext.currentProxy()).validateAndSave(dto); // ✅ } } 5.2 final 方法

@Service public class UserService { @Transactional public final void updateUser(User user) { // ❌ CGLIB 无法重写 final 方法 // 事务注解不生效 } }

原因:CGLIB 通过继承并重写方法来拦截调用,final 方法无法被子类重写,因此代理失效。

解决方案:去掉 final 关键字。

5.3 private 方法

@Service public class UserService { @Transactional private void doSomething() { // ❌ 代理失效 // 事务不生效 } }

原因

JDK 代理基于接口,接口中不存在私有方法 CGLIB 基于继承,子类无法访问父类 private 方法

解决方案:将方法改为 public 或 protected。

5.4 static 方法

@Service public class CacheService { @Cacheable("users") public static User getUser(Long id) { // ❌ 代理失效 return userRepository.findById(id); } }

原因:静态方法属于类,而非对象实例,代理作用于对象实例,因此无法拦截静态方法。

解决方案:将静态方法改为实例方法。

5.5 目标对象未被 Spring 管理

// ❌ 手动 new 出来的对象,不是 Spring Bean,没有代理 UserService userService = new UserServiceImpl(); userService.save(user); // AOP 完全不生效

解决方案:通过 Spring 容器获取 Bean,而不是手动 new。

代理失效场景速查表 # 场景 根本原因 解决方案 1 类内部 this 调用 绕过代理对象 AopContext.currentProxy() / 注入自身 2 final 方法 CGLIB 无法继承重写 去掉 final 3 private 方法 代理无法访问私有方法 改为 public/protected 4 static 方法 静态方法属于类而非实例 改为实例方法 5 非 Spring 管理对象 没有经过容器创建,无代理 通过 Spring 容器获取 Bean

总结

IoC → 解决对象创建和依赖管理问题(控制权交给容器) Bean → IoC 容器管理的对象,有完整的生命周期 AOP → 解决横切关注点问题(基于代理实现) 代理 → AOP 的底层实现(JDK / CGLIB 动态代理)

理解 AOP 代理失效场景的核心要点

Spring AOP 是基于代理的 AOP 实现,只有通过 Spring 容器获取的代理对象发起的方法调用,才能被 AOP 拦截。任何绕过代理对象的调用方式都会导致 AOP 失效。

网址:Spring IoC、AOP 与 Bean 全面详解 https://m.mxgxt.com/news/view/2052293

相关内容

启明星信息、VMware社招Java面经分享
spring的事务是什么?与数据库的事务是否一样
超全面!8 种互联网常用生命周期完整指南~
Java 代理从 0 到彻底搞懂
Spring Cloud Stream
国外vps中Neo4j与Spring框架集成如何进行
小石头哥 (bwoa)
Ed Hardy X Spring 2019明星早春穿搭示范
基于Spring Boot搭建星之语明星周边销售网站(附源码免费下载)
LV大秀解析,时尚圈术语

随便看看