Spring MVC 執行原理
在 Spring Mvc 訪問過程里,每個請求都首先經過 許多的過濾器,經 DispatcherServlet 處理;
一個Spring MVC工程里,可以配置多個的 dispatcherServlet ,每個 DispatcherServlet 可以對應多個的 HandlerMapping ,每個 HandlerMapping 可以有自己的 Interceptor (攔截器)。

1. 請求首先 由 前端 DispatcherServlet 捕獲;
2. DispatcherServlet 經過 HandlerMapping 獲取適當的 Hanlder ,也即 Controller ,並返回給 DispatcherServlet;
3. 如果有設置 攔截器,首選執行攔截器的 preHandler 方法,並把執行結果返回 DispatcherServlet;
4. DispatcherServlet 根據捕獲的請求,以及 Handler (Controller) ,獲取到適當的 HandlerAdapter 處理,並把結果返回給 DispatcherServlet,結果為 (View 和 Model);
5. 如果有設置攔截器,就是執行 攔截器的 postHandler 方法,並返回 DispatcherServlet;
6. DispatcherServlet 根據獲取的 view 跟 model 結合 ViewResolver,返回所指的 視圖模板,並返回給 DispatcherServlet;
7. DispatcherServlet 結合 視圖模型跟model ,執行 render() 渲染界面,並返回給客戶端;
一個DispatcherServlet 都有自己 上下文配置文件, 這個配置繼承自根的上下文配置文件;
每個 DispatcherServlet 都可配置多個不同的 HandlerMapping 映射方式;每個 HandlerMapping 都實現了 Ordered 接口,可以上下文文件配置中設置優先使用的 HandlerMapping;
用戶的請求被 DispatcherServlet 捕獲后,DispatcherServlet 使用優先級高的 HandlerMapping 查找可用的 Handler,如果沒有找到合適的 Handler,就使優先級次之的 HandlerMapping 繼續查找,直到找到為止;
比如:Spring MVC 提供主要的 HanderMapping 有 DefaultAnnotationHandlerMapping, SimpleUrlHandlerMapping, BeanNameUrlHandlerMapping
<bean id="defaultAnnoHandlerMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"> <property name="order" value="1"/> </bean> <bean id="simpleUrlMapping" class="org. springframework.Web.servlet.handler.SimpleUrlHandlerMapping"> <property name="order" value="2"/> <!-- ... --> </bean> <bean id="beanNameUrlMapping" class="org.springframework.Web.servlet. handler.BeanNameUrlHandlerMapping"> <!-- ... --> </bean>
如果不為HandlerMapping明確指定order,那么默認值為Integer.MAX_VALUE,對應最低優先級。
如果 DefaultAnnotationHandlerMapping 找到相應的 Handler ,就把數據傳給相應的 Controller 類下的相應的 HandlerAdapter (方法處)處理;如果沒有找到,就到 SimpleUrlHandlerMapping 處理;

Spring MVC xml配置說明
1. Web.xml 配置
1>. 首先 Spring 監聽器:
<!-- Creates the Spring Container shared by all Servlets and Filters --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
它是整個 spring 框架的入口,會自動裝載 應用程序根上下文的 配置文件;因為他實現了 ServletContextListener 這個接口,項目啟動時,就會執行它實現的方法。
2>. 根應用程序上下文配置:
<!-- The definition of the Root Spring Container shared by all Servlets and Filters --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/root-context.xml</param-value> </context-param>
也可叫 spring 全局配置,在這里可以配置 數據源 等一些 全局的spring 基本的配置;dispatcherServlet 可以繼承自這里的配置;
3>. diapatcherServlet 配置:
<!-- Processes application requests --> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
load-on-startup 值為 "1" 表示該 servlet (dispatcherServlet 也是一個 servlet) 隨 servlet 容器同時啟動。
dispatcherservlet 的 init-param 節點不是必須的,如果沒有配置,該 dispatcherServlet 對應的 上下文配置文件 就會到 /WEB-INF/[servlet-name]-servlet.xml 加載;
servlet-mapping 的 url-pattern 的值 "*.do" 表示,servlet 只會攔截 .do 后綴的請求,這種配置不會影響靜態資源的訪問。
4>. 配置工程項目編碼:
<filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
該過濾器如果沒有配置,請求訪問可能會遇亂碼;
-------------------------------------------------------------
現在配置 diapatcherServlet 對應的 上下文配置文件: /WEB-INF/spring/appServlet/servlet-context.xml (上面配置的)
1>. 啟動掃描所有的 Controller
<context:component-scan base-package="com.study.web"/>
主要作用於 @Controller,假如他的包結構是這樣的:
com.study.web
|---controller
|-----|-----abcController.java
|-----cdeController.java
那么上面的 base-package 寫為 com.study.web.controller 也是正確的;
2>. <mvc:annotation-driven /> 說明:
是一種簡寫形式,可以讓初學者快速成應用默認的配置方案,會默認注冊 DefaultAnnotationHandleMapping以及AnnotionMethodHandleAdapter 這兩個 Bean, 這兩個 Bean ,前者對應類級別, 后者對應到方法級別;
上在面的 DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter 是 Spring 為 @Controller 分發請求所必需的。
annotation-driven 掃描指定包中類上的注解,常用的注解有:
@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的作用域
3>. 視圖解析類,對模型視圖名稱的解析,在請求時模型視圖名稱添加前后綴
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <beans:property name="prefix" value="/WEB-INF/views/" /> <beans:property name="suffix" value=".jsp" /> </beans:bean>
InternalResourceViewResolver 提供了對 JstlView 的支持: <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
Spring Mvc 2.5 版本以前,提供了 注解 功能,實現了對 REST 的全面支持; REST 通過不帶擴展名的 URL 來訪問系統資源;REST 會把每個請求資源當成是靜態,每個 URL 資源都是一個靜態資源;
它通 @RequestMapping主@PathVariable 注釋提供訪問映射,由@RequestMapping 中的 value 及 method 就可以支持Rest式的訪問。
通過策略接口,Spring 框架是高度可配置的,而且包含多種視圖技術,例如 JavaServer Pages(JSP)技術、Velocity、Tiles、iText 和 POI。Spring MVC 框架並不知道使用的視圖,所以不會強迫您只使用 JSP 技術。Spring MVC 分離了控制器、模型對象、分派器以及處理程序對象的角色,這種分離讓它們更容易進行定制。
比如 使用的是 Velocity 技術,那么只要把 suffix 的 value 值改為 ".vm" 即可;
4>. Spring 攔截器:
全局配置:
<mvc:interceptors> <bean class="com.study.web.interceptor.MyInterceptor"></bean> </mvc:interceptors>
基於路徑的攔截器:
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**/*.do"/> <beans:bean class="com.study.interceptor.utils.StuInterceptor"> <beans:property name="startWrk"> <beans:value>1</beans:value> </beans:property> <beans:property name="endWrk"> <beans:value>24</beans:value> </beans:property> <beans:property name="redirectUrl"> <beans:value>http://127.0.0.1:8080/interceptor/static/timeout.htm</beans:value> </beans:property> </beans:bean> </mvc:interceptor> </mvc:interceptors>
相關的 Java 類 : StuInterceptor ( 該類的作用是攔截在指定的時間范圍外,則頁面請求轉到/interceptor/static/timeout.htm )
package com.study.interceptor.utils; import java.util.Calendar; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; //import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import com.study.interceptor.controller.HomeController; public class StuInterceptor implements HandlerInterceptor /*extends HandlerInterceptorAdapter*/ { private static final Logger logger = LoggerFactory.getLogger(HomeController.class); private int startWrk; private int endWrk; private String redirectUrl; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.info("Interceptor", "I am in Custom Interceptor!"); Calendar cal = Calendar.getInstance(); int hour = cal.get(Calendar.HOUR_OF_DAY); if (hour > startWrk && hour < endWrk) { return true; } else { response.sendRedirect(redirectUrl); return false; } } public int getStartWrk() { return this.startWrk; } public void setStartWrk(int startWrk) { this.startWrk = startWrk; } public int getEndWrk() { return this.endWrk; } public void setEndWrk(int endWrk) { this.endWrk = endWrk; } public String getRedirectUrl() { return this.redirectUrl; } public void setRedirectUrl(String redirectUrl) { this.redirectUrl = redirectUrl; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // TODO Auto-generated method stub System.out.println("after posthandler"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // TODO Auto-generated method stub System.out.println("after completion"); } }
5>. 在 HandlerMapping 內配置攔截器:
<!-- Mapping --> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/abc.do">abcController</prop> <prop key="def.kara">defController</prop> </props> </property> <!-- 注冊攔截器 --> <property name="interceptors"> <list> <ref bean="myInterceptor"></ref> </list> </property> </bean>
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"> <property name="interceptors"> <list> <bean class="com.study.web.MyInteceptor"></bean> </list> </property> </bean>
6>. 使用 placeHolder 加載應用程序屬性配置文件
<context:property-placeholder location="/WEB-INF/config.properties"/>
比如 加載 mysql jdbc 配置信息:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/springdb
username=root
password=123
那么 mysql 的數據源配置的 驅動信息,用戶信息,就可以配置成這樣:
<context:property-placeholder location="classpath:META-INF/mybatis/mysql.properties" /> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${driver}"></property> <property name="url" value="${url}"></property> <property name="username" value="${username}"></property> <property name="password" value="${password}"></property> </bean>
