1. 程式人生 > >SpringMVC -03- 詳解 xml 檔案 + DispatcherServlet + 攔截器

SpringMVC -03- 詳解 xml 檔案 + DispatcherServlet + 攔截器

SpringMVC -03- 詳解 xml 檔案 + DispatcherServlet + 攔截器

前面,在上一篇文章:Spring MVC -02- 第一個 Spring MVC 應用 Hello world! 中,已經成功執行第一個 Spring MVC 應用了!

那麼,之前提到的 web.xml 檔案中,前端控制器 DispatcherServlet 在截獲請求後做了什麼工作呢?DispatcherServlet 又是如何分派請求的呢?下面進行詳解:

(本篇部分內容來自網路,部分來自瘋狂軟體的那套書籍,沒聽過不要緊,應該不是很厲害,我感覺還好,有點基礎再讀更好,像博主這樣笨就只能邊百度,邊看書嘍)

DispatcherServlet 前置控制器

使用 Spring MVC,配置 DispatcherServlet 是第一步。DispatcherServlet 是一個Servlet,所以可以配置多個 DispatcherServlet。DispatcherServlet 是前置控制器,配置在 web.xml 檔案中的。攔截匹配的請求,Servlet 攔截匹配規則要自已定義,把攔截下來的請求,依據某某規則分發到目標 Controller(我們寫的Action)來處理

例如這樣的配置:

<servlet>
		<servlet-name>springmvc</
servlet-name
>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/mvc-config.xml</param-value> </init-param> <load-on-startup
>
1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>

DispatcherServlet 是一個Servlet,可以同時配置多個,每個 DispatcherServlet 有一個自己的 WebApplicationContext 上下文,這個上下文繼承了根上下文中所有東西。 儲存在 ServletContext 中,key 是 “org.springframework.web.servlet.FrameworkServlet.CONTEXT”+Servlet 名稱。

當一個 Request 物件產生時,會把這個 WebApplicationContext 上下文儲存在 Request 物件中,key 是 DispatcherServlet.class.getName() + “.CONTEXT”。可以使用工具類取出上下文:RequestContextUtils.getWebApplicationContext(request);

使用 listener 監聽器 來載入配置

什麼是 Listener?

​ 監聽器就是一個 Java類(狗仔) 用來監聽其他的 JavaBean 的變化

​ 在 javaweb 中監聽器就是監聽三個域物件的狀態的。request,session,servletContext

例如這樣的配置:

<listener>   
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>   
</listener> 

Spring 會建立一個全域性的 WebApplicationContext 上下文,稱為根上下文 ,儲存在 ServletContext 中,key 是 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 屬性的值。可以使用工具類取出上下文: WebApplicationContextUtils.getWebApplicationContext(ServletContext);

springmvc-config.xml 配置檔案片段講解

例如這樣的配置:

<!-- 自動掃描的包名 -->  
<context:component-scan base-package="com.app,com.core,JUnit4" >
</context:component-scan>  
     
<!-- 預設的註解對映的支援 -->  
<mvc:annotation-driven />  
     
<!-- 檢視解釋類 -->  
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
    <property name="prefix" value="/WEB-INF/jsp/"/>  
    <property name="suffix" value=".jsp"/><!--可為空,方便實現自已的依據副檔名來選擇檢視解釋類的邏輯  -->  
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />  
</bean>  
     
<!-- 攔截器 -->  
<mvc:interceptors>  
     <bean class="com.core.mvc.MyInteceptor" />  
</mvc:interceptors>       
  
<!-- 對靜態資原始檔的訪問  方案一 (二選一) -->  
<mvc:default-servlet-handler/>  
      
<!-- 對靜態資原始檔的訪問  方案二 (二選一)-->  
<mvc:resources mapping="/images/**" location="/images/" cache-period="31556926"/>  
<mvc:resources mapping="/js/**" location="/js/" cache-period="31556926"/>  
<mvc:resources mapping="/css/**" location="/css/" cache-period="31556926"/> 

<context:component-scan/> 掃描指定的包中的類上的註解

常用的註解有:

  • @Controller 宣告Action元件
  • @Service 宣告Service元件 @Service(“myMovieLister”)
  • @Repository 宣告Dao元件
  • @Component 泛指元件, 當不好歸類時.
  • @RequestMapping("/menu") 請求對映
  • @Resource 用於注入,( j2ee提供的 ) 預設按名稱裝配,
  • @Resource(name=“beanName”)
  • @Autowired 用於注入,(srping提供的) 預設按型別裝配
  • @Transactional( rollbackFor={Exception.class}) 事務管理
  • @ResponseBody
  • @Scope(“prototype”) 設定 bean 的作用域

更多:

<mvc:annotation-driven /> 是一種簡寫形式,完全可以手動配置替代這種簡寫形式,簡寫形式可以讓初學都快速應用預設配置方案。

