Spring Framework的主要功能是用来存储和读取Bean,Bean于Spring框架的地位可见一斑。本节的目的就是从作用域和生命周期的角度更加深入了解一下Bean对象。
1. Bean的作用域
首先我们来学习一下Bean的作用域,源码位置:bean_scope
1.1 作用域的定义
作用域一词在学习JavaSE的时候就有接触过,指的是源代码中定义变量的某个区域,与JavaSE中定义的作用域不同,Bean 的作用域是指Bean在Spring整个框架中的某种行为模式,比如Spring中默认的Ben作用域:singleton单例作用域,指的就是在整个Spring中只有一份。
Spring有6种作用域:
- singleton: 单例作用域
- prototype: 原型作用域(多例)
- request: 请求作用域
- session: 会话作用域
- application: 全局作用域
- websocket: HTTP WebSocket 作用域
后 4 种状态是Spring MVC 中的值, 在普通的 Spring 项目中只有前两种
【问题】默认作用域的不足
Spring中Bean的默认作用域是singleton单例作用域,在业务中可能遇到下面这种场景:
首先在名为model
的包中定义User,用来传输User的数据:
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
创建一个bean
包,并定义一个UserBean,用于初始化User并将User存入Spring容器中:
@Component
public class UserBean {
@Bean(name = "user")
public User getUserBean() {
User user = new User();
user.setName("zhangsan");
user.setAge(20);
return user;
}
}
在controller
包中创建UserController1
和UserController2
分别代表两个业务
程序员A在业务中需要临时创建临时的User
对象并且需要进行修改,出于无心之举,创建的临时对象user2
直接浅拷贝了Bean
,于是修改了Bean
的数据,然后将代码推送到仓库:
@Controller
public class UserController1 {
@Autowired
private User user;
public void doController() {
System.out.println("Do UserController1");
System.out.println("从Spring中取出user:" + user);
//创建的临时对象user2直接浅拷贝了Bean
User user2 = user;
user2.setName("lisi");
user2.setAge(18);
System.out.println("修改后的数据" + user2);
}
}
程序员B拉取了新的代码,并且要将 UserController2 给其他业务部门交付,并展示,预期中User的数据是User{name='zhangsan', age=20}
:
@Controller
public class UserController2 {
@Autowired
private User user;
public void doController() {
System.out.println("Do UserController2");
System.out.println("从Spring中取出user:" + user);
}
}
程序员B展示他的业务代码并一顿鼓吹他是如何拿到这么一个User{name='zhangsan', age=20}
的数据:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-scope.xml");
//UserController1业务
UserController1 userController1 = context.getBean("userController1", UserController1.class);
userController1.doController();
System.out.println("===========================");
//UserController2业务
UserController2 userController2 = context.getBean("userController2", UserController2.class);
userController2.doController();
}
但是他并不知道代码被修改,在展示的过程中直接傻眼了,运行结果如下:
Do UserController1
从Spring中取出user:User{name='zhangsan', age=20}
修改后的数据User{name='lisi', age=18}
===========================
Do UserController2
从Spring中取出user:User{name='lisi', age=18}
1.2 Bean的 6 种作用域
上述问题的解决方案就是设置 Bean 的作用域,在说明设置 Bean 的作用域的方法前,先来介绍下 Bean 的 6 种作用域的定义。
1) singleton作用域(单例)
官方说明:(Default)Scopes a single bean definition to a single object instance for each Spring IoC container.
描述: 该作用域下的Bean在IoC容器中只存在一个实例:singleton
作用域是Spring中Bean的默认作用域,singleton
作用域指的是单例作用域,这里的单例和常规设计模式中的单例又稍微一些不一样,常规设计模式中的单例模式指的是一个类只能对应唯一的实例对象;而 Bean 的单例作用域表示的是 Bean 对象在Spring中只有一份,它是全局共享的。
场景: 通常无状态的Bean使用该作用域,无状态表示Bean的属性不需要更新。
2) prototype作用域 (原型)
官方说明: Scopes a single bean definition to any number of object instances.
描述: 每次该作用域下的获取Bean的请求都会创建新的实例:这种方式对比singleton作用域,会频繁创建实例,因此开销较大。
场景: 通常有状态的Bean使用该作用域,如前面所提到的例子,就可以用到该作用域。
3) request作用域
官方说明: Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTPrequest has its own instance of a bean created off the back of a single bean definition. Only valid in thecontext of a web-aware Spring ApplicationContext.
描述: 每次HTTP请求会创建新的Bean实例,它的生命周期和一个HTTP的生命周期相同。
场景: 一次HTTP的请求和响应的共享Bean,如在处理一个HTTP请求的过程中,可能有多个组件或步骤需要访问或修改同一份数据,此时就可以用上request作用域。
备注: 限定Spring MVC中使用
4) session作用域
官方说明: Scopes a single bean definition to the lifecycle of an HTTP Session. Only a web-aware Spring ApplicationContext.
描述: 在一个http session(会话)中,定义一个Bean实例,它的生命周期和会话的生命周期相同。
场景: 用户会话的共享Bean,常用于在用户的整个会话过程中共享数据,比如:记录一个用户的登录信息。
备注: 限定Spring MVC中使用
5) application作用域
官方说明: Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of
a web-aware Spring ApplicationContext.
描述: 一个http servlet context共享的Bean,它们在服务器开始执行服务时创建,并持续存在直到服务器关闭。
场景: Web应用的上下文信息,比如:记录一个应用的共享信息
备注: 限定Spring MVC中使用
6) websocket作用域
官方说明: Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of web-aware Spring ApplicationContext
描述: 在一个HTTP WebSocket的生命周期中,定义一个Bean实例
对WebSocket的解释: HTTP协议是基于请求/响应模式的,在客户端发起请求,服务端返回响应后,连接就结束了。WebSocket可以看作是可以实现长连接的HTTP的升级,弥补了HTTP协议无法进行长连接的缺点,一次WebSocket的连接可以看作是Websocket会话。
场景: WebSocket的每次会话中,开始时初始化Bean,直到WebSocket结束都是同一个Bean。
备注: 限定Spring MVC中 使用
1.3 【方案】设置作用域
使用@Scope
标签就可以用来声明 Bean 的作用域,如下面代码:
@Component
public class UserBean {
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean(name = "user")
public User getUserBean() {
User user = new User();
user.setName("zhangsan");
user.setAge(20);
return user;
}
}
@Scope
标签既可以修饰方法也可以修饰类,它有两种设置方式:
- 直接设置值:
@Scope("prototype")
- 使用 ConfigurableBeanFactory(推荐):
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
其实SCOPE_PROTOTYPE
就是 ConfigurableBeanFactory 的一个字段:
在设置了prototype作用域后,终于能够如预期拿到结果:
Do UserController1
从Spring中取出user:User{name='zhangsan', age=20}
修改后的数据User{name='lisi', age=18}
===========================
Do UserController2
从Spring中取出user:User{name='zhangsan', age=20}
2 Spring主要执行流程
2.1 容器初始化
ApplicationContext context =
new ClassPathXmlApplicationContext("bean-lifecycle.xml");
2.2 Bean的实例化
读取xml配置文件中配置的所有 Bean 对象以及xml配置文件所配置的扫描路径下添加六大注解的Bean,通过反射实例化Bean(包括分配内存空间和调用构造方法)
<!-- xml中直接配置的Bean -->
<bean id="userXml" class="com.chenshu.lifecycle.model.UserXml"/>
<!-- xml中配置的扫描路径 -->
<content:component-scan base-package="com.chenshu.lifecycle"/>
2.3 Bean的依赖注入
在依赖注入前,对象仍然是一个原生的状态,并没有进行依赖注入,值得一提的依赖注入这一步并不一定是在2. Bean的实例+初始化
之后,如果是构造方法注入的话,会在2. Bean的实例+初始化
的初始化的过程中注入所需依赖。
扩展:如果Bean所需要依赖注入的对象没有完全初始化好,会先去完全初始化好(也就是依赖注入)该对象,再注入该Bean
2.4 存入Spring容器中
Spring容器会将这个已经完全初始化好的Bean存入IoC容器中。IoC容器通常是一个Map结构的实现,它使用Bean的名称或ID作为键,Bean的实例作为值。
2.5 使用Bean
应用程序可以通过依赖注入或依赖查找机制来获取bean的引用,并调用其方法。
2.6 销毁Spring容器
Spring容器关闭时,会触发bean的销毁过程。
3. Bean的生命周期
源码位置:bean-lifecycle
Bean的生命周期分为以下5大部分:
- 实例化 Bean(为Bean分配内存空间)
- 设置属性(Bean的依赖注入,如
@Autowired
) - Bean 初始化
- 执行各种通知(
xxxAware
的接口方法) - 执行初始化方法(各种初始化方法执行顺序如下)
- @PostConstruct定义的初始化方法
- 判断是否为InitializingBean(调用afterPropertiesSet)
- xml中定义的init-method方法
- 执行各种通知(
- 使用Bean
- 销毁Bean
- 销毁Bean前的各种方法,如
@PreDestroy
,DisposableBean
接口方法,destroy-method
.
- 销毁Bean前的各种方法,如
3.1 代码验证
User类,为了演示依赖注入的时机:
@Component
public class User implements BeanPostProcessor {
}
BeanLifeComponent类,演示了生命周期:
@Component
public class BeanLifeComponent implements BeanNameAware, InitializingBean {
private User user;
//构造方法
public BeanLifeComponent() {
System.out.println("执行构造方法");
}
//setter依赖注入
@Autowired
public void setUser(User user) {
this.user = user;
System.out.println("依赖注入");
}
//BeanNameAware的通知方法
@Override
public void setBeanName(String name) {
System.out.println("执行了BeanName通知方法,name="+name);
}
//初始化方法
@PostConstruct
public void initByAnnotation() {
System.out.println("执行@PostConstrut修饰的init方法");
}
//初始化方法
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("执行InitializingBean接口的afterPropertiesSet方法");
}
//初始化方法
public void initByXml() {
System.out.println("执行xml中定义的init方法");
}
//使用Bean
public void use() {
System.out.println("使用use方法");
}
//销毁前调用的方法
@PreDestroy
public void destroy() {
System.out.println("执行@PreDestroy修饰的destroy方法");
}
}
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"
xmlns:content="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="beanLifeComponent"
class="com.chenshu.lifecycle.beanlife.BeanLifeComponent"
init-method="initByXml"/>
<content:component-scan base-package="com.chenshu.lifecycle"/>
</beans>
运行结果:
执行构造方法
依赖注入
执行了BeanName通知方法,name=beanLifeComponent
执行@PostConstrut修饰的init方法
执行InitializingBean接口的afterPropertiesSet方法
执行xml中定义的init方法
使用use方法
执行@PreDestroy修饰的destroy方法