2
votes

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 HttpServletRequest field in controller marked with @Autowired 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 HttpServletRequest)
  2. thread's stack trace from session scoped (proxied) transfer object method invocation

    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)
    

    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 ThreadLocal. DispatcherServlet holds reference to MethodInterceptor which has ThreadLocal field and then injects actual object (e.g. retrieved from session by getAttribute or simply HttpServletRequest) 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

2 Answers

4
votes

Only application context injects beans to each other, and nobody else (not DispatcherServlet nor any other class). And all injections occurs just once - on application starting.

Spring does not care about thread safety of your components. You should do it by your own. That's why official documentation advice:

...As a rule, use the prototype scope for all stateful beans and the singleton scope for stateless beans.

Let's find out, how scoped beans do work.

When Spring meets scoped bean, it finds the corresponding implementation of Scope interface to get bean instance. For example when you declare bean like:

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

and inject it:

@Autowired
private RequestBean requestBean;

spring will ask from RequstScope (this scope-class registered by default) for instance of RequestBean and will inject it to another bean. So, only Scope class knows, how to get an instance of scoped bean, and you should not care about it. By the way, you can write your own implementation of Scope, register it, and use in bean declarations.

Now, about scoped proxies.

As I said, all injection occurs only when application starting. This means, that every time you will operate on the exact same object, that was originally injected on start. This is not behavior you expect for, when injecting, for example, request scoped bean. Of course, you would like to get new instance of this bean every request. To do it, as said in documentation, declare you bean as proxified:

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

In this case Spring will wrap your bean into proxy-object that has the same public interface. Now, every time you call bean's method, proxy-object will get real object from Scope, and then delegate them method calls.

HttpServletRequest working almost same way. Instead of real HttpServletRequest, Spring injects special proxy-object. The only difference is that this proxy-object does not use Scope. When you call its methods, this prox-object gets real HttpServletRequest object and delegates all invocations to it. By the way, according source code and documentation, it seems, Spring really keeps request data in ThreadLocal.

So every request thread has its own instance of HttpServletRequest and controller's methods does not need to be synchronized.

0
votes

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 HttpServletRequest field in a Controller. You can have an HttpServletRequest 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 HttpSession.