您现在的位置是:首页 >学无止境 >【Spring】Bean 的作用域和生命周期网站首页学无止境
【Spring】Bean 的作用域和生命周期
???点进来你就是我的人了
博主主页:???戳一戳,欢迎大佬指点!欢迎志同道合的朋友一起加油喔???
目录
6. HTTP WebSocket 作用域 (websocket)
Bean作用域问题引入
假设有一个Bean对象,但是A用户在使用的时候将Bean对象的数据修改了,导致B用户在使用的时候发生了预期之外的逻辑错误
有一个对象为Animal
public class Animal {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + ''' +
'}';
}
}
将该对象注入到Spring容器中
@Component
public class Animals {
@Bean
public Animal animal(){
Animal animal = new Animal();
animal.setName("老虎");
return animal;
}
}
A用户使用Bean对象时,进行了修改操作
@Controller
public class AnimalController1 {
@Autowired
private Animal animal1;
public void getAnimal(){
System.out.println("修改前的数据 = "+animal1);
System.out.println("=====================");
Animal animal = this.animal1;
animal.setName("加菲猫");
System.out.println("A修改后拿到的数据 = "+animal);
}
}
B用户在去使用Bean对象
@Controller
public class AnimalController2 {
@Autowired
private Animal animal2;
public void getAnimal(){
System.out.println("B未修改拿到的数据 = "+animal2);
}
}
打印A用户和B用户公共Bean的值
public class App {
public static void main(String[] args) {
//得到Spring上下文的对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//执行A用户的代码
AnimalController1 animalController1 = context.getBean("animalController1",AnimalController1.class);
animalController1.getAnimal();
//执行B用户的代码
AnimalController2 animalController2 = context.getBean("animalController2",AnimalController2.class);
animalController2.getAnimal();
}
}
打印结果:都打印了A用户修改后的值
出现上述结果的原因是Bean默认是单例作用域
,也就是所有用户使用的都是同一个对象,但是我们想要的结果是公共Bean可以被用户在自己的类中修改,但是不能影响到其他类,那该如何做?
了解了下面Bean的作用域后,答案自然知晓
Bean的作用域
Bean的作用域指的是Bean在整个Spring框架中的某个行为模式,比如singleton单例作用域表示Bean在整个Spring中只有一份,是全局共享的,当有一个用户修改了这个对象后,其他用户获取的就是这个修改后的对象
Bean的作用域有六种:
1. singleton:单例作⽤域
2. prototype:原型作⽤域(也叫多例作⽤域)
3. request:请求作⽤域
4. session:会话作⽤域
5. application:全局作⽤域
6. websocket:HTTP WebSocket 作⽤域
前两种作用域是在普通的 Spring 项目中使用, 后四种作用域存在于 Spring MVC 项目中.(前四种要知道)
1. 单例作用域 (singleton)
含义: 单例作用域是指在 Spring IoC 容器中只存储一份, 也就是说只有一个实例, 无论我们是通过 @Autowried, @Resource 去获取, 还是通过上下文对象去 getBean(), 拿到的 bean 对象都是同一份. (并且单例作用域是 Spring 中默认的作用域)
场景:: 通常是无状态的 Bean 使用的作用域. (无状态表示 Bean 对象的属性状态不需要修改)
2. 原型作用域 (prototype)
含义: 原型作用域也叫作多例作用域, 每次从 Spring 中获取 Bean 对象, 都会创建一份新的实例, @Autowired, @Resource 注入的对象以及 context 上下文 getBean 拿到的都是不同的 bean 对象.
场景: 通常是有状态的 Bean 使用的作用域 (有状态表示 Bean 对象的属性需要被修改)
3. 请求作用域 (request)
含义: 每一次 HTTP 请求都会创建新的实例, 类似于 prototype.
场景: 一次 HTTP 的请求和响应共享一个 bean. (仅在 Spring MVC 中使用)
4. 会话作用域 (session)
含义: 在一个 HTTP session 中, 定义一个 Bean 实例.
场景: 同一个用户的会话共享 Bean (例如在登录场景中记录一个用户的登录信息) 仅在 Spring MVC 中使用
5. 全局作用域 (application)
含义: 在一个 HTTP Servlet Context 中, 定义一个 Bean 实例
场景: Web 应用的上下文信息, 记录一个应用的共享信息.(仅在 Spring MVC 中使用)
application 作用域和 单例作用域还是有区别的, 它只是同一份上下文对象共享同一个 bean, 当再次创建上下文对象时, 调用 getBean() 就是另一个 Bean 对象了.
6. HTTP WebSocket 作用域 (websocket)
含义: 在⼀个HTTP WebSocket的⽣命周期中,定义⼀个Bean实例
场景: WebSocket的每次会话中,保存了⼀个 Map 结构的头信息,将⽤来包裹客户端消息头。第⼀次初始化后,直到WebSocket结束都是同⼀个Bean。(仅在 Spring MVC 中使用)
解决上述问题
通过了解Bean的作用域,可以得到Bean的作用域为prototype,来解决上述问题
Bean作用域的设置方式:
- 直接设置: @Scope(“prototype”)
- 使用枚举设置; @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
在存储Bean对象的时候添加上述注解,两种方式任选择一种
对上述代码进行修改:只需要在存储Bean时,添加上述两个注解中的一个即可
Spring 的主要执行流程
主要执行流程:
1. 启动 Spring 容器
2. 初始化 Bean 【加载】
3. 将Bean 对象注入到容器中
4. 使用 Bean
最后其实还有销毁 Bean
Bean的生命周期
Bean的生命周期就是一个Bean对象从诞生到销毁的过程
Bean的生命周期分为以下五个步骤:
1. 实例化Bean
为Bean分配内存空间
2.设置属性
Bean注入和装配
3. 初始化Bean
执行各种Aware通知,如 BeanNameAware,BeanFactoryAware、ApplicationContextAware 的接口方法
执行初始化的前置方法
执行构造方法,两种执行方式,一种是@PostConstruct,另一种是自己指定的init-method方法
执行初始化的后置方法
4.使用Bean
5. 销毁Bean
销毁Bean的各种方法,@PreDestroy,DisposableBean接口方法,指定的destroy-method方法
下面通过代码的方法来观察 Bean 的生命周期:
【代码示例】
public class BeanLifeController implements BeanNameAware {
@Override
public void setBeanName(String s) {
System.out.println("执行各种通知:" + s);
}
/**
* xml 中 init-method 指定的前置方法
*/
public void initMethod() {
System.out.println("执行 init-method 前置方法");
}
/**
* 改用注解后的前置方法
*/
@PostConstruct
public void PostConstruct() {
System.out.println("执行 PostConstruct 前置方法");
}
/**
* 销毁前执行方法
*/
@PreDestroy
public void PreDestroy() {
System.out.println("执行 PreDestroy 销毁方法");
}
public void use() {
System.out.println("使用 bean - 执行 use 方法");
}
}
使用原始的 <bean> 标签设置 bean
<bean id="beanLife" class="controller.BeanLifeController"
init-method="initMethod" scope="prototype"></bean>
启动类:
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
// 根据 id 获取 bean 对象
BeanLifeController controller =
context.getBean("beanLife", BeanLifeController.class);
// 使用 bean
controller.use();
// 销毁 bean
context.destroy();
}
}
执行结果:
1. 从代码的运行结果来看, 大致执行顺序还是一致的, init-method() 方法和 postConstruct() 方法的执行先后, 可以理解使用注解的方式是改进后的, 优先级被提高了.
2. 这几个生命周期可以这样理解, 方便我们记住:
- 实例化 Bean --> 【买房】
- 设置属性 --> 【装修】
- Bean 的初始化 --> 【买家电: 桌子, 凳子, 冰箱, 空调......】
- 使用 Bean --> 【入住】
- 销毁 Bean -->【不想住了, 卖房】
具体流程如下图:
【问题】为什么【依赖注入DI】的执行时机要在 【Bean 的初始化之前】?
public class BeanLifeController implements BeanNameAware {
// 依赖注入DI
@Autowired
private UserService userService;
@Override
public void setBeanName(String s) {
System.out.println("执行各种通知:" + s);
}
// 初始化的前置方法
@PostConstruct
public void PostConstruct() {
// 在初始化的前置方法中调用
userService.doUserService();
System.out.println("执行 PostConstruct 前置方法");
}
}
上述代码在初始化的前置方法中使用注入的 Bean, 如果是先初始化 Bean, 就会导致空指针异常, 我初始化方法中需要使用到注入的 Bean , 那么一定是先执行【依赖注入】, 在执行【初始化】。