[Spring]Bean

scope

Spring 中 Bean 的作用域通常有下面几种:

  • singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。

  • prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。

  • request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。

  • session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。

  • application/global-session (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。

  • websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。

Spring bean的作用域有哪些?

Spring框架中的Bean作用域(Scope)定义了Bean的生命周期和可见性。不同的作用域影响着Spring容器如何管理这些Bean的实例,包括它们如何被创建、如何被销毁以及它们是否可以被多个用户共享。

Spring支持几种不同的作用域,以满足不同的应用场景需求。以下是一些主要的Bean作用域:

Singleton(单例):在整个应用程序中只存在一个 Bean 实例。默认作用域,Spring 容器中只会创建一个 Bean 实例,并在容器的整个生命周期中共享该实例。
Prototype(原型):每次请求时都会创建一个新的 Bean 实例。次从容器中获取该 Bean 时都会创建一个新实例,适用于状态非常瞬时的 Bean。
Request(请求):每个 HTTP 请求都会创建一个新的 Bean 实例。仅在 Spring Web 应用程序中有效,每个 HTTP 请求都会创建一个新的 Bean 实例,适用于 Web 应用中需求局部性的 Bean。
Session(会话):Session 范围内只会创建一个 Bean 实例。该 Bean 实例在用户会话范围内共享,仅在 Spring Web 应用程序中有效,适用于与用户会话相关的 Bean。
Application:当前 ServletContext 中只存在一个 Bean 实例。仅在 Spring Web 应用程序中有效,该 Bean 实例在整个 ServletContext 范围内共享,适用于应用程序范围内共享的 Bean。
WebSocket(Web套接字):在 WebSocket 范围内只存在一个 Bean 实例。仅在支持 WebSocket 的应用程序中有效,该 Bean 实例在 WebSocket 会话范围内共享,适用于 WebSocket 会话范围内共享的 Bean。
Custom scopes(自定义作用域):Spring 允许开发者定义自定义的作用域,通过实现 Scope 接口来创建新的 Bean 作用域。
在Spring配置文件中,可以通过标签的scope属性来指定Bean的作用域。例如:

在Spring Boot或基于Java的配置中,可以通过@Scope注解来指定Bean的作用域。例如: ``` @Bean @Scope("prototype") public MyBeanClass myBean() { return new MyBeanClass(); } ```

request 和 session 的区别

session是指用户从登录到登出的时间段
request是指每次请求

如何配置 bean 的作用域呢?

  • xml 方式:
<bean id="..." class="..." scope="singleton"></bean>
  • 注解方式:
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
    return new Person();
}

Bean 是线程安全的吗?

Spring 框架中的 Bean 是否线程安全,取决于其作用域和状态。

我们这里以最常用的两种作用域 prototype 和 singleton 为例介绍。几乎所有场景的 Bean 作用域都是使用默认的 singleton ,重点关注 singleton 作用域即可。

prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题

singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。

