1. 程式人生 > >springMVC注入是執行緒安全的嗎

springMVC注入是執行緒安全的嗎

springmvc的注入執行緒安全嗎

       servlet是單例的,而tomcat則是在多個執行緒中呼叫servlet的處理方法。因此如果servlet存在例項物件,那麼就會引出執行緒安全的問題。而springmvc允許在controller類中通過@Autowired配置requestresponse以及requestcontext等例項物件。這種配置方法是否執行緒安全?答案是——這種配置方法是執行緒安全的,requestresponse以及requestcontext在使用時不需要進行同步。而根據spring的預設規則,controller對於beanfactory而言是單例的。即

controller只有一個,controller中的request等例項物件也只有一個。然而tomcat依舊會以多執行緒的方式訪問controller。這種做法似乎並不能保證執行緒安全。我們如何理解這一矛盾?

   在解釋controller執行緒安全這一問題之前需要首先了解如下的一些問題和概念:

   1.servletrequest域的問題:request域是javaweb的基礎概念,他指的是從發起http請求到返回響應的這一段時間內,存在一個httprequest物件對應於http請求。以上的表述是沒有問題的,然而有些人自作主張的將之前的表述換成了其他的描述方式:(1)request

物件的生命週期以發起http請求開始,當http請求返回時結束;(2):使用者傳送一個請求的時候,request被建立,當用戶關閉請求的時候,request會消亡。以上兩種表述的主要錯誤在於混淆了http請求和request物件這兩個概念。tomcat在接收到http請求的時候並不會建立一個request物件,即request物件並不是一個http請求的例項。只是request物件恰巧擁有了http請求中的所有引數而已。request物件在tomcat發起處理執行緒的時候就被建立,只有當處理執行緒終止的時候request才會被銷燬。我們可以建立一個servlet類,並在dogetdopost
方法上面打上斷點。你會發現如果是同一個程序,即便發起多次訪問,request物件的id始終不變。讀者可以親自嘗試,用以驗證本人說法的真偽。

   2.Threadlocal類:該物件包含兩個關鍵函式:set(Object obj)get()。這兩個函式與呼叫該函式的執行緒相關,set方法將某一物件注入到當前執行緒中,而get方法則是從當前執行緒中獲取物件。

   3.InvocationHandler介面:這是springmvc保證request物件執行緒安全的核心。通過實現該介面,開發者能夠在Java物件方法執行時進行干預,搭配Threadlocal就能夠實現執行緒安全。

  下面將通過例子介紹springmvc如何保證request物件執行緒安全:

   Httprequest介面:

  1. public interface HttpRequest {  
  2.     public void service();  
  3. }  

   HttpRequestImpl類:對httprequest介面的具體實現,為了區別不同的HttpRequestImpl物件,本人為HttpRequestImpl設定了一個Double物件,如果不設定該物件,其預設為null

  1. public class HttpRequestImpl implements HttpRequest{  
  2.     public Double d;  
  3.     @Override  
  4.     public void service() {  
  5.         System.out.println("do some serivce, random value is "+d);  
  6.     }  
  7. }  

   ThreadLocalTest類:負責向ThreadLocal設定物件和獲取物件,本人設定ThreadLocal物件為static,因此ThreadLocalTest類中只能有一個ThreadLocal物件。

  1. public class ThreadLocalTest {  
  2.     public static ThreadLocal<HttpRequest> local=new ThreadLocal<HttpRequest>();  
  3.     public static void set(HttpRequest f){  
  4.         if(get()==null){  
  5.             System.out.println("ThreadLocal is null");  
  6.             local.set(f);  
  7.         }  
  8.     }  
  9.     public static HttpRequest get(){  
  10.         return local.get();  
  11.     }  
  12. }  

   Factory類:該類是一個工廠類並且是單例模式,主要負責向ThreadLocalTest物件中設定和獲取物件

  1. public class Factory{  
  2.     private static Factory factory=new Factory();  
  3.     private Factory(){  
  4.     }  
  5.     public static Factory getInstance(){  
  6.         return factory;  
  7.     }  
  8.     public HttpRequest getObject(){  
  9.         return (HttpRequest)ThreadLocalTest.get();  
  10.     }  
  11.     public void setObject(HttpRequest request){  
  12.         ThreadLocalTest.set(request);  
  13.     }  
  14. }  

  Delegate類:該類實現了InvocationHandler介面,並實現了invoke方法

  1. import java.lang.reflect.InvocationHandler;  
  2. import java.lang.reflect.Method;  
  3. import java.lang.reflect.Proxy;  
  4. public class Delegate implements InvocationHandler{  
  5.     private Factory factory;  
  6.     public Factory getFactory() {  
  7.         return factory;  
  8.     }  
  9.     public void setFactory(Factory factory) {  
  10.         this.factory = factory;  
  11.     }  
  12.     @Override  
  13.     public Object invoke(Object proxy, Method method, Object[] args)  
  14.             throws Throwable {  
  15.         return method.invoke(this.factory.getObject(), args);  
  16.     }  
  17. }  

   ProxyUtils類:該類是一個工具類,負責生成一個httprequest物件的代理

  1. import java.lang.reflect.Proxy;  
  2. public class ProxyUtils {  
  3.     public static HttpRequest getRequest(){  
  4.         HttpRequest request=new HttpRequestImpl();  
  5.         Delegate delegate=new Delegate();  
  6.         delegate.setFactory(Factory.getInstance());  
  7.         HttpRequest proxy=(HttpRequest) Proxy.newProxyInstance(request.getClass().getClassLoader(), request.getClass().getInterfaces(), delegate);  
  8.         return proxy;  
  9.     }  
  10. }  

   TestThread類:該類用來模擬多執行緒呼叫controller的情況,類中擁有一個靜態物件request

  1. public class TestThread implements Runnable{  
  2.     private static HttpRequest request;  
  3.     public void init(){  
  4.         HttpRequestImpl requestimpl=new HttpRequestImpl();  
  5.         requestimpl.d=Math.random();  
  6.         Factory.getInstance().setObject(requestimpl);  
  7.     }  
  8.     @Override  
  9.     public void run() {  
  10.         System.out.println("*********************");  
  11.         init();  
  12.         request.service();  
  13.         System.out.println("*********************");  
  14.     }  
  15.     public static HttpRequest getRequest() {  
  16.         return request;  
  17.     }  
  18.     public static void setRequest(HttpRequest request) {  
  19.         TestThread.request = request;  
  20.     }  
  21. }  

   main:測試類

  1. public class main {  
  2.     /** 
  3.      * @param args 
  4.      */  
  5.     public static void main(String[] args) {  
  6.         HttpRequest request=ProxyUtils.getRequest();  
  7.         TestThread thread1=new TestThread();  
  8.         thread1.setRequest(request);  
  9.         TestThread thread2=new TestThread();  
  10.         thread2.setRequest(request);  
  11.         Thread t1=new Thread(thread1);  
  12.         Thread t2=new Thread(thread2);  
  13.         t1.start();  
  14.         t2.start();  
  15.     }  
  16. }  

   thread1thread2設定了同一個request物件,正常來說這兩個物件呼叫run方法時輸出的隨機值應該為null(因為設定給這兩個物件的request並沒有設定d的值)。然而事實上這兩個執行緒在呼叫時不但輸出了隨機值而且隨機值還各不相同。這是因為request物件設定了代理,當呼叫request物件的service方法時,代理物件會從Threadlocal中獲取實際的request物件以替代呼叫當前的request物件。由於httprequest物件在處理執行緒中保持不變,因此controller通過呼叫httprequest物件的方法能夠獲取當前請求的引數。

   以上都是一家之言,下面將通過展現springmvc原始碼的形式證明以上的說法:

   ObjectFactoryDelegatingInvocationHandler類:該類是AutowireUtils的一個私有類,該類攔截了除了equalshashcode以及toString以外的其他方法,其中的objectFactoryRequestObjectFactory例項。

  1. private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {  
  2.         private final ObjectFactory objectFactory;  
  3.         public ObjectFactoryDelegatingInvocationHandler(ObjectFactory objectFactory) {  
  4.             this.objectFactory = objectFactory;  
  5.         }  
  6.         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  7.             String methodName = method.getName();  
  8.             if (methodName.equals("equals")) {  
  9.                 // Only consider equal when proxies are identical.  
  10.                 return (proxy == args[0]);  
  11.             }  
  12.             else if (methodName.equals("hashCode")) {  
  13.                 // Use hashCode of proxy.  
  14.                 return System.identityHashCode(proxy);  
  15.             }  
  16.             else if (methodName.equals("toString")) {  
  17.                 return this.objectFactory.toString();  
  18.             }  
  19.             try {  
  20.                 return method.invoke(this.objectFactory.getObject(), args);  
  21.             }  
  22.             catch (InvocationTargetException ex) {  
  23.                 throw ex.getTargetException();  
  24.             }  
  25.         }  
  26.     }  

   RequestObjectFactory類:其中currentReuqestAttributes負責從Threadlocal中獲取物件

  1. private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {  
  2.         public ServletRequest getObject() {  
  3.             return currentRequestAttributes().getRequest();  
  4.         }  
  5.         @Override  
  6.         public String toString() {  
  7.             return "Current HttpServletRequest";  
  8.         }  
  9.     }  

  既然需要從Threadlocal中獲取物件,那springmvc在何時向Threadlocal設定了該物件呢?分別在如下兩個類中完成:RequestContextListenerFrameworkServletRequestContextListener負責監聽servletcontext,當servletcontext啟動時,RequestContextListenerThreadlocal設定了httprequest物件。FrameworkServletDispatchServlet的基類,tomcat會在執行過程中啟動新的執行緒,而該執行緒中並沒有httprequest物件。因此servlet會在每次處理http請求的時候檢驗當前的Threadlocal中是否有httprequest物件,如果沒有則設定該物件。

   FrameworkServlet通過布林值previousRequestAttributes檢驗httprequest是否存在的程式碼:

  1. protected final void processRequest(HttpServletRequest request, HttpServletResponse response)  
  2.             throws ServletException, IOException {  
  3.         long startTime = System.currentTimeMillis();  
  4.         Throwable failureCause = null;  
  5.         // Expose current LocaleResolver and request as LocaleContext.  
  6.         LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();  
  7.         LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);  
  8.         // Expose current RequestAttributes to current thread.  
  9.         RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();  
  10.         ServletRequestAttributes requestAttributes = null;  
  11.         if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {  
  12.             requestAttributes = new ServletRequestAttributes(request);  
  13.             RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);  
  14.         }  
  15.         if (logger.isTraceEnabled()) {  
  16.             logger.trace("Bound request context to thread: " + request);  
  17.         }  
  18.         try {  
  19.             doService(request, response);  
  20.         }  
  21.         catch (ServletException ex) {  
  22.             failureCause = ex;  
  23.             throw ex;  
  24.         }  
  25.         catch (IOException ex) {  
  26.             failureCause = ex;  
  27.             throw ex;  
  28.         }  
  29.         catch (Throwable ex) {  
  30.             failureCause = ex;  
  31.             throw new NestedServletException("Request processing failed", ex);  
  32.         }  
  33.         finally {  
  34.             // Clear request attributes and reset thread-bound context.  
  35.             LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);  
  36.             if (requestAttributes != null) {  
  37.                 RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);  
  38.                 requestAttributes.requestCompleted();  
  39.             }  
  40.             if (logger.isTraceEnabled()) {  
  41.                 logger.trace("Cleared thread-bound request context: " + request);  
  42.             }  
  43.             if (logger.isDebugEnabled()) {  
  44.                 if (failureCause != null) {  
  45.                     this.logger.debug("Could not complete request", failureCause);  
  46.                 }  
  47.                 else {  
  48.                     this.logger.debug("Successfully completed request");  
  49.                 }  
  50.             }  
  51.             if (this.publishEvents) {  
  52.                 // Whether or not we succeeded, publish an event.  
  53.                 long processingTime = System.currentTimeMillis() - startTime;  
  54.                 this.webApplicationContext.publishEvent(  
  55.                         new ServletRequestHandledEvent(this,  
  56.                                 request.getRequestURI(), request.getRemoteAddr(),  
  57.                                 request.getMethod(), getServletConfig().getServletName(),  
  58.                                 WebUtils.getSessionId(request), getUsernameForRequest(request),  
  59.                                 processingTime, failureCause));  
  60.             }  
  61.         }  
  62.     }  

   RequestContextListenercontext初始化時通過requestInitialized函式向Threadlocal設定httprequest物件的程式碼:

  1. public void requestInitialized(ServletRequestEvent requestEvent) {  
  2.         if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {  
  3.             throw new IllegalArgumentException(  
  4.                     "Request is not an HttpServletRequest: " + requestEvent.getServletRequest());  
  5.         }  
  6.         HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();  
  7.         ServletRequestAttributes attributes = new ServletRequestAttributes(request);  
  8.         request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);  
  9.         LocaleContextHolder.setLocale(request.getLocale());  
  10.         RequestContextHolder.setRequestAttributes(attributes);  
  11.     }