Spring系列-02-Bean类型, 作用域, 实例化, 生命周期

Bean类型, 作用域, 实例化, 生命周期

Bean类型

在 SpringFramework 中,对于 Bean 的类型,一般有两种设计:

  • 普通Bean
  • FactoryBean

普通Bean

三种方式

  1. @Component注解
  2. 配置类+@Bean
  3. xml + <bean>
@Component
public class Child {}
@Bean
public Child child() {
    return new Child();
}
<bean class="com.linkedbear.spring.bean.a_type.bean.Child"/>

FactoryBean

出现场景: SpringFramework考虑到一些特殊的设计: Bean的创建需要指定一些策略,或者依赖特殊的场景来分别创建,也或者一个对象的创建过程太复杂,使用xml或者注解声明也比较复杂。可以借助**FactoryBean**来使用工厂方法创建对象

FactoryBean介绍

FactoryBean 本身是一个接口,它本身就是一个创建对象的工厂。如果 Bean 实现了 FactoryBean接口,则它本身将不再是一个普通的Bean ,不会在实际的业务逻辑中起作用,而是由创建的对象来起作用

FactoryBean 接口有三个方法:

  • getObject(): 返回创建的对象
  • getObjectType(): 返回创建的对象的类型(即泛型类型)
  • isSingleton(): 创建的对象是单实例Bean还是原型Bean,默认单实例
public interface FactoryBean<T> {
    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}
FactoryBean使用

场景: 小孩子要买玩具,由一个玩具生产工厂来给这个小孩子造玩具

前置准备

孩子类

public class Child {
    // 当前的小孩子想玩球
    private String wantToy = "ball";
    
    public String getWantToy() {
        return wantToy;
    }
}

一个抽象类和两个实现类

@Data
@NoArgsConstructor
public abstract class Toy { // 抽象玩具类
    /*
    玩具名
    */
    private String name;

    public Toy(String name) {
        System.out.println("生产了一个" + name);
        this.name = name;
    }
}

@Data
@EqualsAndHashCode(callSuper = true)
public class Ball extends Toy { // 球
    public Ball(String name) {
        super(name);
    }
}


@Data
@EqualsAndHashCode(callSuper = true)
public class Car extends Toy {
    public Car(String car) {
        super(name);
    }
}
运行示例

创建一个 ToyFactoryBean ,让它实现 FactoryBean 接口, 同时希望能根据小孩子想要玩的玩具来决定生产哪种玩具, 那么这里就需要注入Child 。由于咱这里面使用的不是注解式自动注入, 那咱就用setter注入

@Setter
public class ToyFactoryBean implements FactoryBean<Toy> {
    public ToyFactoryBean() {
        System.out.println("ToyFactoryBean 初始化了。。。");
    }

    /*
    Child类
    */
    private Child child;

    @Override
    public Toy getObject() throws Exception {
        // 根据Child中的wantToy属性, 来决定创建哪个玩具
        switch (child.getWantToy()) {
            case "ball":
                return new Ball("ball");
            case "car":
                return new Car("car");
            default:
                return null;
        }
    }

    /*
    表示这个工厂类返回Toy类
    */
    @Override
    public Class<Toy> getObjectType() {
        return Toy.class;
    }
}

希望能让它根据小孩子想要玩的玩具来决定生产哪种玩具,那咱就得在这里面注入Child 。由于咱这里面使用的不是注解式自动注入,那咱就用setter注入吧

注解方式注册这两个

@Configuration
public class BeanTypeConfiguration {
    @Bean
    public Child child() {
        return new Child();
    }
    
    @Bean
    public ToyFactoryBean toyFactory() {
        ToyFactoryBean toyFactory = new ToyFactoryBean();
        toyFactory.setChild(child());
        return toyFactory;
    }
}
public class BeanTypeAnnoApplication {
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);
        Toy toy = ctx.getBean(Toy.class);
        // ToyFactoryBean 初始化了。。。
        // Ball()
        System.out.println(toy);
    }
}
FactoryBean与Bean同时存在

修改配置类BeanTypeConfiguration, 向IOC容器预先的创建一个Ball , 这样 FactoryBean 再创建一个, IOC容器里就会同时存在两个 Toy

@Configuration
public class BeanTypeConfiguration {
    @Bean
    public Child child() {
        return new Child();
    }

    // ---------- Start ----------
    @Bean
    public Toy ball() {
        return new Ball("ball");
    }
    // ---------- End ----------

    @Bean
    public ToyFactoryBean toyFactory() {
        ToyFactoryBean toyFactory = new ToyFactoryBean();
        toyFactory.setChild(child());
        return toyFactory;
    }
}

