乐趣区

关于后端:Spring-Bean-的作用域Bean-Scope

前言

大家好,我是 god23bin,明天咱们来聊一聊 Spring 框架中的 Bean 作用域(Scope)。

什么是 Bean 的作用域?

咱们在以 XML 作为配置元数据的状况下,进行 Bean 的定义,是这样的:

<bean id="vehicle" class="cn.god23bin.demo.domain.model.Vehicle">
    <!-- 协作者写在这里... -->
</bean>

咱们写了一个 Bean 定义(Bean Definition),就是用于创立所定义的类的实例的。

一个 Bean 定义,咱们能够类比一个类的定义,你定义了一个类,你能够依据这个类创立出许多实例对象。同理,Bean 定义也是,也是能够依据这个定义创立许多实例对象的,只不过这里是 Spring 帮咱们创立,而不是咱们手动 new 这些 Bean 对象实例,咱们能够了解为 Spring IoC 容器中的对象。

在写 Bean 定义的过程中,咱们能够管制各种 Bean 的依赖项和相应的值,将这些依赖项和值注入到 Bean 定义所创立的对象中。同理,这个过程也能够管制 Bean 定义创立的对象的 Scope(作用域)。Bean 的作用域定义了在容器中创立的 Bean 实例的生命周期以及在应用程序中的可见性。

6 种 Bean 的作用域

Spring 反对 6 种 Bean 的作用域,其中有 4 种是在 Web 利用下能力感知到的,如下表所示:

Scope 阐明
singleton (默认状况下)每个 Spring IoC 容器将单个 Bean 定义的 Scope 指定为单个对象实例。
prototype 将单个 Bean 定义的 Scope 扩充到任意数量的对象实例。
request 将单个 Bean 定义的 Scope 扩充到单个 HTTP 申请的生命周期。也就是说,每个 HTTP 申请都有本人的 Bean 实例,该实例是在单个 Bean 定义的根底上创立的。只在 Web 感知的 Spring ApplicationContext 的上下文中无效。
session 将单个 Bean 定义的 Scope 扩充到一个 HTTP 会话的生命周期。只在 Web 感知的 Spring ApplicationContext 的上下文中无效。
application 将单个 Bean 定义的 Scope 扩充到 ServletContext 的生命周期中。只在 Web 感知的 Spring ApplicationContext 的上下文中无效。
websocket 将单个 Bean 定义的 Scope 扩充到 WebSocket 的生命周期。只在 Web 感知的 Spring ApplicationContext 的上下文中无效。

1. Singleton Scope

singleton 作用域的 Bean,在 Spring IoC 容器中就有且仅有一个该类型的实例对象,也就是单例的。

默认状况下,咱们在写 Bean 定义的时候,不指定作用域的话,那么这个 Bean 对象就是单例的。

<!-- 不写 Bean 的作用域,默认作用域为单例 -->
<bean id="accountService" class="cn.god23bin.demo.service.DefaultAccountService"/>

<!-- 写上作用域,这里是冗余的写法,应用 scope 属性 -->
<bean id="accountService" class="cn.god23bin.demo.service.DefaultAccountService" scope="singleton"/>

这个单例对象是存储在一个缓存区域中的,在后续的申请或者援用中,Spring 就会返回这个缓存的对象。

实际上,Spring 中的单例的 Bean 对象是 不同于 Gang of Four 设计模式中的所定义的单例模式的。

设计模式(Design Pattern)是前辈们对代码开发教训的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来进步代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。

1995 年,GoF(Gang of Four,四人组 / 四人帮)单干出版了《设计模式:可复用面向对象软件的根底》一书,共收录了 23 种设计模式,从此建立了软件设计模式畛域的里程碑,人称「GoF 设计模式」。

设计模式中的单例模式是硬编码的形式,以便每个 ClassLoader 只创立一个特定类的一个实例。

Spring 单例的范畴是指每个 IoC 容器的,不同 IoC 容器保护本人的 Bean 的单例对象

2. Prototype Scope

Bean 的作用域是 prototype,中文意思是原型,实际上这里是省略了 non-singleton,这个作用域的全称是 non-singleton prototype scope,即「非单例原型的作用域」。

顾名思义,这个作用域下的 Bean 不是单例的,意思就是说 Bean 是多例的,每一次的申请或者援用,都会创立一个新的 Bean 对象。

当然这里的 申请或者援用 的意思是指,非单例原型的 Bean 被注入到另一个 Bean 中的时候(Bean 作为属性被援用),或者咱们间接通过容器的 getBean() 办法调用来申请它的时候,就会创立一个新的对象。

在 XML 中指定了这个 Bean 的作用域为 prototype

<bean id="accountService" class="cn.god23bin.demo.service.DefaultAccountService" scope="prototype"/>

在 prototype 作用域下的 Bean,Spring 是不会负责该 Bean 的销毁周期中回调的办法的,如果该 Bean 领有一些重要的资源,想在该 Bean 对象销毁时开释这些资源,那么须要自定义 BeanPostProcessor(Bean 的后置处理器),它持有咱们须要清理的 Bean 的援用。

在某些方面来说,在 prototype 作用域下的 Bean 的作用是代替 new 操作的。

其余 4 种作用域

requestsessionapplicationwebsocket scope 只有在应用 Web 感知的 Spring ApplicationContext 实现(如 XmlWebApplicationContext)时才可用。

简而言之,个别是在 Web 利用下,借助 Spring 的 Web 模块,就能应用这 4 种作用域。

如果你将这些 scope 与惯例的 Spring IoC 容器(如 ClassPathXmlApplicationContext)一起应用,就会抛出一个 IllegalStateException,提醒有未知的 Bean scope。

3. Request Scope

<bean id="loginController" class="cn.god23bin.demo.controller.LoginController" scope="request"/>

Spring IoC 容器为每一个 HTTP 申请应用 loginController Bean 定义来创立 LoginController Bean 的新实例,从而实现这种 request 作用域。

你能够得心应手地扭转被创立的实例的外部状态,因为从同一个 loginController Bean 定义中创立的其余实例不会看到这些状态的变动。它们是针对单个申请的,当申请实现解决时,该申请所波及的 Bean 会被抛弃。

4. Session Scope

<bean id="userPreferences" class="cn.god23bin.demo.UserPreferences" scope="session"/>

Spring IoC 容器通过应用 userPreferences Bean 定义,在单个 HTTP Session 的生命周期内创立一个新的 UserPreferences Bean 实例。

request scope 的 Bean 一样,你能够得心应手地扭转被创立的实例的外部状态,要晓得其余 HTTP Session 实例也在应用从同一个 userPreferences Bean 定义中创立的实例,它们不会看到这些状态的变动,因为它们是特定于单个 HTTP Session。当 HTTP Session 最终被抛弃时,作用于该特定 HTTP Session 的 Bean 也被抛弃。

5. Application Scope

<bean id="appPreferences" class="cn.god23bin.demo.AppPreferences" scope="application"/>

Spring 容器通过为整个 Web 应用程序应用一次 appPreferences Bean 定义来创立 AppPreferences Bean 的新实例。

这有点相似于 Spring 的 singleton Bean,但在两个重要方面有所不同。

它是每个 ServletContext 的单例,而不是每个 Spring ApplicationContext(在任何给定的 Web 应用程序中可能有几个)。

6. WebSocket Scope

这里就波及到 WebSocket 了,目前先不探讨。前面再来填坑~

不同作用域的 Bean 之间的依赖关系

这里探讨的,个别就是 单例作用域的 Bean 原型作用域的 Bean 之间的依赖关系。

当初举个例子,假如有两个 Java 类交给了 Spring IoC 容器治理,别离是 SingletonBean 类和 PrototypeBean 类。

其中 SingletonBean 是单例作用域的 Bean,而 PrototypeBean 是原型作用域的 Bean。

那么当:

  1. SingletonBean 的依赖项是 PrototypeBean 时,PrototypeBean 对象只会初始化一次并注入到 SingletonBean,这样 PrototypeBean 就起不到原型作用域的成果。
  2. PrototypeBean 的依赖项是 SingletonBean 时,每次 PrototypeBean 对象都会创立,这些对象都依赖于一个单例对象,此时没任何问题。

办法注入

Spring 提供了一种称为 办法注入 (Method Injection)的机制来 解决原型作用域的 Bean 在被注入到单例作用域的 Bean 中时只创立一个实例的问题。

办法注入容许每次调用办法时都获取一个 新的原型作用域的 Bean 实例

办法注入是通过在 SingletonBean 中定义一个返回 PrototypeBean 实例的办法来实现的。这样,在每次须要应用 PrototypeBean 的中央,能够通过调用该办法获取一个新的实例。

以下是应用办法注入解决 Prototype Bean 作用域的示例:

public abstract class SingletonBean {public abstract PrototypeBean getPrototypeBean();

    public void doSomething() {PrototypeBean prototypeBean = getPrototypeBean();
        // 应用 Prototype Bean 进行操作
    }
}

public class PrototypeBean {// Prototype Bean 的定义}

在上述示例中,SingletonBean 是一个抽象类,其中申明了一个形象办法 getPrototypeBean(),该办法返回一个 PrototypeBean 实例。在 doSomething() 办法中,通过调用 getPrototypeBean() 办法获取一个新的 PrototypeBean 实例,以便在每次调用 doSomething() 时应用不同的实例。

而后,能够通过具体的子类来实现 SingletonBean,并实现 getPrototypeBean() 办法以返回相应的 PrototypeBean 实例。

通过办法注入,每次调用 doSomething() 办法时都会获取一个新的 PrototypeBean 实例,从而解决了在 Singleton Bean 中注入 Prototype Bean 时只创立一个实例的问题。

须要留神的是,办法注入须要在配置文件或应用注解时进行非凡的配置,具体的配置形式根本如下。

1. XML 配置形式

当然,下面举例是一个抽象类,不是抽象类也是能够的,比方:

public class SingletonBean {
    // 办法注入,Spring 会帮咱们返回这个对象,这里写成 null 即可
    public PrototypeBean getPrototypeBean() {return null;}

    public void doSomething() {PrototypeBean prototypeBean = getPrototypeBean();
        // 应用 Prototype Bean 进行操作
    }
}

public class PrototypeBean {// Prototype Bean 的定义}

接着,独自下面是没有实现不了办法注入的,还须要联合配置元数据,当初在 XML 配置文件中应用 <lookup-method /> 标签来实现办法注入。

<bean id="singletonBean" class="cn.god23bin.demo.domain.model.SingletonBean">
    <lookup-method name="getPrototypeBean" bean="prototypeBean"/>
</bean>

<bean id="prototypeBean" class="cn.god23bin.demo.domain.model.PrototypeBean" scope="prototype"/>

下面的配置示例中,singletonBean 是一个单例 Bean,通过 <lookup-method /> 标签指定了一个名为 getPrototypeBean 的办法,并援用了一个原型 Bean prototypeBean

在运行时,每次调用 getPrototypeBean 办法时,都会返回一个新的 prototypeBean 实例。

2. 注解配置形式

应用 @Lookup 注解来实现办法注入。

@Component
public class SingletonBean {

    private PrototypeBean prototypeBean;

    @Lookup
    public PrototypeBean getPrototypeBean() {return null; // 实际上会由 Spring 生成具体实现}

    // 其余代码...
}

@Component
@Scope("prototype")
public class PrototypeBean {// 具体的原型 Bean 实现}

在下面的示例中,SingletonBean 应用了 @Lookup 注解标记了一个名为 getPrototypeBean 的办法。在运行时,Spring 会为这个办法生成具体的实现,以实现办法注入。

总结

简略总结下:

Bean 的作用域在 Bean 定义的时候能够进行指定,默认是单例的,多例的 Bean 就是所谓的原型作用域。

一共 6 种作用域须要相熟,其中 4 种是在具备 Web 感知能力的 Spring IoC(利用上下文)下才有的作用域。

对于单例 Bean 依赖原型 Bean 的问题,能够通过办法注入解决,两种写法实现办法注入,一种是 XML,另一种是注解的形式。

最初的最初

心愿各位屏幕前的 靓仔靓女们 给个三连!你轻轻地点了个赞,那将在我的心里世界削减一颗亮堂而夺目的星!

咱们下期再见!

退出移动版