Spring 是用来存储和读取 Bean,因此 Spring 中 Bean 是最核心的操作资源,我们需要深入学习一下 Bean 对象。
- 一、Bean 的作用域问题
- 1.1 原因分析
- 1.2 作用域定义
- 二、Bean 的六种作用域
- singleton 单例作用域
- prototype 原型作用域
- request 请求作用域
- session 回话作用域
- application 全局作用域
- websocket(HTTP WebSocket作用域)
- 单例作用域(singleton)和全局作用域(application)的区别。
- 三、Bean 原理分析
- 3.1 Bean 执行流程
- 3.2 Bean 执行流程小结(Spring 执行流程)
- 3.3 Bean 生命周期
- 四、Bean 生命周期演示
一、Bean 的作用域问题
通过一个案例来看 Bean 作用域的问题
假设现在有一个公共的 Bean,提供给 A 用户和 B 用户使用,然鹅在使用的途中 A 用户偷偷修改了公共 Bean 的数据,导致 B 用户在使用的时候发生了预期之外的逻辑错误。
公共 Bean:
@Component
public class Users {
@Bean
public User user1(){
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
}
A 用户在使用的时候,进行修改:
@Controller
public class BeanScopesController {
@Autowired
private User user1;
public User getUser1(){
User user = user1;
System.out.println("bean原name:" + user.getName());
user.setName("零一");
return user;
}
}
B 用户再去使用公共 Bean 的时候:
@Controller
public class BeanScopesController2 {
@Autowired
private User user1;
public User getUser1(){
User user = user1;
return user;
}
}
打印 A 用户和 B 用户公共 Bean 的值:
public class BeansScopesTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
BeanScopesController bsc = context.getBean(BeanScopesController.class);
System.out.println("a修改之后" + bsc.getUser1().toString());
BeanScopesController2 bsc2 = context.getBean(BeanScopesController2.class);
System.out.println("b读取到的name" + bsc2.getUser1().toString());
}
}
执行结果如下:
1.1 原因分析
操作以上问题的原因是:Bean 默认情况下是单例状态(singleton),也就是所有人使用的都是同一个对象。
使用单例可以很大程度上提高性能,所以在 Spring 中 Bean 的作用域默认也是 singleton 单例模式。
1.2 作用域定义
限定程序中变量的可用范围叫做作用域,或者说在源代码中定义变量的某个区域叫做作用域。
而 Bean 的作用域是指 Bean 在 Spring 整个框架中的某种行为模式,比如 singleton 单例作用域,就表示 Bean 在整个 Spring 中只有一份,它是全局共享的,那么当其他人修改了这个值之后,那么另一个读取到就是被修改的值。
二、Bean 的六种作用域
Spring 容器在初始化一个 Bean 的实例时,同时会指定该实例的作用域。Spring 有六种作用域,最后四种是基于 Spring MVC 生效的:
- singleton - 单例作用域
- prototype - 原型作用域(多例作用域)
- request - 请求作用域
- session - 回话作用域
- application - 全局作用域
- websocket - HTTP WebSocket 作用域
后四种状态是 Spring MVC 中的值,在普通的 Spring 项目中只有前两种。
singleton 单例作用域
- 描述:该作用域下的 Bean 在 IoC 容器中只存在一个实例,获取 Bean(通过 applicationContext.getBean 等方法获取)以及装配 Bean(通过 @Autowired 注入)都是同一个对象。
- 使用场景:通常无状态的 Bean 使用该作用域,无状态表示 Bean 对象的属性状态不需要更新。
- 备注:Spring 默认选择该作用域。
prototype 原型作用域
- 描述:每次对该作用域下的 Bean 的请求都会创建新的实例。获取Bean(通过 applicationContext.getBean 等方法获取)以及装配 Bean(通过 @Autowired 注入)都是新的对象实例。
- 场景:通常有状态的 Bean 使用该作用域。
request 请求作用域
- 描述:每次 http 请求会创建新的 Bean 实例,类似于 prototype.
- 场景:一次 http 的请求和响应都共享 Bean。
- 备注:限定 SpringMVC 中使用。
session 回话作用域
- 描述:在一个 http session中,定义一个 Bean 实例。
- 场景:用户回话的共享 Bean,比如记录一个用户的登录信息。
- 备注:限定 SpringMVC 中使用。
application 全局作用域
- 描述:在一个 http servlet Context 中,定义一个 Bean 实例。
- 场景:Web 应用的上下文信息,比如记录一个应用的共享信息。
- 备注:限定 SpringMVC 中使用。
websocket(HTTP WebSocket作用域)
- 描述:在一个 HTTP WebSocket 的生命周期中,定义一个 Bean 实例。
- 场景:WebSocket 的每次会话中,保存了一个 Map 结构的头信息,将用来包裹客户端信息头。第一次初始化后,直到 WebSocket 结束都是同一个 Bean。
- 备注:限定 SpringMVC 中使用。
单例作用域(singleton)和全局作用域(application)的区别。
- singleton 是 Spring Core 的作用域;application 是 Spring Web 中的作用域;
- singleton 作用于 IoC 的容器,而 application 作用于 Servlet 容器。
三、Bean 原理分析
3.1 Bean 执行流程
启动容器:
加载配置文件。
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
}
}
根据配置完成 Bean 初始化:
扫描 com.bit.service 包下边的 Spring 注解(@Controller、@Service、@Component、@Repository)。
<?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">
<content:component-scan base-package="com.bit.service">
</content:component-scan>
</beans>
注册 Bean 对象到容器中:
@Component
public class UComponent {
}
@Controller
public class UserController {
}
@Repository
public class UserRepository {
}
装配 Bean 的属性:
如果 Bean 对象需要使用其它 Bean 对象作为属性,可以使用注解 @Autowired、@Resource.
@Controller
public class UserController {
@Autowired
private UserService userService;
}
3.2 Bean 执行流程小结(Spring 执行流程)
①启动 Spring 容器 -> ②实例化 Bean(分配内存空间,从无到有) -> ③Bean 注册到 Spring 中(存储操作)-> ④将 Bean 装配到需要的类中(取操作)。
3.3 Bean 生命周期
所谓的生命周期指的是一个对象从诞生到销毁的整个生命过程,我们把这个过程就叫做一个对象的生命周期。
Bean 的生命周期分为以下五大部分:
1. 实例化 Bean(为 Bean 分配内存空间)
2. 设置属性(Bean 注入和装配)
3. Bean 初始化
① 实现了各种 Aware 通知的方法,如 BeanNameAware、BeanFactoryAware、ApplicationContextAware 的接口方法;
② 执行 BeanPostProcessor 初始化前置方法;
③ 执行 @PostConstruct 初始化方法,依赖注入操作之后被执行;
④ 执行自己指定的 init-method 方法(如果有指定的话);
⑤ 执行 BeanPostProcessor 初始化后置方法。
4. 使用 Bean
5. 销毁 Bean
实例化和初始化的区别
实例化和属性设置是 Java 级别的系统 “事件”,其操作过程不可人工干预和修改;而初始化是给开发者提供的看,可以在实例化之后,类加载完成之前进行自定义 “事件” 处理。
生命流程的 “故事”
Bean 的生命流程看似复杂,但我们可以使用生活中的例子来理解它。
假设我们现在需要买房子,那么我们的流程是这样的:
- 先买房(实例化,从无到有);
- 装修(设置属性);
- 买家电,如洗衣机、冰箱、电视、空调等(各种 初始化);
- 入住(使用 Bean);
- 卖出去(Bean 销毁)。
四、Bean 生命周期演示
@Component
public class BeanLifeComponent implements BeanNameAware {
@PostConstruct
public void postConstruce() {
System.out.println("执行了 postConstruce 方法");
}
public void init() {
System.out.println("执行了 BeanListComponent init 方法");
}
@PreDestroy
public void preDestroy() {
System.out.println("执行了 preDestroy 方法");
}
public void setBeanName(String s) {
System.out.println("执行了 setBeanName 方法" + s);
}
}
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">
<content:component-scan base-package="com.bit">
</content:component-scan>
<beans>
<bean id="beanLifeComponent" class="com.bit.service.BeanLifeComponent" init-method="init"></bean>
</beans>
</beans>
测试方法:
public class BeanListTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
BeanLifeComponent bean = context.getBean(BeanLifeComponent.class);
System.out.println("执行 main 方法");
context.destroy();
}
}
代码执行结果: