文章目录
Spring Bean的生命周期
一、为什么知道 Bean 的生命周期?
生命周期的本质:在哪个时间节点上调用了哪个类的哪个方法?我们需要充分的了解在这个生命线上,都有哪些特殊的时间节点!只有我们知道了这些特殊的时间节点都在哪,到时候我们才可以确定代码写到哪。我们可能需要在某个特殊的时间节点上执行一段特定的代码,这段代码就可以放到这个节点上;当生命线走到这里的时候,自然会被调用。
二、生命周期大致了解
1、Spring Bean 的生命周期是指一个 Bean 在容器中从创建、初始化、销毁的全过程。
2、容器启动 ----> 实例化 Bean ----> 依赖注入 ----> 初始化前逻辑(BeanPostProcessor
)----> 初始化(@PostConstruct/InitializingBean/init-method
)----> 初始化后逻辑(BeanPostProcessor
)----> 使用 Bean ----> 销毁(@PreDestory/DisposableBean/destroy-method
)
3、各阶段解析与方法:
阶段 | 描述 | 可插入的自定义方法/接口 |
---|---|---|
实例化 | 容器通过反射创建 Bean 实例,但未设置属性或依赖 | 调用无参构造器创建对象 |
依赖注入 | 容器将依赖注入到 Bean 的属性中 | 通过依赖注入(例如 @Autowired 、@Resource、XML 配置或 Java 配置)将属性注入到 Bean 中 |
初始化前 | Bean 实例化和依赖注入后,执行初始化前的一些逻辑 | 实现 BeanPostProcessor 接口的 postProcessBeforeInitialization 方法 |
初始化 | 执行 Bean 的初始化逻辑 | @PostConstruct 、实现 InitializingBean 、配置文件指定的 init-method 方法 |
初始化后 | 执行初始化后的一些逻辑 | 实现 BeanPostProcessor 接口的 postProcessAfterInitialization 方法 |
使用 Bean | ||
销毁 | 容器关闭或 Bean 的生命周期结束时,执行销毁逻辑 | @PreDestroy 、实现 DisposableBean 、配置文件指定的 destroy-method 方法 |
三、详细分析生命周期
3.1 ① 初步划分为 5 步:
第一步:实例化 Bean(调用无参数构造方法)
第二步:给 Bean 属性赋值(调用set方法)
第三步:初始化 Bean(会调用 Bean 的 init 方法。注意:这个 init 方法需要自己写)
第四步:使用 Bean
第五步:销毁 Bean(会调用 Bean 的 destroy 方法。注意:这个 destroy 方法需要自己写)
3.1.1 spring 框架中怎么理解
代码测试:spring.xml 版
1、定义一个 Bean:
public class User {
private String username;
public void setUsername(String username) {
System.out.println("第二步:给对象的属性赋值");
this.username = username;
}
public User() {
System.out.println("第一步:实例化Bean,无参数构造方法执行了");
}
// 初始化Bean,需要自己写,自己配,方法名随意
public void initBean() {
System.out.println("第三步:初始化Bean");
}
// 销毁Bean,需要自己写,自己配,方法名随意
public void destroyBean() {
System.out.println("第五步:销毁Bean");
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
'}';
}
}
2、spring.xml 中配置:需要在 <bean>
标签中 设置 init-method 和 destroy-method 属性,手动指定初始化方法和销毁方法!
<bean id="user" class="全类名" init-method="initBean" destroy-method="destroyBean">
<!--给属性赋值-->
<property name="username" value="张三"/>
</bean>
3、开始测试:
@Test
public void test(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println("第四步:使用Bean" + User);
// 需要手动关闭Spring容器
ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
context.close();
注意:需要手动关闭 Spring 容器(调用close方法),这样 Spring 容器才会销毁 Bean,才会去调用我们定义的 destroyBean 方法!
结果:
3.1.2 spring boot 项目中怎么理解
代码测试:spring boot 版(Spring Boot 中没有 xml 怎么配置?)
1、同样定义一个 User(内容不变)
2、启动类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
User user = run.getBean(User.class);
//属性赋值
user.setUsername("张三");
//使用bean
System.out.println("第四步:使用Bean" + user);
//关闭容器
run.close();
}
//直接在注解中指明
@Bean(initMethod = "initBean", destroyMethod = "destroyBean")
public User getUserBean() {
return new User();
}
}
结果:
为什么?当前的行为是因为属性赋值是手动触发的,而非通过 Spring 的依赖注入机制完成。如果希望严格按照 Spring 生命周期,请确保所有操作都交由 Spring 管理,而不是手动调用属性方法。
- Spring 在生命周期的 第二步 时(属性赋值),它会使用 依赖注入机制 来完成,但你的
User
类没有定义依赖属性,也没有配置@Value
或 XML 属性,因此 Spring 不会自动调用setUsername
方法。 - 当 Spring 完成了实例化和初始化过程后,你手动调用
setUsername
,此时才出现 “第二步” 的输出。
修改:使用 @Value;
3.2 ② 细分 5 步为 7 步:
在以上的5步中,第3步是初始化Bean,如果你还想在初始化前和初始化后添加代码,可以编写一个类(这个类叫做 Bean 后处理器)实现BeanPostProcessor接口,并重写里面的 befor 和 after 方法。
第一步:实例化 Bean
第二步:Bean 属性赋值
第三步:初始化前逻辑
第四步:初始化 Bean
第五步:初始化后逻辑
第六步:使用 Bean
第七步:销毁 Bean
3.2.1 spring 框架中怎么理解
代码测试:spring.xml 版
1、编写类实现接口,重写方法,该方法有两个参数:创建的 Bean 对象、Bean 的名字
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Bean后处理器的before方法执行,即将开始初始化");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Bean后处理器的after方法执行,已完成初始化");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
2、在 spring.xml 文件中配置 Bean后处理器(MyBeanPostProcessor)
<bean id="user" class="全类名" init-method="initBean" destroy-method="destroyBean">
<!--给属性赋值-->
<property name="username" value="张三"/>
</bean>
<!-- 配置Bean后处理器。这个后处理器将作用于当前配置文件中所有的bean。-->
<bean class="MyBeanPostProcessor的全类名"/>
注意:是当前配置文件中所有的 bean。也就是有几个 bean,就会执行几次这个 后处理器,怎么解决?----> 下面会讲
运行结果:
3.2.2 spring boot 项目中怎么理解
代码测试:spring boot 版(Spring Boot 中没有 xml 怎么配置?)
同样:跟 xml 版一样,需要写个类去实现 BeanPostProcessor 接口,然后只需要在类上直接加 @Configuration
注解
注意:也会执行很多次,比如:
原因:这两个方法对 每个注册到容器的 Bean 都会被调用,Spring 容器默认会加载一些内部 Bean,因此输出会多次触发。
解决:加条件判断即可
@Configuration
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof User)
System.out.println("Bean后处理器的before方法执行,即将开始初始化");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof User)
System.out.println("Bean后处理器的after方法执行,已完成初始化");
return bean;
}
}
结果:
3.3 ③ 细分 7 步为 10 步:
问:在 Bean 后处理器 before方法之前 干了什么事?
答:检查 Bean 是否实现了 Aware 相关的接口,如果实现了接口则调用这些接口中的方法;调用这些方法的目的是为了给你传递一些数据,让你更加方便使用。
问:在 Bean 后处理器 before方法之后 干了什么事?
答:检查 Bean 是否实现了 InitializingBean 接口,如果实现了,则调用接口中的方法。
问:使用 Bean 之后,或者说 销毁 Bean 之前 干了什么事?
答:检查 Bean 是否实现了 DisposableBean 接口,如果实现了,则调用接口中的方法。
总结: 添加的这三个点位的特点,都是在检查你这个 Bean 是否实现了某些特定的接口,如果实现了这些接口,则 Spring 容器会调用这个接口中的方法!
Aware 相关的接口包括:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware
① 当 Bean 实现了 BeanNameAware,Spring 会将 Bean 的名字传递给 Bean。
② 当 Bean 实现了 BeanClassLoaderAware,Spring 会将加载该 Bean 的类加载器传递给 Bean。
③ 当 Bean实现了 BeanFactoryAware,Spring 会将 Bean 工厂对象传递给 Bean。
代码测试:两个版本都一样:只需修改 Bean(User)即可:
public class User implements
BeanNameAware, BeanClassLoaderAware, BeanFactoryAware,
InitializingBean, DisposableBean {
private String username;
@Value("张三")
public void setUsername(String username) {
System.out.println("第二步:给对象的属性赋值");
this.username = username;
}
public User() {
System.out.println("第一步:实例化Bean,无参数构造方法执行了");
}
// 初始化Bean,需要自己写,自己配,方法名随意
public void initBean() {
System.out.println("第三步:初始化Bean");
}
// 销毁Bean,需要自己写,自己配,方法名随意
public void destroyBean() {
System.out.println("第五步:销毁Bean");
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
'}';
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println("类加载器:" + classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("Bean工厂:" + beanFactory);
}
@Override
public void setBeanName(String s) {
System.out.println("Bean的名字:" + s);
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean destroy方法执行了");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean afterPropertiesSet方法执行了");
}
}
结果:
四、再谈scpoe下bean的管理方式
我们已经知道 Bean 中的 scpoe 就是 Bean 的作用域:
这里结合 Bean 的生命周期再谈 scope。
1、Spring 根据 Bean 的作用域来选择管理方式:
-
对于 singleton(单例)作用域的 Bean:Spring 能够精确地知道该 Bean 何时被创建、何时初始化完成、以及何时被销毁。
-
而对于 prototype(多例/原型) 作用域的 Bean:Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。
Spring 容器关闭,不会销毁类的对象,而是交给 Java 内存回收机制。
4.1 spring 框架中理解
2、测试:spring.xml 版
<!-- 指明scope属性-->
<bean id="user" class="全类名" init-method="initBean" destroy-method="destroyBean" scope="prototype">
<!--给属性赋值-->
<property name="username" value="张三"/>
</bean>
再次测试 10 步的代码,发现:检查 Bean 是否实现了 DisposableBean接口 和 销毁Bean 就不管了!
4.2 spring boot 项目中理解
spring boot 版:只需要在返回对象那里加个注解即可:
@Bean(initMethod = "initBean", destroyMethod = "destroyBean")
@Scope(value = "prototype")
public User getUserBean() {
return new User();
}
五 、自己new的对象如何让Spring管理
需求:有些时候可能会遇到这样的需求,某个 java 对象是我们自己手动 new 的,然后希望这个对象被 Spring 容器管理,怎么实现呢?
解决:
- 创建
DefaultListableBeanFactory
对象! - 注册Bean:调用上面创建的对象的
registerSingleton()
方法,把自己创建的对象传进去,并起一个名字! - 根据名字,调用
getBean
方法从 Spring 容器当中获取 Bean 对象!
测试:
public class Student {
}
@Test
public void test(){
//自己new的对象,没有被spring管理
Student student = new Student();
System.out.println(student);
DefaultListableBeanFactory factory = new DefaultListableBeanFactoru();
factory.registerSingleton("studentBean", student);
factory.getBean("studentBean", Student.class);
System.out.println(studentBean);
}
结果:输出的对象地址一摸一样,说明:获取到的是同一个 Bean 对象,说明确实是把我们创建的对象放到Spring 容器当中进行管理!