<mvc:annotation-driven /> 會自動註冊 DefaultAnnotationHandlerMapping 與AnnotationMethodHandlerAdapter 兩個 bean,是 spring MVC 為 @Controllers 分發請求所必須的。

並提供了:資料繫結支援,@NumberFormatannotation支援,@DateTimeFormat支援,@Valid支援,讀寫XML的支援(JAXB),讀寫JSON的支援(Jackson)。

後面,我們處理響應ajax請求時,就使用到了對json的支援。

後面,對action寫JUnit單元測試時,要從spring IOC容器中取 DefaultAnnotationHandlerMapping 與 AnnotationMethodHandlerAdapter 兩個bean,來完成測試,取的時候要知道是

<mvc:annotation-driven />這一句註冊的這兩個bean。

<mvc:interceptors/> 是一種簡寫形式。可以配置多個HandlerMapping。

<mvc:interceptors/>會為每一個HandlerMapping,注入一個攔截器。其實我們也可以手動配置為每個HandlerMapping注入一個攔截器。

<mvc:default-servlet-handler/> 使用預設的Servlet來響應靜態檔案。

<mvc:resources mapping="/images/*" location="/images/" cache-period=“31556926”/> 匹配URL /images/** 的 URL 被當做靜態資源,由Spring讀出到記憶體中再響應 http。

訪問到靜態的檔案,如 jpg,js,css

如何你的 DispatcherServlet 攔截 *.do 這樣的 URL,就不存在訪問不到靜態資源的問題。如果你的 攔截“/”,攔截了所有的請求,同時對 *.js,*.jpg 的訪問也就被攔截了。

目的: 可以正常訪問靜態檔案,不要找不到靜態檔案報404。

方案一:啟用Tomcat的defaultServlet來處理靜態檔案

例如這樣的配置:

<servlet-mapping>   
    <servlet-name>default</servlet-name>  
    <url-pattern>*.jpg</url-pattern>     
</servlet-mapping>    
<servlet-mapping>       
    <servlet-name>default</servlet-name>    
    <url-pattern>*.js</url-pattern>    
</servlet-mapping>    
<servlet-mapping>        
    <servlet-name>default</servlet-name>       
    <url-pattern>*.css</url-pattern>      
</servlet-mapping>    

要配置多個,每種檔案配置一個

要寫在 DispatcherServlet 的前面, 讓 defaultServlet 先攔截,這個就不會進入Spring了

Tomcat, Jetty, JBoss, and GlassFish 預設 Servlet 的名字 – “default”
Google App Engine 預設 Servlet 的名字 – “_ah_default”
Resin 預設 Servlet 的名字 – “resin-file”
WebLogic 預設 Servlet 的名字 – “FileServlet”
WebSphere 預設 Servlet 的名字 – “SimpleFileServlet”

方案二: 在 spring3.0.4 以後版本提供了 mvc:resources

mvc:resources 的使用方法:

<!-- 對靜態資原始檔的訪問 -->    
<mvc:resources mapping="/images/**" location="/images/" />  

mapping 對映到 ResourceHttpRequestHandler 進行處理
location 指定靜態資源的位置。可以是 web application 根目錄下、jar 包裡面,這樣可以把靜態資源壓縮到 jar 包中
cache-period 可以使得靜態資源進行 web cache

如果出現下面的錯誤,可能是沒有配置<mvc:annotation-driven />的原因。
報錯WARNING: No mapping found for HTTP request with URI [/mvc/user/findUser/lisi/770] in DispatcherServlet with name ‘springMVC’

使用 <mvc:resources/> 元素,把 mapping 的 URI 註冊到 SimpleUrlHandlerMapping 的 urlMap 中,key 為 mapping 的 URI pattern 值 ,而 value 為 ResourceHttpRequestHandler,這樣就巧妙的把對靜態資源的訪問由 HandlerMapping 轉到 ResourceHttpRequestHandler 處理並返回,所以就支援classpath 目錄,jar 包內靜態資源的訪問。另外需要注意的一點是,不要對 SimpleUrlHandlerMapping 設定 defaultHandler。因為對 static uri 的 defaultHandler 就是 ResourceHttpRequestHandler,否則無法處理 static resources request。

方案三 ,使用 <mvc:default-servlet-handler/>

<mvc:default-servlet-handler/>

會把"/**" url,註冊到 SimpleUrlHandlerMapping 的 urlMap 中,把對靜態資源的訪問由 HandlerMapping 轉到 org.springframework.web.servlet.resource。

DefaultServletHttpRequestHandler 處理並返回。DefaultServletHttpRequestHandler 使用就是各個 Servlet 容器自己的預設 Servlet。

補充說明: 多個 HandlerMapping 的執行順序問題:

DefaultAnnotationHandlerMapping 的 order 屬性值是:0
<mvc:resources/ >自動註冊的 SimpleUrlHandlerMapping 的 order 屬性值是: 2147483646

<mvc:default-servlet-handler/> 自動註冊的 SimpleUrlHandlerMapping 的 order 屬性值是: 2147483647

spring 會先執行 order 值比較小的。當訪問一個 a.jpg 圖片檔案時,先通過 DefaultAnnotationHandlerMapping 來找處理器,一定是找不到的,我們沒有叫a.jpg 的 Action。再按 order 值升序找,由於最後一個 SimpleUrlHandlerMapping 是匹 “/**” 的,所以一定會匹配上,再響應圖片。

Spring 中的攔截器:

Spring為我們提供了:org.springframework.web.servlet.HandlerInterceptor 介面, org.springframework.web.servlet.handler.HandlerInterceptorAdapter 介面卡,實現這個介面或繼承此類,可以非常方便的實現自己的攔截器。

 public boolean preHandle(HttpServletRequest request,
     HttpServletResponse response, Object handler);
 public void postHandle(HttpServletRequest request,
     HttpServletResponse response, Object handler,ModelAndView modelAndView);
 public void afterCompletion(HttpServletRequest request,
     HttpServletResponse response, Object handler, Exception ex)

分別實現預處理、後處理(呼叫了Service並返回ModelAndView,但未進行頁面渲染)、返回處理(已經渲染了頁面)
在preHandle中,可以進行編碼、安全控制等處理;
在postHandle中,有機會修改ModelAndView;
在afterCompletion中,可以根據ex是否為null判斷是否發生了異常,進行日誌記錄。
引數中的 Object handler 是下一個攔截器。

自定義一個攔截器,要實現 HandlerInterceptor 介面:

public class MyInteceptor implements HandlerInterceptor

Spring MVC並沒有總的攔截器,不能對所有的請求進行前後攔截。Spring MVC的攔截器,是屬於HandlerMapping級別的,可以有多個HandlerMapping ,每個HandlerMapping可以有自己的攔截器。

在spring MVC的配置檔案中配置有三種方法:

方案一 (近似)總攔截器,攔截所有 url

<mvc:interceptors>  
    <bean class="com.app.mvc.MyInteceptor" />  
</mvc:interceptors> 

mvc:interceptors/會為每一 個HandlerMapping,注入一個攔截器。總有一個HandlerMapping是可以找到處理器的,最多也只找到一個處理器,所以這個攔截器總會被執行的。起到了總攔截器的作用。

方案二 (近似) 總攔截器, 攔截匹配的 URL。比方案一多一個 URL 匹配。

<mvc:interceptors >    
  <mvc:interceptor>    
        <mvc:mapping path="/user/*" /> <!-- /user/*  -->    
        <bean class="com.mvc.MyInteceptor"></bean>    
    </mvc:interceptor>    
</mvc:interceptors> 

方案三 HandlerMapping 上的攔截器

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">       
 <property name="interceptors">       
     <list>       
         <bean class="com.mvc.MyInteceptor"></bean>      
     </list>       
 </property>       
</bean>   

<mvc:annotation-driven /> 會自動註冊 DefaultAnnotationHandlerMapping 與AnnotationMethodHandlerAdapter 這兩個 bean,所以就沒有機會再給它注入interceptors屬性,就無法指定攔截器。

如果我們手動配置上面的兩個 Bean,不使用 <mvc:annotation-driven />,就可以 給 interceptors 屬性 注入攔截器了。

實現全域性的異常處理

<!-- 總錯誤處理-->  
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">  
    <property name="defaultErrorView">    
        <value>/error/error</value>  
    </property>  
    <property name="defaultStatusCode">    
        <value>500</value>  
    </property>     
    <property name="warnLogCategory">    
        <value>org.springframework.web.servlet.handler.SimpleMappingExceptionResolver</value>  
    </property>     
</bean> 

通過 SimpleMappingExceptionResolver 我們可以將不同的異常對映到不同的 jsp 頁面(通過 exceptionMappings 屬性的配置)。如果所丟擲的異常在 exceptionMappings 中沒有對應的對映,則 Spring 將用此預設配置顯示異常資訊(通過 defaultErrorView 屬性的配置)。

<html>  
<head>  
<meta http-equiv="Content-Type" content="text/html; charset=GBK">  
<title>錯誤頁面</title>  
</head>  
<body>  
<h1>出錯了</h1>  
<%  
Exception e = (Exception)request.getAttribute("exception");  
out.print(e.getMessage());  
%>  
</body>  
</html> 

其中一句:request.getAttribute(“exception”),key 是 exception,也是在 SimpleMappingExceptionResolver 類預設指定的,是可能通過配置檔案修改這個值的。

更多文章連結: