Spring - @Scope详解

@Scope简介

@Scope注解是为了指明Spring IOC容器中Bean的作用域的注解,可以配合@Component和@Bean注解一起使用。当与@Component一起使用时,表示该对象的作用域,与@Bean一起使用时,表示@Bean注解的方法返回对象的作用域。对于XML声明的Bean需要在XML文件中进行定义,无法使用该注解。

@Scope注解源码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
    
   @AliasFor("scopeName")
   String value() default "";

   @AliasFor("value")
   String scopeName() default "";
   
   ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}

@Scope注解使用示例

@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON, proxyMode = ScopedProxyMode.DEFAULT)
@Configuration  //@Configuration使用了@Component
public class MyConfig {

    @Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON, proxyMode = ScopedProxyMode.DEFAULT)
    @Bean
    public Stock getStock() {
        return new Stock();
    }
}

从上面的示例代码中我们可以注意到,我们向@Scope注解传入了两个参数,scopName和proxyMode。在Scope注解中一共有三个方法,其中value()和scopeName()互为别名,scopeName()在语义上更为贴近;proxyMode()指定了该Bean的代理模式,返回类型为ScopeProxyMode。下面我们详细介绍scopeName和proxyMode。

scopeName

scopeName是为了声明Bean的作用域,在Spring4.2版本以前,有singleton, prototype两种模式,4.2之后新加了web作用域(request, session, globalsession)。

  • singleton:单例模式,顾名思义即Spring IOC容器对于一个Bean,只会有一个共享的Bean实例。这一个单一的实例会被存储到单例缓存(singleton cache)中,当有请求或者是引用时,IOC容器都会返回存储在singleton cache的同一个实例。
  • prototype:多实例模式,即当每次客户端向容器获取Bean的时候,IOC容器都会新建一个实例并返回。与单例不同的是,在IOC容器启动的时候并不会创建Bean的实例,并且在有请求创建Bean实例之后也不会管理该实例的生命周期,而是由客户端自行处理。
  • request:web应用针对每一次HTTP请求都会创建一个新的Bean实例,且该实例仅在该次HTTP请求有效。
  • session:针对每一个session会创建一个Bean实例,且生命周期为该session有效期间。
  • globalsession:仅基于portlet的web应用才有意义,否则可以当作session使用。

这五种scopeName的使用方法

@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON)
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Scope(scopeName = WebApplicationContext.SCOPE_REQUEST)
@Scope(scopeName = WebApplicationContext.SCOPE_SESSION)
@Scope(scopeName = "globalSession")

ScopeProxyMode

proxyMode表明了@Scope注解的Bean是否需要代理。

首先我们要先知道为什么需要Bean的代理对象。我们看下面的代码,ClassA的作用域是singleton,而ClassB的作用域是prototype,当Spring应用上下文加载时会去创建ClassA的实例对象,并将ClassB的实例注入到ClassA中,但是由于ClassB此时并没有实例,所以在初始化的时候调用ClassB的方法会抛出NullPointerException。因此我们需要创建一个ClassB的代理来解决这种情况。

ClassA示例代码:

@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON)
@Configuration
public class ClassA {
    
    @Autowired
    private ClassB classB;
    
    public ClassA() {
    	this.classB.test();
     }
}

ClassB没有代理示例代码:

@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class ClassB {
    public void test() {
    	System.out.println("This is test-B");
    }
}

在@Scope注解中我们通过proxyMode来表示是否需要Bean代理,以及使用哪种代理。proxyMode有四个值:

  • DEFAULT:proxyMode的默认值,一般情况下等同于NO,即不需要动态代理。
  • NO:不需要动态代理,即返回的是Bean的实例对象。
  • INTERFACES:代理的对象是一个接口,即@Scope的作用对象是接口,这种情况是基于jdk实现的动态代理。
  • TARGET_CLASS:代理的对象是一个类,即@Scope的作用对象是一个类,上面例子中的ClassB就可以用这种代理,是以生成目标类扩展的方式创建代理,基于CGLib实现动态代理。
@Scope(proxyMode = ScopedProxyMode.DEFAULT)
@Scope(proxyMode = ScopedProxyMode.NO)
@Scope(proxyMode = ScopedProxyMode.INTERFACES)
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)


版权声明:本文为landaflame原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。