Spring 中 Bean 的作用域以及生命周期

简介: Spring 中 Bean 的作用域以及生命周期

在 Spring 中, 最主要的操作就是存储 Bean 和 从容器中拿取 Bean, 因此 Bean 对象有关的东西就显得很重要了, 比如作用域


一. 什么是作用域



作用域应该都不陌生, 作用域在前面的学习中, 无论是数据结构还是SE 等地方, 都有涉及过作用域的问题, **简单来说作用域就是起作用的范围, **前面的所学中, 有如下几种作用域 :


  • 类级别作用域 : 在类的声明中定义的变量, 被称为局部变量或静态变量, 它们的作用域是整个类
  • 方法级别作用域 : 在大括号范围内定义的变量, 被称为局部变量, 他们的作用域只在该代码块内有效. 一般在方法、循环体、条件语句等代码块内定义.
  • 方法级别作用域 : 在方法中定义的变量, 也是局部变量, 它们的作用域只在该方法内部有效
  • 形式参数作用域 : 在方法签名中定义的参数, 也是局部变量, 他的作用域只在方法中有效.


需要注意的是, 变量的作用域是不能嵌套的, 即内部的作用域的变量不能访问外部作用域的变量, 但外部作用域的变量可以被内部作用域访问到


二. Bean 的作用域问题



在 Spring 框架中, Bean 对象的作用域指的是 Spring 容器管理的 Bean 对象的生命周期和可见性范围


为什么要去了解 Bean 的作用域 ? 一方面除了上面所说的 Bean 对象在 Spring 框架中的重要性, 另一方面更是想要用好 Bean 对象的关键, 因为作用域的问题, 如果不理解, 可能会让你的代码中 Bean 对象出现在不该出现的地方, 赋值给不该给的对象等问题


比如下面这段代码 :

  1. 先创建一个 People 类

image.png


  1. 将 People 注入到 Spring 容器中

image.png


  1. 模拟 A、B 两个用户去使用 People 这个 Bean 对象

c7d1385d76fa5e3345aa28590292f0ac.png

fd43ffd0dd258cad1397c09c3774e639.png


  1. 输出看看 A B 用户在使用 Bean 时有什么变化

353e0257c41a55dbbf95f148b625d67e.png

a87676a576173942aa3ae41c93072772.png


输出后发现, B 去读取的时候居然读取到了 A 修改之后的内容, 这就出现问题了, 如果在后续的项目中, 你修改了这个对象, 而另一个人也需要用这个对象, 他改了你再去用拿到的就是错误得数据, 影响后续项目的开发.


哪这是什么原因呢 ?


出现以上问题, 其实是因为 Bean 默认情况下是单例的, 也就是大家在使用的都是同一个对象, 对于单例模式而言我们知道, 单例模式是可以提高性能的, 在 Spring 中 Bean 的作用域同样是默认为单例模式的


三. Bean 的六种作用域



哪上面这个问题怎么解决呢 ? Bean 又有哪些作用域申明呢 ? 对于作用域, 又是在什么时候生效的 ?


1. 单例作用域(singleton)


对于单例作用域, 写过单例模式来说应该都不会太陌生, 对于 Spring 中 Bean 的单例模式是差不多的


定义: 该作用域下的 Bean 在 IoC 容器中只存在一个实例, 获取 Bean (ApplicationContontext.getBean) 以及装配 Bean 对象(@Autowired) 都是同一个对象


在 Spring 中通常无状态的 Bean 使用该作用域, 无状态表示 Bean 对象的属性状态不需要更新, 就像上面一样, 这个对象的内容它是不可变的, 谁去获取都是同一个对象, 就可以用单例模式


最后需要注意的是, Spring 中的 Bean 对象创建时默认都是单例模式的, 如果需要更改则需要重新指定作用域


2. 原型作用域(prototype)


定义 : 每次对该作用域下的 Bean 的请求都会去创建一个新的实例, 获取 Bean (ApplicationContontext.getBean) 以及装配 Bean 对象(@Autowired) 都是新的对象实例


