您现在的位置是:首页 >技术杂谈 >Spring(Bean 作用域和生命周期)网站首页技术杂谈
Spring(Bean 作用域和生命周期)
目录
1. 案例1: Bean作用域的问题
现在有一个公共的 Bean,通过给 A 用户 和 B 用户使用, 然后在使用的过程中 A 偷偷的修改了公共 Bean 的数据, 导致 B 在使用时发生了预期之外的逻辑错误
(1)公共 Bean(我们赋予它为张三所作)
/**
* 作者:张三
*/
@Controller
public class UserBeans {
@Bean
public User user1(){
User user = new User();
user.setId(1);
user.setName("张三");
user.setPassword("123");
return user;
}
}
(2)李四去使用的时候对其公共对象进行了改名操作
/**
* 作者:李四
*/
@Controller
public class UserController {
@Autowired
private User user1;
public void getUser(){
System.out.println("User1:" + user1);
User user = user1;
user.setName("李四");
System.out.println("User:" + user);
}
}
(3)王五在李四修改了公共对象后继续使用
/**
* 作者:王五
*/
@Controller
public class UserAdviceController {
@Resource
private User user1;
public void getUser(){
System.out.println("王五 | User1:" + user1);
}
}
(4)打印结果,观察起获得的公共 Bean 的值
public class APP {
public static void main(String[] args) {
//1.获取上下文对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
//2.获取 Bean
UserController userController = applicationContext.getBean("userController",UserController.class);
userController.getUser();
UserAdviceController userAdviceController = applicationContext.getBean("userAdviceController",UserAdviceController.class);
userAdviceController.getUser();
}
}
得到两个张三一个李四才是我们理想的结果
这里王五我们预期是要拿到公共对象(张三)的,但是现在拿到的是李四为什么呢?
1.1 原因分析
存在这个问题的原因就是 Bean 默认情况下是 单例模式 (singleton), 也就是所有类使用的都是同一个对象, 使用同一个对象,那就是没有过多的生命周期的创建和销毁,从而可以提高性能,所以在 Spring 中 Bean 的作用域也是 单例模式的
也就是说李四拿到了张三这个对象之后,将其姓名修改为李四,也就是把全局的单例对象也改成了李四了,这个时候后面的人去拿时拿到的就是李四了不再是张三了
2. 作用域定义
限定程序中变量的可⽤范围叫做作⽤域,或者说在源代码中定义变量的某个区域就叫做作⽤域。
2.1 Bean的五种作用域
Spring 容器在初始化一个 Bean 的实例时,会同时指定该实例的作用域,Spring中有 6 种作用域
singleton: 单例模式 (默认,每次请求取的是同一个对象)
prototype: 原型模式 (多例模式,每次请求创建一个新的对象)
request: 请求作用域 (Spring MVC,每次请求创建一个对象)
session: 会话作用域 (Spring MVC,在一个会话中共享一个对象)
application: 全局作用域 (Spring MVC,所有人共用一个)
websocket:HTTP WebSocket 作⽤域其中前两种是 spring 核⼼作⽤域,⽽后 4 种是 spring mvc 中的作⽤域
2.2 设置作用域
@Scope 标签既可以修饰⽅法也可以修饰类,@Scope 有两种设置⽅式:1. 直接设置值:@Scope("prototype")2. 使⽤枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
在这里我们设置为原型模式每次请求创建一个新的对象
1. 直接设置值:@Scope("prototype")
@Controller
public class UserBeans {
@Scope("prototype")
@Bean
public User user1(){
User user = new User();
user.setId(1);
user.setName("张三");
user.setPassword("123");
return user;
}
}
2. 使⽤枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
3. Spring执行流程和 Bean 生命周期
3.1 Spring执行流程
1.启动spring容器(启动项目)
2.读取配置文件,初始化
a)使用 xml 直接注册 bean
b)配置 bean 根(扫描)路径
3.将 Bean 存储到 spring 中,通过类注释进行扫描和装配
4.将 Bean 从 spring 读取出来,装配到相应的类
Bean 执行流程(Spring 执行流程)︰
启动Spring容器->实例化Bean(分配内存空间,从无到有)->Bean注册到Spring 中(存操作)->将Bean装配到需要的类中(取操作)。
3.2 Bean 生命周期
Bean生命周期:
1.实例化(给 Bean 分配内存)
2.设置属性( Bean 注入和装配)
3.Bean 初始化
a)执行各种通知
b)初始化的前置工作
c)进行初始化工作两种执行方式,一种是执行 @PostConstruct,另一种是执行 init-method
d)初始化的后置工作
4.使用 Bean
5.销毁 Bean
注解 @PostConstruct
- 作用:如果在生成对象时要完成某些初始化操作,并且这些初始化操作又依赖于依赖注入,那么可以使用注解 @PostConstruct 加在要初始化的方法上,那么这个初始化方法就能够在依赖注入完成后自动被调用
- 要求:修饰一个非静态的 void() 方法
- 调用:@PostConstruct 修饰的方法会在服务器加载 Spring框架 的时候运行,并且只会被服务器执行一次,并且在构造函数之后执行,init() 方法之前执行
- 该注解的方法在整个 Bean 初始化中执行的顺序:Constructor(构造方法)——》@Autowired(依赖注入)——》@PostConstruct(注解的方法)——》init()方法
注解 @PreDestroy
- 要求:修饰一个非静态的 void() 方法
- 调用:被 @PreDestroy 修饰的方法会在服务器卸载 Servlet 的时候运行,并且只会被服务器执行一次
- 执行顺序:构造方法 ——》@PostConstruct ——》init() 方法 ——》destroy() 方法 ——》bean 销毁
package com.demo.component;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
* Created with IntelliJ IEDA.
* Description:
* User:86186
* Date:2023-05-26
* Time:23:09
*/
@Component
public class BeanLifeComponent implements BeanNameAware {
@Override
public void setBeanName(String s) {
System.out.println("执行了通知方法");
}
@PostConstruct
public void postConstruct(){
System.out.println("执行了postConstruct");
}
public void Init(){
System.out.println("执行了 init-method 方法");
}
@PreDestroy
public void preDestroy(){
System.out.println("执行销毁方法");
}
}
<?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.demo"></content:component-scan>
<bean id="myComponent" class="com.demo.component.BeanLifeComponent"
init-method="Init"></bean>
</beans>
package com.demo;
import com.demo.component.BeanLifeComponent;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created with IntelliJ IEDA.
* Description:
* User:86186
* Date:2023-05-26
* Time:23:14
*/
public class App2 {
public static void main(String[] args) {
//获得上下文用这个方法ClassPathXmlApplicationContext而不用ApplicationContext是因为ClassPathXmlApplicationContext这个中有销毁方法
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
BeanLifeComponent beanLifeComponent = context.getBean("myComponent",BeanLifeComponent.class);
System.out.println("使用 Bean");
//销毁 Bean 直接销毁容器
context.destroy();
}
}
这里执行两边的原因是:我们即使用了xml又使用了@Component
先设置属性再进行初始化,作用是
如果先初始化,就会出现空引用了,所以了为了防止空引用,就要先设置属性注入,然后再进行初始化