【Spring】Bean 的作用域和生命周期

news/2024/11/8 15:15:46/

🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!

欢迎志同道合的朋友一起加油喔🤺🤺🤺


目录

Bean作用域问题引入

Bean的作用域

1. 单例作用域 (singleton)

2. 原型作用域 (prototype)

3. 请求作用域 (request)

4. 会话作用域 (session)

5. 全局作用域 (application)

6. HTTP WebSocket 作用域 (websocket)

解决上述问题

Spring 的主要执行流程 

Bean的生命周期



Bean作用域问题引入

假设有一个Bean对象,但是A用户在使用的时候将Bean对象的数据修改了,导致B用户在使用的时候发生了预期之外的逻辑错误

有一个对象为Animal

public class Animal {private String name;public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Animal{" +"name='" + name + '\'' +'}';}
}

将该对象注入到Spring容器中

@Component
public class Animals {@Beanpublic Animal animal(){Animal animal = new Animal();animal.setName("老虎");return animal;}
}

A用户使用Bean对象时,进行了修改操作

@Controller
public class AnimalController1 {@Autowiredprivate 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 {@Autowiredprivate 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作用域的设置方式:

  1. 直接设置: @Scope(“prototype”)
  2. 使用枚举设置; @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 {@Overridepublic void setBeanName(String s) {System.out.println("执行各种通知:" + s);}/*** xml 中 init-method 指定的前置方法*/public void initMethod() {System.out.println("执行 init-method 前置方法");}/*** 改用注解后的前置方法*/@PostConstructpublic void PostConstruct() {System.out.println("执行 PostConstruct 前置方法");}/*** 销毁前执行方法*/@PreDestroypublic 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);// 使用 beancontroller.use();// 销毁 beancontext.destroy();}
}

 执行结果: 

1. 从代码的运行结果来看, 大致执行顺序还是一致的, init-method() 方法和 postConstruct() 方法的执行先后, 可以理解使用注解的方式是改进后的, 优先级被提高了.

2. 这几个生命周期可以这样理解, 方便我们记住: 

  • 实例化 Bean  --> 【买房】
  • 设置属性 -->  【装修】
  • Bean 的初始化 --> 【买家电: 桌子, 凳子, 冰箱, 空调......】
  • 使用 Bean --> 【入住】
  • 销毁 Bean -->【不想住了, 卖房】

具体流程如下图:​​​​​​

【问题】为什么【依赖注入DI】的执行时机要在 【Bean 的初始化之前】?

public class BeanLifeController implements BeanNameAware {// 依赖注入DI@Autowiredprivate UserService userService;@Overridepublic void setBeanName(String s) {System.out.println("执行各种通知:" + s);}// 初始化的前置方法@PostConstructpublic void PostConstruct() {// 在初始化的前置方法中调用userService.doUserService();System.out.println("执行 PostConstruct 前置方法");}
}

上述代码在初始化的前置方法中使用注入的 Bean, 如果是先初始化 Bean,  就会导致空指针异常, 我初始化方法中需要使用到注入的 Bean , 那么一定是先执行【依赖注入】, 在执行【初始化】。


http://www.ppmy.cn/news/273810.html

相关文章

华为OD机试真题 JavaScript 实现【合法IP】【牛客练习题】

一、题目描述 IPV4地址可以用一个32位无符号整数来表示&#xff0c;一般用点分方式来显示&#xff0c;点将IP地址分成4个部分&#xff0c;每个部分为8位&#xff0c;表示成一个无符号整数&#xff08;因此正号不需要出现&#xff09;&#xff0c;如10.137.17.1&#xff0c;是我…

亲身体验,不要再拖! 拖! 拖!

亲身体验&#xff0c;不要再拖! 拖! 拖! 拖延症其实是自我管理的一种放松&#xff0c;意志力极强或者非常自律的人是很少有拖延症的。我一直在思考对于像自己这样意志力薄弱的人&#xff0c;该如何摆脱拖延的坏习惯呢&#xff1f;方法总是有的&#xff0c;效果也总是有的。 >…

32bitwin7硬盘安装win8-64bit

32bitwin7硬盘安装win8-64bit 1、硬件支持64bit是最基本的&#xff0c;可以用cpu-z查看。 2、下载win7-32bit和win8-64bit&#xff0c;虚拟或提取到非c盘下&#xff0c;如果格式化c盘装win8-64bit&#xff0c;建议提取的好。 3、在cmd下用32位里的setup加一个installfrom参数,指…

SpringBoot整合JPA并简单使用示例

干了这碗毒鸡汤~ 相关知识说明&#xff1a; JPA(Java Persistence API)&#xff1a;JPA用于描述对象——关系表的映射关系&#xff0c;并将运行期的实体对象持久化到数据库中。同时也提供有多达十八种增删改查操作数据库表的方法。JPA既可以JPA可以进行单表的增删改查操作&…

完美电脑主机一拖二,让你夫妻不再抢电脑,一台主机两人独立玩

近来闲着无事&#xff0c;突然想把家里的两台主机省出来一台&#xff0c;俩人玩一台主机&#xff0c;那电费得省多少啊&#xff0c;于是乎就开始找各种软件进行测试&#xff0c;最后选定了ASTER使用。 下面是本人的主机配置&#xff0c;以及需要的东西。 首先需要的是你的显卡有…

mysql查询bween_Mysql查询语句

一查询数值型数据: SELECT * FROM tb_name WHERE sum > 100; 查询谓词:>,,,!,!>,!,< 二查询字符串 SELECT * FROM tb_stu WHERE sname 小刘 SELECT * FROM tb_stu WHERE sname like 刘% SELECT * FROM tb_stu WHERE sname like %程序员 SELECT * FROM tb_stu WHER…

解决Betwin客户机经常无法正常登录带来的困扰

BeTwin是一套基于Windows系统环境下的软件,它可让多个用户同时、独立地共享一台PC机的软硬件及外部资源.....&#xff08;在这里不废话了&#xff0c;这些内容还是让读者自己去Google吧 :P&#xff09; 本文的主题是如何解决使用Betwin过程中主机正常启动&#xff0c;客…

JDK8-1-Lambda表达式(2)-方法传递(行为参数化)

JDK8-1-Lambda表达式&#xff08;2&#xff09;-方法传递&#xff08;行为参数化&#xff09; Java 8 允许程序将方法作为参数传递&#xff0c;先看一个例子&#xff1a; 苹果实体类&#xff1a; public class Apple {//颜色private String color;//重量&#xff0c;单位克&…