- 主题:Java八股文总结一:Spring的Bean的生命周期、作用域
1. Bean的生命周期
实例化 -> 依赖注入 -> 初始化 -> 销毁
回答就说分成这四步。
重点是,
在第二步完成依赖注入,
在第三步里面完成AOP。即AOP代理是在【初始化】【后置处理阶段】织入。
这里通常有八股文的问题:
第一步中 通过反射创建对象,
第三步中 aop通过JDK动态代理或CGLIB动态代理创建对象,
这三者有什么区别和联系?
容器启动
│
▼
读取配置(XML/注解/JavaConfig)
│
▼
定位 Bean 定义(BeanDefinition)
│
▼
实例化 Bean(反射)
│ └── 使用 Class.forName() + Constructor.newInstance()
│
▼
属性填充(依赖注入)
│ └── 通过反射设置 @Autowired / @Resource 等字段
│
▼
Aware 回调(可选)
│ └── BeanNameAware, ApplicationContextAware 等
│
▼
BeanPostProcessor 前置处理
│ └── postProcessBeforeInitialization()
│
▼
初始化(init-method / afterPropertiesSet)
│
▼
BeanPostProcessor 后置处理
│ └── ★ AOP 在这里织入增强逻辑
│ ├─ 如果有接口 → JDK 动态代理
│ └─ 如果没有接口 → CGLIB 代理
│
▼
得到最终 Bean(可能是代理对象)
│
▼
放入容器,交给应用使用
│
▼
容器关闭 → 调用 destroy() / destroy-method()
2. Bean的作用域
Spring 定义了多种作用域(Scope),控制 Bean 的 生命周期范围:
singleton(默认)
容器中只有一个实例(全局单例)。
适合无状态 Bean(如 Service、DAO)。
线程安全要自己保证。
prototype(原型)
每次获取 Bean 都会新建一个实例。
适合有状态 Bean(如带临时数据的对象)。
Spring 只负责创建,不负责销毁。
request(Web 环境)
每个 HTTP 请求创建一个 Bean,随请求结束而销毁。
适合请求级别的数据处理。
session(Web 环境)
每个 HTTP Session 创建一个 Bean,随 Session 结束而销毁。
适合用户会话级别的数据。
application(Web 环境)
基于 ServletContext 的全局作用域,相当于一个 Web 应用只有一个实例。
websocket(Web 环境)
每个 WebSocket 连接对应一个 Bean。
面试高频问题:
单例 Bean 是线程安全的吗?Spring 不保证线程安全,需自己处理(加锁、ThreadLocal)。
原型 Bean 的销毁方法会调用吗?默认不会,Spring 不负责 prototype Bean 的销毁,需要手动管理。
循环依赖是在哪个阶段解决的?在 属性注入阶段,通过 三级缓存 提前暴露代理对象。
所以啊,那么多作用域,记住两个就行。单例作用域和原型作用域。然后是单例作用域自己保证线程安全,原型作用域自己手动销毁。然后是循环依赖的问题。就这三个问题。
--
修改:PlutoKey FROM 223.104.83.*
FROM 223.104.83.*
Spring现在都是用ByteBuddy?不用CGLIB了?
好问题 ? 这个点很多人容易搞混。
---
## ? Spring 用代理技术的历史演变
1. **早期版本(Spring 3.x 及以前)**
* **JDK 动态代理**(有接口时,默认选择)
* **CGLIB**(无接口时,退而求其次)
* CGLIB 基于 ASM,Spring 通过 `org.springframework.cglib` 包来生成子类代理。
2. **Spring 4.x**
* 还是 JDK 动态代理 + CGLIB 的组合。
* 官方文档里明确写过:
* “有接口用 JDK 动态代理,没有接口时用 CGLIB。”
3. **Spring 5.x 以后(特别是 Spring Framework 5.2)**
* Spring 内部引入了 **ByteBuddy**,主要用于 Spring AOP 和 Spring Test 中的一些字节码生成任务。
* 例如:Mockito 2.x+ 默认用 ByteBuddy 来生成 mock 对象。
* 但是:**Spring AOP 依然保留了 CGLIB,作为默认的字节码增强工具。**
---
## ? 为什么 ByteBuddy 逐渐取代 CGLIB?
* ByteBuddy 是一个现代化的字节码操作库,API 友好,不需要手动拼 ASM 指令。
* 更加可维护、扩展性强,能动态定义类、修改方法、插入字节码。
* Mockito、Hibernate、Spring Security 等新框架已经全面转向 ByteBuddy。
* CGLIB 在 2015 年之后就不再积极维护了。Spring 自己打包了一个 `spring-cglib`(shaded)版本来保证兼容。
---
## ? Spring 现在的实际情况
* **Spring AOP 仍然依赖 CGLIB**(如果类没有实现接口)。
* **Spring 5.x 内部部分功能开始用 ByteBuddy**,但并没有完全替换 CGLIB。
* 未来很可能逐渐迁移到 ByteBuddy,但目前(Spring Framework 6.x / Spring Boot 3.x)两者共存。
---
## ? 面试速答版
> **Spring 默认代理策略**:
* 有接口 → **JDK 动态代理**
* 无接口 → **CGLIB**(Spring 自带一个内部维护版)
* **ByteBuddy** 已经在 Spring 内部部分模块使用(例如 Spring Test),但 **Spring AOP 还没完全替代 CGLIB**。
---
要不要我帮你整理一个 **“JDK 动态代理 / CGLIB / ByteBuddy 在 Spring 中的使用对照表(含源码位置)”**,这样你面试时可以直接举例?
==
ASM 指令不是字节码?
你的问题很关键 ?,很多人会混淆 **ASM 指令** 和 **JVM 字节码**。我来帮你梳理清楚:
---
### 1. JVM 字节码
* **JVM 字节码**是 `.class` 文件里存储的指令集,运行在 **JVM** 上。
* 比如:
```java
int a = 1 + 2;
```
编译后的字节码可能是:
```
iconst_1 // 压入常量1
iconst_2 // 压入常量2
iadd // 执行整数加法
istore_1 // 将结果存到局部变量表
```
---
### 2. ASM
* **ASM** 不是字节码本身,而是一个 **字节码操作框架**。
* 它提供 API 来 **读取、生成、修改** `.class` 文件里的字节码。
* 换句话说,ASM 操作的对象就是 JVM 字节码。
* 例如,你可以用 ASM 生成一个新的类、在方法前后插入指令(比如 AOP 增强)。
---
### 3. 区别
* **JVM 字节码**:是指令本身,类似于“机器语言”,JVM 执行的就是它。
* **ASM**:是一个工具(库),用来操作这些字节码,就像写汇编语言的“编辑器”。
---
### 4. 联系
Spring 里的 **CGLIB** 和 **ByteBuddy** 底层都会用到 ASM:
* CGLIB:用 ASM 直接生成新的类字节码。
* ByteBuddy:更高级,它用链式 API 封装了 ASM,减少了开发者直接写字节码的痛苦。
---
? 总结:
ASM 本身不是字节码,而是一个操作字节码的库。
字节码是 **数据**,ASM 是 **工具**。
要不要我给你举个例子,展示 ASM 生成一个 `HelloWorld.class`(带 `main` 方法打印 "Hello ASM")的对比,和直接写 Java 编译后的字节码对比?
【 在 PlutoKey 的大作中提到: 】
: 1. Bean的生命周期
: 实例化 -> 依赖注入 -> 初始化 -> 销毁
: 回答就说分成这四步。
: ...................
--
FROM 223.104.83.*
通过反射这种方式是有Class定义的,jdk和cglib那是只有接口没有class的定义的
【 在 PlutoKey (铠甲勇士) 的大作中提到: 】
: 1. Bean的生命周期
:
: 实例化 -> 依赖注入 -> 初始化 -> 销毁
: 回答就说分成这四步。
--
FROM 223.104.44.*