详述 Spring 中 Bean 的作用域、事务的隔离级别以及传播行为

Bean 作用域

Spring IOC 容器创建一个 Bean 实例时,可以为 Bean 指定实例的作用域,作用域包括:

  • singleton(单例模式)
  • prototype(原型模式)
  • request(HTTP 请求)
  • session(会话)
  • globalSession(全局会话)

其类别及对应的说明分别为:

类别说明
singleton在 Spring IOC 容器中仅存在一个 Bean 实例,Bean 以单例方式存在,默认值
prototype每次从容器中调用 Bean 的时候,都返回一个新的实例,即每次调用getBean()时,相当于执行new XxxBean()
request每次 HTTP 请求都会创建一个新的 Bean,该作用域仅适用于WebApplicationContext环境
session同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例,该作用域仅适用于WebApplicationContext环境
globalSession一般用于 Portlet 应用环境,该作用域仅适用于WebApplicationContext环境

通常,有两种声明 Bean 作用域的方式,分别为 XML 配置声明和注解声明。

首先,以 XML 声明的方式,详细讲述以上五种作用域。

XML 声明

singleton

当一个 Bean 作用域为singleton的时候,那么 Spring IOC 容器中只会存在一个共享的 Bean 实例,并且所有对 Bean 的请求,只要id与该 Bean 定义相匹配,则只会返回 Bean 的同一实例。singleton是单例类型,就是在创建容器同时自动创建了一个 Bean 的对象,不管你是否使用,它都存在了,每次获取到的对象都是同一个对象。注意,singleton作用域是 Spring 中的缺省作用域。要在 XML 中将 Bean 定义成singleton,可以这样配置:

  • <bean id="chatService" class="net.csdn.service.ChatService" scope="singleton">

prototype

