springmvc中request的線程安全問題

 

SpringMvc學習心得(四)springmvc中request的線程安全問題

標籤: springspring mvc框架線程安全
 分類:

    servlet是單例的,而tomcat則是在多個線程中調用servlet的處理方法。所以若是servlet存在實例對象,那麼就會引出線程安全的問題。而springmvc容許在controller類中經過@Autowired配置request、response以及requestcontext等實例對象。這種配置方法是否線程安全?答案是——這種配置方法是線程安全的,request、response以及requestcontext在使用時不須要進行同步。而根據spring的默認規則,controller對於beanfactory而言是單例的。即controller只有一個,controller中的request等實例對象也只有一個。然而tomcat依舊會以多線程的方式訪問controller。這種作法彷佛並不能保證線程安全。咱們如何理解這一矛盾?web

   在解釋controller線程安全這一問題以前須要首先了解以下的一些問題和概念:spring

   1.servlet的request域的問題: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類,並在doget和dopost方法上面打上斷點。你會發現若是是同一個進程,即使發起屢次訪問,request對象的id始終不變。讀者能夠親自嘗試,用以驗證本人說法的真僞。tomcat

   2.Threadlocal類:該對象包含兩個關鍵函數:set(Object obj)和get()。這兩個函數與調用該函數的線程相關,set方法將某一對象「注入」到當前線程中,而get方法則是從當前線程中獲取對象。安全

   3.InvocationHandler接口:這是springmvc保證request對象線程安全的核心。經過實現該接口,開發者可以在Java對象方法執行時進行干預,搭配Threadlocal就可以實現線程安全。多線程

  下面將經過例子介紹springmvc如何保證request對象線程安全:mvc

   Httprequest接口:框架

[java]  view plain  copy
 
  1. public interface HttpRequest {  
  2.     public void service();  
  3. }  
   HttpRequestImpl類:對httprequest接口的具體實現,爲了區別不一樣的HttpRequestImpl對象,本人爲HttpRequestImpl設置了一個Double對象,若是不設置該對象,其默認爲null

 

 

[java]  view plain  copy
 
  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.   
  8. }  
   ThreadLocalTest類:負責向ThreadLocal設置對象和獲取對象,本人設置ThreadLocal對象爲static,所以ThreadLocalTest類中只能有一個ThreadLocal對象。
[java]  view plain  copy
 
  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對象中設置和獲取對象
[java]  view plain  copy
 
  1. public class Factory{  
  2.     private static Factory factory=new Factory();  
  3.     private Factory(){  
  4.           
  5.     }  
  6.     public static Factory getInstance(){  
  7.         return factory;  
  8.     }  
  9.     public HttpRequest getObject(){  
  10.         return (HttpRequest)ThreadLocalTest.get();  
  11.     }  
  12.     public void setObject(HttpRequest request){  
  13.         ThreadLocalTest.set(request);  
  14.     }  
  15. }  
  Delegate類:該類實現了InvocationHandler接口,並實現了invoke方法
[java]  view plain  copy
 
  1. import java.lang.reflect.InvocationHandler;  
  2. import java.lang.reflect.Method;  
  3. import java.lang.reflect.Proxy;  
  4.   
  5.   
  6. public class Delegate implements InvocationHandler{  
  7.     private Factory factory;  
  8.       
  9.     public Factory getFactory() {  
  10.         return factory;  
  11.     }  
  12.   
  13.     public void setFactory(Factory factory) {  
  14.         this.factory = factory;  
  15.     }  
  16.   
  17.     @Override  
  18.     public Object invoke(Object proxy, Method method, Object[] args)  
  19.             throws Throwable {  
  20.         return method.invoke(this.factory.getObject(), args);  
  21.     }  
  22.       
  23. }  
   ProxyUtils類:該類是一個工具類,負責生成一個httprequest對象的代理

 

 

[java]  view plain  copy
 
  1. import java.lang.reflect.Proxy;  
  2.   
  3.   
  4. public class ProxyUtils {  
  5.     public static HttpRequest getRequest(){  
  6.         HttpRequest request=new HttpRequestImpl();  
  7.         Delegate delegate=new Delegate();  
  8.         delegate.setFactory(Factory.getInstance());  
  9.         HttpRequest proxy=(HttpRequest) Proxy.newProxyInstance(request.getClass().getClassLoader(), request.getClass().getInterfaces(), delegate);  
  10.         return proxy;  
  11.     }  
  12. }  
   TestThread類:該類用來模擬多線程調用controller的狀況,類中擁有一個靜態對象request。
[java]  view plain  copy
 
  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.       
  22. }  
   main:測試類

 

 

[java]  view plain  copy
 
  1. public class main {  
  2.   
  3.     /** 
  4.      * @param args 
  5.      */  
  6.     public static void main(String[] args) {  
  7.         HttpRequest request=ProxyUtils.getRequest();  
  8.           
  9.         TestThread thread1=new TestThread();  
  10.         thread1.setRequest(request);  
  11.           
  12.         TestThread thread2=new TestThread();  
  13.         thread2.setRequest(request);  
  14.           
  15.         Thread t1=new Thread(thread1);  
  16.         Thread t2=new Thread(thread2);  
  17.           
  18.         t1.start();  
  19.         t2.start();  
  20.     }  
  21. }  
   thread1和thread2設置了同一個request對象,正常來講這兩個對象調用run方法時輸出的隨機值應該爲null(由於設置給這兩個對象的request並無設置d的值)。然而事實上這兩個線程在調用時不但輸出了隨機值並且隨機值還各不相同。這是由於request對象設置了代理,當調用request對象的service方法時,代理對象會從Threadlocal中獲取實際的request對象以替代調用當前的request對象。因爲httprequest對象在處理線程中保持不變,所以controller經過調用httprequest對象的方法可以獲取當前請求的參數。

 

   以上都是一家之言,下面將經過展示springmvc源碼的形式證實以上的說法:dom

   ObjectFactoryDelegatingInvocationHandler類:該類是AutowireUtils的一個私有類,該類攔截了除了equals、hashcode以及toString之外的其餘方法,其中的objectFactory是RequestObjectFactory實例。ide

[java]  view plain  copy
 
  1. private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {  
  2.   
  3.         private final ObjectFactory objectFactory;  
  4.   
  5.         public ObjectFactoryDelegatingInvocationHandler(ObjectFactory objectFactory) {  
  6.             this.objectFactory = objectFactory;  
  7.         }  
  8.   
  9.         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  10.             String methodName = method.getName();  
  11.             if (methodName.equals("equals")) {  
  12.                 // Only consider equal when proxies are identical.  
  13.                 return (proxy == args[0]);  
  14.             }  
  15.             else if (methodName.equals("hashCode")) {  
  16.                 // Use hashCode of proxy.  
  17.                 return System.identityHashCode(proxy);  
  18.             }  
  19.             else if (methodName.equals("toString")) {  
  20.                 return this.objectFactory.toString();  
  21.             }  
  22.             try {  
  23.                 return method.invoke(this.objectFactory.getObject(), args);  
  24.             }  
  25.             catch (InvocationTargetException ex) {  
  26.                 throw ex.getTargetException();  
  27.             }  
  28.         }  
  29.     }  
   RequestObjectFactory類:其中currentReuqestAttributes負責從Threadlocal中獲取對象
[java]  view plain  copy
 
  1. private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {  
  2.   
  3.         public ServletRequest getObject() {  
  4.             return currentRequestAttributes().getRequest();  
  5.         }  
  6.   
  7.         @Override  
  8.         public String toString() {  
  9.             return "Current HttpServletRequest";  
  10.         }  
  11.     }  
  既然須要從Threadlocal中獲取對象,那springmvc在什麼時候向Threadlocal設置了該對象呢?分別在以下兩個類中完成:RequestContextListener和FrameworkServlet。RequestContextListener負責監聽servletcontext,當servletcontext啓動時,RequestContextListener向Threadlocal設置了httprequest對象。FrameworkServlet是DispatchServlet的基類,tomcat會在運行過程當中啓動新的線程,而該線程中並無httprequest對象。所以servlet會在每次處理http請求的時候檢驗當前的Threadlocal中是否有httprequest對象,若是沒有則設置該對象。

 

   FrameworkServlet經過布爾值previousRequestAttributes檢驗httprequest是否存在的代碼:

[java]  view plain  copy
 
  1. protected final void processRequest(HttpServletRequest request, HttpServletResponse response)  
  2.             throws ServletException, IOException {  
  3.   
  4.         long startTime = System.currentTimeMillis();  
  5.         Throwable failureCause = null;  
  6.   
  7.         // Expose current LocaleResolver and request as LocaleContext.  
  8.         LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();  
  9.         LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);  
  10.   
  11.         // Expose current RequestAttributes to current thread.  
  12.         RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();  
  13.         ServletRequestAttributes requestAttributes = null;  
  14.         if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {  
  15.             requestAttributes = new ServletRequestAttributes(request);  
  16.             RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);  
  17.         }  
  18.   
  19.         if (logger.isTraceEnabled()) {  
  20.             logger.trace("Bound request context to thread: " + request);  
  21.         }  
  22.   
  23.         try {  
  24.             doService(request, response);  
  25.         }  
  26.         catch (ServletException ex) {  
  27.             failureCause = ex;  
  28.             throw ex;  
  29.         }  
  30.         catch (IOException ex) {  
  31.             failureCause = ex;  
  32.             throw ex;  
  33.         }  
  34.         catch (Throwable ex) {  
  35.             failureCause = ex;  
  36.             throw new NestedServletException("Request processing failed", ex);  
  37.         }  
  38.   
  39.         finally {  
  40.             // Clear request attributes and reset thread-bound context.  
  41.             LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);  
  42.             if (requestAttributes != null) {  
  43.                 RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);  
  44.                 requestAttributes.requestCompleted();  
  45.             }  
  46.             if (logger.isTraceEnabled()) {  
  47.                 logger.trace("Cleared thread-bound request context: " + request);  
  48.             }  
  49.   
  50.             if (logger.isDebugEnabled()) {  
  51.                 if (failureCause != null) {  
  52.                     this.logger.debug("Could not complete request", failureCause);  
  53.                 }  
  54.                 else {  
  55.                     this.logger.debug("Successfully completed request");  
  56.                 }  
  57.             }  
  58.             if (this.publishEvents) {  
  59.                 // Whether or not we succeeded, publish an event.  
  60.                 long processingTime = System.currentTimeMillis() - startTime;  
  61.                 this.webApplicationContext.publishEvent(  
  62.                         new ServletRequestHandledEvent(this,  
  63.                                 request.getRequestURI(), request.getRemoteAddr(),  
  64.                                 request.getMethod(), getServletConfig().getServletName(),  
  65.                                 WebUtils.getSessionId(request), getUsernameForRequest(request),  
  66.                                 processingTime, failureCause));  
  67.             }  
  68.         }  
  69.     }  
   RequestContextListener在context初始化時經過requestInitialized函數向Threadlocal設置httprequest對象的代碼:
[java]  view plain  copy
 
  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.     }  
相關文章
相關標籤/搜索