Spring单例模式下线程内共享数据 刚好这两天的开发任务有遇到多个Service共同依赖某些数据的情况,又不想通过修改接口参数去适配,就想了这个辙
2934
2018-01-26
在Spring Web开发中,一次请求内,逻辑涉及的多个模块均需要执行数据库查询获取结果,而事实上使用的是同一份数据,因此我们希望仅第一次执行真实查询、并将数据暂存起来,后续直接获取即可。
在Spring MVC中,Controller、Service、Dao默认都为单例模式,在大多数情况下,单例Bean是很理想的方案,降低了初始化和垃圾回收对象实例所带来的成本,但数据的环境隔离就不那么方便了。
Java中的类在实例化后其自身保存在JVM的堆区,成员变量作为类实例的一部分,自然也是保存在堆区,被所有线程共享。因此在单例模式下,保存在成员变量中的数据会被并发的多个线程同时访问,无法进行环境隔离,至于加锁的解决办法在Web应用高并发的情况下并不适用。
不过还好Spring在装配Bean时提供了作用域的选项,默认均为单例(singleton)模式。
Bean的作用域
Spring提供了四种作用域的选项,在@Scope注解的源码中可见:
/**
* Specifies the name of the scope to use for the annotated component/bean.
* <p>Defaults to an empty string ({@code ""}) which implies
* {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}.
* @since 4.2
* @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
* @see ConfigurableBeanFactory#SCOPE_SINGLETON
* @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
* @see #value
*/
即
作用域名称 | 描述 |
---|---|
单例(Singleton) | 在整个应用中,只会创建一个bean的实例 |
原型(Prototype) | 每次注入或者通过上下文获取的时候,都会创建一个新的bean实例 |
会话(Session) | 在Web应用中,为每个会话创建一个bean实例 |
请求(Request) | 在Web应用中,为每个请求创建一个bean实例 |
因此我们可以利用Spring提供的Request作用域来实现在单次请求内的数据缓存。
eg:
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopeCache {
private Map<UUID, List<PResourceRelation>> local1 = new HashMap<>();
/**
* Sets map to resource relation list.
*
* @param mapId the map id
* @param resourceRelations the resource relations
*/
public void setMapToResourceRelationList(UUID mapId, List<PResourceRelation> resourceRelations) {
local1.put(mapId, resourceRelations);
}
/**
* Gets map to resource relation list.
*
* @param mapId the map id
* @return the map to resource relation list
*/
public List<PResourceRelation> getMapToResourceRelationList(UUID mapId) {
return local1.get(mapId);
}
}
在请求所经历的Controller、Service中,通过@Autowired注入即可,可以实现单次请求内的数据共享。
额外需要注意的是@Scope注解中的proxyMode属性,当在单例模式的Bean中注入非单例模式的Bean时,该属性会让Spring在单例Bean实例化时先不要注入非单例Bean的实例,而是根据上下文环境选择何时实例化Bean。查看该属性注释可见:
/**
* Specifies whether a component should be configured as a scoped proxy
* and if so, whether the proxy should be interface-based or subclass-based.
* <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
* that no scoped proxy should be created unless a different default
* has been configured at the component-scan instruction level.
* <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
* @see ScopedProxyMode
*/
当代理的为具体Class时,使用ScopedProxyMode.TARGET_CLASS;当代理的为接口(Interface)时,则使用ScopedProxyMode.INTERFACES;默认为DEFAULT。