再次运行BeanTypeAnnoApplication类, 发现抛出NoUniqueBeanDefinitionException异常, 提示有两个 Toy

异常信息如下

Exception in thread “main” org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.linkedbear.spring.bean.a_type.bean.Toy’ available: expected single matching bean but found 2: ball,toyFactory

异常表明**FactoryBean创建的Bean是直接放在IOC容器中了**

查看IOC 容器中现有的 Toy

public class BeanTypeAnnoApplication {
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);

        // 获取指定类型的所有Bean,并返回Map集合
        Map<String, Toy> toys = ctx.getBeansOfType(Toy.class);
        toys.forEach((name, toy) -> {
            System.out.println("toy name : " + name + ", " + toy.toString());
        });
        /*
			输出结果如下:
			ToyFactoryBean 初始化了。。。
			toy name : ball, Ball()
			toy name : toyFactory, Ball()
        */
    }
}
FacotryBean的加载和创建Bean的时机
FactoryBean的加载时机

之前的准备已经铺垫好了

Toy的构造方法中调用System打印

public class Toy  {
public Toy(String name) {
    System.out.println("生产了一个" + name);
    this.name = name;
}

ToyFactoryBean 的默认构造方法中也有System打印

public ToyFactoryBean() {
    System.out.println("ToyFactoryBean 初始化了。。。");
}

修改BeanTypeAnnoApplication的main方法, 只初始化IOC容器

public class BeanTypeAnnoApplication {
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);
    }
}

控制台输出结果如下: ToyFactoryBean 初始化了。。。, 这表示只有ToyFactoryBean被初始化, 说明**FactoryBean本身的加载是伴随IOC容器的初始化时机一起的**

Factory创建Bean的时机

经过之前的代码, 发现如果只初始化IOC容器, 那么此时是Facotry没有创建Bean的(即有了玩具工厂, 但是玩具工厂还没有生产出玩具), 说明 FactoryBean 中要创建的 Bean 还没有被加载, 也就得出: FactoryBean 生产Bean的机制是延迟生产

修改BeanTypeAnnoApplication的main方法, 添加获取玩具的方法

public class BeanTypeAnnoApplication {
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);
        Toy toy = ctx.getBean(Toy.class);
    }
}

控制台打印结果如下, 表明了也就是获取Bean的时候, Factory才会生产Bean

ToyFactoryBean 初始化了。。。
生产了一个ball

FactoryBean创建Bean的实例数

FactoryBean 接口中有一个默认的方法 isSingleton , 默认是 true , 代表默认是单实例的

调用两次getBean(), 判断两次对象是否一致

public class BeanTypeAnnoApplication {
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);
        Toy toy1 = ctx.getBean(Toy.class);
        Toy toy2 = ctx.getBean(Toy.class);
        // 打印结果: true
        System.out.println(toy1 == toy2);
    }
}
获取FactoryBean本体

Toy 本体去取, 取到的都是 FactoryBean 生产的 Bean 。一般情况下咱也用不到 FactoryBean 本体, 但如果真的需要取, 使用的方法也很简单: 要么直接传 FactoryBean 的 class (很容易理解), 也可以传 ID 。不过, 如果真的靠传ID的话, 传配置文件 / 配置类声明的 ID 就不好使了, 因为那样只会取出生产出来的 Bean

public class BeanTypeAnnoApplication {
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);

        // 打印结果: com.linkedbear.spring.bean.a_type.bean.ToyFactoryBean@d29f28
        System.out.println(ctx.getBean("toyFactory"));
        
    }
}

FactoryBean 的方式, 需要在Bean的ID前面加&符号

public class BeanTypeAnnoApplication {
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);

        // 打印结果: Toy{name='ball'}
        System.out.println(ctx.getBean("&toyFactory"));
        
    }
}
BeanFactory与FactoryBean的区别

BeanFactory: SpringFramework 中实现 IOC的最底层容器(此处的回答可以从两种角度出发: 从类的继承结构上看, 它是最顶级的接口, 也就是最顶层的容器实现;从类的组合结构上看, 它则是最深层次的容器, ApplicationContext 在最底层组合了 BeanFactory )

FactoryBean: 创建对象的工厂Bean, 可以使用它来直接创建一些初始化流程比较复杂的对象

Bean作用域

为什么会出现多种不同的作用域呢

肯定是它可以被使用的范围不同了。那为什么不都统一成一样的作用范围呢?说白了, 资源是有限的, 如果一个资源允许同时被多个地方访问(如全局常量), 那就可以把作用域提的很高;反之, 如果一个资源伴随着一个时效性强的, 带强状态的动作, 那这个作用域就应该局限于这一个动作, 不能被这个动作之外的干扰。

SpringFramework中内置的作用域

SpringFramework 中内置了 6 种作用域(5.x 版本):

作用域类型概述
singleton一个 IOC 容器中只有一个【默认值】
prototype每次获取创建一个
request一次请求创建一个(仅Web应用可用)
session一个会话创建一个(仅Web应用可用)
application一个 Web 应用创建一个(仅Web应用可用)
websocket一个 WebSocket 会话创建一个(仅Web应用可用)

划分为三类,

  1. singleton(Spring核心)
  2. peorotype(Spring核心)
  3. web应用领域: request, session, application, websocket

singleton

SpringFramework中默认所有的 Bean 都是单实例的, 即: 一个IOC容器中只有一个

前置准备

创建ChildToy共两个类

@Data
public class Child { 
    private Toy toy;
}

// Toy中标注@Component注解, 表示给Spring托管
@Component
public class Toy {}

创建配置类, 并注册两个Child类, 表示两个小孩

@Configuration
@ComponentScan("com.linkedbear.spring.bean.b_scope.bean")
public class BeanScopeConfiguration {
    
    @Bean
    public Child child1(Toy toy) {
        Child child = new Child();
        child.setToy(toy);
        return child;
    }
    
    @Bean
    public Child child2(Toy toy) {
        Child child = new Child();
        child.setToy(toy);
        return child;
    }
    
}
运行示例

获取其中的 Child , 打印里面的 Toy

public class BeanScopeAnnoApplication {
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanScopeConfiguration.class);
        ctx.getBeansOfType(Child.class)
            .forEach((name, child) -> {
                System.out.println(name + ":" + child);
            });
			/*
			打印结果: 
			Toy constructor run ...
			child1:Child(toy=com.linkedbear.spring.bean.b_scope.bean.Toy@4b0d79fc)
			child2:Child(toy=com.linkedbear.spring.bean.b_scope.bean.Toy@4b0d79fc)
        */
    }

}

可以看到上述两个child的内存地址都一样, 证明默认情况下, Bean 的作用域是单实例的

prototype: 原型Bean

Spring官方的定义是: 每次对原型Bean提出请求时, 都会创建一个新的Bean实例。 这里面提到的"提出请求" , 包括任何依赖查找, 依赖注入的动作, 都算做一次"提出请求"

由此咱也可以总结一点: 如果连续 getBean() 两次, 那就应该创建两个不同的Bean实例;向两个不同的Bean中注入两次, 也应该注入两个不同的Bean实例

其实对于原型这个概念, 在设计模式中也是有对应的: 原型模式。原型模式实质上是使用对象深克隆, 乍看上去跟 SpringFramework 的原型 Bean 没什么区别, 但咱仔细想, 每一次生成的原型Bean本质上都还是一样的, 只是可能带一些特殊的状态等等, 这个可能理解起来比较抽象, 可以跟下面的 request 域结合着理解。

前置准备和运行示例

修改Toy 类: 给 Toy 的类上标注一个额外的注解: @Scope , 并声明为原型类型

@Component
@Scope("prototype")
// @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 这种写法也行, 建议使用常量
public class Toy {}

这个 prototype 不是随便写的常量, 而是在 ConfigurableBeanFactory 中定义好的常量

public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {
    String SCOPE_SINGLETON = "singleton";

    String SCOPE_PROTOTYPE = "prototype";

    // 省略其它代码
}

重新运行BeanScopeAnnoApplication类, 打印结果如下

child1:Child(toy=com.linkedbear.spring.bean.b_scope.bean.Toy@273e7444)
child2:Child(toy=com.linkedbear.spring.bean.b_scope.bean.Toy@7db12bb6)
原型Bean的创建时机

单实例 Bean 的创建咱已经知道, 是在 ApplicationContext 被初始化时就已经创建好了, 那这些原型 Bean 又是什么时候被创建的呢?其实也不难想出, 它都是什么时候需要, 什么时候创建

从以下角度时候, 原型Bean每次创建都是新, 即是初始化就创建也无济于事

修改Toy, 添加一个无参构造方法

@Component
@Scope("prototype")
public class Toy {
    public Toy() {
        System.out.println("Toy constructor run ...");
    }
}

修改BeanScopeAnnoApplication, 只让它扫描bean包(注意Toy类上要添加@Component注解), 不加载配置类, 这样就相当于只有一个Toy类被扫描进去了, Child 不会注册到 IOC 容器中

public class BeanScopeAnnoApplication {
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new AnnotationConfigApplicationContext("com.linkedbear.spring.bean.b_scope.bean");
        // 控制台什么也没打印, 因为没有Toy的使用需求嘛, 它当然不会被创建
    }
}

