文章目录
- Bean作用域问题案例分析
- 公共 Bean
- A 用户使用时修改
- B 用户使用时
- 原因分析
- 作用域定义
- Bean 的6种作用域
- singleton
- prototype
- request
- session
- application
- websocket
- 设置作用域
- Spring 执行流程
- 1、启动容器
- 2、Bean 初始化
- 3、注册Bean对象到容器中
- 4、装配Bean属性
- Bean 生命周期
- 1、实例化
- 2、设置属性
- 3、初始化
- 4、使用Bean
- 5、销毁Bean
- 生命周期演示
Bean作用域问题案例分析
假设现在有⼀个公共的 Bean,提供给 A ⽤户和 B ⽤户使⽤,然⽽在使⽤的途中 A ⽤户却“悄悄”地修改了公共 Bean 的数据,导致 B ⽤户在使⽤时发⽣了预期之外的逻辑错误。
公共 Bean
@Component
public class DogBean {
@Bean
public Dog dog() {
Dog dog = new Dog();
dog.setName("旺财");
dog.setId(1);
dog.setAge(5);
return dog;
}
}
A 用户使用时修改
@Controller
public class ScopeController {
@Autowired
private Dog dog;
public void doScope(){
System.out.println("do scope controller");
System.out.println("原始dog对象:" + dog.toString());
Dog dog2 = dog;
dog2.setAge(10);
dog2.setId(2);
dog2.setName("小黑");
System.out.println("修改后的dog对象:" + dog.toString());
}
}
B 用户使用时
@Controller
public class ScopeController2 {
@Resource
private Dog dog;
public void doScope(){
System.out.println("do scope controller2");
System.out.println(dog.toString());
}
}
原因分析
我们可以看到,B 用户在使用这个Bean对象时,得到的Dog是被A 用户修改过的,这无疑会给 B 用户带来很大的麻烦。操作以上问题的原因是因为 Bean 默认情况下是单例状态(singleton),也就是所有⼈的使⽤的都是同⼀个对象,之前我们学单例模式的时候都知道,使⽤单例可以很⼤程度上提⾼性能,所以在 Spring 中 Bean 的作⽤域默认也是 singleton 单例模式。
作用域定义
限定程序中变量的可⽤范围叫做作⽤域,或者说在源代码中定义变量的某个区域就叫做作⽤域。⽽ Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式,⽐如 singleton 单例作⽤域,就表示 Bean 在整个 Spring 中只有⼀份,它是全局共享的,那么当其他⼈修改了这个值之后,那么另⼀个⼈读取到的就是被修改的值。
Bean 的6种作用域
Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作⽤域。Spring有 6 种作⽤域,最后
四种是基于 Spring MVC ⽣效的:
- singleton:单例作⽤域
- prototype:原型作⽤域(多例作⽤域)
- request:请求作⽤域
- session:回话作⽤域
- application:全局作⽤域
- websocket:HTTP WebSocket 作⽤域
后4种状态是Spring MVC 中的值,在普通的 Spring 项目中只有前两种。
singleton
- 官⽅说明:(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
- 描述:该作⽤域下的Bean在IoC容器中只存在⼀个实例:获取Bean(即通过applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是同⼀个对象。
- 场景:通常⽆状态的Bean使⽤该作⽤域。⽆状态表示Bean对象的属性状态不需要更新
- 备注:单例模式默认的作用域,只有一个全局对象
prototype
- 官⽅说明:Scopes a single bean definition to any number of object instances.
- 描述:每次对该作⽤域下的Bean的请求都会创建新的实例:获取Bean(即通过applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是新的对象实例。
- 场景:通常有状态的Bean使⽤该作⽤域
- 备注:原型模式【多例默认】,每次访问都会创建一个新对象
request
- 官⽅说明:Scopes a single bean definition to the lifecycle of a single HTTP request. That is,each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
- 描述:每次http请求会创建新的Bean实例,类似于prototype
- 场景:⼀次http的请求和响应的共享Bean
- 备注:限定SpringMVC中使⽤
session
- 官⽅说明:Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
- 描述:在⼀个http session中,定义⼀个Bean实例
- 场景:⽤户回话的共享Bean, ⽐如:记录⼀个⽤户的登陆信息
- 备注:限定SpringMVC中使⽤
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应⽤的上下⽂信息,⽐如:记录⼀个应⽤的共享信息
- 备注:限定SpringMVC中使⽤
websocket
- 官⽅说明:Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.
- 描述:在⼀个HTTP WebSocket的⽣命周期中,定义⼀个Bean实例
- 场景:WebSocket的每次会话中,保存了⼀个Map结构的头信息,将⽤来包裹客户端消息头。第⼀次初始化后,直到WebSocket结束都是同⼀个Bean。
- 备注:限定Spring WebSocket中使⽤
设置作用域
使⽤ @Scope 标签就可以⽤来声明 Bean 的作⽤域,@Scope 标签既可以修饰⽅法也可以修饰类,@Scope 有两种设置⽅式:
- 直接设置值:@Scope(“prototype”)
- 使⽤枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class DogBean {
@Scope("prototype")
@Bean
public Dog dog() {
Dog dog = new Dog();
dog.setName("旺财");
dog.setId(1);
dog.setAge(5);
return dog;
}
}
这样设置完成后,A 用户的修改对 B 用户的使用就不会造成影响了。
Spring 执行流程
1、启动容器
2、Bean 初始化
配置文件中的bean、配置了加载组件路径下的类进行扫描【看有没有类注解】
3、注册Bean对象到容器中
通过五大注解来把对象注入到容器中,并且只有在包扫描路径上的类,且使用Spring的注解才可以被注册到容器中。
具体参考博客:使用spring注解储存对象
4、装配Bean属性
如果Bean对象 需要使用其他Bean对象作为属性,可以使用注解@Autowired、@Resource
具体参考博客:spring依赖注入
Bean 生命周期
1、实例化
这是生命周期的第一步。在这个阶段,Spring会创建一个Bean的实例。这就像在工厂里制造一个产品一样,但在这里,Spring负责创建和管理Bean对象。
2、设置属性
一旦Bean实例化了,Spring会通过依赖注入的方式设置Bean的属性。这就像给产品添加特性和功能一样。你可以在配置文件中指定属性值,然后Spring会把这些值设置给Bean。
3、初始化
在这个阶段,Bean被初始化。你可以定义初始化方法,Spring会在设置属性后调用这些方法。这允许你在Bean准备好之前做一些额外的设置或者操作。
- 执行各种通知: 实现了各种 Aware 通知的方法,如 BeanNameAware、BeanFactoryAware、ApplicationContextAware 的接口方法;
- BeanPostProcessor的前置处理(postProcessBeforeInitialization): 如果在应用中定义了BeanPostProcessor接口的实现类,Spring会在Bean初始化之前调用这些实现类的postProcessBeforeInitialization方法。这提供了一个机会,你可以在Bean被初始化之前进行一些定制化的操作。
- 初始化方法(InitializingBean和init-method): 如果Bean实现了InitializingBean接口,Spring会在属性设置后调用它的afterPropertiesSet方法。执⾏ @PostConstruct 初始化⽅法,依赖注⼊操作之后被执⾏。此外,你还可以通过配置指定一个自定义的初始化方法(通常使用init-method属性)。在这个方法中,你可以执行任何你需要在Bean初始化时完成的逻辑。
- BeanPostProcessor的后置处理(postProcessAfterInitialization): 类似于前置处理,如果有BeanPostProcessor接口的实现类,Spring会在Bean初始化之后调用这些实现类的postProcessAfterInitialization方法。这个阶段可以用来进行一些额外的操作,例如修改Bean的属性或状态。
4、使用Bean
一旦Bean被初始化,它就准备好被使用了。你可以在应用程序的其他部分中引用并使用这个Bean,执行你所需的操作。这就像使用你制造的产品一样。
5、销毁Bean
当应用程序关闭或者不再需要Bean时,Spring会执行销毁操作。你可以定义销毁方法,在Bean不再需要时执行一些清理工作,比如关闭数据库连接或者释放资源。
生命周期演示
package com.fyd.controller;
import org.springframework.beans.factory.BeanNameAware;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class BeanLifeComponent implements BeanNameAware {
@Override
public void setBeanName(String name) {
System.out.println("执行了 Bean Name 通知" + name);
}
public void init(){
System.out.println("执行了 init 方法");
}
@PostConstruct
public void myPostConstruct(){
System.out.println("执行了 myPostConstruct 方法");
}
/**
* 销毁前执行方法
*/
@PreDestroy
public void myPreDestroy(){
System.out.println("执行了 myPreDestroy 方法");
}
public void use(){
System.out.println("执行了 use 方法");
}
}
xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd ">
<context:component-scan base-package="com.fyd"/>
<bean id="beanlife" class="com.fyd.controller.BeanLifeComponent"
init-method="init"></bean>
</beans>
调用代码
import com.fyd.controller.BeanLifeComponent;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanLifeTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
BeanLifeComponent beanLifeComponent = context.getBean("beanlife",BeanLifeComponent.class);
beanLifeComponent.use();
// 关闭容器
context.destroy();
}
}
执行结果: