Bean 的作用域和生命周期
引言
在实际工作中或者日常做项目的时候,我们都会更加注重 " 如何使用 spring 框架 ",也就是将核心放在 【存 / 取 spring 中的对象】这件事情上。
然而,如果我们需要更加深入理解一个 bean 对象 " 在存取的过程中发生了什么 ",就需要明白它的作用域和生命周期。
一、Bean 的作用域
引入案例
User 类:
public class User{
public int id;
public String name;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
UserBean 类,用来往 spring 中存放 User对象。
@Controller
public class UserBean {
@Bean(name = "user3")
public User getUser3() {
User user = new User();
user.id = 3;
user.name = "小明";
return user;
}
}
BeanScpoe1 类,进行 user3 对象注入,之后拿一个新的 user 对象接收。
@Component
public class BeanScope1 {
@Autowired
private User user3;
public User hello() {
User user = user3;
user.name = "小红";
return user;
}
}
BeanScope2 类,进行 user3 对象注入。
@Component
public class BeanScope2 {
@Autowired
private User user3;
public User hello() {
return user3;
}
}
Run 启动类:
public class Run {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
BeanScope1 beanScope1 = context.getBean( "beanScope1", BeanScope1.class);
User user = beanScope1.hello();
System.out.println( " beanScope1: " + user);
BeanScope2 beanScope2 = context.getBean("beanScope2", BeanScope2.class);
User user2 = beanScope2.hello();
System.out.println(" beanScope2: " + user2);
}
}
输出结果:
描述一下上述的代码逻辑:
我们在最初的时候,设计一个 User 类,里面放着 " id " 和 " name " 字段;之后,我们预期将 User 类的对象放入 spring 容器中,所以就设计了一个 UserBean 类,最终,我们将 " id = 3 " 和 " name = 小明 " 的对象放入了 spring 容器中,bean 对象名字为 user3.
接着,我们又设计了一个 BeanScpoe1 类,进行了 user3 对象注入,我们期望通过一个新的 user 对象接收,并为新的对象设置 " name = 小红 ",然而,不巧的是,这一操作,直接影响到了 spring 容器中的 user3 对象。
所以,后面我们在 BeanScope2 类,注入 user3 对象的时候,原来 " name = 小明 " 就变成了 " name = 小红 "。
分析原因
造成以上的结果,其原因是 Bean 默认情况下是单例状态,bean 对象只有一个,也就是说,所有类使用的其实是同一个 bean 对象。
而上面的这种单例状态的情况,实际上只是 Bean 作用域的其中一种模式。
Bean 的作用域
Bean 的作用域是指 Bean 在 Spring 整个框架中的某种行为模式。
比如 singleton 单例作用域,就表示 bean 对象在整个 Spring 中只有一份,它是全局共享的,那么当其他人修改了这个值之后,那么另一个人读取到的就是被修改的值。
Bean 的六种作用域
在普通的 Spring 项目中只有 前两种,而 后四种状态是 Spring MVC 中的值。
1. singleton:单例模式 ( 默认 )
单例模式下,获取 bean 对象 / 注入 bean 对象,都是针对于同一个对象。
2. prototype:原型模式 ( 多例模式 )
原型模式实际上和默认的单例模式正好相反。
原型模式下,获取 bean 对象 / 注入 bean 对象,都是新的对象实例。也就是说,每次对该作用域下的 Bean 请求都会创建新的实例。
3. request:请求作用域 ( Spring MVC )
当前模式下,每次 HTTP 请求都会创建新的 bean 对象,类似于 " prototype " ,只不过,此模式限定在 Spring MVC 中使用。
4. session:会话作用域 ( Spring MVC )
在一个 HTTP Session 中,定义一个 bean 对象,这就和之前的 Servlet 提供的会话机制差不多。比方说它用到的一个典型场景:记录一个用户的登录信息。
5. application:全局作用域 ( Spring MVC )
在一个 HTTP Servlet Context 中,定义一个 bean 对象。
6. websocket:HTTP WebSocket作用域 ( Spring WebSocket )
在一个 HTTP WebSocket 中,定义一个 bean 对象。
更改作用域
由于当前的是一个普通的 spring 项目,我们就将 " 单例模式 " 换成 " 原型模式 ",看一下效果。
对于上面的 UserBean 类进行改变,也就是让 User 对象存入 spring 容器时,添加一个 " @Scope " 注解。其他的代码不需要改动。
@Controller
public class UserBean {
@Bean(name = "user3")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public User getUser3() {
User user = new User();
user.id = 3;
user.name = "小明";
return user;
}
}
输出结果:
在原型模式下,获取 bean 对象 / 注入 bean 对象,都是新的对象实例。也就是说,BeanScope1 类无论怎么修改,都不影响 BeanScope2 类拿 user3 对象。
格式
// 两种写法
@Scope("prototype")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
二、Bean 的生命周期
所谓的生命周期指的是一个对象从诞生到销毁的整个生命过程,我们把这个过程就叫做一个 bean 对象的生命周期。
一个 bean 对象的生命周期大致分为下面的五个部分:
1. 实例化 ( 给 bean 分配内存空间 )
2. 设置属性 ( 对象注入 )
3. 初始化
(1) 执行各种通知 ( 执行各种 Aware )
(2) 执行初始化的前置方法
(3) 执行构造方法,两种执行方式,一种是执行 " @PostConstruct " , 另一种是执行 " init-method " .
(4) 执行初始化的后置方法
4. 使用 Bean
5. 销毁Bean
(1) " @PreDestroy "
(2) 重写 DisposableBean 接口方法
(3) destroy - method
例子
我们可以将一个 bean 对象想象成一个买房到卖房的过程。
- 先买房(实例化,从无到有)
- 装修(设置属性)
- 买家电,如洗衣机、冰箱、电视、空调等([各种]初始化)
- 入住(使用 Bean)
- 卖出去(Bean 销毁)