Spring Bean的作用域和生命周期

文章目录

  • 1. Bean的作用域
  • 2. Spring的生命周期
  • 3. Bean的生命周期
  • 4. 相关注解总结

1. Bean的作用域

Bean 的作用域指的是 Bean 在 Spring 容器中的行为(Bean 实例创建及生命周期),它的行为是由 Spring 来管理的,可以根据具体情况选择不同的作用域来达到性能优化、资源利用最大化和可维护性等目的。

Bean 作用域(Scope)类型主要有如下几种:

其中前两种是 Spring 核心作用域,而后 4 种是 Spring MVC 中的作⽤域;

  1. singleton:单例模式(默认的作用域),Spring IoC 容器中只会存在一个共享的 Bean 实例,无论有多少个 Bean 引用它,始终指向同一对象,通常无状态的 Bean 使用该模式(无状态表示Bean对象的属性不需要更新),这也是 Spring 出于性能方面的考虑。
  2. prototype:原型模式(多例作用域),每次从 Spring 容器中获取 prototype 模式的 Bean 时,容器都将创建一个新的 Bean 实例,每个Bean实例都有自己的属性和状态,而 singleton 全局只有一个对象,通常有状态的 Bean 使用该作用域,即Bean的属性可能会更新。
  3. request:请求模式,在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例,在这次的请求和响应中共享这个 Bean,但在不同的请求中会创建新的实例,也就是说每次 HTTP 请求,使用的是不同的Bean, 类似于 prototype,在 Spring MVC 中使用,即使用需要在 WebApplicationContext 环境下。
  4. session:会话模式,在同一个 HTTP Session 中,共享使用的是一个 Bean 实例,例如记录用户的登录信息,在 Spring MVC 中使用。
  5. application:全局模式,表示整个 Web 应用的 http servlet context 共享同一个 Bean,主要记录的是的 Web 应用的上下文信息,例如记录一个应用的共享信息,在 Spring MVC 中使用。
  6. websocket:网络长连接,只适用于 Spring WebSocket 项目,在一个 HTTP WebSocket 的生命周期中(一次长连接),共享一个 Bean 实例;WebSocket 的每次会话中,保存了一个 Map 结构的头信息,用来保存客户端消息头,第一次初始化后,直到一次长连接结束都是用一个 Bean。

这里就简单用singletonprototype这两种可以在 Spring 核心代码中使用的模式来演示区别一下。

package com.tr.demo.model;

public class User {
    private int id;
    private String name;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

一般有两种方式设置 Spring Bean 作用域:

  1. 使用xml配置的方式,我们将bean标签当中的scope属性设置为singleton就为单例,设置为prototype就为多例模式。Spring Bean的作用域和生命周期_第1张图片
  2. 二是直接通过@Scope注解,不设置参数或设置参数为singleton就为单例,设置为prototype就为多例模式。
    我们还可在 Scope 注解中可以传入另一种参数形式,和上面直接拼写出来效果一样。

    这种方式是 ConfigurableBeanFactory 类中为我们提供了静态属性,相较于自己拼写不容易出错。Spring Bean的作用域和生命周期_第2张图片

我们来看一下 singleton 和 prototype 两种模式下获取的 Bean 有什么不同,我们这里演示在这两种不同的模式下将 User 对象放到 Spring 容器中,从容器中先后两次取出 User,看这两次取出的 Bean 是否相同。

public static void main(String[] args) {
    ApplicationContext context =
            new ClassPathXmlApplicationContext("spring-config.xml");

    User user1 = context.getBean("user", User.class);
    User user2 = context.getBean("user",User.class);
    System.out.println(user1 == user2);
}

singleton 模式下的结果:

可以看到,此时 User 类在 Ioc 容器中只创建了一个实例,而通过 applicationContext.getBean() 方法多次获取到的 Bean,都是同一个对象实例,相当于是一个浅拷贝。

Spring Bean的作用域和生命周期_第3张图片
prototype 模式下的结果:

这里之所以这里打印出来的 Bean 是 2 个而不是 3 个,是因为prototype模式下在我们创建容器时并不会进行 Bean 的实例化,它在我们获取 Bean 的时候才会去创建 1 个实例,而且每次获取 Bean 时都会创建新的实例,它们彼此之间都是不同的实例,相当于是一个深拷贝。

Spring Bean的作用域和生命周期_第4张图片

2. Spring的生命周期

Spring 的生命周期大致走的的是如下流程

  1. 首先启动容器,加载配置文件(类加载路径下的 spring-config.xml 文件)。
    img
  2. 根据配置文件完成 Bean 的实例化(分配内存空间)和初始化(初始化空间),包括扫描配置文件下带有五大类或者方法注解的类和 bean 标签中要注册的对象,配置文件中的 Bean 是按顺序实例化和初始化的。
    Spring Bean的作用域和生命周期_第5张图片
  3. 将 Bean 注册到 Spring 容器中
  4. 装配 Bean 的属性,如果 Bean 对象需要其他的 Bean 作为属性,可以使用注解的方式注入(@Autowired、Resource)
    Spring Bean的作用域和生命周期_第6张图片

小结,Bean执行流程(Spring执行流程):启动 Spring 容器 -> 实例化 Bean(分配内存空间,从无到有)-> Bean 注册到 Spring 容器中(存操作) -> 将 Bean 装配到需要的类中(取操作)。

3. Bean的生命周期

Bean 的生命周期,就是 Bean 对象从诞生到销毁的整个过程,Bean 的生命周期大致有 5 个阶段:

①Bean 的实例化(分配内存空间)。

②设置 Bean 属性(进行依赖注入,将依赖的 Bean 辅助到当前类的属性上)。

③Bean 的初始化阶段:

