java - Spring MVC 请求/ session 作用域 bean 线程安全

标签 java spring multithreading spring-mvc spring-aop

我试图弄清楚 spring 如何设法在 Controller 组件中注入(inject)线程安全的请求/ session 作用域 bean(这是单例和通过方法访问这些 bean 的多个线程)。
例如,考虑 Controller 中标有 @Autowired 注释的 HttpServletRequest 字段(我知道将 Controller 耦合到 servlet-api 是不好的,但出于学习目的,这是可以的)。我了解到这些bean是使用CGLib代理的,但仍然无法弄清楚代理如何处理线程 当前线程的安全性和范围 bean。

这就是我到目前为止学到的:

  1. 从请求到请求 Controller 字段指向同一实例(即使是 HttpServletRequest)
  2. 来自 session 范围(代理)传输对象方法调用的线程堆栈跟踪

    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)
    

    正如您所看到的,一些代码是在运行时由 CGLib 增强器在方法中生成的。


想到的唯一想法是ThreadLocalDispatcherServlet 保存对具有 ThreadLocal 字段的 MethodInterceptor 的引用,然后注入(inject)实际对象(例如,通过 getAttribute 从 session 中检索或简单地使用 HttpServletRequest code>) 到该字段,然后拦截器使用实际的 bean(存储在线程本地)并使用反射调用原始 bean 上的方法。

任何建议或有趣的链接表示赞赏!
谢谢。

最佳答案

应用程序上下文向彼此注入(inject) Bean,而没有其他人(不是 DispatcherServlet 或任何其他类)。所有注入(inject)仅发生一次 - 在应用程序启动时。

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接口(interface)的相应实现来获取bean实例。例如,当您声明 bean 时:

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

并注入(inject)它:

@Autowired
private RequestBean requestBean;

spring将从RequstScope(默认注册的范围类)请求RequestBean实例,并将其注入(inject)到另一个bean。因此,只有 Scope 类知道如何获取作用域 bean 的实例,并且您不应该关心它。顺便说一句,您可以编写自己的 Scope 实现、注册它并在 bean 声明中使用。

现在,关于作用域代理。

正如我所说,所有注入(inject)仅在应用程序启动时发生。这意味着,每次您都会对最初在启动时注入(inject)的完全相同的对象进行操作。这不是您期望的行为,例如注入(inject)请求范围的 bean 时。当然,您希望每个请求都获得此 bean 的新实例。为此,as said in documentation ,声明您的 bean 为代理:

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

在这种情况下,Spring 会将您的 bean 包装到具有相同公共(public)接口(interface)的代理对象中。现在,每次调用 bean 的方法时,代理对象都会从 Scope 获取真实对象,然后将方法调用委托(delegate)给它们。

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

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

关于java - Spring MVC 请求/ session 作用域 bean 线程安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35372258/

相关文章:

Java AMF 序列化

java - 如何在 main 中设置 Nimbus 外观

java - 常见 spring NoRepositoryBean 基接口(interface)上的 PreAuthorize 问题

c++ - moveToThread() 而不是 start() 适用于未构造的对象?

android - 如何异步调用 MediaScannerConnection.scanFile(),同时收到其完成通知?

c++ - shared_ptr 与 QThreadPool

java - 动态访问java对象变量

java - Spring请求验证不起作用

java - 我在哪里可以在 Spring 捕获非休息 Controller 异常?

java - 在 Spring 应用程序中获取 HttpServletRequest 主体数据