天天看點

Servlet 接口(第二篇)

Servlet接口是Java Servlet API 最上層抽象接口,其他都是擴充這個接口,然而Servlet包本身實作這個接口(分别是GenericServlet 和 HttpServlet),為啥要Servlet要實作兩個接口,友善開發者擴充,也算提供一個示例,每個接口都有一個正常實作,開發者有兩種選擇,要麼重寫,要麼用預設的。一般開發者都是擴充HttpServlet,畢竟web浏覽器HTTP協定是天下
  • Servlet 接口(第二篇)

1、請求處理方法

  • Servlet接口定義一個方法 service, 顧名思義就是服務用戶端請求的,一個請求過來,servlet容器會将請求路由到某個servlet執行個體上。
  • 在想一個問題,servlet執行個體一般應該比用戶端請求要少,難道多請求不處理嗎?所有在實作這個servlet方法時候需要考慮到并發請求情況,通常處理方式,web伺服器會利用多線程去調用servlet的方法。

1.1、HTTP 特有處理請求的方法

  • Servlet 接口(第二篇)
  • HttpServlet實作了service()方法,這個抽象類定義7個方法對應HTTP 7種請求方式處理
  • doGet – Get請求
  • dePost – Post請求
  • doPut – Put請求(如果是restful風格的話,這個應該是修改)
  • doDelete – Delete請求
  • doHead – Head請求 (擷取請求頭資訊)
  • doOptions – Options請求 (預先請求),一般出現ajax表單送出時候
  • doTrace – Trace請求(這個沒有用過)

一般你隻需關心 doGet 和doPost方法

1.2、添加額外的方法

  • doPut 和doDelete 隻是為了支援HTTP/1.1支援的新特性
  • doHead 其實就是doGet,隻是傳回是頭上資訊
  • doOptions, 試探請求,(跨域經常會出現)
  • doTrace 所有請求頭資訊都會發送

1.3、有條件GET方式支援

  • getLastModified方法,簡單來說,用戶端來請求伺服器某個資源,如果這個資源沒有修改過,還需要傳給用戶端嗎?顯然可以不用傳,客戶已經緩存這個資源,減少網絡傳輸

2、執行個體的數量

  • 一般來說Servlet容器中,每種servlet有且隻存在一個Servlet執行個體,但是如果它實作SingleThreadModel接口,就有多個執行個體,(簡單來說,一般情況下是單例,如果你實作SingleThreadModel接口,就原生了)

2.1、需要注意Single Thread Model接口

  • 簡單來說不建議使用實作Single Thread Model接口, HttpSession在多個servlet可能共享這個session,這就一緻性問題,同時修改,本質上這個限制它使用。線程不安全的

3、Servlet 生命周期

  • servlet從建立,初始化,服務請求,銷毀,對應API方法(init, service, destroy)

3.1、加載和執行個體化

  • 不管立即和延時,servlet 容器有義務去加載servlet的類和執行個體化servlet執行個體。

3.2、初始化

  • 執行個體化之後,需要進行初始化操作,初始化啥呢?無非是建構可以提供服務的環境,肯定就有一些配置資訊,那麼配置資訊是怎麼加載到servlet中去,是以需要實作ServletConfig, 當然也可以實作ServletContext接口,詳情需要檢視第四章關于ServletContext接口介紹

3.2.1、初始化時發生異常

  • 可能會抛出UnavailableException 或 ServletException
  • 如果初始化不成功,servlet 容器會釋放資源,destroy方法不會調用,沒有初始化,何來銷毀
  • 還有種可能,執行個體化部分成功,部分失敗,這個時候需要等初始化過程階段完成之後,等資源釋放之後,才可以新增一個新servlet執行個體。

3.2.2、工具考慮

  • 工具靜态初始化和直接調用init方法有差別
  • 如果調用靜态初始化之後,并不代表servlet已經起來,可以去做連接配接資料庫相關操作

3.3、請求處理

  • 支援request/response 模式, 那麼servlet定義兩個對象,ServletRequest和ServletResponse
  • 當然對于HTTP協定,定義HttpServletRequest和HttpServletResponse
  • 注意:人可以一生無所事事,servlet也有可能在整個生命周期不會處理任何請求

