Spring MVC request/session scoped bean thread-safety

时间:2016-02-12 21:12:28

标签: java spring multithreading spring-mvc spring-aop

I'm trying to figure out how spring manages to inject thread safe request/session scoped bean in controller component (which is singleton and multiple threads accessing those beans through methods).
As example consider T field in controller marked with HttpServletRequest annotation (I know that this is bad to couple controller to servlet-api but in learning purposes it is ok). I learned that such beans are proxied using CGLib, but still cannot figure out how proxy can handle thread safety and scope beans to current thread.

That's what I learned so far:

  1. from request to request controller field points on same instance (even in case of @Autowired)
  2. thread's stack trace from session scoped (proxied) transfer object method invocation

    HttpServletRequest

    As you can see some of the code is generated at runtime in methods by CGLib enhancer.


The only idea that comes in mind is java.lang.Thread.getStackTrace(Thread.java:1552) com.company.market.to.User.setUid(User.java:73) com.company.market.to.User$$FastClassBySpringCGLIB$$8eb69e9e.invoke(<generated>) org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:133) org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:121) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655) com.company.market.to.User$$EnhancerBySpringCGLIB$$f4f46820.setUid(<generated>) com.company.market.controllers.UserController.signIn(UserController.java:97) . ThreadLocal holds reference to DispatcherServlet which has MethodInterceptor field and then injects actual object (e.g. retrieved from session by getAttribute or simply ThreadLocal) to that field, and then interceptor uses actual bean (stored in thread local) and using reflection invokes methods on original bean.

Any suggestions or interesting links appreciated!
Thanks.

2 个答案:

答案 0 :(得分:3)

只有应用程序上下文互相注入bean,没有其他人(不是DispatcherServlet也不是任何其他类)。在应用程序启动时,所有注入仅发生 -

Spring并不关心组件的线程安全性。你应该自己做。这就是官方documentation advice

的原因
  

...通常,将原型范围用于所有有状态bean和   无状态bean的单例范围。

让我们看看,范围内的bean如何运作

当Spring遇到scoped bean时,它会找到Scope接口的相应实现来获取bean实例。例如,当你声明bean之类的时候:

@Bean
@Scope(value="request")
public RequestBean createBean(){
   return new RequsetBean();
}

并注入它:

@Autowired
private RequestBean requestBean;

spring将从RequstScope(默认情况下注册此范围类)询问RequestBean的实例,并将其注入另一个bean。所以,只有Scope类知道,如何获取scoped bean的实例,你不应该关心它。顺便说一句,您可以编写自己的Scope实现,注册它,并在bean声明中使用。

现在,关于作用域代理。

正如我所说,所有注射仅在应用程序启动时发生。这意味着,每次您将对完全相同的对象进行操作时,最初是在开始时注入的。注入时,这不是您期望的行为,例如 request scoped bean。当然,您希望获得此bean 的每个请求的新实例。要做到这一点as said in documentation,请将bean声明为proxiated:

@Bean
@Scope(value="request")
@ScopeProxy
public RequestBean createBean(){
   return new RequsetBean();
}

在这种情况下,Spring会将您的bean包装到具有相同公共接口的proxy-object中。现在,每次调用bean的方法时,proxy-object将从Scope获取实际对象,然后委托他们进行方法调用。

HttpServletRequest的工作方式几乎相同。 Spring注入了特殊的代理对象,而不是真正的HttpServletRequest。唯一的区别是此代理对象不使用Scope。当您调用其方法时,此prox-object将获得真正的HttpServletRequest对象并将所有调用委托给它。顺便说一句,根据source codedocumentation,看来,Spring确实在ThreadLocal中保留了请求数据。

因此每个请求线程都有自己的HttpServletRequest实例,并且控制器的方法不需要同步。

答案 1 :(得分:0)

If you are using a request-scoped bean, then Spring just creates a brand new instance for every new HTTP request that it processes. So of course in that case there wouldn't be any thread safety issues.

However, since you don't share any code I don't fully follow what you are saying about having a View -> Syntax -> Open all with current extension as... field in a HttpServletRequest. You can have an Controller as a method parameter to your controller methods and that is totally fine, but you would never have it as a field in the controller itself.

For session-scoped beans, it is analogous to request-scoped beans -- a new instance is created for every new HttpServletRequest.