Web应用领域的作用域

  • request: 请求Bean, 每次客户端向 Web 应用服务器发起一次请求, Web 服务器接收到请求后, 由 SpringFramework 生成一个 Bean , 直到请求结束
  • session: 会话Bean, 每个客户端在与 Web 应用服务器发起会话后, SpringFramework 会为之生成一个 Bean , 直到会话过期
  • application: 应用Bean, 每个 Web 应用在启动时, SpringFramework 会生成一个 Bean , 直到应用停止(有的也叫 global-session )
  • websocket: WebSocket Bean , 每个客户端在与 Web 应用服务器建立 WebSocket 长连接时, SpringFramework 会为之生成一个 Bean , 直到断开连接

Bean实例化方式

普通Bean实例化

所有<bean> 标签@Bean 注解的方式, 都是普通Bean的对象, 它们默认是单实例, 在 IOC 容器初始化时就已经被初始化了

借助FactoryBean创建Bean

普通Bean+FactoryBean
public class Ball {}

/*
Ball的FacotryBean
*/
public class BallFactoryBean implements FactoryBean<Ball> {

    /*
    返回Bean
    */
    @Override
    public Ball getObject() {
        return new Ball();
    }

    /*
    返回Bean的类型
    */
    @Override
    public Class<Ball> getObjectType() {
        return Ball.class;
    }
}

@Configuration
public class BeanConfiguration {
    /*
    注册BallFactoryBean
    */
    @Bean
    public BallFactoryBean ballFactoryBean() {
        return new BallFactoryBean();
    }
}

注册Bean时, 只需要注入FactoryBean , IOC容器会自动识别, 并默认在第一次获取时创建对应的Bean并缓存(针对默认的单实例 FactoryBean )

借助静态工厂创建Bean

测试示例

创建Bean+静态工厂

public class Car {    
    public Car() {
        System.out.println("Car constructor run ...");
    }
}

/*
静态类工厂
*/
public class CarStaticFactory {
    public static Car getCar() {
        return new Car();
    }
}

配置xml

静态工厂的使用通常运用于 xml 方式比较多(主要是注解驱动没有直接能让它起作用的注解, 编程式配置又可以直接调用, 显得没那么大必要)

<!--注册Car-->
<bean id="car1" class="com.linkedbear.spring.bean.c_instantiate.bean.Car"/>

<!--注册工厂类, 并指定创建对象的工厂方法-->
<bean id="car2" class="com.linkedbear.spring.bean.c_instantiate.bean.CarStaticFactory" factory-method="getCar"/>

运行启动类进行测试

public class BeanInstantiateXmlApplication {
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean/bean-instantiate.xml");
        ctx.getBeansOfType(Car.class).forEach((beanName, car) -> {
            System.out.println(beanName + " : " + car);
        });
		/*
		控制台打印结果如下: 
		Car constructor run ...
		Car constructor run ...
		car1 : com.linkedbear.spring.bean.c_instantiate.bean.Car@5b239d7d
		car2 : com.linkedbear.spring.bean.c_instantiate.bean.Car@6572421
		*/
    }
}

即是创建两个Car, 一个Car的Bean, 一个CarStaticFactorygetCar()生产的Bean

静态工厂是否在IOC容器内

car2注册到IOC容器内了, 静态工厂是否会像FacotryBean一样注册到IOC容器中, 尝试从IOC容器中获取试试

public static void main(String[] args) throws Exception {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("bean/bean-instantiate.xml");
    ctx.getBeansOfType(Car.class).forEach((beanName, car) -> {
        System.out.println(beanName + " : " + car);
    });
    // 尝试取一下试试
    System.out.println(ctx.getBean(CarInstanceFactory.class));
}

运行, 发现程序抛出了 NoSuchBeanDefinitionException 异常:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.linkedbear.spring.bean.c_instantiate.bean.CarStaticFactory' available

结论: 静态工厂本身不会被注册到 IOC 容器中

编程式使用静态工厂

由于 SpringFramework 中并没有提供关于静态工厂相关的注解, 所以只能使用注解配置类+编程式使用静态工厂了, 而这个使用方式相当的简单

@Configuration
public class BeanConfiguration {    
    @Bean
    public Car car2() {
        // 直接 类.方法 调用即可
        return CarStaticFactory.getCar();
    }
}

借助实例工厂创建Beans

测试示例

创建实例工厂

创建一个CarInstanceFactory代表实例工厂, 它跟静态工厂唯一的区别是方法不再是static方法了

public class CarInstanceFactory {
    public Car getCar() {
        return new Car();
    }
}