和单例模式恰好相反, 原型作用域通常用于有状态的 Bean 对象. 比如上面未解决的问题, 我们期望的是 A 去修改以后获取的是 A 修改以后的值, 而 B 去读取的时候, 读取的是原来的值, 这样 A B 都取操作容器里的 Bean 对象是就不会互相干扰了. 这样就需要在注入 Bean 的时候修改它的作用域为原型作用域


f8f93618fec4c1f2fa45200056da2094.png


修改作用域使用的是 @Scope 注解, Spring 在注解里面提供的 ConfigurableBeanFactory 方法去设置作用域.

9b292574f429ca5988bdc4b0a24ad382.png


可以看到, 重新设置 Bean 对象的作用域以后, 当 A B 去获取 Bean 对象时, 都是一份新的实例, 各自使用自己的, 互不干扰.


3. 请求作用域(request)


对于请求作用域, 很容易联想到 HTTP 请求中的 Request, 这和 HTTP 请求也是有一定关系的

定义 : 每次 HTTP 请求都会创建新的 Bean 实例, 和原型作用域类似.


通常用在一次 HTTP 请求和响应都是共享一份 Bean 的, 这样一说还有点抽象. 具体而言就是和一次 HTTP 请求的声明周期是有关的. 当客户端发送一个 HTTP 请求时, Spring 会创建一个上下文对象也就是 Request Context , 在这个上下文中所有的请求作用域的 Bean 实例都会存活, 只要请求结束或者超时, 这些 Bean 就会销毁.


所以通常情况下, 请求作用域适用于 Web 应用程序的处理流中需要共享的数据的情况, 当请求一结束, 数据就消失了.


需要注意的是, 这个请求作用域在 Spring 中不存在, 只限定于 Spring MVC 中使用 !


4. 会话作用域(session)


对于 session 也不陌生, 在 Web 项目中, 是非常常见的


定义 : 每次 HTTP 会话(session) 都会创建一个新的 Bean 实例, 类似于原型模式.


通常适用于需要与用户会话相关的场景, 具体来说, 比如当用户登陆系统后, 系统会为它建立一个会话,在这个会话期间, 所有会话作用域下的 Bean 实例都会存活, 当会话结束或超时, 这些 Bean 对象就会被销毁. 又例如一个在线购物程序, 用户可能需要在多个页面之间添加商品到购物车中并进行结算, 同时还需要信息登录验证等操作, 这时候可以将用户的购物车、登陆信息等状态存储在会话作用域的 Bean实例中, 以便在不同的页面之间共享和保持这些状态和数据


需要注意的时, 会话作用域同样是只限定于 Spring MVC 中使用 !


5. 全局作用域(application)


定义 : 在整个 Web 应用程序中, 只创建一个 Bean 实例, 并且该实例会一直存在于应用程序的整个生命周期, 也就是说, 在初始加载应用程序时, Spring 容器会创建该 Bean 实例, 并将其放入容器中供所有的请求使用, 直至程序结束.

通常用于 Web 项目的上下文信息, 比如记录一个应用的共享信息. 由于全局作用域在 Web 应用中只创建一个 Bean 实例, 并且供其他所有请求使用, 也可以存一些 全局的配置信息和状态(例如系统日志记录器、缓存管理), 共享的资源和对象(比如数据库连接池、线程池等), 提供全局服务和功能(邮件发送,、短信通知、定时任务调度服务)


需要注意的是, 全局作用域同样只现定于 Spring MVC 中使用 !


6. WebSocket作用域(websocket)


定义 : 在 WebSocket 连接的整个生命周期中共享同一个 Bean 实例. WebSocket 的每次会话中, 保存了一个 Map 结构的头信息, 将用来包裹客户端的消息头. 在第一次初始化后, 直到 WebSocket 结束都是同一个 Bean


由于 WebSocket 会话作用域的 Bean 是在每个 WebSocket 连接时创建的, 并且会一直保留到连接关闭. 这种作用域的 Bean 实例通常用于处理跨越多个 WebSocket 消息的数据或状态, 比如聊天室的用户信息、在线游戏中房间状态等

需要注意的是, WebSocket 作用域只限定于 Spring WebSocket 中使用 !


四. 设置 Bean 的作用域



