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 類預設指定的,是可能通過配置檔案修改這個值的。