配置xml

对于实例工厂, 要想调用对象的方法, 那自然得先把对象实例化才行了, 所以咱就需要先在 xml中注册实例工厂, 随后才能创建真正的目标 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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="carInstanceFactory" class="com.linkedbear.spring.bean.c_instantiate.bean.CarInstanceFactory"/>
    <bean id="car3" factory-bean="carInstanceFactory" factory-method="getCar"/>

</beans>

发现car3<bean> 标签可以不传入class属性, 用 factory-beanfactory-method 属性也可以完成Bean的创建

运行启动类

public class BeanInstantiateXmlApplication {
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean/bean-instantiate.xml");
        ctx.getBeansOfType(Car.class).forEach((beanName, car) -> {
            System.out.println(beanName + " : " + car);
        });
    }
}
实例工厂是否在IOC容器中

在, 实例工厂的<bean>中都注册了, 那么必然是在IOC容器当中的

编程式使用实例工厂
@Configuration
public class BeanConfiguration {      
    @Bean
    public Car car3(CarInstanceFactory carInstanceFactory) {
        return carInstanceFactory.getCar();
    }
}

Bean生命周期(简易版)

Bean生命周期阶段划分

一个对象从被创建, 到被垃圾回收, 可以宏观的划分为 5 个阶段:

  • 创建/实例化阶段: 此时会调用类的构造方法, 产生一个新的对象
  • 初始化阶段: 此时对象已经创建好, 但还没有被正式使用, 可能这里面需要做一些额外的操作(如预初始化数据库的连接池)
  • 运行使用期: 此时对象已经完全初始化好, 程序正常运行, 对象被使用
  • 销毁阶段: 此时对象准备被销毁, 已不再使用, 需要预先的把自身占用的资源等处理好(如关闭, 释放数据库连接)
  • 回收阶段: 此时对象已经完全没有被引用了, 被垃圾回收器回收

Spring能干预的生命周期阶段(针对单例Bean)

生命周期阶段是否能干预
创建/实例化
初始化
运行使用
销毁
回收

SpringFramework如何能让我们干预Bean的初始化和销毁

Servlet里面有两个方法, 分别叫initdestroy, 被Web容器(Tomcat等)调用的吧, 用来初始化和销毁Servlet的

方法的设计思想其实就是回调机制, 它都不是自己设计的, 而是由父类/接口定义好的, 由第三者(框架, 容器等)来调用

生命周期的触发, 更适合叫回调, 因为生命周期方法是咱定义的, 但方法被调用, 是框架内部帮我们调的, 那也就可以称之为"回调"了

init-method&desotry-method

Bean的初始化和销毁阶段做一个额外操作

前置准备
创建Cat和Dog类
@Data
public class Cat {
    private String name;
    
    public void init() {
        System.out.println(name + "被初始化了。。。");
    }
    public void destroy() {
        System.out.println(name + "被销毁了。。。");
    }
}
@Data
public class Dog {    
    private String name;
    
    public void init() {
        System.out.println(name + "被初始化了。。。");
    }
    public void destroy() {
        System.out.println(name + "被销毁了。。。");
    }
}
创建xml配置文件/配置类

配置文件

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="com.linkedbear.spring.lifecycle.a_initmethod.bean.Cat"
          init-method="init" destroy-method="destroy">
        <property name="name" value="mimi"/>
    </bean>
</beans>
  • init-method: 指向初始化方法
  • destroy-method: 执行销毁方法

配置类

@Configuration
public class InitMethodConfiguration {
    @Bean(initMethod = "init", destroyMethod = "destroy")
    public Dog dog() {
        Dog dog = new Dog();
        dog.setName("wangwang");
        return dog;
    }
    
}

initMethod和destroyMethod的作用的同理

初始化和销毁方法的特征

  • 方法访问权限无限制要求(SpringFramework底层会反射调用的)
  • 方法无参数(如果真的设置了参数, SpringFramework也不知道传什么进去)
  • 方法无返回值(返回给 SpringFramework 也没有意义)
  • 可以抛出异常(异常不由自己处理, 交予SpringFramework可以打断Bean的初始化/销毁步骤)
运行示例

分别初始化xml和注解驱动的容器, 不过需要注意的是, 这次咱接收的类型不再用 ApplicationContext , 而是用实现类本身, 目的是为了调用 close 方法对容器进行关闭, 以触发 Bean 的销毁动作

xml驱动
public class InitMethodXmlApplication {