使用 @Scope 注解则可以声明作用域.

  • 直接设置值

image.png


  • 使用枚举设置

Spring 中提供了 ConfigurableBeanFactory 里面列举了一些作用域(由于我这是 Spring 项目, 没有添加 Spring MVC 因此没有其他的作用域可以选择)


c1034413c3d4a07c4e6312b55410f89c.png313e273c101fac63ced2d578f6cfd410.png


五. Bean 的生命周期



1. Spring 的执行流程


在去了解 Bean 的声明周期之前, 需要先去了解 Spring 的执行流程

Spring 的执行流程(Bean 的执行流程) - > 启动 Spring 容器 - >配置文件的加载 - > 实例化 Bean ( 分配内存空间 ) - > 将 Bean 注入到 Spring 容器中( 存操作 ) - > 装配 Bean 对象( 取操作, 将 Bean 装配到指定类 )


2. 生命周期


  • 实例化 Bean ( 为 Bean 分配内存空间 , 实例化 ≠ 初始化 )
  • 设置属性 ( Bean 的注入 )

Bean 的初始化

  • 实现各种 Aware 通知, 例如 : BeanNameAware、BeanFactoryAware、ApplicationContextAware 的接⼝⽅法
  • 执⾏初始化前置⽅法, 如 : BeanPostProcessor
  • 执⾏初始化⽅法,依赖注⼊操作之后被执⾏( 设置了指定方法才执行, 不设置不执行 )
  • 注解方式 : @PostConstruct
  • xml 方式 : 指定的 init-method ⽅法
  • 执⾏ BeanPostProcessor 初始化后置⽅法
  • 使用 Bean
  • 销毁 Bean , 例如 : @PreDestroy、DisposableBean 接口方法 、destroy-method.

下面, 通过一组代码实例来演示 Bean 的生命周期过程


实例化 : 通过 BeanDefinition 对象创建 Bean 实例, Spring 根据配置文件或者注解的方式获取 Bean 的元数据信息, 并将这些信息封装为 BeanDefinition 对象. 当 Spring 容器启动时,会根据这些 BeanDefinition 对象创建相应的 Bean 实例

  • 注解方式


31c83e7dc5e9924185cb5e424fc674c5.png


  • 配置文件方式

41542c62220f4891b60d05cc609fc6db.png


此处并非是去设置属性, 在这是申明是通过配置文件的形式来将 Bean 对象实例, 得先创建这个实例才能存到 Spring 中


  1. 设置属性: 注册 Bean 实例的方式主要有两种
  • 在配置文件中使用 标签进行声明

1b8c468058f72a10bf6ee5142dc55120.png


这里的属性设置, 是指将 Bean 实例化后, 将这些对象注入到容器之中的过程

  • 使用注解 @Bean 声明 Bean

此处由于没有使用 @Bean 注解, 因此没有去实现, 可以自己试试


3. Bean 的初始化

  1. 实现各种通知 :

429f0583e1908e17dae6c16397af2e02.png


去实现 BeanNameAware 接口, 并重写setBeanName 方法, 即可重写执行通知

  1. 执行初始化前置方法

6196e22ab2117cb99f483aa6843bc5a6.png


实现 InitalizingBean 接口, 重写 afterPropertiesSet 方法, 即可重写前置初始化方法

  1. 执行初始化方法
  • 注解的方式

43b358b5106d9d3c1de054e95271bb3e.png

使用 @PostConstruct 注解可以完成初始化

- XML 方式


f8c073652f38e8aaa0e0bcbb8e5fbc4a.pnge2758f5217a56510167662fd170a5c2e.png


  1. 执行后置化方法

image.png


实现 BeanPostProcessor 接口, 重写里面的 postProcessBeforeInitialization 方法, 这是自定义的前置方法

重写 postProcessAfterInitialization 这是自定义的后置方法, 需要注意的是, 如果需要启动这个自定义的后置初始化方法, 前提是容器中有两个及以上的 Bean 对象, 从而 Spring 在管理的时候才会进行区分

比如此时, 我的整个 Spring 容器中只有我此刻注入的 PeopleController2 这个类


703d8f6a83a67916b0f2bdab2bf999d1.png


