- 博主简介:想进大厂的打工人
- 博主主页:@xyk:
- 所属专栏: JavaEE进阶
在前面的播客中讲解了如何从Spring中存取Bean对象,那么本篇我们来讲解Bean对象的生命周期是什么,Bean对象的6种作用域分别是什么,都有哪些区别,还有Spring容器的是如何启动并执行的,请看下文~~
目录
一、Bean作用域问题案例
二、Bean的作用域
2.1 Bean的6种作用域
2.2 Spring中如何设置作用域
三、Spring的执行流程
四、Bean的生命周期
4.1 Bean 实例化与 Bean 初始化的区别?
4.2 为什么先设置属性而后再初始化?
一、Bean作用域问题案例
首先,我们创建一个 Student 类,作为 Bean。通过 StudentBeans 类中的 student 配合 @Bean 注解存储 Bean。而后,StudentController 依次先访问 Bean 后对其进行修改。
最后,通过 StudentAdviceController 再次访问 Bean。观察 StudentController2 访问的 Bean 是否为 StudentController 修改 Bean 之前的值。
预期结果: 我们希望 公共 Bean 可以在各⾃的类中被修改,但不影响到其他类,然而(说好⼀起到⽩头,你却悄悄焗了油)
StudentBeans
@Component
public class StudentBeans {
@Bean
public Student student1() {
// 伪代码,构建对象
Student stu = new Student();
stu.setId(1);
stu.setName("张三");
stu.setAge(18);
return stu;
}
}
StudentController
@Controller
public class StudentController {
@Autowired
private Student student;
public void printStudent(){
System.out.println(student);
Student student1 = this.student;
student1.setName("李四");
System.out.println(student);
}
}
StudentController2
@Controller
public class StudentController2 {
@Autowired
private Student student;
public void printStudent(){
System.out.println(student);
}
}
main方法
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
StudentController student = context.getBean("studentController",StudentController.class);
student.printStudent();
StudentController2 student2 = context.getBean("studentController2",StudentController2.class);
student2.printStudent();
}
}
预期结果:
可见,studentController 对 Bean 修改后,studentController2 访问的是修改后的 Bean,这与预期不符。为了达到预期,我们还需要了解 Spring 中 Bean 的作用域~
原因分析:
操作以上问题的原因是因为 Bean 默认情况下是单例状态(singleton),也就是所有⼈的使⽤的都是同⼀个对象,之前我们学单例模式的时候都知道,使⽤单例可以很⼤程度上提⾼性能,所以在 Spring 中Bean 的作⽤域默认也是 singleton 单例模式
二、Bean的作用域
2.1 Bean的6种作用域
作用域定义:
限定程序中变量的可⽤范围叫做作⽤域,或者说在源代码中定义变量的某个区域就叫做作⽤域。
⽽ Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式,⽐如 singleton 单例作⽤域,就表示 Bean 在整个 Spring 中只有⼀份,它是全局共享的,那么当其他⼈修改了这个值之后,那么另⼀个⼈读取到的就是被修改的值。
Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作⽤域。Spring有 6 种作⽤域,最后四种是基于 Spring MVC ⽣效的:
singleton:单例作⽤域:该作⽤域下的Bean在IoC容器中只存在⼀个实例,通常⽆状态的Bean使⽤该作⽤域。⽆状态表示Bean对象的属性状态不需要更新。Spring默认选择该作⽤域!
prototype:原型作⽤域(多例作⽤域):每次对该作⽤域下的Bean的请求都会创建新的实例,通常有状态的Bean使⽤该作⽤域
request:请求作⽤域:每次http请求会创建新的Bean实例,类似于prototype,⼀次http的请求和响应的共享Bean
session:回话作⽤域:在⼀个http session中,定义⼀个Bean实例;⽤户回话的共享Bean, ⽐如:记录⼀个⽤户的登陆信息
application:全局作⽤域:在⼀个http servlet Context中,定义⼀个Bean实例,Web应⽤的上下⽂信息,⽐如:记录⼀个应⽤的共享信息
websocket:HTTP WebSocket 作⽤域:在⼀个HTTP WebSocket的⽣命周期中,定义⼀个Bean实例;WebSocket的每次会话中,保存了⼀个Map结构的头信息,将⽤来包裹客户端消息头。第⼀
次初始化后,直到WebSocket结束都是同⼀个Bean
单例作用域与全局作用域的区别?
单例作用域意味着在容器中,每一个Bean只会被创建一次,并且所有的请求都会返回同一实例。而全局作用域与单例作用域不同,在全局作用域下,Bean 被创建一次后,会在整个应用程序的上下文中共享使用,即每一个请求返回的都是同一个实例。
- singleton 是 Spring Core 的作⽤域;application 是 Spring Web 中的作⽤域
- singleton 作⽤于 IoC 的容器,⽽ application 作⽤于 Servlet 容器。
2.2 Spring中如何设置作用域
我们可以使用 @Scope
标签来声明 Bean 的作⽤域。@Scope
标签既可以修饰⽅法也可以修饰类,并且,有两种设置⽅式。这里以设置作用域为 prototype 为例:
- 直接设置值:@Scope(“prototype”)
- 使⽤枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
我们将上面的例子中的作用域进行修改,就不会出现只有一个Bean实例的情况了
@Component
public class StudentBeans {
@Scope("prototype")
// @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean
public Student student1() {
// 伪代码,构建对象
Student stu = new Student();
stu.setId(1);
stu.setName("张三");
stu.setAge(18);
return stu;
}
}
三、Spring的执行流程
Spring 的执行流程大致如下:
启动 Spring 容器 -> 根据配置完成 Bean 的初始化 -> Bean 注册到 Spring 容器中(存操作) -> 将 Bean 装配到需要的类中(取操作)Spring 的执行流程大致如下:
四、Bean的生命周期
生命周期指的是 一个对象从创建到销毁的全过程~,这里所说的 Bean 的生命周期,是指 Bean 从创建到使用再到销毁的完整过程。Bean 的⽣命周期分为以下 5 ⼤部分:
- 实例化(对应JVM中的“加载”):从无到有,将字节码转换成内存中的对象,只是分配了内存 eg:买了一套毛坯房
- 设置属性(Bean注入和装配) eg:购买装修材料(引入外部资源)
- 初始化 eg:对房子的装修 a)各种通知 eg:打电话给各个装修的师傅来施工 b)初始化的前置工作 eg:师傅达到现场,先勘察环境,制定装修方案,比如测量房子的面积等~ c)进行初始化工作(两种方式:注解或者xml。使用注解@PostConstruct 初始化、使用(xml) init-method 初始化) eg:两类师傅进行装修:水工、瓦工、电工等。
- 使用Bean eg:房子可以住人了
- 销毁Bean eg:卖房
4.1 Bean 实例化与 Bean 初始化的区别?
Bean 实例化和 Bean 初始化都是 Spring 容器处理 Bean 生命周期中的不同阶段。
- Bean实例化是指为Bean分配内存空间,即根据配置文件将Bean对象创建出来并且存入到容器中管理,这个过程包括实例化Bean,调用构造函数,注入依赖等
- Bean初始化是指在Bean对象创建完成之后,Spring容器调用特定的方法进行对Bean进行初始化配置的过程,可以为 Bean 配置一些属性、调用一些初始化方法等。
- 比如使用@PostConstruct@PreDestroy注解等
4.2 为什么先设置属性而后再初始化?
在 Bean 的生命周期中,先设置属性再初始化,是因为在 Spring IoC 容器中,Bean 的创建和初始化分为两个阶段,即 Bean 的实例化和属性注入阶段,和 Bean 的初始化阶段。在实例化阶段,Spring IoC 容器会先创建 Bean 的实例对象,但此时 Bean 的属性还未被注入。接着,Spring IoC 容器调用 Bean 的 set 方法注入属性值。当所有属性都被注入后,Spring IoC 容器才会执行 Bean 的初始化方法。因此,先设置属性而后再初始化。
如果顺序反过来进行设置,会发生空指针异常!!