前言
我们都知道,Spring框架为开发人员提供了很多便捷,这使得开发人员能够更加专注于应用程序的核心业务逻辑,而不需要花费大量时间和精力在技术细节上。作为一个包含众多工具方法的IoC容器,存取JavaBean是其极为重要的一个环节。本文就对Spring中的Bean的作用域和生命周期详细展开,希望对读者有所帮助~
文章目录
- 前言
- 1 问题引入
- 2 Bean 的作用域
- 2.1 作用域及 Bean 的 6 种作用域
- 2.2 Spring 中设置作用域
- 2.3 对案例中的代码进行修改
- 3 Spring 的执行流程及 Bean 的生命周期
- 3.1 Spring 的执行流程
- 3.2 Bean 的生命周期及常见问题
- 写在最后
1 问题引入
首先,我们创建一个 Student 类,作为 Bean。通过 StudentBean 类中的 student 配合 @Bean 注解存储 Bean。而后,StudentController 依次先访问 Bean 后对其进行修改。
最后,通过 StudentAdviceController 再次访问 Bean。观察 StudentAdviceController 访问的 Bean 是否为 StudentController 修改 Bean 之前的值。
预期结果: 我们希望 StudentController 对 Bean 的修改并不会影响 StudentAdviceController 对 Bean 的访问,然而事与愿违~
StudentBean
@Component
public class StudentBean {
@Bean
public Student student() {
Student student = new Student();
student.setName("黄小黄");
student.setAge(17);
student.setGender("男");
return student;
}
}
StudentController
@Controller
public class StudentController {
@Autowired
private Student student;
public void printStudent() {
System.out.println("studentController | student: " + student);
Student s = student;
s.setName("李大明");
System.out.println("studentController 修改后: " +student);
}
}
StudentAdviceController
@Controller
public class StudentAdviceController {
@Autowired
private Student student;
public void printStudent() {
System.out.println("studentAdviceController | student: " + student);
}
}
主方法
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
StudentController studentController =
context.getBean("studentController", StudentController.class);
StudentAdviceController studentAdviceController =
context.getBean("studentAdviceController", StudentAdviceController.class);
studentController.printStudent();
studentAdviceController.printStudent();
}
案例结果
可见,studentController 对 Bean 修改后,studentAdviceController 访问的是修改后的 Bean,这与预期不符。为了达到预期,我们还需要了解 Spring 中 Bean 的作用域~
2 Bean 的作用域
2.1 作用域及 Bean 的 6 种作用域
何为作用域?
作用域(Scope)是指在编程语言中,定义变量时可以被访问的区域。作用域规定了变量的可见性和访问权限,即定义一个变量或函数时,该变量或函数可以被访问的代码区域。在不同的编程语言中,作用域的具体实现方式可能会略有不同。常见的作用域有全局作用域、函数作用域、块级作用域等。
而我们所说的,Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式
Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作⽤域。Spring有 6 种作⽤域,最后四种是基于 Spring MVC ⽣效的。对于前 4 种,需要重点掌握,后两种了解即可~
- singleton:单例作⽤域(默认情况) 该作⽤域下的Bean在IoC容器中只存在⼀个实例。通常⽆状态的Bean使⽤该作⽤域。⽆状态表示Bean对象的属性状态不需要更新。
- prototype:原型作⽤域(多例作⽤域) 每次对该作⽤域下的Bean的请求都会创建新的实例。即,获取与装配Bean都是新的对象的实例。通常有状态的Bean使⽤该作⽤域。
- request:请求作⽤域 每次http请求会创建新的Bean实例,⼀次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 被创建一次后,会在整个应用程序的上下文中共享使用,即每一个请求返回的都是同一个实例。
单例作用域适用于那些状态较少或无状态的 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)
2.3 对案例中的代码进行修改
只需要将案例中 Bean 的作用域设置成 prototype 即可~
修改后的 StudentBean 代码如下:
@Component
public class StudentBean {
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean
public Student student() {
Student student = new Student();
student.setName("黄小黄");
student.setAge(17);
student.setGender("男");
return student;
}
}
至此,运行结果达到预期,即 A 类对 Bean 的修改并不会影响 B 对 Bean 的读取~
3 Spring 的执行流程及 Bean 的生命周期
3.1 Spring 的执行流程
Spring 的执行流程大致如下:
启动 Spring 容器 -> 根据配置完成 Bean 的初始化 -> Bean 注册到 Spring 容器中(存操作) -> 将 Bean 装配到需要的类中(取操作)
3.2 Bean 的生命周期及常见问题
生命周期指的是 一个对象从创建到销毁的全过程~,这里所说的 Bean 的生命周期,是指 Bean 从创建到使用再到销毁的完整过程。
- 实例化(对应JVM中的“加载”):从无到有,将字节码转换成内存中的对象,只是分配了内存 eg:买了一套毛坯房
- 设置属性(Bean注入和装配)eg:购买装修材料(引入外部资源)
- 初始化 eg:对房子的装修
a)各种通知 eg:打电话给各个装修的师傅来施工
b)初始化的前置工作 eg:师傅达到现场,先勘察环境,制定装修方案,比如测量房子的面积等~
c)进行初始化工作(两种方式:注解或者xml。使用注解@PostConstruct
初始化、使用(xml)init-method
初始化)eg:两类师傅进行装修:水工、瓦工、电工等。 - 使用Bean eg:房子可以住人了
- 销毁Bean eg:卖房
具体图示如下:
question1. Bean 实例化与 Bean 初始化的区别?
答: Bean 实例化和 Bean 初始化都是 Spring 容器处理 Bean 生命周期中的不同阶段。
- Bean 实例化是指 Spring 容器创建 Bean 实例的过程,即根据配置的 Bean 定义、类信息和构造函数等,创建一个 Bean 实例并放入容器中管理。这个过程包括实例化 Bean、调用构造函数、注入依赖等。
- Bean 初始化是指在 Bean 实例创建完成之后,Spring 容器调用特定的方法对 Bean 进行初始化配置的过程。这些特定的方法可以是自定义的方法,也可以是 Spring 提供的初始化方法,比如:InitializingBean 接口的 afterPropertiesSet() 方法和 @PostConstruct 注解等。在这个阶段,可以为 Bean 配置一些属性、调用一些初始化方法等。
question2. 为什么先设置属性而后再初始化?
答: 在 Bean 的生命周期中,先设置属性再初始化,是因为在 Spring IoC 容器中,Bean 的创建和初始化分为两个阶段,即 Bean 的实例化和属性注入阶段,和 Bean 的初始化阶段。在实例化阶段,Spring IoC 容器会先创建 Bean 的实例对象,但此时 Bean 的属性还未被注入。接着,Spring IoC 容器调用 Bean 的 set 方法注入属性值。当所有属性都被注入后,Spring IoC 容器才会执行 Bean 的初始化方法。因此,先设置属性而后再初始化。
写在最后
本文被 JavaEE编程之路 收录点击订阅专栏 , 持续更新中。
以上便是本文的全部内容啦!创作不易,如果你有任何问题,欢迎私信,感谢您的支持!