    public static void main(String[] args) throws Exception {
        System.out.println("准备初始化IOC容器。。。");
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("lifecycle/bean-initmethod.xml");
        System.out.println("IOC容器初始化完成。。。");

        System.out.println();

        System.out.println("准备销毁IOC容器。。。");
        ctx.close();
        System.out.println("IOC容器销毁完成。。。");

        /*
			控制台输出结果如下:
			Cat 构造方法执行了。。。
			setName方法执行了。。。
			mimi被初始化了。。。
			IOC容器初始化完成。。。

			准备销毁IOC容器。。。
			mimi被销毁了。。。
			IOC容器销毁完成。。。
        */
    }
}

结论:

  • 在 IOC 容器初始化之前, 默认情况下Bean已经创建好了, 而且完成了初始化动作;容器调用销毁动作时, 先销毁所有Bean, 最后IOC容器全部销毁完成
  • Bean 的生命周期中, 是先对属性赋值, 后执行 init-method 标记的方法

initMethod和destroyMethod方法作用范围如下图
在这里插入图片描述

注解驱动
public class InitMethodAnnoApplication {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(InitMethodConfiguration.class);
        System.out.println("IOC容器初始化完成。。。");
        Thread.sleep(3000);
        ctx.close();
    }
}

控制台打印结果如下

wangwang被初始化了。。。
IOC容器初始化完成。。。
wangwang被销毁了。。。
JSR250规范: @PostConstruct和@PreDestroy

之前手动声明注册的Bean, 对于那些使用模式注解的Bean, 没有可以让你声明 init-methoddestroy-method 的地方了, @Component 注解上也只有一个 value 属性而已

JSR250 规范中除了有 @Resource 这样的自动注入注解, 还有负责生命周期的注解, 包括 @PostConstruct , @PreDestroy 两个注解, 分别对应init-methoddestroy-method

使用示例

创建Bean

钢笔与墨水, 刚买来的动作代表实例化, 加墨水的动作代表初始化, 倒掉所有墨水的动作代表销毁

对于JSR250规范的这两个注解的使用, 直接标注在Bean的方法上即可

@Data
@Component
public class Pen {
    /*
    墨水含量
    */
    private Integer ink;
    
    /*
    添加墨水
    */
    @PostConstruct // 实现了initMethod相似的作用, 方法声明一样的, 修饰符可以是private
    public void addInk() {
        System.out.println("钢笔中已加满墨水。。。");
        this.ink = 100;
    }
    
    /*
    倒掉所有墨水
    */
    @PreDestroy // 实现了destroyMethod相似的作用, 方法声明一样的, 修饰符可以是private
    public void outwellInk() {
        System.out.println("钢笔中的墨水都放干净了。。。");
        this.ink = 0;
    }
}

启动

public class JSR250AnnoApplication {
    public static void main(String[] args) throws Exception {
        System.out.println("准备初始化IOC容器。。。");
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JSR250Configuration.class);
        System.out.println("IOC容器初始化完成。。。");
        
        System.out.println();
        
        System.out.println("准备销毁IOC容器。。。");
        ctx.close();
        System.out.println("IOC容器销毁完成。。。");
        /*
			控制台打印结果如下: 
			准备初始化IOC容器。。。
			@PostConstruct - 钢笔中已加满墨水。。。
			init-method - 打开钢笔。。。
			IOC容器初始化完成。。。

			准备销毁IOC容器。。。
			@PreDestroy - 钢笔中的墨水都放干净了。。。
			destory-method - 合上钢笔。。。
			IOC容器销毁完成。。。
        */
    }
}

如下图
在这里插入图片描述

JSR250规范与init-method共存

如果不使用@Component注解来注册Bean而转用<bean>/@Bean的方式, 那@PostConstruct@PreDestroy注解是可以与init-method/destroy-method共存的

前期准备

新建一个Pen2, 方法如下

@Data
@Component
public class Pen2 {
    /**
     * 墨水含量
     */
    private Integer ink;

    public void open() {
        System.out.println("init-method - 打开钢笔。。。");
    }
    
    public void close() {
        System.out.println("destory-method - 合上钢笔。。。");
    }
    
    @PostConstruct // JSR250规范
    public void addInk() {
        System.out.println("@PostConstruct - 钢笔中已加满墨水。。。");
        this.ink = 100;
    }
    
    @PreDestroy // JSR250规范
    public void outwellInk() {
        System.out.println("@PreDestroy - 钢笔中的墨水都放干净了。。。");
        this.ink = 0;
    }
}

配置类注册Pen2, 并使用指定initMethod和destroyMethod

@Configuration
public class JSR250Configuration {
    @Bean(initMethod = "open", destroyMethod = "close")
    public Pen2 pen() {
        return new Pen2();
    }
}
运行示例
public class JSR250AnnoApplication {
    
