【深入浅出Spring6】第四期——实例化Bean和Bean的生命周期

一、获取 Bean

  • Spring 提供了多种实例化Bean的方式:【只是表现形式不同,底层都是通过构造方法创建对象的】
    • 通过构造方法实例化 【最简单的方式直接声明bean
    • 通过简单工厂模式实例化 【定义一个简单模式工厂,然后通过工厂的静态方法获得Bean
    • 通过factory-bean实例化 【定义一个方法工厂,通过实例方法(需要创建对象才能调用)获取Bean
    • 通过FactoryBean接口实例化 【我们工厂类实现了FactoryBean 接口,声明工厂类的bean就能返回特定Bean的实例】

$ 通过构造方法实例化

  • 需求:我们创建一个普通的Bean,然后在配置文件中声明一下,最后测试是否可以成功获得Bean

编写一个Bean类 SpringBean.java

package com.powernode.spring6.bean;

/**
 * @author Bonbons
 * @version 1.0
 */
public class SpringBean {
}

编写我们的配置文件 spring.xml

<!--
Bean对象实例化方法一: 通过声明bean,给出全限定类名,spring会自动调用该bean的无参构造方法来实例化Bean
-->
<bean id="sb" class="com.powernode.spring6.bean.SpringBean"/>

编写测试方法 SpringBeanTest.java

@Test
    public void testInstantiation1(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        SpringBean sb = applicationContext.getBean("sb", SpringBean.class);
        System.out.println(sb);
    }

$ 通过简单工厂实例化

  • 在之前工厂模式专题我们可以知道,简单工厂就是通过一个静态方法来获取Bean对象

编写我们的测试类 Gun.java

package com.powernode.spring6.bean;

/**
 * @author Bonbons
 * @version 1.0
 */
public class Star {
    public Star() {
        System.out.println("调用了Star的无参构造方法。");
    }
}

编写我们的简单工厂类 StarFactory.java

package com.powernode.spring6.bean;

/**
 * @author Bonbons
 * @version 1.0
 */
public class StarFactory {
    // 在简单工厂中,通过静态方法获取我们的产品对象
    public static Star get(){
        return new Star();
    }
}

编写我们的配置文件

<!--方法二:通过简单工厂模式实例化Bean
               我们需要指定使用哪个工厂,指定调用工厂的哪个静态方法获取Bean
               这个Bean实际还是自己 new 的,只不过是通过Spring的bean来获取
               底层还是调用的构造方法,只是外在的展示形式不同
               -->
    <bean id="starBean" class="com.powernode.spring6.bean.StarFactory" factory-method="get" />

编写我们的测试方法

@Test
    public void testInstantiation2(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Star star = applicationContext.getBean("starBean", Star.class);
        System.out.println(star);
    }

在这里插入图片描述

$ 通过 factory-bean 实例化

  • 利用工厂方法模式来获取Bean对象,与简单工厂模式的区别在于 获取对象的方法方法不是静态的 【需要创建这个工厂的Bean对象】
  • 需求:我们创建一个类和对应的工厂,在配置文件中演示如果通过工厂方法模式实例化我们的Bean

编写我们的 Gun.java

package com.powernode.spring6.bean;

/**
 * 具体产品角色
 * @author Bonbons
 * @version 1.0
 */
public class Gun {
    public Gun() {
        System.out.println("调用了Gun的无参构造方法。");
    }
}

编写我们的工厂方法类 GunFactory.java

package com.powernode.spring6.bean;

/**
 * 具体工厂角色
 * @author Bonbons
 * @version 1.0
 */
public class GunFactory {
    // 方法是实例的 >> 需要创建对象才能调用
    public Gun get(){
        return new Gun();
    }
}

配置我们的 bean

<!--
        方法三:通过工厂方法模式实例化Bean
        因为需要创建工厂的Bean,
        通过 factory-bean 属性告诉Spring使用哪个工厂的对象,
        然后通过 factory-method告诉Spring调用哪个方法获取Bean
    -->
    <bean id="factory" class="com.powernode.spring6.bean.GunFactory" />
    <bean id="gunBean" factory-bean="factory" factory-method="get"/>
  • 第一步,我们要声明我们工厂的bean,因为要用到这个对象
  • 第二步,不需要指定class属性,通过 factory-beanfactory-method 两个属性指定了使用哪个对象的什么方法获取Bean