不过,大部分 Bean 实际都是无状态(没有定义可变的成员变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。

对于有状态单例 Bean 的线程安全问题,常见的有两种解决办法:

  • 在 Bean 中尽量避免定义可变的成员变量。

  • 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。

Bean 的生命周期

image

  • 创建 Bean 的实例:Bean 容器首先会找到配置文件中的 Bean 定义,然后使用 Java 反射 API 来创建 Bean 的实例。
  • Bean 属性赋值/填充:为 Bean 设置相关属性和依赖,例如@Autowired 等注解注入的对象、@Value 注入的值、setter方法或构造函数注入依赖和值、@Resource注入的各种资源。
  • Bean 初始化:
    如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的名字。
    如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader对象的实例。
    如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory()方法,传入 BeanFactory对象的实例。
    与上面的类似,如果实现了其他 *.Aware接口,就调用相应的方法。
    如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法
    如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法。
    如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
    如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法。
  • 销毁 Bean:销毁并不是说要立马把 Bean 给销毁掉,而是把 Bean 的销毁方法先记录下来,将来需要销毁 Bean 或者销毁容器的时候,就调用这些方法去释放 Bean 所持有的资源。
    如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
    如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的 Bean 销毁方法。或者,也可以直接通过@PreDestroy 注解标记 Bean 销毁之前执行的方法。

AbstractAutowireCapableBeanFactory 的 doCreateBean() 方法中能看到依次执行了这 4 个阶段:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    throws BeanCreationException {

    // 1. 创建 Bean 的实例
    BeanWrapper instanceWrapper = null;
    if (instanceWrapper == null) {
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }

    Object exposedObject = bean;
    try {
        // 2. Bean 属性赋值/填充
        populateBean(beanName, mbd, instanceWrapper);
        // 3. Bean 初始化
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }

    // 4. 销毁 Bean-注册回调接口
    try {
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }

    return exposedObject;
}

Aware 接口能让 Bean 能拿到 Spring 容器资源。

Spring 中提供的 Aware 接口主要有:

BeanNameAware:注入当前 bean 对应 beanName;
BeanClassLoaderAware:注入加载当前 bean 的 ClassLoader;
BeanFactoryAware:注入当前 BeanFactory 容器的引用。
BeanPostProcessor 接口是 Spring 为修改 Bean 提供的强大扩展点。

public interface BeanPostProcessor {

	// 初始化前置处理
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	// 初始化后置处理
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

}

postProcessBeforeInitialization:Bean 实例化、属性注入完成后,InitializingBean#afterPropertiesSet方法以及自定义的 init-method 方法之前执行;
postProcessAfterInitialization:类似于上面,不过是在 InitializingBean#afterPropertiesSet方法以及自定义的 init-method 方法之后执行。
InitializingBean 和 init-method 是 Spring 为 Bean 初始化提供的扩展点。

public interface InitializingBean {
 // 初始化逻辑
	void afterPropertiesSet() throws Exception;
}

指定 init-method 方法,指定初始化方法:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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">

    <bean id="demo" class="com.chaycao.Demo" init-method="init()"/>

</beans>

如何记忆呢?

整体上可以简单分为四步:实例化 —> 属性赋值 —> 初始化 —> 销毁。
初始化这一步涉及到的步骤比较多,包含 Aware 接口的依赖注入、BeanPostProcessor 在初始化前后的处理以及 InitializingBean 和 init-method 的初始化操作。
销毁这一步会注册相关销毁回调接口,最后通过DisposableBean 和 destory-method 进行销毁。
最后,再分享一张清晰的图解(图源:如何记忆 Spring Bean 的生命周期)。

生命周期#

首先理解scope:

  1. Singleton(单例) 在整个应用中,只创建bean的一个实例
  2. Propotype(原型) 每次注入或者通过Spring应用上下文获取的时候,都会创建一个新
    的bean实例。
  3. Session(会话) 在Web应用中,为每个会话创建一个bean实例。
  4. Request(请求) 在Web应用中,为每个请求创建一个bean实例。

他们是什么时候创建的:
1一个单例的bean,而且lazy-init属性为false(默认),在Application Context创建的时候构造
2一个单例的bean,lazy-init属性设置为true,那么,它在第一次需要的时候被构造.
3 其他scope的bean,都是在第一次需要使用的时候创建

他们是什么时候销毁的:
1 单例的bean始终 存在与application context中, 只有当 application 终结的时候,才会销毁
2 和其他scope相比,Spring并没有管理prototype实例完整的生命周期,在实例化,配置,组装对象交给应用后,spring不再管理.只要bean本身不持有对另一个资源(如数据库连接或会话对象)的引用,只要删除了对该对象的所有引用或对象超出范围,就会立即收集垃圾.
3 Request: 每次客户端请求都会创建一个新的bean实例,一旦这个请求结束,实例就会离开scope,被垃圾回收.
4 Session: 如果用户结束了他的会话,那么这个bean实例会被GC.

作者:Esofar

出处:https://www.cnblogs.com/DCFV/p/18342866

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Duancf  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)
· 全程使用 AI 从 0 到 1 写了个小工具
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示