Bootstrap

Spring学习笔记_18——@Scope

@Scope

1. 说明

@Scope注解是Spring中提供的一个能够指定Bean的作用范围的注解,通过@Scope注解可以指定创建的Bean是单例的,还是原型的,也可以使用@Scope注解指定Bean在Web中的作用域,还可以自定义作用域。

2. 场景

  1. 非单例Bean
  • 默认情况下,Spring容器中的bean是单例的(singleton),这意味着在整个应用程序的生命周期中,Spring容器只会为该bean创建一个实例。如果你需要每次请求一个新的bean实例(例如,对于有状态的服务),你可以使用@Scope("prototype")来指定bean的作用域。
  1. Web作用域
  • 在Web应用程序中,你可能会希望bean的作用域与HTTP请求或会话相关联。
    • 使用@Scope("request"):每次HTTP请求都会创建一个新的bean实例,并且该实例仅在当前请求内有效。
    • 使用@Scope("session"):每次HTTP会话都会创建一个新的bean实例,并且该实例在当前会话内有效。这对于需要在用户会话期间保持状态的bean非常有用。
  • 注意:要使用这些作用域,你需要确保Spring的Web应用上下文(例如,XmlWebApplicationContextAnnotationConfigWebApplicationContext)已正确配置。
  1. 自定义作用域
  • 如果你需要一种Spring未提供的作用域(例如,基于每个线程或每个自定义上下文的bean实例),你可以通过实现org.springframework.beans.factory.config.Scope接口来创建自定义作用域,并使用@Scope注解的value属性或scopedProxy属性来引用它。
  1. 代理作用域Bean(使用scopedProxy):
  • 当你在单例bean中注入作用域为requestsession或自定义作用域的bean时,由于单例bean的生命周期与作用域bean的生命周期不匹配,直接注入可能会导致问题。为了解决这个问题,你可以使用scopedProxy属性来创建一个代理对象,该代理对象会在需要时延迟获取作用域bean的实例。
  • 例如:@Scope(value = "request", scopedProxy = ScopedProxyMode.TARGET_CLASS)
  1. 在配置类中定义Bean时
  • 当你使用Java配置(@Configuration类)来定义bean时,你可以使用@Scope注解来指定bean的作用域。这通常与@Bean注解一起使用。
  1. 在组件类上
  • 你也可以在用@Component@Service@Repository注解标记的类上使用@Scope注解来指定这些组件的作用域。

3. 源码

/**
 * @author Mark Fisher
 * @author Chris Beams
 * @author Sam Brannen
 * @since 2.5
 * @see org.springframework.stereotype.Component
 * @see org.springframework.context.annotation.Bean
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {

        // value:表示作用范围,可以取如下值。
            // singleton:表示单例Bean,IOC容器在启动时,就会创建Bean对象。如果标注了@Lazy注解,IOC容器在启动时,就不会创建Bean对象,会在第一次从IOC容器中获取Bean对象时,创建Bean对象。后续每次从IOC容器中获取的都是同一个Bean对象,同时,IOC容器会接管单例Bean对象的生命周期。
            // prototype:表示原型Bean。IOC容器在启动时,不会创建Bean对象,每次从IOC容器中获取Bean对象时,都会创建一个新的Bean对象。并且@Lazy注解对原型Bean不起作用,同时,IOC容器不会接管原型Bean对象的生命周期
            // request:表示作用域是当前请求范围。
            // session:表示作用域是当前会话范围。
            // application:表示作用域是当前应用范围。
        @AliasFor("scopeName")
    String value() default "";


    /**
     * @since 4.2
     */
        // Spring4.2版本开始新增的属性,作用与value属性相同
    @AliasFor("value")
    String scopeName() default "";

        // 指定Bean对象使用的代理方式,可以取如下值。
            // DEFAULT:默认值,作用与NO相同。
            // NO:不使用代理。
            // INTERFACES:使用JDK基于接口的代理。
            // TARGET_CLASS:使用CGLIB基于目标类的子类创建代理对象。
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}

4. Demo

4.1 单例
import org.springframework.stereotype.Component;
import org.springframework.context.annotation.Scope;

@Component
@Scope("singleton")
public class SingletonBean {
    public SingletonBean() {
        System.out.println("SingletonBean created: " + this);
    }
}
4.2 原型

每次请求都会创建一个新的实例

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

@Component
@Scope("prototype")
public class PrototypeBean {
    public PrototypeBean() {
        System.out.println("PrototypeBean created: " + this);
    }
}
4.3 请求

每个HTTP请求都有自己的Bean实例

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

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopedBean {
    public RequestScopedBean() {
        System.out.println("RequestScopedBean created: " + this);
    }
}
4.4 会话

同一个HTTP Session 共享一个Bean实例

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

@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionScopedBean {
    public SessionScopedBean() {
        System.out.println("SessionScopedBean created: " + this);
    }
}
4.5 应用

整个Web应用程序范围内只有一个实例

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

@Component
@Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ApplicationScopedBean {
    public ApplicationScopedBean() {
        System.out.println("ApplicationScopedBean created: " + this);
    }
}
4.6 WebSocket

每个WebSocket链接都有自己的Bean实例

import org.springframework.stereotype.Component;
import org.springframework.context.annotation.Scope;
import org.springframework.web.socket.config.annotation.WebSocketScope;

@Component
@Scope(value = WebSocketScope.class, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class WebSocketScopedBean {
    public WebSocketScopedBean() {
        System.out.println("WebSocketScopedBean created: " + this);
    }
}
4.7 自定义作用域

首先,定义一个自定义作用域

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;

import java.util.HashMap;
import java.util.Map;

public class CustomScope implements Scope {

    private final Map<String, Object> scopeMap = new HashMap<>();

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        return scopeMap.computeIfAbsent(name, k -> objectFactory.getObject());
    }

    @Override
    public Object remove(String name) {
        return scopeMap.remove(name);
    }

    // 其他方法可以根据需求进行实现
    @Override
    public void registerDestructionCallback(String name, Runnable callback) {}

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Override
    public String getConversationId() {
        return "custom-scope";
    }
}

在配置类中注册这个自定义作用域

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public static CustomScope customScope() {
        return new CustomScope();
    }

    @Bean
    public static CustomScopeConfigurer customScopeConfigurer(CustomScope customScope) {
        CustomScopeConfigurer configurer = new CustomScopeConfigurer();
        configurer.addScope("custom", customScope);
        return configurer;
    }
}

使用自定义作用域的Bean

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

@Component
@Scope("custom")
public class CustomScopedBean {
    public CustomScopedBean() {
        System.out.println("CustomScopedBean created: " + this);
    }
}

以下是一些可能需要自定义作用域的业务场景:

  1. 基于特定条件的实例化策略
  • 当Bean的创建需要基于某些特定的业务条件或逻辑时,可以使用自定义作用域来控制Bean的实例化过程。例如,可能需要根据用户的角色或权限来创建不同配置的Bean实例。
  1. 自定义生命周期管理
  • 对于某些特殊的Bean,可能需要实现自定义的生命周期管理逻辑,包括创建、缓存、使用和销毁等。自定义作用域允许开发者完全控制这些过程,以适应特定的业务需求。
  1. 非标准的请求作用域
  • 在某些情况下,可能需要在非标准的请求范围内共享Bean实例。例如,在批处理应用程序中,可能需要为每个批处理任务创建一个独立的Bean实例,而这些任务并不是由HTTP请求触发的。此时,可以通过自定义作用域来实现这种需求。
  1. 跨多个HTTP请求或会话的状态管理
  • 在某些Web应用程序中,可能需要跨多个HTTP请求或会话来维护某些状态信息。虽然Session作用域可以在单个会话内共享状态,但如果需要在多个会话之间共享状态,或者需要更复杂的状态管理逻辑,那么自定义作用域可能是一个更好的选择。
  1. 与第三方系统集成
  • 当Spring应用程序需要与第三方系统集成时,可能需要遵循特定的生命周期管理规则或协议。例如,某些第三方库可能要求在每个请求开始时创建一个新的实例,并在请求结束时销毁该实例。此时,可以通过自定义作用域来确保与这些系统的兼容性和集成性。
  1. 测试与模拟
  • 在进行单元测试或集成测试时,可能需要模拟不同的作用域行为来验证应用程序的正确性。自定义作用域提供了一种灵活的方式来模拟这些行为,并帮助开发者发现潜在的问题。
;