我试图弄清楚spring如何在 Controller 组件中注入(inject)线程安全的请求/ session 作用域bean(这是单例和多个线程通过方法访问那些bean)。
作为示例,考虑标记有HttpServletRequest
注释的 Controller 中的@Autowired
字段(我知道将 Controller 耦合到servlet-api不好,但是出于学习目的,这是可以的)。我了解到此类 bean 是使用CGLib代理的,但仍无法弄清代理如何处理线程
安全性和作用域bean到当前线程。
这是我到目前为止所学到的:
在同一实例上从请求到请求 Controller 字段点的
HttpServletRequest
的情况下)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 Enhancer在方法中生成的。
想到的唯一想法是
ThreadLocal
。 DispatcherServlet
保留对具有MethodInterceptor
字段的ThreadLocal
的引用,然后将实际对象(例如,通过getAttribute或简单地从HttpServletRequest
从 session 中检索到)注入(inject)该字段,然后拦截器使用实际的bean(存储在本地线程中)并使用反射对原始bean调用方法。任何建议或有趣的链接表示赞赏!
谢谢。
最佳答案
只有应用程序上下文将Bean相互注入(inject),而没有其他人(不是DispatcherServlet或任何其他类)注入(inject)。并且所有注入(inject)仅发生一次-在应用程序启动时。
Spring并不关心组件的线程安全性。您应该自己做。这就是为什么官方documentation advice:
让我们找出范围内的Bean是如何工作的。
当Spring遇到有作用域的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声明为proxified:
@Bean
@Scope(value="request")
@ScopeProxy
public RequestBean createBean(){
return new RequsetBean();
}
在这种情况下,Spring会将您的bean包装到具有相同公共(public)接口(interface)的proxy-object中。现在,每次调用bean的方法时,proxy-object都会从
Scope
获得真实的对象,然后将它们委托(delegate)给方法调用。HttpServletRequest
的工作方式几乎相同。 Spring注入(inject)了特殊的代理对象,而不是真正的HttpServletRequest
。唯一的区别是此代理对象不使用Scope
。当您调用其方法时,该代理对象将获得真实的HttpServletRequest
对象,并将所有调用委派给该对象。顺便说一句,按照source code和documentation看来,Spring确实将请求数据保留在ThreadLocal
中。因此,每个请求线程都有其自己的
HttpServletRequest
实例,并且 Controller 的方法不需要同步。