1. 程式人生 > >SpringMVC處理靜態檔案原始碼分析

SpringMVC處理靜態檔案原始碼分析

SpringMVC處理靜態資源,主要是兩個標籤,mvc:resources和 mvc:default-servlet-handler。在詳細說明他們的原理之前,需要先簡單說明下SpringMVC中請求處理機 制:HandlerMapping和HandlerAdapter。

1 HandlerMapping和HandlerAdapter的來由

然而對於SpringMVC框架來說,由於java的面向物件,就要找到對應的類以及對應的方法,所以就需要分成2步走

第一步 先找到url對應的處理類,叫handler,這裡就用到HandlerMapping來尋找

第二步 找到了對應的handler之後,我們該呼叫這個handler的哪個方法呢?這就需要HandlerAdapter來決定

2 常用的HandlerMapping和HandlerAdapter簡單介紹

2.1 HandlerMapping介面設計和實現


public interface HandlerMapping {
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

根據request請求,找到對應的HandlerExecutionChain,HandlerExecutionChain是handler和攔截器的結合。如下:

public class HandlerExecutionChain {
    private final Object handler;
    private List<HandlerInterceptor> interceptorList;

即針對某個請求,會有對應的handler和攔截器來處理。HandlerMapping僅僅是找到對應的handler和攔截器罷了,它並不限制 handler的型別,任何一個存在於Spring的IOC容器中的bean都可以成為handler,所以這個handler是Object。 下面來看下常見的幾個HandlerMapping實現:

  • BeanNameUrlHandlerMapping : 對url直接配置一個bean作為這個url的handler。如在xml中如下配置
<bean name="/index" class="com.lg.mvc.HomeAction"></bean>
  • SimpleUrlHandlerMapping : 上述只能配置一個url對應的bean,SimpleUrlHandlerMapping就可以配置多個,功能上更強大,它內部有一個Map urlMap,存放著各個url對應的handler,如下

<bean id="handler1" class="XXXX"/>
<bean id="handler2" class="XXXXX"/>
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
     <property name="urlMap">
         <map>
            <entry key="/user/login.do" value-ref="handler1"/>
            <entry key="/admin/admin.do" value-ref="handler2"/>
         </map>
     </property>
</bean>

2.2 HandlerAdapter介面設計和實現

public interface HandlerAdapter {
    boolean supports(Object handler);
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response,
             Object handler) throws Exception;
}

根據HandlerMapping找到了handler之後,我們該呼叫handler的哪個方法呢?handler又有哪些方法呢?這裡就需要採 用介面卡的模式,對不同的handler進行不同的處理。因此HandlerAdapter的supports方法首先判斷這個handler是否是我能 支援的,如果能支援,那我就按照我的處理模式來處理,即呼叫上述的handle方法。

下面來看下常見的幾個HandlerAdapter的實現:

  • SimpleServletHandlerAdapter : 它支援的handler必須是Servlet,這樣的話該handler就必然有service(request, response)方法,所以就會呼叫handler的service(request, response)方法來處理請求,原始碼如下

public class SimpleServletHandlerAdapter implements HandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        return (handler instanceof Servlet);
    }
    @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response
            , Object handler)throws Exception {
        ((Servlet) handler).service(request, response);
        return null;
    }
}
  • SimpleControllerHandlerAdapter : 它支援的handler必須是Controller,Controller介面定義了一個ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)方法,所以我們知道該handler必然有一個handleRequest方法,就呼叫它來處理請求,原始碼如下

public class SimpleControllerHandlerAdapter implements HandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        return (handler instanceof Controller);
    }
    @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response
            , Object handler)throws Exception {
        return ((Controller) handler).handleRequest(request, response);
    }
}
  • HttpRequestHandlerAdapter : 它支援的handler必須是HttpRequestHandler,HttpRequestHandler介面定義了一個void handleRequest(HttpServletRequest request, HttpServletResponse response)方法,所以就知道該呼叫這個handler的handleRequest方法,原始碼如下:

public class HttpRequestHandlerAdapter implements HandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        return (handler instanceof HttpRequestHandler);
    }
    @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response
            , Object handler)throws Exception {
        ((HttpRequestHandler) handler).handleRequest(request, response);
        return null;
    }
}

以上的幾個HandlerMapping和HandlerAdapter屬於SpringMVC最初的設計思路。即HandlerMapping和 HandlerAdapter毫無關係,HandlerMapping只負責找到對應的handler,HandlerAdapter負責找到 handler的哪個方法。然而隨著註解的興起,即@RequestMapping註解直接標註請求對應某個類的某個方法,使得後來的 HandlerMapping和HandlerAdapter即 DefaultAnnotationHandlerMapping、AnnotationMethodHandlerAdapter 、RequestMappingHandlerMapping 、RequestMappingHandlerAdapter(前兩者已被後兩者取代)不再像之前的思路那樣,開始爭奪權力 了,RequestMappingHandlerMapping 在尋找對應的handler時,不僅要匹配到對應的handler,還要找到對應的方法。

接下來就輪到重點了(上面的鋪墊夠長的了,哈哈)

3 mvc:resources原始碼分析

來看下一般的mvc:resources的使用,如下:

<mvc:resources location="/WEB-INF/views/css/**" mapping="/css/**"/>

然後來看原始碼。首先要再次宣告下,所有在xml中配置的標籤,都會有對應的BeanDefinitionParser的實現類來進行處理,對於 mvc:resources標籤,對應的實現類是ResourcesBeanDefinitionParser,檢視其中的原始碼(這裡不再列出,自行去查 看),可以知道