3.3.1、多線程的問題

  • 考慮并發通路的問題,但是又不推薦實作SingleThreadModel接口,這個要求容器能夠保證有且僅有一個請求線程去在執行service方法,servlet容器要麼通過序列實作或servlet執行個體池
  • 或者synchronized關鍵字修飾,但是它會影響性能(那到底采用什麼方式實作呢?)

3.3.2、在處理請求過程發生異常

  • 發生異常有兩種,一種是ServletException,一種是UnavailableException
  • ServletException 是處理請求過程中發生的異常,這個異常servlet容器需要采取适合的方式去清理這些請求
  • UnavailableException 這個異常表示服務處理不了請求,比如服務擠爆,是以這個不可用異常可能是臨時或者永久的
  • 如果UnavailableException暗示這個servlet是永久不可用, 那麼Servlet容器就會移除這個servlet,調用destroy方法,釋放servlet執行個體的資源,任何請求請求到這個servlet上都會傳回404(SC_NOT_FOUND)
  • 如果UnavailableException暗示這個servlet是臨時不可用, Servlet容器将在臨時不可用階段路由其他servlet提供服務,這個時候由這個Servlet拒絕将會傳回SC_SERVICE_UNAVAILABLE(503)狀态,然後header會帶有retry-after字段表示等會重試
  • 當然容器實作可以把UnavailableException都當做永久不可用,按永久不可用處理

3.3.3、異步處理

  • 異步就是為提高web提供服務能力
  • 異步處理過程
    1. 一個請求經過正常過濾器驗證等等到達servlet服務(客人風塵仆仆騎馬來到一家龍門客棧)
    2. servlet根據請求參數和内容去确定請求性質(店長:請問你打尖還是住店?)
    3. servlet根據請求做一些事情,比如請求資料庫擷取資料,就需要等待擷取JDBC連接配接(招呼小二過來,去二樓打掃一間房,這個肯定需要花費時間)
    4. servlet直接傳回不産生響應(response)結果(店長:先坐下來吃點東西,房間馬上收拾好)
    5. 經過一段時間,需要資源已經準備好,處理該事件的線程繼續同一線程繼續處理或使用AsyncContext對象分派給容器處理(小二告訴店長已經收拾好房間,店長通知客人入住)
  • ServletRequest 類核心方法
    • /**
       這個方法是将一個請求放入到異步模型中,然後請求對象和響應對象,以及getAsyncTimeout方法傳回值去初始化一個AsynContext對象
      **/
      public AsyncContext startAsync(ServletRequest req, ServletResponse res)
      
        /**
       使用原生對象去進行異步處理,你應該展現使用者在調用這個方法之後需要flush一下,防止資料丢失
      **/
      public AsyncContext startAsync()
        
       /**
       擷取異步模型時,異步上下文資訊,如果是非異步模型,調用這個方法是非法的
      **/
      public AsyncContext getAsyncContext()  
        
         /**
       表示請求是否支援異步,如果一個請求經過不支援異步過濾器和servlet,這個傳回false
      **/
      public boolean isAsyncSupported()
        
       /**
       如果異步處理已經開始,傳回true,否則傳回false,比如調用AsyncContext.dispatch或調用AsynContext.complete方法
      **/
      public boolean isAsyncStarted()  
        
         /**
      擷取派發類型,DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ERROR.
      **/
      public DispatcherType getDispatcherType() 
                 
  • AsyncContext 類, 這個類代表執行異步操作上下文,它在ServletRequest.startAsync建立的
    • public ServletRequest getRequest()  擷取請求
        public ServletResponse getResponse()
        /**
      		預設是30000, 
        */
        public void setTimeout(long timeoutMilliseconds) 
         /**
      		注冊一個給定監聽器去通知onTimeout, onError, onComplete或onStartAsync, 
        */
        public void addListener(AsyncListener listener, ServletRequest req, ServletResponse res) 
        
        /**
         轉發請求
        */
        public void dispatch(String path) 
        
        /**
        利用 AsyncContext去轉發請求
        */
        public void dispatch() 
        
        
         /**
          表示請求是不是原始,有沒有經過包裝,那麼怎麼算是經過包裝?
        */
        public boolean hasOriginalRequestAndResponse()
        
        /**
          容器會分一個線程去處理Runnable r的run方法内容, 這個線程可能來自于線程池,當然容器也有可能将上下文資訊傳遞到Runnable對象中
        */
        public void start(Runnable r)
        
         /**
          異步的處理時候,由servlet去調用這個方法,送出和關閉響應,而同步請求則是需要容器去調用這個方法。
        */
        public void complete()
        
        
                 
    • // 例子1
      // 請求到 /url/A
      AsyncContext ac = request.startAsync();
      // ...
      ac.dispatch(); // 将請求異步轉發到  /url/A 處理
      
      // 例子2
      // 請求 /url/A
      // 轉發 /url/B
      request.getRequestDispatcher("/url/B").forward(request, response);
      // 在Forward的目标操作中開啟異步操作
      AsyncContext ac = request.startAsync();
      ac.dispatch();  // 異步轉發到 /url/A
      
      // 例子3
      // 請求 /url/A
      // 轉發 /url/B
      request.getRequestDispatcher("/url/B").forward(request, response);
      // 在Forward的目标操作中開啟異步操作
      AsyncContext ac = request.startAsync(request, response);
      ac.dispatch();  // 異步轉發到 /url/B
      
      
                 
    • 在執行dispatch方法會發生異常,是以需要妥善處理
      • AsyncListener.onError(AsyncEvent) , 處理AsyncEvent事件異常
      • 如果監聽器調用AsyncContext.complete或AsyncContext.dispatch方法,那麼就會有異常code, HttpServletResponse.SC_INTERNAL_SERVER_ERROR
      • 如果比對不到錯誤頁,或者錯誤頁沒有調用AsyncContext.complete()方法, 容器必須調用AsyncContext.complete
  • ServletRequestWrapper
    • /**
         遞歸去檢查是否包裝指定的ServletRequest對象, 有傳回true
        */
        public boolean isWrapperFor(ServletRequest req)
                 
  • ServletResponseWrapper
    • /**
         遞歸去檢查是否包裝指定的ServletResponse對象
        */
        public boolean isWrapperFor(ServletResponse req)
                 
  • AsyncListener
    • /**
         通知監聽器異步操作已經完成
        */
       public void onComplete(AsyncEvent event)
         
       /**
         通知監聽器異步操作已經發生了逾時
        */
       public void onTimeout(AsyncEvent event)
         
       /**
         通知監聽器處理發生異常
        */
       public void onError(AsyncEvent event)   
      
         /**
          調用ServletRequest.startAsync進行通知監聽器
        */
       public void onStartAsync(AsyncEvent event)   
         
                 
    • 異步發生逾時,容器需要執行如下步驟
      1. 首先會調用AsyncListener.onTimeout方法,這個監聽器是在ServletRequest初始化注冊的
      2. 如果沒有監聽器去調用AsyncContext.complete()或AsyncContext.dispatch方法,将會産生一個錯誤狀态code,HttpServletResponse.SC_INTERNAL_SERVER_ERROR.
      3. 沒有比對到錯誤頁,或錯誤頁沒有調用AsyncContext.dispatch或AsyncContext.complete()方法,那麼容器就需要調用這個AsyncContext.complete方法。(事情總有人做,你不做,就是别人做)
    • AsyncListener 是獨立,不會互相影響,如果一個AsyncListener發生異常,不會影響到另一個AsyncListener的執行
    • 異步處理狀态流轉圖
      • Servlet 接口(第二篇)

3.3.4、線程安全

  • 除了startAsync 和complete 方法, 其他方法都有可能不是線程安全的

3.3.5、更新處理過程

  • 在HTTP/1.1,通過header切換協定,也就是可以自定義協定,但是容器是不知道的,HttpUpgradeHandler中進行處理
  • 開發者需要保證ServletInputStream 和ServletOutputStream是線程安全的

4、Service的結束

  • 簡單來說就是要調用destroy() 方法去釋放資源