Spring MVC request/session 作用域 bean 线程安全
Spring MVC request/session scoped bean thread-safety
我想弄清楚 spring 如何设法在控制器组件中注入线程安全的 request/session 作用域 bean(这是单例和多线程通过方法访问这些 bean)。
例如,考虑控制器中标有 @Autowired
注释的 HttpServletRequest
字段(我知道将控制器耦合到 servlet-api 是不好的,但出于学习目的,它是可以的)。我了解到此类 bean 是使用 CGLib 代理的,但仍然无法弄清楚 代理如何处理线程
当前线程的安全和作用域 bean.
这就是我到目前为止学到的:
- 从请求到同一实例上的请求控制器字段点(即使在
HttpServletRequest
的情况下)
来自会话范围(代理)传输对象方法调用的线程堆栈跟踪
java.lang.Thread.getStackTrace(Thread.java:1552)
com.company.market.to.User.setUid(User.java:73)
com.company.market.to.User$$FastClassBySpringCGLIB$eb69e9e.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)
如您所见,一些代码是在运行时由 CGLib 增强器在方法中生成的。
唯一想到的是ThreadLocal
。 DispatcherServlet
持有对具有 ThreadLocal
字段的 MethodInterceptor
的引用,然后将实际对象(例如通过 getAttribute 从会话中检索或简单地 HttpServletRequest
)注入该字段,然后拦截器使用实际bean(存储在本地线程中)并使用反射调用原始 bean 上的方法。
感谢任何建议或有趣的链接!
谢谢。
如果您使用的是请求范围的 bean,那么 Spring 只需为它处理的每个新 HTTP 请求创建一个全新的实例。所以当然在那种情况下不会有任何线程安全问题。
但是,由于您没有共享任何代码,所以我没有完全理解您所说的在 Controller
中有一个 HttpServletRequest
字段。您可以将 HttpServletRequest
作为控制器方法的方法参数,这完全没问题,但您永远不会将它作为控制器本身的字段。
对于会话范围的 bean,它类似于请求范围的 bean -- 为每个新的 HttpSession
.
创建一个新实例
只有 应用程序上下文相互注入bean,而不是其他任何人(不是DispatcherServlet 或任何其他class)。所有注入 只发生一次 - 在应用程序启动时。
Spring 不关心组件的线程安全。你应该自己做。这就是为什么官方 documentation advice:
...As a rule, use the prototype scope for all stateful beans and the
singleton scope for stateless beans.
让我们来看看作用域 bean 是如何工作的。
当Spring遇到scoped bean时,找到对应的Scope
接口实现,获取bean实例。例如,当你像这样声明 bean 时:
@Bean
@Scope(value="request")
public RequestBean createBean(){
return new RequsetBean();
}
并注入它:
@Autowired
private RequestBean requestBean;
spring 将从 RequstScope
(此范围 - class 默认注册)询问 RequestBean
的实例并将其注入另一个 bean。所以,只有 Scope
class 知道,如何获取 scoped bean 的实例,你不应该关心它。顺便说一句,您可以编写自己的 Scope
实现,注册它,并在 bean 声明中使用。
现在,关于范围代理。
正如我所说,所有注入仅在应用程序启动时发生。这意味着,每次您将对完全相同的对象进行操作时,该对象最初是在启动时注入的。这不是您期望的行为,例如,在注入 request scoped bean 时。当然,您希望每个请求 都获得此 bean 的新实例。为此,as said in documentation,将您的 bean 声明为代理:
@Bean
@Scope(value="request")
@ScopeProxy
public RequestBean createBean(){
return new RequsetBean();
}
在这种情况下,Spring 会将您的 bean 包装到具有相同 public 接口的代理对象中。现在,每次 你调用 bean 的方法时,proxy-object 都会从 Scope
获取真正的对象,然后将方法调用委托给它们。
HttpServletRequest
工作方式几乎相同。 Spring 不是真正的 HttpServletRequest
,而是注入特殊的代理对象。唯一的区别是这个代理对象不使用 Scope
。当您调用它的方法时,此代理对象会获得真实的 HttpServletRequest
对象并将所有调用委托给它。顺便说一句,根据source code and documentation,看来,Spring确实把请求数据保存在了ThreadLocal
.
所以每个请求线程都有自己的HttpServletRequest
实例,控制器的方法不需要同步。
我想弄清楚 spring 如何设法在控制器组件中注入线程安全的 request/session 作用域 bean(这是单例和多线程通过方法访问这些 bean)。
例如,考虑控制器中标有 @Autowired
注释的 HttpServletRequest
字段(我知道将控制器耦合到 servlet-api 是不好的,但出于学习目的,它是可以的)。我了解到此类 bean 是使用 CGLib 代理的,但仍然无法弄清楚 代理如何处理线程
当前线程的安全和作用域 bean.
这就是我到目前为止学到的:
- 从请求到同一实例上的请求控制器字段点(即使在
HttpServletRequest
的情况下) 来自会话范围(代理)传输对象方法调用的线程堆栈跟踪
java.lang.Thread.getStackTrace(Thread.java:1552) com.company.market.to.User.setUid(User.java:73) com.company.market.to.User$$FastClassBySpringCGLIB$eb69e9e.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)
如您所见,一些代码是在运行时由 CGLib 增强器在方法中生成的。
唯一想到的是ThreadLocal
。 DispatcherServlet
持有对具有 ThreadLocal
字段的 MethodInterceptor
的引用,然后将实际对象(例如通过 getAttribute 从会话中检索或简单地 HttpServletRequest
)注入该字段,然后拦截器使用实际bean(存储在本地线程中)并使用反射调用原始 bean 上的方法。
感谢任何建议或有趣的链接!
谢谢。
如果您使用的是请求范围的 bean,那么 Spring 只需为它处理的每个新 HTTP 请求创建一个全新的实例。所以当然在那种情况下不会有任何线程安全问题。
但是,由于您没有共享任何代码,所以我没有完全理解您所说的在 Controller
中有一个 HttpServletRequest
字段。您可以将 HttpServletRequest
作为控制器方法的方法参数,这完全没问题,但您永远不会将它作为控制器本身的字段。
对于会话范围的 bean,它类似于请求范围的 bean -- 为每个新的 HttpSession
.
只有 应用程序上下文相互注入bean,而不是其他任何人(不是DispatcherServlet 或任何其他class)。所有注入 只发生一次 - 在应用程序启动时。
Spring 不关心组件的线程安全。你应该自己做。这就是为什么官方 documentation advice:
...As a rule, use the prototype scope for all stateful beans and the singleton scope for stateless beans.
让我们来看看作用域 bean 是如何工作的。
当Spring遇到scoped bean时,找到对应的Scope
接口实现,获取bean实例。例如,当你像这样声明 bean 时:
@Bean
@Scope(value="request")
public RequestBean createBean(){
return new RequsetBean();
}
并注入它:
@Autowired
private RequestBean requestBean;
spring 将从 RequstScope
(此范围 - class 默认注册)询问 RequestBean
的实例并将其注入另一个 bean。所以,只有 Scope
class 知道,如何获取 scoped bean 的实例,你不应该关心它。顺便说一句,您可以编写自己的 Scope
实现,注册它,并在 bean 声明中使用。
现在,关于范围代理。
正如我所说,所有注入仅在应用程序启动时发生。这意味着,每次您将对完全相同的对象进行操作时,该对象最初是在启动时注入的。这不是您期望的行为,例如,在注入 request scoped bean 时。当然,您希望每个请求 都获得此 bean 的新实例。为此,as said in documentation,将您的 bean 声明为代理:
@Bean
@Scope(value="request")
@ScopeProxy
public RequestBean createBean(){
return new RequsetBean();
}
在这种情况下,Spring 会将您的 bean 包装到具有相同 public 接口的代理对象中。现在,每次 你调用 bean 的方法时,proxy-object 都会从 Scope
获取真正的对象,然后将方法调用委托给它们。
HttpServletRequest
工作方式几乎相同。 Spring 不是真正的 HttpServletRequest
,而是注入特殊的代理对象。唯一的区别是这个代理对象不使用 Scope
。当您调用它的方法时,此代理对象会获得真实的 HttpServletRequest
对象并将所有调用委托给它。顺便说一句,根据source code and documentation,看来,Spring确实把请求数据保存在了ThreadLocal
.
所以每个请求线程都有自己的HttpServletRequest
实例,控制器的方法不需要同步。