    public static void main(String[] args) throws Exception {
        System.out.println("准备初始化IOC容器。。。");
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JSR250Configuration.class);
        System.out.println("IOC容器初始化完成。。。");

        System.out.println();

        System.out.println("准备销毁IOC容器。。。");
        ctx.close();
        System.out.println("IOC容器销毁完成。。。");
        /*
        控制条打印结果: 
        准备初始化IOC容器。。。
        @PostConstruct - 钢笔中已加满墨水。。。
        init-method - 打开钢笔。。。
        IOC容器初始化完成。。。

        准备销毁IOC容器。。。
        @PreDestroy - 钢笔中的墨水都放干净了。。。
        destory-method - 合上钢笔。。。
        IOC容器销毁完成。。。
        */
    }
}

结论: JSR250规范的执行优先级高于init/destroy

如下图
在这里插入图片描述

InitializingBean&DisposableBean

实际上是两个接口, 而且是Spring内部预先定义好的两个关于生命周期的接口。他们的触发时机与上面的init-method/destroy-method以及JSR250规范的两个注解一样, 都是在Bean的初始化和销毁阶段要回调的

前置准备

使用Pen作为演示对象, 实现InitializingBean和DisposableBean接口

@Data
@Component
public class Pen implements InitializingBean, DisposableBean {
    /*
    墨水含量
    */
    private Integer ink;

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("钢笔中已加满墨水。。。");
        this.ink = 100;
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("钢笔中的墨水都放干净了。。。");
        this.ink = 0;
    }
}

配置类不变

@Configuration
public class InitializingDisposableConfiguration {   
    @Bean(initMethod = "open", destroyMethod = "close")
    public Pen3 pen() {
        return new Pen3();
    }
}
运行示例
public class InitializingDisposableAnnoApplication {
    public static void main(String[] args) throws Exception {
        System.out.println("准备初始化IOC容器。。。");
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.linkedbear.spring.lifecycle.c_initializingbean.bean");
        System.out.println("IOC容器初始化完成。。。");

        System.out.println();

        System.out.println("准备销毁IOC容器。。。");
        ctx.close();
        System.out.println("IOC容器销毁完成。。。");
        /*
        控制台打印结果:
        准备初始化IOC容器。。。
        钢笔中已加满墨水。。。
        IOC容器初始化完成。。。

        准备销毁IOC容器。。。
        钢笔中的墨水都放干净了。。。
        IOC容器销毁完成。。。
        */
    }
}
三种生命周期并存

复制出一个 Pen 来, 命名为 Pen3 , 并同时实现三种生命周期的控制, Servelt的init/destory, JSR250规范的@PostConstruct和@PreDestroy, Spring的InitializingBean和DisposableBean

@Data
public class Pen3 implements InitializingBean, DisposableBean {
    private Integer ink;
    
    public void open() {
        System.out.println("init-method - 打开钢笔。。。");
    }
    
    public void close() {
        System.out.println("destroy-method - 合上钢笔。。。");
    }
    
    @PostConstruct // JSR250规范
    public void addInk() {
        System.out.println("@PostConstruct - 钢笔中已加满墨水。。。");
        this.ink = 100;
    }
    
    @PreDestroy // JSR250规范
    public void outwellInk() {
        System.out.println("@PreDestroy - 钢笔中的墨水都放干净了。。。");
        this.ink = 0;
    }
    
    // Spring
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean - 准备写字。。。");
    }
    
    // Spring
    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean - 写完字了。。。");
    }
}

注解驱动方式注册这个 Pen3

@Configuration
public class InitializingDisposableConfiguration {
    // Sevlet
    @Bean(initMethod = "open", destroyMethod = "close")
    public Pen3 pen() {
        return new Pen3();
    }
}
public class InitializingDisposableAnnoApplication {
    public static void main(String[] args) throws Exception {
        System.out.println("准备初始化IOC容器。。。");
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(InitializingDisposableConfiguration.class);
        System.out.println("IOC容器初始化完成。。。");
        System.out.println();
        System.out.println("准备销毁IOC容器。。。");
        ctx.close();
        System.out.println("IOC容器销毁完成。。。");
        /*
        控制台打印: 
        准备初始化IOC容器。。。
        @PostConstruct - 钢笔中已加满墨水。。。
        InitializingBean - 准备写字。。。
        init-method - 打开钢笔。。。
        IOC容器初始化完成。。。

        准备销毁IOC容器。。。
        @PreDestroy - 钢笔中的墨水都放干净了。。。
        DisposableBean - 写完字了。。。
        destroy-method - 合上钢笔。。。
        IOC容器销毁完成。。。
        */
    }
}