註冊了一個SimpleUrlHandlerMapping(上文已提到)。它是擁有一個Map urlMap的,它把mvc:resources標籤中的mapping屬性作為key,把ResourceHttpRequestHandler作為 handler。即/css/**類似的url請求,會由這個SimpleUrlHandlerMapping匹配到 ResourceHttpRequestHandler上。

再看下,到底呼叫ResourceHttpRequestHandler的哪個方法來處理請求呢?

ResourceHttpRequestHandler實現了HttpRequestHandler,即是上文提到的 HttpRequestHandlerAdapter支援的handler型別,所以就會呼叫ResourceHttpRequestHandler的 void handleRequest(HttpServletRequest request, HttpServletResponse response)方法

其實很容易就明白了,ResourceHttpRequestHandler會根據mvc:resources標籤中的location屬性作為目 錄,去尋找對應的資源,然後返回資源的內容。這裡就不再詳細說明了,可以自行檢視ResourceHttpRequestHandler的所實現的 handleRequest方法。

4 mvc:default-servlet-handler 原始碼分析

同理,mvc:default-servlet-handler標籤對應的BeanDefinitionParser的實現類是DefaultServletHandlerBeanDefinitionParser。

這裡註冊了SimpleUrlHandlerMapping,它的Map urlMap中存放了一個 key為/* ,對應的handler為DefaultServletHttpRequestHandler。即請求路徑匹配 / * 的時候,這個SimpleUrlHandlerMapping會交給DefaultServletHttpRequestHandler來處理。這種情況 一般是其他HandlerMapping無法匹配處理,最後才無奈交給DefaultServletHttpRequestHandler。

來看下DefaultServletHttpRequestHandler是怎麼處理的:

它同樣實現了HttpRequestHandler介面,擁有void handleRequest(HttpServletRequest request, HttpServletResponse response)方法,如下:


@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
 
    RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
    if (rd == null) {
        throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" +
                this.defaultServletName +"'");
    }
    rd.forward(request, response);
}

我們可以看到,這裡其實就是轉發給了web容器自身的servlet。這個servlet名稱可以在mvc:default-servlet-handler標籤中進行配置,如果沒有配置,採用預設的配置,如下:


/** Default Servlet name used by Tomcat, Jetty, JBoss, and GlassFish */
private static final String COMMON_DEFAULT_SERVLET_NAME = "default";
 
/** Default Servlet name used by Google App Engine */
private static final String GAE_DEFAULT_SERVLET_NAME = "_ah_default";
 
/** Default Servlet name used by Resin */
private static final String RESIN_DEFAULT_SERVLET_NAME = "resin-file";
 
/** Default Servlet name used by WebLogic */
private static final String WEBLOGIC_DEFAULT_SERVLET_NAME = "FileServlet";
 
/** Default Servlet name used by WebSphere */
private static final String WEBSPHERE_DEFAULT_SERVLET_NAME = "SimpleFileServlet";

即tomcat、Jetty等,在容器啟動的時候,自身就預設註冊了一個name叫default的servlet,可以從我的上一篇文章進行瞭解tomcat的url-pattern的原始碼分析。DefaultServletHttpRequestHandler就是轉發給這些servlet。

其實這個時候,請求先經過tomcat的servlet的url-pattern的匹配,進入到了SpringMVC,然後經過SpringMVC 的HandlerMapping的一系列匹配,沒有對應的handler匹配,導致又再次轉發給tomcat等預設的servlet上了,繞了很大的彎, 所以要儘量避免這樣的操作。

5 結合tomcat的url-pattern來綜合案例

這裡舉一個案例進行分析,在tomcat釋出的根目錄中,有一個a.html和a.jsp檔案,以及一個SpringMVC專案,如下: 在這裡插入圖片描述 其中SpringMVC專案配置了mvc:default-servlet-handler標籤,接下來以SpringMVC的DispatcherServlet的兩種配置進行說明,分別是

/ 和 /* 兩種方式

結果分別是:

  • DispatcherServlet配置為 / 的時候,a.html和a.jsp都可以正常訪問到,如下 在這裡插入圖片描述 在這裡插入圖片描述
  • DispatcherServlet配置為 /* 的時候,a.html可以正常訪問到,a.jsp就不行了,如下 在這裡插入圖片描述 在這裡插入圖片描述

分析如下:

我們知道 /* 的優先順序大於 .jsp的優先順序,.jsp的優先順序大於 / (可以由我的上一篇文章瞭解到tomcat的url-pattern的原始碼分析),在這個前提下

訪問a.html時:

  • 當DispatcherServlet配置為 / 的時候,tomcat仍會選擇SpringMVC的DispatcherServlet來處理a.html-》它也處理不了,交給預設配置的 mvc:default-servlet-handler來處理-》轉發到tomcat預設的servlet的,即DefaultServlet來處理 -》DefaultServlet去尋找有沒有該檔案,找到了,返回檔案內容
  • 當DispatcherServlet配置為 /* 的時候,tomcat仍然是選擇SpringMVC的DispatcherServlet來處理a.html,同上面是一樣的過程

訪問a.jsp時:

  • 當DispatcherServlet配置為 / 的時候,tomcat會優先選擇自己已經預設註冊的JspServlet來處理-》JspServlet翻譯檔案內容,返回
  • 當DispatcherServlet配置為 /* 的時候,tomcat會選擇SpringMVC的DispatcherServlet來處理a.jsp-》發現SpringMVC找不到匹配的 handler,交給配置的mvc:default-servlet-handler來處理-》轉發到tomcat預設的servlet的,即 DefaultServlet來處理-》DefaultServlet僅僅將a.jsp的原始碼內容進行返回