现在是时候讨论Spring Bean从产生到销毁整个过程的细节了,也就是Spring Bean的生命周期。在这里文哥先温馨提示:Spring Bean的生命周期是面试高频点之一,希望大家好好掌握哦~
一. Spring Bean生命周期的概述
如果没有Spring的环境,Java Bean的生命周期非常简单,通过new关键字创建的对象就可以被使用,一旦这个对象不再被使用了(JVM中通过可达性搜索算法判断对象是否可用),这个对象就会被判定为垃圾对象,然后被垃圾回收器回收。
但是在Spring中,Bean的生命周期就不是这么简单的了。由于Spring对Bean管理灵活度非常高,这就导致Spring Bean的生命周期非常复杂。接下来,文哥带领大家一探Spring Bean生命周期的细节。为了完整地展示Spring的生命周期,文哥用一幅图来描述Spring Bean生命周期的整个过程。
我们发现整个生命周期异常复杂,别慌,文哥这就给大家慢慢地分析,跟我来。
二. 详解Spring Bean的生命周期
1. BeanNameAware
这是一个接口,在Spring源码中是这样描述的。如果Spring中的Bean实现了这个接口的话,Spring的Bean的id将会传入给setBeanName的形参里面。
现在我们验证一下是不是如我们上面定义的那样,接下来我们通过几个步骤来进行分析。
1.1 第一步:定义一个类,该类必须实现BeanNameAware接口
public class User implements BeanNameAware
{
public void setBeanName(String name)
{
System.out.println("bean的名称是:" + name);
}
}
1.2 第二步:在Spring的配置文件中管理Bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.qf.bean.User"></bean>
</beans>
1.3 第三步:编写测试类
public class TestUser {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
1.4 第四步:打断点测试
我们在User的setBeanName上打上断点,查看一下效果:
以上结果就验证了刚刚说的,这个方法里面会接收Bean的id的值。
2. BeanClassLoaderAware
在源码中,会将其描述成一个接口。
这个接口里面定义了setBeanClassLoader方法。这个方法就是设置Bean加载器的方法,方法的形式参数传递的是一个类加载器对象。
3. BeanFactoryAware
Spring也是将其描述成了一个接口。
如果一个Bean实现了这个接口,可以获取这个Bean的Bean工厂。
4. EnvironmentAware
如果一个Bean实现了这个接口,就可以获取当前Bean对应的运行环境。
如果我们想获取Bean对应的环境信息,我们可以这么做:
public class User implements EnvironmentAware{
public void setEnvironment(Environment environment) {
System.out.println(environment);
}
}
5. ResourceLoaderAware
这也是一个接口。
如果一个Bean实现了这个接口我们可以获得一个资源加载器,用来去加载Bean所需要用到的资源信息。
6. ApplicationEventPublisherAware
这个接口是用来做事件发布用的。Spring设计这个组件主要是用来组件解耦使用的,如果一个Bean所属的类实现了这个接口,就可以获取一个事件发布器。
7. MessageSourceAware
Spring同样将其设计成了一个接口,我们可以通过这个接口去获取Bean相关的国际化资源信息。
8. ApplicationContextAware
当一个Bean所属的类实现了这个接口之后,这个类就可以方便地获得ApplicationContext对象(Spring上下文),Spring发现某个Bean实现了ApplicationContextAware接口,Spring容器会在创建该Bean之后,自动调用该Bean的setApplicationContext(参数)方法,调用该方法时,会将容器本身ApplicationContext对象作为参数传递给该方法。
我们在Bean所属的类上面实现这个接口:
public class User implements ApplicationContextAware {
private ApplicationContext context;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}
我们打断点验证我们的猜想:
我们看看这个context是什么:
这不就是我们上面说的,将ApplicationContext实例对象传入进来了吗?
9. ServletContextAware
在上面我们获取了Spring的Bean工厂实例对象,接下来Spring框架会去判断当前容器是否是一个Web类型的容器实例,如何判断?就需要调用ServletContextAware中的setServletContext方法,获取一个ServletContext容器。那到底是如何获取的呢?
private ServletContext servletContext;
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
10. BeanPostProcessor
该接口我们也叫Bean的后置处理器,作用是在Bean对象在实例化和依赖注入完毕后,在显式调用初始化方法的前后添加我们自己自定义的逻辑。注意是Bean实例化完毕后及依赖注入完成后触发的。接口的源码如下
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
这两个方法是什么意思呢,给大家详细介绍:
postProcessBeforeInitialization:Bean实例化、依赖注入完毕之后,在调用Bean初始化之前完成一些定制的初始化任务
postProcessAfterInitialization:Bean实例化、依赖注入、初始化完毕时执行
现在,我们就自定义一个Bean的后置处理器,有以下几个实现步骤。
10.1 第一步:创建一个类,实现BeanPostProcessor接口
public class CustomizeBeanProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行before方法,bean是:" + bean + "bean的名称是:" + beanName);
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行after方法,bean是:" + bean + "bean的名称是:" + beanName);
return bean;
}
}
文哥在这里特别提示:这两个方法的返回值千万不能返回为null。如果返回null那么在后续初始化方法因为通过getBean()方法获取不到Bean实例对象会报空指针异常,因为后置处理器从Spring IoC容器中取出Bean实例对象没有再次放回IoC容器中。
10.2 第二步:定义一个Pojo类
我们定义一个Student类,在里面定义一个init方法。
public class Student {
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
//定义一个初始化bean的时候需要执行的方法
public void init(){
System.out.println("初始化方法init执行了.....");
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
10.3 第三步:在Spring的配置文件中配置pojo和Bean的后置处理器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--管理student-->
<bean id="student" class="com.qf.bean.Student" init-method="init">
<property name="id" value="10010"></property>
<property name="name" value="eric"></property>
<property name="age" value="12"></property>
</bean>
<!--管理bean的后置处理器-->
<bean class="com.qf.processor.CustomizeBeanProcessor"></bean>
</beans
10.4 第四步:测试
我们定义一个测试类,初始化一个IOC容器,然后从容器中获取Bean,然后查看控制台效果。
public class TestStudent {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
System.out.println(student);
}
}
我们查看控制台输出信息:
通过验证,我们终于知道了Bean的后置处理器的作用了。
11. InitializingBean
该接口只有这一个接口。这个方法将在所有的属性被初始化后调用,但是会在 init 前调用。这个接口我们一般在工作中用于工厂+策略模式优化过多的if else代码块。
现在文哥给大家演示一下,这个接口的简单用法:
11.1 第一步:在pojo类上实现这个接口。
11.2 第二步:运行测试代码,查看控制台效果
到这里我们基本上给大家把Spring Bean生命周期中所涉及的主要接口给大家讲解清楚了。接下来文哥再给大家用代码演示Spring Bean的生命周期。
三. 验证Spring Bean的生命周期
1. 定义一个POJO类
定义一个POJO类,我们分别实现上面刚刚给大家介绍的生命周期中的接口。
public class Student implements BeanNameAware, BeanFactoryAware, ApplicationContextAware,InitializingBean, DisposableBean {
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
//定义一个初始化bean的时候需要执行的方法
public void init(){
System.out.println("初始化方法init执行了.....");
}
//定义销毁bean的时候需要指定的方法
public void preDestroy(){
System.out.println("销毁bean的方法preDestroy执行了....");
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("Student的setBeanFactory方法执行了.....");
}
public void setBeanName(String beanName) {
System.out.println("Student的bean的名称是:" + beanName);
}
public void destroy() throws Exception {
System.out.println("destroy方法执行了....");
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet被调用了....");
}
}
2. 定义Bean的后置处理器
这个后置处理器,在上面文哥给大家介绍过,我们需要实现里面的两个方法,注意方法的返回值一定不能为null。
public class CustomizeBeanProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行before方法,bean是:" + bean + "bean的名称是:" + beanName);
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行after方法,bean是:" + bean + "bean的名称是:" + beanName);
return bean;
}
}
3. 定义Spring的配置文件
定义Spring的配置文件,管理我们的POJO类和后置处理器类。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--管理student-->
<bean id="student" class="com.qf.bean.Student" init-method="init" destroy-method="preDestroy">
<property name="id" value="10010"></property>
<property name="name" value="eric"></property>
<property name="age" value="12"></property>
</bean>
<!--管理bean的后置处理器-->
<bean class="com.qf.processor.CustomizeBeanProcessor"></bean>
</beans>
4. 定义测试类,查看控制台输出效果
public class TestStudent {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
System.out.println(student);
//关闭容器,销毁Bean
((ClassPathXmlApplicationContext) context).close();
}
}
我们此时来查看一下控制台的效果,如下图所示:
通过这篇文章,给大家非常详细地介绍了Spring中Bean的生命周期,可以说本文是从源码角度,深入浅出地剖析了Spring中Bean的生命周期整个执行流程。虽然本文的讲解步骤比较繁琐,但如果大家在面试中能够回答的这么深入,相信大家一定可以让面试官刮目相看!