此刻去执行会发现, 没有输出后置初始化中的打印信息, 就是因为我的容器中此时只有一个 Bean 对象可以管理, 当我们去添加一个 Bean 对象时, 此处通过注解添加一个 ServiceStudent 到容器中, 再次去执行后才会启动了后置初始化方法

bc2db8b1e0e79ebe473d7032ee1f5644.png


这里由于我在后置初始化都得方法中并没有去设置执行前后逻辑, 因此它是连在一起打印的, 在重写这个接口的时候, 可以根据自己的逻辑实现前置初始化和后置初始化, 可以不用去实现 InitalizingBean 接口.


  1. 使用 Bean

dc7d9487649fabb0dc712172d158de50.png


创建 Spring 容器并获得 Bean 对象调用其中的 fun 方法验证是否注入成功

  1. **销毁 Bean **
  • 注解的方式


fcad18ce4555f30c3eb36d3e678d3154.png


  • XML 方式


ff5fea2bc0f851cea689bde448cc9389.png


  1. 演示代码


image.png


PS1 : 可以看到的不一样的是, 在创建 Spring 容器的时候使用的是 ClassPathXmlApplicationContext , 此处是因为 ApplicationContext 来创建的上下文对象没有 destroy() 方法, 此处为了演示调用这个方法 因此选择的是 ClassPathXmlApplicationContext.

PS2 : 上面由于Bean 的声明周期问题, 如果注入多个会初始化多次, 因此此处只注入了申明的 PeopleCenter2 这个类, 只有一个 Bean 对象在容器中, 从而没有启动后置初始化方法( BeanPostProcessor 中重写的前置和后置方法 )


总结 : 从上面的代码演示中可以看出整个 Bean 的生命周期, 了解好 Bean 对象的声明周期, 可以加深对 Spring 中 Bean 对象的使用, 从而写出更符合要求的代码!

相关文章
|
13天前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
1月前
|
XML Java 数据格式
Spring从入门到入土(bean的一些子标签及注解的使用)
本文详细介绍了Spring框架中Bean的创建和使用,包括使用XML配置文件中的标签和注解来创建和管理Bean,以及如何通过构造器、Setter方法和属性注入来配置Bean。
67 9
Spring从入门到入土(bean的一些子标签及注解的使用)
|
2月前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
208 24
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
1月前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
2月前
|
XML Java 数据格式
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
Spring 第二节内容补充 关于Bean配置的更多内容和细节 万字详解!
212 18
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
|
2月前
|
XML Java 数据格式
spring复习02,xml配置管理bean
详细讲解了Spring框架中基于XML配置文件管理bean的各种方式,包括获取bean、依赖注入、特殊值处理、属性赋值、集合类型处理、p命名空间、bean作用域及生命周期和自动装配。
spring复习02,xml配置管理bean
|
1月前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细解析Spring Bean的生命周期及其核心概念,并深入源码分析。Spring Bean是Spring框架的核心,由容器管理其生命周期。从实例化到销毁,共经历十个阶段,包括属性赋值、接口回调、初始化及销毁等。通过剖析`BeanFactory`、`ApplicationContext`等关键接口与类,帮助你深入了解Spring Bean的管理机制。希望本文能助你更好地掌握Spring Bean生命周期。
77 1
|
1月前
|
Java Spring
获取spring工厂中bean对象的两种方式
获取spring工厂中bean对象的两种方式
39 1
|
1月前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细介绍了Spring框架中的核心概念——Spring Bean的生命周期,包括实例化、属性赋值、接口回调、初始化、使用及销毁等10个阶段,并深入剖析了相关源码,如`BeanFactory`、`DefaultListableBeanFactory`和`BeanPostProcessor`等关键类与接口。通过理解这些核心组件,读者可以更好地掌握Spring Bean的管理和控制机制。
84 1
|
2月前
|
XML Java 数据格式
spring复习03,注解配置管理bean
Spring框架中使用注解配置管理bean的方法,包括常用注解的标识组件、扫描组件、基于注解的自动装配以及使用注解后的注意事项,并提供了一个基于注解自动装配的完整示例。
spring复习03,注解配置管理bean