编写测试方法:

@Test
    public void testInstantiation3(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Gun gun = applicationContext.getBean("gunBean", Gun.class);
        System.out.println(gun);
    }

在这里插入图片描述

  • 为什么此处还打印了 调用了Star的无参构造方法。
    • 因为我们生命Bean的时候scope使用了默认的 singleton >> 单例模式
    • 创建 Bean 对象的时机是初始化上下文的时候,也就是解析XML文件时就给XML文件中的所有Bean创建了对象。

$ 通过 FactoryBean 实例化

  • 属于第三种方法的简化版,我们只要将工厂实现了FactoryBean接口,就不用去配置 factory-beanfactory-method 两个属性
  • 在我们声明工厂Bean的时候,就会返回一个我们指定的普通Bean的实例
  • factory-bean会自动指向实现FactoryBean接口的类,factory-method会自动指向**getObject()**方法
  • 需求:通过一个类和对应的工厂类来演示

定义我们的具体角色类 Person.java

package com.powernode.spring6.bean;

/**
 * @author Bonbons
 * @version 1.0
 */
public class Person {
    public Person() {
        System.out.println("调用了Person的无参构造方法。");
    }
}

定义我们的具体工厂类 PersonFactory.java

package com.powernode.spring6.bean;

import org.springframework.beans.factory.FactoryBean;

/**
 * 工厂Bean >> 可以获得普通Bean
 * @author Bonbons
 * @version 1.0
 */
public class PersonFactoryBean implements FactoryBean<Person> {
    @Override
    public Person getObject() throws Exception {
        // 获取我们的Bean对象
        return new Person();
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        // 在接口中就实现了这个方法,默认为 true >> 单例
        return FactoryBean.super.isSingleton();
    }
}

编写配置文件

<!--
        方法四:通过FactoryBean接口来实例化我们的Bean
            属于方法三的简化形式,我们的工厂方法模式实现了这个接口,在配置的时候就不需要
            指定factory-bean、factory-bean两个属性
            直接通过创建工厂Bean就能直接返回一个具体的普通Bean的对象
    -->
    <bean id="person" class="com.powernode.spring6.bean.PersonFactoryBean" />

编写我们的测试文件

@Test
    public void testInstantiation4(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Person person = applicationContext.getBean("person", Person.class);
        System.out.println(person);
    }

在这里插入图片描述

$ BeanFactory 和 FactoryBean 的区别

  • BeanFactory

    • 工厂
    • Spring IoC 容器的顶级对象,被称为“Bean工厂”,负责创建Bean对象
  • FactoryBean

    • Bean
    • 能够辅助Spring实例化其它Bean对象的一个Bean。
  • 在Spring中,Bean可以分为两类:

    • 普通的Bean
    • 工厂Bean

$ 注入 Date 针对方法四的实现案例分析

  • 我们知道 Date 可以作为简单类型也可以作为非简单类型使用
    • 作为简单类型时,通过 value 传递参数值要采用特定的语法格式
    • 作为非简单类型时,我们通过 ref 传入指定日期的 Bean
  • 需求:接下来我们演示如何通过工厂类实现非简单类型日期的注入

编写我们的普通角色类 Person.java

package com.powernode.spring6.bean;
package com.powernode.spring6.bean;

import java.util.Date;

/**
 * @author Bonbons
 * @version 1.0
 */
public class Student {
    // 为了演示如何注入Date类型 >> 定义一个私有日期类型的birth
    private Date birth;

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    @Override
    public String toString() {
        return "Student{" +
                "birth=" + birth +
                '}';
    }
}

编写我们的工厂类 DateFactory.java

package com.powernode.spring6.bean;

import org.springframework.beans.factory.FactoryBean;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 使用工厂类获取我们的日期对象
 * @author Bonbons
 * @version 1.0
 */
public class DataFactoryBean implements FactoryBean<Date> {
    // 通过构造方法传递我们要生成的日期
    private final String strDate;

    public DataFactoryBean(String srtDate) {
        this.strDate = srtDate;
    }

    @Override
    public Date getObject() throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        // 格式化我们的日期字符串
        return sdf.parse(strDate);
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
}

编写我们配置文件

