淺談從純Servlet到Spring的請求分發機制

本文將分享從純 Servlet 時代到 Spring 框架時代的過程中,關於請求分發的一些思考。

在講請求分發之前先梳理一下一個Web請求的交互邏輯:

  1. 首先用戶在客戶端發送一個請求到服務器。
  2. 這個請求首先會經過操作系統的 TCP/IP 協議棧解析後發送至某一個端口
  3. 在該端口運行著一個 Web 應用服務器(假設是 Tomcat)
  4. 接著 Tomcat 會把請求根據請求路徑傳送給對應的 Servlet 處理(要注意的是,Web 服務器本身是不處理請求的,比如說 Tomcat,它只負責分發請求)

1.Servlet時期的請求分發

在還沒有 spring 框架的時候,只能單純用 Servlet 處理請求。

具體做法是:把 Servlet 及其映射路徑配置在一個叫 web.xml 的配置文件中,當服務器啟動時,Tomcat 會自動讀取這個文件,然後根據文件中的配置,把請求分配到對應的 Servlet。

這個時候請求分發的工作是在 Tomcat 中完成的,通常一個業務對應一個 Servlet。比如說關於用戶的增刪改查,對應的 Servlet 很有可能是這樣子的:

  1. AddUserServlet
  2. DeleteUserServlet
  3. UpdateUserServlet…

對應的結構圖如下:

淺談從純Servlet到Spring的請求分發機制

這和我們如今開發中的架構很不一樣,因為現在通常是一個類裡面包含了對同個業務所有處理邏輯。比如說對用戶的增刪改查都放在同一個類裡,用不同的方法區分。

但在以前,一個 Servlet 裡面通常只有一個 service 是有用的,其他和生命週期相關的方法基本只是給一個空殼的重寫,所以一個 service 方法就對應一個業務處理邏輯。這種分類方法不僅給 web 服務器帶來很大負擔的,而且會導致 web.xml 文件十分龐大。(User 的增刪改查邏輯對應4個 Servlet,要是能把和 User 相關的處理邏輯分發給一個大的 UserServlet 就好了)

後來有人找到了把和 User 相關的處理邏輯分發給一個大的 UserServlet 的方法。具體方法就是使用 Java 中的反射。詳細代碼如下:

<code>/** * 該Servlet不需要進行配置,因為該Servlet從來不需要被直接訪問,使用來被繼承的 * 可以定義為abstract class */public abstract class BaseServlet extends HttpServlet{@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {//解決post表單中文亂碼問題request.setCharacterEncoding("utf-8");//獲取method屬性的值(方法名)String methodName = request.getParameter("method");if(methodName == null || methodName.trim().isEmpty()) {throw new RuntimeException("您沒有傳遞method參數!無法確定您想要調用的方法!");}//使用反射調用方法try {//獲取當前Servlet的Class信息Class clazz = this.getClass();//實際訪問的Servlet,不是BaseServlet,是BaseServlet的子類比如UserServlet//使用反射創建對象//Object obj = clazz.newInstance();//獲取方法Method method = clazz.getMethod(methodName, HttpServletRequest.class,HttpServletResponse.class);//使用反射執行方法method.invoke(this, request,response);} catch (Exception e) {e.printStackTrace();} }}/<code>

這樣一來,分發請求的結構就變成了這樣:

淺談從純Servlet到Spring的請求分發機制

也就是說同類業務的請求分配給同一個 Serlvet,當具體到細分的業務邏輯時,使用不同的方法進行區分;在運行的時候使用反射調用具體的方法。

這麼做能明顯減輕服務器分發請求的壓力,也讓 web.xml 文件變得更加簡潔。而且同類型的業務邏輯能夠編寫在同一個類中,方便管理維護。但是有沒有可能做得更好呢?

2.Spring框架的請求分發

先用一幅圖來描述 Spring 框架的請求分發,圖片通俗易懂:

淺談從純Servlet到Spring的請求分發機制

從圖中可以看到 web 服務器此時已經不用考慮分發請求的問題了,它只需要把請求發送給 DispatcherServlet 即可。請求分發的任務將落到 DispatcherServlet 身上。

而 DispatchServlet 也不再是把請求分發給其他 Servlet,而是根據請求路徑分發給對應的 Controller,然後再分發到 Controller 中對應的處理方法。

下面給出一個迷你簡寫版的 DispatchServlet 代碼:

<code>public class DispatcherServlet implements Servlet {    @Override    public void init(ServletConfig servletConfig) throws ServletException {}    @Override    public ServletConfig getServletConfig() {        return null;    }    @Override    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {        for(MappingHandler mappingHandler : HandlerManager.mappingHandlerList){            try{                if (mappingHandler.handle(servletRequest, servletResponse)){                    return;                }            } catch (IllegalAccessException e) {                e.printStackTrace();            } catch (InstantiationException e) {                e.printStackTrace();            } catch (InvocationTargetException e) {                e.printStackTrace();            }        }    }    @Override    public String getServletInfo() {        return null;    }    @Override    public void destroy() {}}/<code>

從上面的代碼可以看到 DispatcherServlet 處理請求的邏輯也很簡單,就是根據請求路徑找到對應可以處理的處理器。

至於 MappingHandler 是怎麼初始化的,這就涉及到 Spring 框架中控制反轉和依賴注入的知識了。簡單來說就是,在開發的過程中,我們不再把請求路徑配置在 web.xml 裡面,而是通過註解的方式配置在每一個個處理方法的上方。當 spring 框架啟動後,會根據註解把每個處理方法初始化為一個個 MappingHandler(裡面包括請求路徑和處理邏輯),供 DispatcherServlet 調配使用。

這樣的請求分發方式無疑比 Serlvet 時期的請求分發更加靈活,強大。它把請求分發的工作從 web 服務器上移植到框架中,可擴展性更強。而且配合 spring 框架的控制反轉機制,把處理邏輯類的初始化及生命週期控制交給框架管理,更加安全高效。


分享到:


相關文章: