@Scope
1. 说明
@Scope注解是Spring中提供的一个能够指定Bean的作用范围的注解,通过@Scope注解可以指定创建的Bean是单例的,还是原型的,也可以使用@Scope注解指定Bean在Web中的作用域,还可以自定义作用域。
2. 场景
- 非单例Bean:
- 默认情况下,Spring容器中的bean是单例的(
singleton
),这意味着在整个应用程序的生命周期中,Spring容器只会为该bean创建一个实例。如果你需要每次请求一个新的bean实例(例如,对于有状态的服务),你可以使用@Scope("prototype")
来指定bean的作用域。
- Web作用域:
- 在Web应用程序中,你可能会希望bean的作用域与HTTP请求或会话相关联。
- 使用
@Scope("request")
:每次HTTP请求都会创建一个新的bean实例,并且该实例仅在当前请求内有效。 - 使用
@Scope("session")
:每次HTTP会话都会创建一个新的bean实例,并且该实例在当前会话内有效。这对于需要在用户会话期间保持状态的bean非常有用。
- 使用
- 注意:要使用这些作用域,你需要确保Spring的Web应用上下文(例如,
XmlWebApplicationContext
或AnnotationConfigWebApplicationContext
)已正确配置。
- 自定义作用域:
- 如果你需要一种Spring未提供的作用域(例如,基于每个线程或每个自定义上下文的bean实例),你可以通过实现
org.springframework.beans.factory.config.Scope
接口来创建自定义作用域,并使用@Scope
注解的value
属性或scopedProxy
属性来引用它。
- 代理作用域Bean(使用
scopedProxy
):
- 当你在单例bean中注入作用域为
request
、session
或自定义作用域的bean时,由于单例bean的生命周期与作用域bean的生命周期不匹配,直接注入可能会导致问题。为了解决这个问题,你可以使用scopedProxy
属性来创建一个代理对象,该代理对象会在需要时延迟获取作用域bean的实例。 - 例如:
@Scope(value = "request", scopedProxy = ScopedProxyMode.TARGET_CLASS)
- 在配置类中定义Bean时:
- 当你使用Java配置(
@Configuration
类)来定义bean时,你可以使用@Scope
注解来指定bean的作用域。这通常与@Bean
注解一起使用。
- 在组件类上:
- 你也可以在用
@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<>();@Overridepublic Object get(String name, ObjectFactory<?> objectFactory) {return scopeMap.computeIfAbsent(name, k -> objectFactory.getObject());}@Overridepublic Object remove(String name) {return scopeMap.remove(name);}// 其他方法可以根据需求进行实现@Overridepublic void registerDestructionCallback(String name, Runnable callback) {}@Overridepublic Object resolveContextualObject(String key) {return null;}@Overridepublic String getConversationId() {return "custom-scope";}
}
在配置类中注册这个自定义作用域
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class AppConfig {@Beanpublic static CustomScope customScope() {return new CustomScope();}@Beanpublic 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);}
}
以下是一些可能需要自定义作用域的业务场景:
- 基于特定条件的实例化策略:
- 当Bean的创建需要基于某些特定的业务条件或逻辑时,可以使用自定义作用域来控制Bean的实例化过程。例如,可能需要根据用户的角色或权限来创建不同配置的Bean实例。
- 自定义生命周期管理:
- 对于某些特殊的Bean,可能需要实现自定义的生命周期管理逻辑,包括创建、缓存、使用和销毁等。自定义作用域允许开发者完全控制这些过程,以适应特定的业务需求。
- 非标准的请求作用域:
- 在某些情况下,可能需要在非标准的请求范围内共享Bean实例。例如,在批处理应用程序中,可能需要为每个批处理任务创建一个独立的Bean实例,而这些任务并不是由HTTP请求触发的。此时,可以通过自定义作用域来实现这种需求。
- 跨多个HTTP请求或会话的状态管理:
- 在某些Web应用程序中,可能需要跨多个HTTP请求或会话来维护某些状态信息。虽然Session作用域可以在单个会话内共享状态,但如果需要在多个会话之间共享状态,或者需要更复杂的状态管理逻辑,那么自定义作用域可能是一个更好的选择。
- 与第三方系统集成:
- 当Spring应用程序需要与第三方系统集成时,可能需要遵循特定的生命周期管理规则或协议。例如,某些第三方库可能要求在每个请求开始时创建一个新的实例,并在请求结束时销毁该实例。此时,可以通过自定义作用域来确保与这些系统的兼容性和集成性。
- 测试与模拟:
- 在进行单元测试或集成测试时,可能需要模拟不同的作用域行为来验证应用程序的正确性。自定义作用域提供了一种灵活的方式来模拟这些行为,并帮助开发者发现潜在的问题。