文章目录
- 1. Bean的作用域
- 2. Spring的生命周期
- 3. Bean的生命周期
- 4. 相关注解总结
1. Bean的作用域
Bean 的作用域指的是 Bean 在 Spring 容器中的行为(Bean 实例创建及生命周期),它的行为是由 Spring 来管理的,可以根据具体情况选择不同的作用域来达到性能优化、资源利用最大化和可维护性等目的。
Bean 作用域(Scope)类型主要有如下几种:
其中前两种是 Spring 核心作用域,而后 4 种是 Spring MVC 中的作⽤域;
- singleton:单例模式(默认的作用域),Spring IoC 容器中只会存在一个共享的 Bean 实例,无论有多少个 Bean 引用它,始终指向同一对象,通常无状态的 Bean 使用该模式(无状态表示Bean对象的属性不需要更新),这也是 Spring 出于性能方面的考虑。
- prototype:原型模式(多例作用域),每次从 Spring 容器中获取 prototype 模式的 Bean 时,容器都将创建一个新的 Bean 实例,每个Bean实例都有自己的属性和状态,而 singleton 全局只有一个对象,通常有状态的 Bean 使用该作用域,即Bean的属性可能会更新。
- request:请求模式,在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例,在这次的请求和响应中共享这个 Bean,但在不同的请求中会创建新的实例,也就是说每次 HTTP 请求,使用的是不同的Bean, 类似于 prototype,在 Spring MVC 中使用,即使用需要在 WebApplicationContext 环境下。
- session:会话模式,在同一个 HTTP Session 中,共享使用的是一个 Bean 实例,例如记录用户的登录信息,在 Spring MVC 中使用。
- application:全局模式,表示整个 Web 应用的 http servlet context 共享同一个 Bean,主要记录的是的 Web 应用的上下文信息,例如记录一个应用的共享信息,在 Spring MVC 中使用。
- websocket:网络长连接,只适用于 Spring WebSocket 项目,在一个 HTTP WebSocket 的生命周期中(一次长连接),共享一个 Bean 实例;WebSocket 的每次会话中,保存了一个 Map 结构的头信息,用来保存客户端消息头,第一次初始化后,直到一次长连接结束都是用一个 Bean。
这里就简单用singleton
和prototype
这两种可以在 Spring 核心代码中使用的模式来演示区别一下。
package com.tr.demo.model;
public class User {
private int id;
private String name;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + ''' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
一般有两种方式设置 Spring Bean 作用域:
- 使用
xml
配置的方式,我们将bean
标签当中的scope
属性设置为singleton
就为单例,设置为prototype
就为多例模式。 - 二是直接通过
@Scope
注解,不设置参数或设置参数为singleton
就为单例,设置为prototype
就为多例模式。
我们还可在 Scope 注解中可以传入另一种参数形式,和上面直接拼写出来效果一样。
这种方式是 ConfigurableBeanFactory 类中为我们提供了静态属性,相较于自己拼写不容易出错。
我们来看一下 singleton 和 prototype 两种模式下获取的 Bean 有什么不同,我们这里演示在这两种不同的模式下将 User 对象放到 Spring 容器中,从容器中先后两次取出 User,看这两次取出的 Bean 是否相同。
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
User user1 = context.getBean("user", User.class);
User user2 = context.getBean("user",User.class);
System.out.println(user1 == user2);
}
singleton 模式下的结果:
可以看到,此时 User 类在 Ioc 容器中只创建了一个实例,而通过 applicationContext.getBean() 方法多次获取到的 Bean,都是同一个对象实例,相当于是一个浅拷贝。
prototype 模式下的结果:
这里之所以这里打印出来的 Bean 是 2 个而不是 3 个,是因为prototype模式下在我们创建容器时并不会进行 Bean 的实例化,它在我们获取 Bean 的时候才会去创建 1 个实例,而且每次获取 Bean 时都会创建新的实例,它们彼此之间都是不同的实例,相当于是一个深拷贝。
2. Spring的生命周期
Spring 的生命周期大致走的的是如下流程
- 首先启动容器,加载配置文件(类加载路径下的 spring-config.xml 文件)。
- 根据配置文件完成 Bean 的实例化(分配内存空间)和初始化(初始化空间),包括扫描配置文件下带有五大类或者方法注解的类和 bean 标签中要注册的对象,配置文件中的 Bean 是按顺序实例化和初始化的。
- 将 Bean 注册到 Spring 容器中
- 装配 Bean 的属性,如果 Bean 对象需要其他的 Bean 作为属性,可以使用注解的方式注入(@Autowired、Resource)
小结,Bean执行流程(Spring执行流程):启动 Spring 容器 -> 实例化 Bean(分配内存空间,从无到有)-> Bean 注册到 Spring 容器中(存操作) -> 将 Bean 装配到需要的类中(取操作)。
3. Bean的生命周期
Bean 的生命周期,就是 Bean 对象从诞生到销毁的整个过程,Bean 的生命周期大致有 5 个阶段:
①Bean 的实例化(分配内存空间)。
②设置 Bean 属性(进行依赖注入,将依赖的 Bean 辅助到当前类的属性上)。
③Bean 的初始化阶段:
- 执行各种通知,底层实现了 Aware 接口,执行其中的方法,如:BeanNameAware、BeanFactoryAware、ApplicationContextAware 的接口方法;
- 执行初始化的前置方法,即执行 BeanPostProcessor 方法(before);
- 执行初始化方法 ,依赖注⼊操作之后被执⾏(两种重写方式:xml方式,注解方式@PostConstruct);
- 执⾏⾃⼰指定的 init-method ⽅法(如果有指定的话);
- 执行 BeanPostProcessor 初始化后置方法(after)。
④使用 Bean。
⑤销毁 Bean,执行 销毁容器的各种⽅法,如 @PreDestroy、DisposableBean 接⼝⽅法、destroy-method。
要注意设置属性必须在初始化之前,因为有可能在初始化的时候使用Bean。
上述流程可以以⽣活中的场景来理解它,⽐如我们现在需要买⼀栋房⼦,那么我们的流程是这样的:
- 先买房(毛坯房),对应 Bean 的实例化;
- 装修,对应给 Bean 设置属性;
- 买家电,如洗⾐机、冰箱、电视、空调等,对应 Bean 的各种初始化
- ⼊住,对应使⽤ Bean;
- 卖房,对应 Bean 的销毁。
执行流程如下图所示:
还需要注意区分一下实例化和初始化,实例化和属性设置是 Java 级别的系统“事件”,其操作过程不可⼈⼯⼲预和修改;⽽初始化是给开发者 提供的,可以在实例化之后,类加载完成之前进⾏⾃定义“事件”处理。
下面就简单的写几个周期方法来演示一下这个过程,在类中添加两个方法,一个是init
方法表示初始化,另外一个是destroy
方法,表示Bean销毁前执行的事情,使用@PostConstruct
注解或者xml
的bean
标签中的init-method
属性表示在构造后执行,@PreDestroy
注解或者xml
的bean
标签中的destroy-method
表示在 Bean 销毁前做的事情。
import com.tr.demo.model.User;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class BeanLifeComponent implements BeanNameAware {
@Autowired
private User user;
@Override
public void setBeanName(String s) {
System.out.println("执行了 BeanNameAware 通知 -> " + s);
}
@PostConstruct
public void doPostConstruct() {
System.out.println("执行了 @PostConstruct (注解式初始化)");
System.out.println(user.toString());
}
public void myInit(){
System.out.println("执行了 myinit (xml 式初始化)");
}
@PreDestroy
public void doPreDestroy(){
System.out.println("执行了 @PreDestroy (销毁 Bean 前执行)");
}
public void myDestroy() {
System.out.println("执行了 myDestroy (销毁 Bean 前执行)");
}
public void sayHi(){
System.out.println("使用 Bean");
}
}
Spring 配置文件,将 Bean 手动存储在 Spring 容器中,要注意init-method
,destroy-method
这两个属性的值要和上面实现的方法名对应。
bean id="mybean" class="BeanLifeComponent"
init-method="myInit" destroy-method="myDestroy">
/bean>
测试代码:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanLifeTest {
public static void main(String[] args) {
// 父类 ApplicationContext 中没有 close, destroy 系列方法
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
BeanLifeComponent component =
context.getBean("mybean",BeanLifeComponent.class);
component.sayHi();
// 关闭容器
context.close();
}
}
运行结果:
如果代码中飘红说 @PostConstruct 和 @PreDestroy 注解如果找不到,需要导入下面的jar包。
dependency>
groupId>javax.annotation/groupId>
artifactId>javax.annotation-api/artifactId>
version>1.3.2/version>/dependency>
找不到的原因是,从 JDK9 以后 jdk 中的 javax.annotation 包被移除了,这两个注解刚好就在这个包中。
4. 相关注解总结
@Scope
名称 | @Scope |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类创建对象的作用范围 可用于设置创建出的bean是否为单例对象 |
属性 | value(默认):定义bean作用范围, 默认值singleton(单例),可选值prototype(非单例) |
@PostConstruct
名称 | @PostConstruct |
---|---|
类型 | 方法注解 |
位置 | 方法上 |
作用 | 设置该方法为初始化方法 |
属性 | 无 |
@PreDestroy
名称 | @PreDestroy |
---|---|
类型 | 方法注解 |
位置 | 方法上 |
作用 | 设置该方法为销毁方法 |
属性 | 无 |
注解功能与 XML 实现对应关系: