SpringMVC處理靜態文件源碼分析

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

#1 HandlerMapping和HandlerAdapter的來由html

用過python Django框架的都知道Django對於訪問方式的配置就是,一個url路徑和一個函數配對,你訪問這個url,就會直接調用這個函數,簡單明瞭java

然而對於SpringMVC框架來講,因爲java的面向對象,就要找到對應的類以及對應的方法,因此就須要分紅2步走python

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

  • 第二步 找到了對應的handler以後,咱們該調用這個handler的哪一個方法呢?這就須要HandlerAdapter來決定spring

#2 經常使用的HandlerMapping和HandlerAdapter簡單介紹tomcat

##2.1 HandlerMapping接口設計和實現mvc

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

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

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>

    當咱們訪問 http://localhost:8080/index 時,就直接找到這個bean做爲handler。

  • SimpleUrlHandlerMapping : 上述只能配置一個url對應的bean,SimpleUrlHandlerMapping就能夠配置多個,功能上更強大,它內部有一個Map<String, Object> 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,還要找到對應的方法。它們具體詳細的內容,能夠參考個人以前的博客 mvc:annotation-driven以及@Controller和@RequestMapping的那些事

接下來就輪到重點了(上面的鋪墊夠長的了,哈哈) #3 mvc:resources源碼分析

來看下通常的mvc:resources的使用,以下:

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

而後來看源碼。首先要再次聲明下,全部在xml中配置的標籤,都會有對應的BeanDefinitionParser的實現類來進行處理,對於mvc:resources標籤,對應的實現類是ResourcesBeanDefinitionParser,查看其中的源碼(這裏再也不列出,自行去查看),能夠知道

註冊了一個SimpleUrlHandlerMapping(上文已提到)。它是擁有一個Map<String, Object> 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<String, Object> 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均可以正常訪問到,以下

    訪問a.html 訪問a.jsp

  • DispatcherServlet配置爲 /* 的時候,a.html能夠正常訪問到,a.jsp就不行了,以下

    訪問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的源碼內容進行返回
相關文章
相關標籤/搜索