执行顺序才是最关键的:@PostConstructInitializingBeaninit-method

如下图
在这里插入图片描述

prototype(原型)Bean的生命周期

单实例Bean的生命周期是陪着IOC容器一起的, 容器初始化, 单实例Bean也跟着初始化(当然不绝对, 后面会介绍延迟Bean);容器销毁, 单实例Bean也跟着销毁。原型Bean由于每次都是取的时候才产生一个, 所以它的生命周期与IOC容器无关

前置准备

Pen类

@Data
public class Pen implements InitializingBean, DisposableBean {
    
    private Integer ink;
    
    public void open() {
        System.out.println("init-method - 打开钢笔。。。");
    }
    
    public void off() {
        System.out.println("destroy-method - 合上钢笔。。。");
    }
    
    @PostConstruct
    public void addInk() {
        System.out.println("@PostConstruct - 钢笔中已加满墨水。。。");
        this.ink = 100;
    }
    
    @PreDestroy
    public void outwellInk() {
        System.out.println("@PreDestroy - 钢笔中的墨水都放干净了。。。");
        this.ink = 0;
    }
    
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean - 准备写字。。。");
    }
    
    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean - 写完字了。。。");
    }
}

配置类

@Configuration
public class PrototypeLifecycleConfiguration {
    @Bean(initMethod = "open", destroyMethod = "off")
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 注意这里的Bean范围是prototype
    public Pen pen() {
        return new Pen();
    }
}
IOC容器初始化时原型Bean不初始化
public class PrototypeLifecycleApplication {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
                PrototypeLifecycleConfiguration.class);
        System.out.println("IOC容器初始化完成。。。");
    }
}

控制台只打印了 IOC容器初始化完成。。。 表明了原型 Bean 的创建不随 IOC 的初始化而创建

原型Bean的初始化动作与单实例Bean一致

修改main方法, 获取一次Pen实例

public class PrototypeLifecycleAnnoApplication {
    
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
                PrototypeLifecycleConfiguration.class);
        System.out.println("IOC容器初始化完成。。。");
        System.out.println();
        System.out.println("准备获取一个Pen。。。");
        Pen pen = ctx.getBean(Pen.class);
        /*
        控制台打印结果如下: 
        IOC容器初始化完成。。。

        准备获取一个Pen。。。
        @PostConstruct - 钢笔中已加满墨水。。。
        InitializingBean - 准备写字。。。
        init-method - 打开钢笔。。。
        */
    }
}

三种初始化的动作都执行了, 证明原型Bean的初始化动作与单实例Bean完全一致, 注意这里强调了是初始化动作

原型Bean的销毁不包括destroy-method

main 方法中再添加几行代码, 将这个 Pen 销毁掉

public class PrototypeLifecycleAnnoApplication {
    
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
                PrototypeLifecycleConfiguration.class);
        System.out.println("IOC容器初始化完成。。。");
        System.out.println();
        System.out.println("准备获取一个Pen。。。");
        Pen pen = ctx.getBean(Pen.class);
        System.out.println("已经取到了Pen。。。");
        System.out.println();
        System.out.println("用完Pen了, 准备销毁。。。");
        ctx.getBeanFactory().destroyBean(pen);
        System.out.println("Pen销毁完成。。。");
        /*
        控制台打印结果如下: 
        IOC容器初始化完成。。。

        准备获取一个Pen。。。
        @PostConstruct - 钢笔中已加满墨水。。。
        InitializingBean - 准备写字。。。
        init-method - 打开钢笔。。。
        已经取到了Pen。。。

        用完Pen了, 准备销毁。。。
        @PreDestroy - 钢笔中的墨水都放干净了。。。
        DisposableBean - 写完字了。。。
        Pen销毁完成。。。
        */
    }
}

控制台中只打印了 @PreDestroy 注解和 DisposableBean 接口的执行, 没有触发 destroy-method 的执行

原型Bean在销毁时不处理destroy-method标注的方法, 可以从以下角度理解, 由于destroy-method方法是Servlet的,原型Bean不知道是否还继续生产, 所以Servlet容器不能销毁

SpringFramework中控制Bean生命周期的三种方式

init-method & destroy-method@PostConstruct & @PreDestroyInitializingBean & DisposableBean
执行顺序最后最先中间
组件耦合度无侵入(只在 <bean>@Bean 中使用)与 JSR 规范耦合与 SpringFramework 耦合
容器支持xml 、注解原生支持注解原生支持, xml需开启注解驱动xml 、注解原生支持
单实例Bean
原型Bean只支持 init-method

如下图
在这里插入图片描述

参考资料

从 0 开始深入学习 Spring

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值