<bean id="date" class="com.powernode.spring6.bean.DataFactoryBean">
        <!--通过它的构造方法传递我们的日期字符串-->
        <constructor-arg index="0" value="2022-11-15" />
    </bean>
    <bean id="student2" class="com.powernode.spring6.bean.Student">
        <property name="birth" ref="date" />
</bean>
  • 我们通过构造注入传入指定的日期,在工厂类中通过实例方法将字符串类型的日期转化为对应的格式
  • 再创建我们Student的bean的时候,将属性值注入我们工厂类获得的Bean

编写我们测试方法

@Test
    public void testStudent2(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Student student2 = applicationContext.getBean("student2", Student.class);
        System.out.println(student2);
    }

在这里插入图片描述

可以看到我们输入的日期还是那个格式,只是通过工厂类把输入日期的格式变成了我们习惯的格式。

二、Bean的生命周期

🌔 1、什么是Bean的生命周期?

  • 从对象创建到销毁的一个过程
  • Spring 框架就是一个Bean的工厂,负责Bean对象的创建和销毁

🌔 2、那么一个Bean的一个完整生命周期都包括哪些部分?

  • 粗略的分,可以将生命周期分为五步
  • 考虑Bean后处理器,可以将生命周期分为七步
  • 再考虑接口的识别,可以将生命周期分为十步

$ 分为五步的生命周期

  • 都包含哪五步:
    • 第一步:实例化Bean
    • 第二步:Bean属性赋值
    • 第三步:初始化Bean
    • 第四步:使用Bean
    • 第五步:销毁Bean
  • 需求:需要我们自己编写初始化和销毁方法,并在配置文件中进行配置

编写我们的 User 类,我们通过User类演示Bean的这五步生命周期

package com.powernode.spring6.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;

/**
 * Bean的生命周期被分为五步的情况
 * @author Bonbons
 * @version 1.0
 */
public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
    public User(){
        System.out.println("第一步,无参构造方法被调用");
    }
    private String name;

    public void setName(String name) {
        System.out.println("第二步,set方法被调用 >> 属性赋值");
        this.name = name;
    }

    // 让我们的User类去实现这三个接口 >> 用于在执行Bean后处理器的before方法之前调用
    public void initBean(){
        System.out.println("第三步,初始化Bean");
    }

    public void destroyBean(){
        System.out.println("第五步,销毁Bean");
    }
}

在配置文件中声明一下我们的Bean

<!--将bean的生命周期分为五步:需要配置我们的初始化方法和销毁方法-->
    <bean id="user" class="com.powernode.spring6.bean.User" init-method="initBean" destroy-method="destroyBean">
        <property name="name" value="白居易" />
    </bean>
  • 通过 init-method 属性指定我们的初始化方法
  • 通过 destroy-method 属性指定我们的销毁方法 【销毁不会自动执行,需要我们在测试方法中手动完成】

编写我们的测试方法

@Test
    public void testBeanLifecycleFive(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        User user = applicationContext.getBean("user", User.class);
        System.out.println("第四步,使用Bean" + user);
        // 需要我们手动销毁Bean,但是这个方法属于ClassPathXMLApplicationContext的,所以我们需要强转
        ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
        context.close();
    }
  • 只有正常关闭spring容器,bean的销毁方法才会被调用。
  • ClassPathXmlApplicationContext类才有close()方法,所以我们需要强制类型转换
    在这里插入图片描述

$ 分为七步的生命周期

  • 我们使用了Bean后处理器,就会在初始化Bean前后添加两个方法
    • 一个方法为Bean后处理器的 before 方法
    • 另一个方法为Bean后处理器的after方法
  • 我们这个Bean后处理器需要实现 BeanPostProcessor 接口中的方法,才能作为Bean后处理器使用
  • 而且这个Bean后处理器的作用范围是整个XML文件,配置后对整个XML文件中的bean都生效
  • 需求:我们写一个Bean后处理器,查看是否生命周期变成了七步

编写我们的Bean后处理器:LogBeanPostProcessor

package com.powernode.spring6.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;

/**
 * 日志Bean后处理器 >> 用来演示初始化前后插入代码的
 * @author Bonbons
 * @version 1.0
 */
public class LogBeanPostProcessor implements BeanPostProcessor {
    // bean 我们的bean对象、beanName 我们bean的名字
    @Nullable
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行Bean后处理器的before方法");
        return bean;
    }

    @Nullable
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行Bean后处理器的after方法");
        return bean;
    }
}

