【Spring高级】Bean作用域Scope及失效分析

Scope种类

在 Spring 里面,可以设置创建 bean 的作用域。

目前Spring Bean的作用域或者说范围主要有五种。

作用域描述
singleton在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。
prototype每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。
request每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境。
session同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境。
application限定一个Bean的作用域为ServletContext的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境。

Singleton

表示Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。spring中作用域默认是singleton。要在XML中将bean定义成singleton,可以这样配置:

<bean id="ServiceImpl" class="cn.example.service.ServiceImpl" scope="singleton">

Prototype

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

<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>  
 <!-- 或者 -->
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>

Request

表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。

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

Session

表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

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

Global Session

表示在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。

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

Scope失效

为什么失效

案例演示:

创建F1,设置为多例,

package com.cys.spring.chapter07;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class F1 {
}

在配置类中注入F1:

package com.cys.spring.chapter07;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Component
public class E {

    @Autowired
    private F1 f1;

    private F1 getF1() {
        return f1;
    }

}

测试类

package com.cys.spring.chapter07;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;


@ComponentScan("com.cys.spring.chapter07")
public class TestScope {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestScope.class);
        E e = context.getBean(E.class);

        // 打印两次观察两个F1是否是同一个
      	System.out.println(e.getF1().getClass());
        System.out.println(e.getF1());
        System.out.println(e.getF1());

        context.close();

    }
}

运行结果两个对象是同一个

class com.cys.spring.chapter07.F1$$EnhancerBySpringCGLIB$$6816bc69
com.cys.spring.chapter07.F1@2d710f1a
com.cys.spring.chapter07.F1@2d710f1a

而我们期望的是多例,不同对象。

那是因为E是单例的,单例对象的依赖注入只会执行一次,因此E的f1属性总是第一次依赖注入的对象。

问题就是单例对象注入多例对象时,多例注入失效,注入的是同一个对象。

那么如何解决呢?

失效解决

使用@Lazy 进行代理生成对象

@Lazy 是一个Spring框架中的注解,用于实现延迟加载(懒加载)的Bean。当我们在Spring配置中标记一个Bean为@Lazy时,这个Bean的初始化会被推迟到第一次使用时才进行,而不是在容器启动时立即进行。

这个注解通常与@Component、@Service、@Repository等注解一起使用,来标记类为Spring容器管理的Bean。当这些类被标记为@Lazy时,它们的实例化会在第一次被注入或使用时才进行,而不是在容器启动时。

此外,@Lazy注解还可以用于方法上,特别是与@Bean注解一起使用时。当方法上标记了@Lazy,该方法返回的Bean对象会在第一次使用时才进行初始化。

修改E如下:

package com.cys.spring.chapter07;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;


@Component
public class E {

    @Lazy
    @Autowired
    private F1 f1;

    public F1 getF1() {
        return f1;
    }

}

再次运行结果如下:

com.cys.spring.chapter07.F1@53f3bdbd
com.cys.spring.chapter07.F1@7d61eb55

指定Scope的proxyMode属性

proxyMode 属性是 Spring 框架中 @Scope 注解的一个属性,它用于配置当前类的代理模式。这个属性主要用于非 Singleton 作用域的 Bean。因为在非 Singleton 作用域中,Spring 并不会立即创建 Bean 的实例,而是根据需要才创建。当需要注入时就产生一个代理对象,这时 proxyMode 属性就起作用了。

proxyMode 属性有几个可选值:

  1. DEFAULT(或 NO):这是默认值,表示不使用代理。如果需要 Bean 的实例,Spring 会立即创建它。
  2. INTERFACES:表示使用 JDK 提供的动态代理来实现代理。这通常适用于 Bean 实现了至少一个接口的情况。
  3. TARGET_CLASS:表示使用 CGLib 库来实现动态代理。这通常适用于 Bean 没有实现任何接口的情况。

使用 proxyMode 属性可以在不改变原有代码结构的情况下,通过声明开启作用域代理来实现一些高级功能,比如在单例 Bean 中使用多实例 Bean。

需要注意的是,proxyMode 属性的设置应该根据具体的业务需求和 Bean 的特点来选择。同时,还需要注意代理模式可能对性能产生的影响,因此在选择代理模式时需要权衡利弊。

这里我们指定Scope的proxyMode属性为ScopedProxyMode.TARGET_CLASS

新建F2如下:

package com.cys.spring.chapter07;

import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;


@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class F2 {
}

修改E

package com.cys.spring.chapter07;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;


@Component
public class E {

    @Lazy
    @Autowired
    private F1 f1;

    @Autowired
    private F2 f2;


    public F1 getF1() {
        return f1;
    }

    public F2 getF2() {
        return f2;
    }

}

测试类:

package com.cys.spring.chapter07;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan("com.cys.spring.chapter07")
public class TestScope {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestScope.class);
        E e = context.getBean(E.class);

        // 打印两次观察两个F1是否是同一个
//        System.out.println(e.getF1().getClass());
//        System.out.println(e.getF1());
//        System.out.println(e.getF1());

        System.out.println(e.getF2().getClass());
        System.out.println(e.getF2());
        System.out.println(e.getF2());

        context.close();

    }
}

运行结果如下:

class com.cys.spring.chapter07.F2$$EnhancerBySpringCGLIB$$cedb4f5a
com.cys.spring.chapter07.F2@5170bcf4
com.cys.spring.chapter07.F2@2812b107

使用工厂来创建

如下,创建一个F3,指定多例,在E中代码如下:

package com.cys.spring.chapter07;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;


@Component
public class E {

    @Lazy
    @Autowired
    private F1 f1;

    @Autowired
    private F2 f2;

    @Autowired
    private ObjectFactory<F3> f3;

    public F1 getF1() {
        return f1;
    }

    public F2 getF2() {
        return f2;
    }

    public F3 getF3() {
        return f3.getObject();
    }

}

测试类

package com.cys.spring.chapter07;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;


@ComponentScan("com.cys.spring.chapter07")
public class TestScope {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestScope.class);
        E e = context.getBean(E.class);

        System.out.println(e.getF3().getClass());
        System.out.println(e.getF3());
        System.out.println(e.getF3());

        context.close();
    }
}

运行结果如下:

class com.cys.spring.chapter07.F3
com.cys.spring.chapter07.F3@6304101a
com.cys.spring.chapter07.F3@2812b107

直接使用容器中获取

将容器注入到E,在E中直接从容器中获取

修改E如下:

package com.cys.spring.chapter07;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
public class E {

    @Lazy
    @Autowired
    private F1 f1;

    @Autowired
    private F2 f2;

    @Autowired
    private ObjectFactory<F3> f3;

    @Autowired
    private ApplicationContext applicationContext;

    public F1 getF1() {
        return f1;
    }

    public F2 getF2() {
        return f2;
    }

    public F3 getF3() {
        return f3.getObject();
    }

    public F4 getF4() {
        return applicationContext.getBean(F4.class);
    }

}

applicationContext注入到E中,然后通过applicationContext获取F4的对象。

  • 24
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ethan-running

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值