> 文章列表 > Spring中Bean对象的作用域和生命周期详解

Spring中Bean对象的作用域和生命周期详解

Spring中Bean对象的作用域和生命周期详解

Spring作为一个具有众多工具方法的IoC容器,其核心功能就是Bean对象的存储和取出,那么学习Bean对象的作用域和生命周期能让我们更清楚地了解Bean对象在Spring容器中的整个加载过程!

一,案例演示(Bean对象的修改)

假设现在有一个公共的Bean对象(用Student对象来表示),整个Bean对象需要给用户A和用户B使用,但是A在B使用之前对Student类中的属性进行了修改,那么此时B用户从Spring中拿到的Bean对象是否是预期的对象(期望拿到未被修改过的Bean对象)?

公共的Bean对象:

package com.java.demo.entity;import lombok.Getter;
import lombok.Setter;
import lombok.ToString;//加上lombok的相关注解减少相关代码量
@Setter
@Getter
@ToString
public class Student {//给学生赋予id name 的属性private int id;private String name;
}

使用Bean方法注解将Bean对象注册到Spring容器中:

package com.java.demo.component;import com.java.demo.entity.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;/*** 使用该类将 Bean 对象注册到 Spring 容器中*/@Component
public class StudentBeans {@Bean   //使用方法注解将student对象注册到 Spring 容器中public Student student() {//给student对象相关属性初始化//注意这里是伪代码 不涉及与数据库之间的交互Student student = new Student();student.setId(1);student.setName("张三");return student;}
}

用户A使用并修改Bean对象:

package com.java.demo.controller;import com.java.demo.entity.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;/*** StudentController1 代表用户A* A用户使用Bean对象的同时修改了Bean对象*/@Controller
public class StudentController1 {@Autowired  //使用属性注入的方式private Student student;public void printStudent1() {System.out.println(student);//这里对Bean对象进行修改Student myStudent = student;student.setName("李四");System.out.println("student -> " + student.getName());System.out.println("myStudent -> " + myStudent.getName());}
}

用户B使用Bean对象:

package com.java.demo.controller;import com.java.demo.entity.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;/*** StudentController2 代表用户B* 用户B使用Bean对象*/@Controller
public class StudentController2 {@Autowired  //使用属性注入的方式private Student student;public void printStudent2() {System.out.println("B用户:student -> " + student.getName());}
}

创建启动类进行测试:

package com.java.demo;import com.java.demo.controller.StudentController1;
import com.java.demo.controller.StudentController2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class App {public static void main(String[] args) {//获取 Spring 上下文对象ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");//用户A//从 Spring 容器中获取 Bean 对象StudentController1 studentController1 = context.getBean("studentController1",StudentController1.class);//使用 Bean 对象studentController1.printStudent1();//用户B//从 Spring 容器中获取 Bean 对象StudentController2 studentController2 = context.getBean("studentController2",StudentController2.class);//使用 Bean 对象studentController2.printStudent2();}
}

 我们预期的结果用户B得到的结果应该是一开始注册到Spring容器中的Bean对象(即name应该是张三),但是结果打印的却是李四,说明此时用户B得到的Bean对象是用户A修改之后的;这是因为在默认不加任何条件控制的情况下,Spring认为Bean对象是一个单例的对象,只有一份,在这个工程中的任何操作都是对同一个Bean对象在操作,这也就是Bean对象作用域的一种——单例模式!

二,Bean对象的作用域

定义:限定程序中变量的可⽤范围叫做作⽤域,或者说在源代码中定义变量的某个区域就叫做作⽤域;⽽ Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式,⽐如 singleton 单例作⽤域,就 表示 Bean 在整个 Spring 中只有⼀份,它是全局共享的,那么当其他⼈修改了这个值之后,那么另⼀ 个⼈读取到的就是被修改的值。

Bean对象作用域的分类:

Spring容器在初始化一个Bean的实例时,同时会指定该实例的作用域;Spring有6中作用域,最后四种是基于Spring MVC生效的:

  1.  singleton:单例作用域;
  2. prototype:原型作用域(多例作用域);
  3. request:请求作用域;
  4. session:会话作用域;
  5. application:全局作用域;
  6. webSocket:HTTP WebSocket作用域.
    singleton 默认情况,出于对性能的考虑
    prototype 与单例模式相对,俗称多例模式
    request 每次HTTP请求都会创建一个 Bean 对象
    session

    每次Session会话就会共享一个 Bean 对象

    application 一个http servlet context 中共享一个 Bean 对象
    webSocket 网络长连接

 三,设置作用域(@Scope)

Spring中一般使用@Scope 注解来声明Bean的作用域,对上述案例进行修改,将Bean对象的作用域改成prototype,使得用户B读取到的Bean对象是一开始注册到Spring容器中的未进行修改的对象

@Component
public class StudentBeans {@Bean   //使用方法注解将student对象注册到 Spring 容器中@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //声明Bean对象的作用域public Student student() {//给student对象相关属性初始化//注意这里是伪代码 不涉及与数据库之间的交互Student student = new Student();student.setId(1);student.setName("张三");return student;}
}

 这两种方法均可!

 四,Spring(Bean)的执行流程

 

执行流程:spring的执行流程也是Bean的执行流程

  • 启动 Spring 容器
  • 实例化 Bean(分配内存空间,从无到有)
  • 将 Bean 注册到 Spring 容器中(存操作)
  • 将 Bean 装配到需要的类中(取操作)

五,Bean的生命周期

所谓的⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程,我们把这个过程就叫做⼀个对象的⽣命周期;Bean的生命周期分为以下5个部分:
  1. 实例化 Bean(开辟内存空间)
  2. 设置属性(注入属性)
  3. 初始化
    • 各种通知
    • 初始化前置方法
    • 初始化方法(两种实现方式:XML方式,注解方式)
    •  初始化后置方法
  4. 使用B ean 对象
  5. 销毁 Bean 对象

注意:这里的实例化并不等于初始化,实例化是操作系统完成的,操作过程不可人工干预和修改;而初始化是给开发者提供的,在实例化之后进行初始化!

生命周期的演示:

BeanCompoent:

package com.java.demo.component;import org.springframework.beans.factory.BeanNameAware;import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;public class BeanComponent implements BeanNameAware {@Overridepublic void setBeanName(String s) {System.out.println("执行了通知 BeanName ->  " + s);}public void myInit() {System.out.println("XML 方式初始化");}@PostConstructpublic void doPostConstruct() {System.out.println("注解初始化方法");}public void sayHi() {System.out.println("执行 sayHi()");}@PreDestroypublic void doPreDestroy() {System.out.println("执行 doPreDestroy()");}
}

 启动类App:

package com.java.demo;import com.java.demo.component.BeanComponent;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class App {public static void main(String[] args) {//1.获取 Spring 上下文对象ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");//2.获取 Bean 对象BeanComponent beanComponent = context.getBean("beanComponent", BeanComponent.class);//3.使用 Bean 对象beanComponent.sayHi();context.destroy();}
}

 

路由器知识