在配置文件中配置我们的Bean后处理器

<!--配置Bean后处理器,作用范围是整个配置文件-->
<bean class="com.powernode.spring6.bean.LogBeanPostProcessor" />

测试方法我们依旧使用我们上面五步生命周期的测试方法 >> 我们可以很清楚的看到生命周期变成了七步

在这里插入图片描述

$ 分为十步的生命周期

  • 在七步的基础上,增添了识别Bean是否实现了某些特定的接口
  • 如果实现了特定的接口,就调用接口中的方法,给我们传递一些与Bean相关的一些参数信息
  • 重新梳理一下,生命周期这十步都包括什么?
    • 第一步,实例化Bean
    • 第二步,Bean属性赋值
    • 第三步,检查Bean是否实现了Aware的相关接口,并设置相关依赖
      • BeanNameAware:通过setBeanName方法Spring会将Bean的名字传递给Bean
      • BeanClassLoaderAware:通过setBeanClassLoader方法Spring会将加载该Bean的类加载器传递给Bean
      • BeanFactoryAware :通过setBeanFactory方法Spring会将Bean工厂对象传递给Bean
    • 第四步,Bean后处理器的before执行
    • 第五步,检查Bean是否实现了InitializingBean接口,井调用接口方法 【afterPropertiesSet
    • 第六步,初始化Bean
    • 第七步,Bean后处理器的after执行
    • 第八步,使用Bean
    • 第九步,检查Bean是否实现了DisposableBean接口,井调用接口方法【destroy
    • 第十步,销毁Bean
  • 需求:让我们的User实现这三种接口中的方法,查看一下这十步的具体执行情况

让我们User类实现这五个接口 【第一种三个、第二种一个、第三种一个】

package com.powernode.spring6.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;

/**
 * Bean的生命周期被分为五步的情况
 * @author Bonbons
 * @version 1.0
 */
public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean{

    public User(){
        System.out.println("第一步,无参构造方法被调用");
    }
    private String name;

    public void setName(String name) {
        System.out.println("第二步,set方法被调用 >> 属性赋值");
        this.name = name;
    }

    // 让我们的User类去实现这三个接口 >> 用于在执行Bean后处理器的before方法之前调用
    public void initBean(){
        System.out.println("第三步,初始化Bean");
    }

    public void destroyBean(){
        System.out.println("第五步,销毁Bean");
    }

    @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's destroy method");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean's afterPropertiesSet method");
    }
}

配置文件、测试文件都不需要改变

在这里插入图片描述

$ 不同的作用域的管理方式不同

  • 对于上面的生命周期,是针对我们采用默认的作用域 singleton 而言的
  • 如果我们让 scope = prototype,那么spring的工作内容只负责到使用Bean,之后的工作内容交给我们的客户端
  • 需求:我们将上面十步生命周期的配置文件进行修改,将scope设置为多例模式
<!--将bean的生命周期分为五步:需要配置我们的初始化方法和销毁方法-->
    <bean id="user" class="com.powernode.spring6.bean.User" init-method="initBean" destroy-method="destroyBean" scope="prototype">
        <property name="name" value="白居易" />
    </bean>

执行测试方法 >> Spring在完成Bean对象初始化之后,就不再追踪其生命周期了

在这里插入图片描述

$ 将我们自己创建的对象添加到Spring容器中

  • 需求:
    • 我们定义一个 Student 类,然后通过测试方法将这个类的实例添加到Spring容器中
    • 通过getBean再获取一下,看是否添加成功【返回同一个对象说明我们注入成功】

编写我们的Student

package com.powernode.spring6.bean;

/**
 * @author Bonbons
 * @version 1.0
 */
public class Student {
}

编写我们的测试方法:

@Test
    public void testRegisterBean(){
        // 我们自己创建一个对象
        Student student = new Student();
        System.out.println(student);

        // 创建一个可以将对象加入到Bean中的工厂
        DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
        defaultListableBeanFactory.registerSingleton("studentBean", student);

        // 通过getBean方法获取我们的Bean
        Student studentBean = defaultListableBeanFactory.getBean("studentBean", Student.class);
        System.out.println(studentBean);
    }

通过测试结果我们可以得出结论 >> 注入我们自己new的对象成功
在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bow.贾斯汀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值