当一个 Bean 的作用域为prototype的时候,表示一个 Bean 定义对应多个对象实例。prototype作用域的 Bean 会导致在每次对该 Bean 请求(将其注入到另一个 Bean 中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的 Bean 实例。prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取 Bean 的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的 Bean 应该使用prototype作用域,而对无状态的 Bean 则应该使用singleton作用域。在 XML 中将 Bean 定义成prototype,可以这样配置:

  • <bean id="account" class="net.csdn.account.DefaultAccount" scope="prototype"/>

或者

  • <bean id="account" class="net.csdn.account.DefaultAccount" singleton="false"/>

request

当一个 Bean 的作用域为request的时候,表示在一次 HTTP 请求中,一个 Bean 定义对应一个实例,即每个 HTTP 请求都会有各自的 Bean 实例,它们依据某个 Bean 定义创建而成。该作用域仅在基于 Web 的 Spring ApplicationContext 情形下有效。考虑下面 Bean 定义:

  • <bean id="loginAction" class="net.csdn.account.LoginAction" scope="request"/>

针对每次 HTTP 请求,Spring 容器会根据loginAction的定义创建一个全新的LoginAction Bean 实例,且该LoginAction Bean 实例仅在当前 HTTP Request 内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据LoginAction Bean 定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的 Bean 实例将被销毁。

session

当一个 Bean 的作用域为 session的时候,表示在一个 HTTP Session 中,一个 Bean 定义对应一个实例。该作用域仅在基于 Web 的 Spring ApplicationContext 情形下有效。考虑下面 Bean 定义:

  • <bean id="userPreferences" class="net.csdn.UserPreferences" scope="session"/>

针对某个 HTTP Session,Spring 容器会根据userPreferences Bean 定义创建一个全新的UserPreferences Bean 实例,且该 UserPreferences Bean 仅在当前 HTTP Session 内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的 HTTP Session 中根据userPreferences创建的实例,将不会看到这些特定于某个 HTTP Session 的状态变化。当 HTTP Session 最终被废弃的时候,在该 HTTP Session 作用域内的 Bean 也会被废弃掉。

globalSession

当一个 Bean 的作用域为globalSession的时候,表示在一个全局的 HTTP Session 中,一个 Bean 定义对应一个实例。典型情况下,仅在使用 Portlet Context 的时候有效。该作用域仅在基于 Web 的 Spring ApplicationContext 情形下有效。考虑下面 Bean 定义:

  • <bean id="user" class="net.csdn.Preferences "scope="globalSession"/>

globalSession作用域类似于标准的 HTTP Session 作用域,不过仅仅在基于 Portlet 的 Web 应用中才有意义。Portlet 规范定义了全局 Session 的概念,它被所有构成某个 Portlet Web 应用的各种不同的 Portlet 所共享。在globalSession作用域中定义的 Bean 被限定于全局 Portlet Session 的生命周期范围内。

注解声明

在了解完基于 XML 的声明之后,我们以prototype为例,再来看看注解声明以及其使用方式。

@Data
@NoArgsConstructor
@AllArgsConstructor
@Component
@Scope("prototype")
public class Coder {
    private String name;
    private int age;
}

如上述代码所示,我们定义了一个Coder类,之所以没有写setter/getter方法,是因为使用了lombok注解。

@RestController
public class HelloCoderController {
 
    @Resource
    private Coder coder;
 
    @Resource
    private Coder coder2;
 
    @RequestMapping("/hello")
    public String hello(){
        coder.setName("CG");
        coder.setAge(18);
        System.out.println(coder);
        return coder.toString();
    }
 
    @RequestMapping("/hello2")
    public String hello2(){
        System.out.println(coder2);
        return coder2.toString();
    }
}

如上述代码所示,虽然我们注入的都是Coder类,但是因为我们对其使用了@Scope("prototype")注解,所以实际上注入的是两个不同的Coder实例。至于具体输出结果,大家可以自行测试。

接下来,我们了解一下事务。事务是逻辑处理原子性的保证手段,通过使用事务控制,可以极大的避免出现逻辑处理失败导致的脏数据等问题。事务最重要的两个特性,就是事务的数据隔离级别和传播行为,其中

  • 隔离级别定义的是事务在数据库读写方面的控制范围;
  • 传播行为定义的是事务的控制范围。

下面,我们就一起来了解 Spring 中事务的数据隔离级别和传播行为。

事务隔离级别

Spring 在TransactionDefinition接口中定义了事务的隔离级别:

    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;

如上述代码所示,Spring 一共定义了五个隔离级别,其具体含义分别为:

  • ISOLATION_DEFAULT,这是PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别,另外四个与 JDBC 的隔离级别相对应。
  • ISOLATION_READ_UNCOMMITTED,这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。
  • ISOLATION_READ_COMMITTED,保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
  • ISOLATION_REPEATABLE_READ,这种事务隔离级别可以防止脏读和不可重复读,但是可能出现幻读。
  • ISOLATION_SERIALIZABLE,这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。

事务传播行为

Spring 在TransactionDefinition接口中定义了事务传播行为:

    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;

如上述代码所示,Spring 一共定义了七个传播行为,其具体含义分别为:

  • PROPAGATION_REQUIRED,如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。Spring 的默认传播行为。
  • PROPAGATION_SUPPORTS,如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。
  • PROPAGATION_MANDATORY,如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
  • PROPAGATION_REQUIRES_NEW,总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。
  • PROPAGATION_NOT_SUPPORTED,总是非事务地执行,并挂起任何存在的事务。
  • PROPAGATION_NEVER,总是非事务地执行,如果存在一个活动事务,则抛出异常
  • PROPAGATION_NESTED,如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。

对于 Spring 的事务传播行为,最有可能让大家产生疑惑的可能就是PROPAGATION_NESTED了,即嵌套事务。嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执行结束,父事务继续执行。重点就在于save point,我们一起看几个问题就明白了:

  • 如果子事务回滚,会发生什么

父事务会回滚到进入子事务前建立的save point,然后尝试其他的事务或者其他的业务逻辑,父事务之前的操作不会受到影响,更不会自动回滚。

  • 如果父事务回滚,会发生什么

父事务回滚,子事务也会跟着回滚。因为父事务结束之前,子事务是不会提交的,我们说子事务是父事务的一部分。

  • 事务的提交,是什么情况

子事务先提交,然后父事务再提交。同上,子事务是父事务的一部分,由父事务统一提交。


参考资料

©️2020 CSDN 皮肤主题: 代码科技 设计师:Amelia_0503 返回首页
实付 9.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值