Bootstrap

Bean的生命周期详解保姆级教程,结合spring boot和spring.xml两种方式讲解,5/7/10大小阶段详细分析

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-methoddestroy-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 容器当中进行管理!

;