  • 执行各种通知,底层实现了 Aware 接口,执行其中的方法,如:BeanNameAware、BeanFactoryAware、ApplicationContextAware 的接口方法;
  • 执行初始化的前置方法,即执行 BeanPostProcessor 方法(before);
  • 执行初始化方法 ,依赖注⼊操作之后被执⾏(两种重写方式:xml方式,注解方式@PostConstruct);
  • 执⾏⾃⼰指定的 init-method ⽅法(如果有指定的话);
  • 执行 BeanPostProcessor 初始化后置方法(after)。

④使用 Bean。

⑤销毁 Bean,执行 销毁容器的各种⽅法,如 @PreDestroy、DisposableBean 接⼝⽅法、destroy-method。

要注意设置属性必须在初始化之前,因为有可能在初始化的时候使用Bean。

上述流程可以以⽣活中的场景来理解它,⽐如我们现在需要买⼀栋房⼦,那么我们的流程是这样的:

  1. 先买房(毛坯房),对应 Bean 的实例化;
  2. 装修,对应给 Bean 设置属性;
  3. 买家电,如洗⾐机、冰箱、电视、空调等,对应 Bean 的各种初始化
  4. ⼊住,对应使⽤ Bean;
  5. 卖房,对应 Bean 的销毁。

执行流程如下图所示:

Spring Bean的作用域和生命周期_第7张图片

还需要注意区分一下实例化和初始化,实例化和属性设置是 Java 级别的系统“事件”,其操作过程不可⼈⼯⼲预和修改;⽽初始化是给开发者 提供的,可以在实例化之后,类加载完成之前进⾏⾃定义“事件”处理。
下面就简单的写几个周期方法来演示一下这个过程,在类中添加两个方法,一个是init方法表示初始化,另外一个是destroy方法,表示Bean销毁前执行的事情,使用@PostConstruct注解或者xmlbean标签中的init-method属性表示在构造后执行,@PreDestroy注解或者xmlbean标签中的destroy-method表示在 Bean 销毁前做的事情。

import com.tr.demo.model.User;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class BeanLifeComponent implements BeanNameAware {
    @Autowired
    private User user;

    @Override
    public void setBeanName(String s) {
        System.out.println("执行了 BeanNameAware 通知 -> " + s);
    }

    @PostConstruct
    public void doPostConstruct() {
        System.out.println("执行了 @PostConstruct (注解式初始化)");
        System.out.println(user.toString());
    }

    public void myInit(){
        System.out.println("执行了 myinit (xml 式初始化)");
    }

    @PreDestroy
    public void doPreDestroy(){
        System.out.println("执行了 @PreDestroy (销毁 Bean 前执行)");
    }

    public void myDestroy() {
        System.out.println("执行了 myDestroy (销毁 Bean 前执行)");
    }

    public void sayHi(){
        System.out.println("使用 Bean");
    }
}

Spring 配置文件,将 Bean 手动存储在 Spring 容器中,要注意init-methoddestroy-method这两个属性的值要和上面实现的方法名对应。

<bean id="mybean" class="BeanLifeComponent"
      init-method="myInit" destroy-method="myDestroy">
</bean>

测试代码:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BeanLifeTest {
    public static void main(String[] args) {
        // 父类 ApplicationContext 中没有 close, destroy 系列方法
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("spring-config.xml");
        BeanLifeComponent component =
                context.getBean("mybean",BeanLifeComponent.class);
        component.sayHi();
        // 关闭容器
        context.close();
    }
}

运行结果:

Spring Bean的作用域和生命周期_第8张图片如果代码中飘红说 @PostConstruct 和 @PreDestroy 注解如果找不到,需要导入下面的jar包。

<dependency>  
<groupId>javax.annotation</groupId>  
<artifactId>javax.annotation-api</artifactId>  
<version>1.3.2</version></dependency>

找不到的原因是,从 JDK9 以后 jdk 中的 javax.annotation 包被移除了,这两个注解刚好就在这个包中。

4. 相关注解总结

@Scope

名称 @Scope
类型 类注解
位置 类定义上方
作用 设置该类创建对象的作用范围 可用于设置创建出的bean是否为单例对象
属性 value(默认):定义bean作用范围, 默认值singleton(单例),可选值prototype(非单例)

@PostConstruct

名称 @PostConstruct
类型 方法注解
位置 方法上
作用 设置该方法为初始化方法
属性

@PreDestroy

名称 @PreDestroy
类型 方法注解
位置 方法上
作用 设置该方法为销毁方法
属性

注解功能与 XML 实现对应关系:

Spring Bean的作用域和生命周期_第9张图片

你可能感兴趣的:(JavaEE,spring,java,后端,作用域,生命周期,注解)