some面试题2
一.Spring 容器中 Bean 生命周期的核心流程。
✅ Spring 中 Bean 的完整生命周期(按顺序)
1. 通过 BeanDefinition
获取 Bean 的定义信息
- Spring 启动时会加载配置文件(XML 或注解),将每个 Bean 的元数据封装成一个
BeanDefinition
对象。 - 包括:类名、作用域(singleton / prototype)、是否懒加载、构造函数参数、属性值等。
// 示例:获取某个 bean 的定义
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
BeanDefinition bd = beanFactory.getBeanDefinition("yourBeanName");
2. 调用构造函数实例化 Bean
- 根据
BeanDefinition
中的信息,使用默认构造方法或带参数的构造方法创建 Bean 实例。 - 如果是原型(prototype)作用域,则每次请求都会新建一个实例;如果是单例(singleton),则只会创建一次。
YourBean yourBean = new YourBean(); // 实际由 Spring 内部反射调用
3. Bean 的依赖注入
- 自动装配或手动配置的依赖项(如
@Autowired
、@Resource
、setter 注入、构造器注入)在此阶段被注入。 - 包括字段注入、setter 方法注入、构造方法注入等。
@Autowired
private SomeService someService;
4. 处理 Aware 接口
- Spring 检查该 Bean 是否实现了某些特定的 Aware 接口,并自动回调相应的方法:
BeanNameAware.setBeanName()
:传入当前 Bean 的名称。BeanFactoryAware.setBeanFactory()
:传入当前使用的 BeanFactory。ApplicationContextAware.setApplicationContext()
:传入上下文对象。
public class MyBean implements BeanNameAware, ApplicationContextAware {private String beanName;private ApplicationContext context;@Overridepublic void setBeanName(String name) {this.beanName = name;}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) {this.context = applicationContext;}
}
5. BeanPostProcessor 前置处理
- 所有注册的
BeanPostProcessor.postProcessBeforeInitialization()
方法会被依次调用。 - 可以对 Bean 实例进行修改或包装(例如 AOP 代理生成)。
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if ("myBean".equals(beanName)) {System.out.println("前置处理:" + beanName);}return bean; // 返回原始或包装后的 Bean}
}
6. 初始化方法
a. InitializingBean.afterPropertiesSet()
- 如果 Bean 实现了
InitializingBean
接口,会调用其afterPropertiesSet()
方法。
public class MyBean implements InitializingBean {@Overridepublic void afterPropertiesSet() {// 初始化逻辑}
}
b. init-method
- 在 XML 配置中指定的
init-method
,或使用@Bean(initMethod = "xxx")
注解的方法也会在此时被调用。
@Bean(initMethod = "customInit")
public MyBean myBean() {return new MyBean();
}public class MyBean {public void customInit() {// 自定义初始化方法}
}
7. BeanPostProcessor 后置处理
- 所有
BeanPostProcessor.postProcessAfterInitialization()
方法被调用。 - 这是 AOP 最常见的介入点,比如为 Bean 创建动态代理。
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if ("myBean".equals(beanName)) {System.out.println("后置处理:" + beanName);}return bean;
}
8. 使用 Bean
- 此时 Bean 已完全初始化完成,可以正常使用。
- 如果是单例 Bean,它将一直存在于 Spring 容器中直到容器关闭。
9. 销毁 Bean
- 当容器关闭时,如果 Bean 是单例的,Spring 会执行销毁逻辑。
a. DisposableBean.destroy()
- 如果 Bean 实现了
DisposableBean
接口,会调用destroy()
方法。
public class MyBean implements DisposableBean {@Overridepublic void destroy() {// 销毁资源}
}
b. destroy-method
- XML 配置或
@Bean(destroyMethod = "xxx")
指定的方法也会被执行。
@Bean(destroyMethod = "customDestroy")
public MyBean myBean() {return new MyBean();
}public class MyBean {public void customDestroy() {// 自定义销毁方法}
}
📌 总结:完整的 Spring Bean 生命周期流程图
[BeanDefinition加载]↓[实例化]↓[依赖注入]↓[Aware接口处理]↓
[BeanPostProcessor 前置]↓[InitializingBean/ init-method]↓
[BeanPostProcessor 后置]↓[使用中...]↓[DisposableBean/ destroy-method]↓[销毁完成]
💡 小贴士
-
单例 vs 原型作用域:
- 单例 Bean 只创建一次,且在容器关闭时才会销毁。
- 原型 Bean 每次请求都创建新实例,Spring 不负责管理其销毁。
-
AOP 是在哪个阶段生效?
- 通常是在
BeanPostProcessor
的后置处理阶段完成的,例如AbstractAutoProxyCreator
。
- 通常是在
-
自定义扩展点:
- 可以实现
BeanFactoryPostProcessor
修改 BeanDefinition; - 使用
ApplicationListener
监听容器事件; - 使用
SmartLifecycle
控制启动/停止逻辑。
- 可以实现
二.类装载的执行过程
✅ Java 类加载机制的完整生命周期(7个阶段)
1. 加载(Loading)
- 作用:查找并加载
.class
文件(可以来自本地磁盘、网络、加密文件等)。 - 触发方式:
- 显式调用
Class.forName()
; - 隐式加载(如 new 一个对象时自动加载类);
- 反射、序列化、反序列化等操作也会触发类加载。
- 显式调用
- 结果:
- 将类的二进制字节流转换为方法区的数据结构;
- 在堆中生成一个
java.lang.Class
对象作为访问入口。
Class<?> clazz = Class.forName("com.example.MyClass"); // 触发类加载
2. 验证(Verification)
- 作用:确保被加载的类的字节码是合法的、安全的,不会危害 JVM。
- 验证内容:
- 文件格式验证(是否符合
.class
文件规范); - 元数据验证(是否有不正确的继承关系、错误的方法签名);
- 字节码验证(保证方法体中的指令不会做出危害行为);
- 符号引用验证(确保解析时能正确找到对应的类/方法/字段)。
- 文件格式验证(是否符合
⚠️ 如果验证失败,JVM会抛出
VerifyError
错误。
3. 准备(Preparation)
- 作用:为类变量(static 修饰的变量)分配内存,并设置默认初始值(不是代码中赋的值)。
- 注意:
- 不包括实例变量;
- 初始值通常是零值(如
int=0
,boolean=false
,Object=null
); - 常量(final static)在该阶段就会被显式初始化。
public static int value = 123; // 准备阶段会被设为 0,初始化阶段才会赋值为 123
public static final String NAME = "Tom"; // 常量会在准备阶段直接赋值
4. 解析(Resolution)
- 作用:将常量池中的符号引用替换为直接引用(即实际内存地址)。
- 符号引用:用一组符号来描述目标,比如类名、方法名、字段名;
- 直接引用:指向目标的指针、偏移量或句柄。
例如:
SomeClass obj = new SomeClass(); // 调用 new 指令时,SomeClass 是符号引用
解析可以在初始化之前完成,也可以在初始化之后按需进行(称为 延迟解析)。
5. 初始化(Initialization)
- 作用:真正执行类中定义的 Java 程序代码(静态变量赋值、静态代码块)。
- 执行顺序:
- 父类先于子类;
- 静态变量赋值与静态代码块按照代码顺序执行;
- 触发时机:
- 主动使用类时才触发初始化(如创建实例、访问静态变量、调用静态方法等)。
static {System.out.println("静态代码块执行");
}
6. 使用(Using)
- 作用:JVM 开始从
main
方法开始执行用户程序代码。 - 此阶段属于程序运行期,不再属于类加载过程本身。
public static void main(String[] args) {MyClass obj = new MyClass(); // 使用类
}
7. 卸载(Unloading)
- 作用:当类不再被使用且满足一定条件时,由 JVM 的垃圾回收器回收其占用的内存。
- 卸载条件:
- 该类的所有实例都被回收;
- 加载该类的
ClassLoader
被回收; - 该类对应的
java.lang.Class
对象没有被引用;
- 通常发生在自定义类加载器加载的类中,Bootstrap 类加载器加载的类一般不会被卸载。
📌 总结图示
[加载] → [验证] → [准备] → [解析] → [初始化] → [使用] → [卸载]
阶段 | 是否由JVM主导 | 是否可扩展 | 是否必须 |
---|---|---|---|
加载 | ✅ | ✅(可通过自定义ClassLoader) | ✅ |
验证 | ✅ | ❌ | ✅ |
准备 | ✅ | ❌ | ✅ |
解析 | ✅ | ❌ | ✅ |
初始化 | ✅ | ❌ | ✅ |
使用 | ❌ | ✅ | ✅ |
卸载 | ✅ | ❌ | ❌(可选) |
💡 示例代码演示类加载顺序
class Parent {static {System.out.println("父类静态代码块");}
}class Child extends Parent {static {System.out.println("子类静态代码块");}
}public class TestClassLoad {public static void main(String[] args) {Child child = new Child();}
}
输出结果:
父类静态代码块
子类静态代码块
三.自动装配(Autowiring)和 自动配置(Auto-Configuration)
✅ 一、Spring 的自动装配(Autowiring)
📌 定义:
自动装配 是 Spring IoC 容器的一种依赖注入方式,它可以根据类型(或名称)自动将 Bean 注入到其他 Bean 中,而无需手动通过 XML 配置或 Java Config 显式指定依赖关系。
🧩 常见的实现方式:
- @Autowired
- 根据类型自动装配。
- 可用于字段、构造方法、setter 方法等。
@Service
public class OrderService {@Autowiredprivate OrderRepository orderRepository;
}
- @Resource
- 默认按名称自动装配(来自 J2EE 标准)。
- 可以通过
name
属性指定 bean 名称。
@Resource(name = "userRepository")
private UserRepository userRepo;
- 构造函数注入 / setter 注入
- 自动装配也可以通过构造器或 setter 实现。
@Autowired
public OrderService(OrderRepository repo) {this.repo = repo;
}
✅ 优点:
- 减少冗余代码;
- 提高可维护性和解耦性;
- 更加符合面向接口编程的思想。
✅ 二、Spring Boot 的自动配置(Auto-Configuration)
📌 定义:
自动配置 是 Spring Boot 提供的一项功能,它基于类路径中的 jar 包和项目配置,自动创建并注册一些常用的 Bean 到 Spring 容器中,从而简化开发流程。
🔍 工作原理:
Spring Boot 使用了以下机制来实现自动配置:
- @Conditional 注解家族:根据某些条件决定是否加载某个 Bean。
- spring.factories 文件:位于
META-INF/spring.factories
,列出所有自动配置类。 - @EnableAutoConfiguration 注解:启用自动配置功能(通常由
@SpringBootApplication
间接引入)。
🧩 示例说明:
当你添加了如下依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Spring Boot 会自动为你配置:
- 数据源(DataSource)
- Hibernate/JPA 相关 Bean
- 事务管理器(TransactionManager)
- 如果你配置了数据库连接信息,还会自动创建 EntityManagerFactory
✅ 优点:
- 极大简化了 Spring 应用的初始搭建;
- 减少了大量的样板配置代码;
- 支持开箱即用的默认行为;
- 同时支持自定义覆盖默认配置。
🔄 对比总结:自动装配 vs 自动配置
特性 | 自动装配(Autowiring) | 自动配置(Auto-Configuration) |
---|---|---|
所属框架 | Spring Framework | Spring Boot |
作用 | 将已有的 Bean 自动注入到其他 Bean 中 | 根据依赖和环境自动创建 Bean 并注册进容器 |
关键注解 | @Autowired , @Resource | @EnableAutoConfiguration , @ConditionalOn... |
是否需要先有 Bean | 是 | 否(自动帮你创建) |
主要用途 | 依赖注入 | 简化配置,减少手动编写配置类 |
示例 | 注入 Service 到 Controller | 自动配置数据源、WebMvc、Security 等 |
🧪 举个例子对比理解:
场景:我们要使用一个 EmailService
✅ 自动装配(前提:已经有 EmailService Bean 存在)
@Component
public class EmailService {public void sendEmail() { ... }
}@RestController
public class UserController {@Autowiredprivate EmailService emailService; // 自动注入已存在的 Bean
}
✅ 自动配置(前提:添加了相关 starter,如 spring-boot-starter-mail)
Spring Boot 会自动配置好 JavaMailSender
这个 Bean,你只需直接注入使用即可:
@RestController
public class UserController {@Autowiredprivate JavaMailSender mailSender; // 自动配置的 Bean
}
✅ 总结一句话:
自动装配(Autowiring) 是把已有的 Bean 自动注入到其他组件中;
自动配置(Auto-Configuration) 是根据依赖自动帮你创建并注册 Bean。
两者结合使用,是 Spring Boot 快速开发的核心思想之一。
如果你还想了解如何自定义自动配置类(比如写自己的 Starter),我也可以继续讲解 😄