最近工作上重新接觸一些和SpringMVC有關(guān)的內(nèi)容,之前接觸的時候沒有形成自己的筆記,最近來償還之前欠下的技術(shù)債。
1. 入門程序
1.1. 創(chuàng)建Spring MVC項(xiàng)目
在開發(fā)工具IDEA中,創(chuàng)建基于Maven的Spring MVC項(xiàng)目,創(chuàng)建的時候我們基于以下archetype(Maven 的41種骨架功能介紹)。

注意:此骨架生成的代碼中,JSP的版本比較低不支持el表達(dá)式,需要生成后手動進(jìn)行修改。
1.2. 加入相關(guān)依賴
在POM文件中,只需導(dǎo)入spring-webmvc。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
導(dǎo)入該依賴之后,其會自動導(dǎo)入其他所需的依賴,間接引入的依賴,如下圖所示:

1.3. 配置程序攔截入口
和Java中其他MVC框架一樣,SpringMVC所有的請求都會經(jīng)過一個前端控制器Servlet,這個Servlet就是SpringMVC框架的統(tǒng)一入口,所以需要對其進(jìn)行配置
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.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>
load-on-startup:當(dāng)值為0或者大于0時,表示容器在應(yīng)用啟動時就加載并初始化這個servlet,當(dāng)值小于0或者沒有指定時,則表示容器在該servlet被選擇時才會去加載。另外,它的值只表示優(yōu)先級而非啟動延遲時間。
url-pattern:/ 表示默認(rèn)的URL映射,當(dāng)request匹配不到其他Servlet時,會默認(rèn)進(jìn)入此Servlet,包括靜態(tài)資源請求。注意:用/*配置來表示攔截所有請求是錯誤的。
contextConfigLocation:指定springmvc配置的加載位置,如果不指定則默認(rèn)加載WEB-INF/[DispatcherServlet 的Servlet 名字]-servlet.xml。
1.4. 配置HandlerAdapter和HandlerMapping
在springmvc.xml文件中加入以下配置:
<!--處理器適配器-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
<!--處理器映射器-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
一般實(shí)際開發(fā)過程中,不會單獨(dú)配置HandlerAdapter和HandlerMapping。而是使用mvc:annotation-driven,它會自動加入HandlerAdapter和HandlerMapping的配置。
1.5. 開發(fā)處理器Controller
處理器是需要程序員自己開發(fā)的,先來思考這么一個問題,SpringMVC框架是如何識別你寫出來的Controller呢? SpringMVC要求處理器需實(shí)現(xiàn)org.springframework.web.servlet.mvc.Controller接口。而我們在實(shí)際開發(fā)一般只在開發(fā)的處理器類上添加@Controller注解即可。
@Controller
@RequestMapping("/item")
public class ItemsController {
@RequestMapping("/list")
public ModelAndView queryItems()throws Exception{
//調(diào)用service查找 數(shù)據(jù)庫,查詢商品列表,這里使用靜態(tài)數(shù)據(jù)模擬
List<Items> itemsList = new ArrayList<Items>();
//向list中填充靜態(tài)數(shù)據(jù)
Items items_1 = new Items();
items_1.setName("聯(lián)想筆記本");
items_1.setPrice(6000f);
items_1.setDetail("ThinkPad T430 聯(lián)想筆記本電腦!");
itemsList.add(items_1);
//返回ModelAndView
ModelAndView modelAndView = new ModelAndView();
//相當(dāng) 于request的setAttribut,在jsp頁面中通過itemsList取數(shù)據(jù)
modelAndView.addObject("itemsList", itemsList);
modelAndView.setViewName("items/itemsList");
return modelAndView;
}
}
1.6. 配置視圖解析器
在springmvc的配置文件中,加入如下配置:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
InternalResourceViewResolver:支持JSP視圖解析
viewClass:JstlView表示JSP模板頁面需要使用JSTL標(biāo)簽庫,所以classpath中必須包含jstl的相關(guān)jar 包;
prefix 和suffix:查找視圖頁面的前綴和后綴,最終視圖的址為:
前綴+邏輯視圖名+后綴,邏輯視圖名需要在controller中返回ModelAndView指定,比如邏輯視圖名為hello,則最終返回的jsp視圖地址 “WEB-INF/jsp/hello.jsp”
到目前為止,springmvc完整的配置文件內(nèi)容如下
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.20.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.20.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.20.xsd">
<!-- 可以掃描controller、service、... 這里讓掃描controller,指定controller的包 -->
<context:component-scan base-package="cn.zgc.mvc.controller"></context:component-scan>
<!-- mvc:annotation-driven默認(rèn)加載很多的參數(shù)綁定方法, 比如json轉(zhuǎn)換解析器就默認(rèn)加載了。
如果使用mvc:annotation-driven不用單獨(dú)配置RequestMappingHandlerMapping和RequestMappingHandlerAdapter -->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 視圖解析器 解析jsp解析,默認(rèn)使用jstl標(biāo)簽,classpath下的得有jstl的包 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 配置jsp路徑的前綴 -->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!-- 配置jsp路徑的后綴 -->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
1.7. 開發(fā)頁面
根據(jù)視圖解析器的配置,在/WEB-INF/jsp/目錄下添加對應(yīng)的頁面文件。
1.8. 測試
任何時候都需要謹(jǐn)記測試代碼很重要!那么SpringMVC的代碼如何進(jìn)行測試呢?我們可以借助MockMvc來進(jìn)行測試。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:testSpringmvc.xml")
@WebAppConfiguration
public class ItemsControllerTest {
protected MockMvc mockMvc;
@Autowired
protected WebApplicationContext wac;
@Before()
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); //初始化MockMvc對象
}
@Test
public void queryItems() throws Exception {
String response = mockMvc.perform(
get("/item/list") //請求的url,請求的方法是get
.contentType( MediaType.APPLICATION_FORM_URLENCODED ) //請求數(shù)據(jù)的格式是URL
//.param( "userId","22" ) //URL中的參數(shù)
).andExpect(status().isOk()) //返回的狀態(tài)是200
.andDo(print()) //打印出請求和相應(yīng)的內(nèi)容
.andReturn().getResponse().getContentAsString(); //將相應(yīng)的數(shù)據(jù)轉(zhuǎn)換為字符串
System.out.println(response);
}
}
完整的代碼請參考:SpringMVC入門程序
2. SpringMVC原理小探
2.1 SpringMVC運(yùn)行流程

1、 用戶發(fā)送請求至前端控制器DispatcherServlet
2、 DispatcherServlet收到請求調(diào)用HandlerMapping處理器映射器。
3、 處理器映射器根據(jù)請求url找到具體的處理器,生成處理器對象及處理器攔截器(如果有則生成)一并返回給DispatcherServlet。
4、 DispatcherServlet通過HandlerAdapter處理器適配器調(diào)用處理器
5、 執(zhí)行處理器(Controller,也叫后端控制器)。
6、 Controller執(zhí)行完成返回ModelAndView
7、 HandlerAdapter將controller執(zhí)行結(jié)果ModelAndView返回給DispatcherServlet
8、 DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器
9、 ViewReslover解析后返回具體View
10、 DispatcherServlet對View進(jìn)行渲染視圖(即將模型數(shù)據(jù)填充至視圖中)。
11、 DispatcherServlet響應(yīng)用戶
2.2. SpringMVC中的常見組件
- DispatcherServlet:前端控制器
DispatcherServlet是SpringMVC程序的入口。用戶請求到達(dá)前端控制器,它就相當(dāng)于mvc模式中的c,dispatcherServlet是整個流程控制的中心,由它調(diào)用其它組件處理用戶的請求,dispatcherServlet的存在降低了組件之間的耦合性。
DispatcherServlet在加載的過程中,會初始化其他組件。
protected void onRefresh(ApplicationContext context) {
this.initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
this.initMultipartResolver(context);
this.initLocaleResolver(context);
this.initThemeResolver(context);
this.initHandlerMappings(context);
this.initHandlerAdapters(context);
this.initHandlerExceptionResolvers(context);
this.initRequestToViewNameTranslator(context);
this.initViewResolvers(context);
this.initFlashMapManager(context);
}
DispatcherServlet的默認(rèn)配置在DispatcherServlet.properties(和DispatcherServlet類在一個包下)中,而且是當(dāng)Spring配置文件中沒有指定配置時使用的默認(rèn)策略:

- HandlerMapping:處理器映射器
HandlerMapping負(fù)責(zé)根據(jù)用戶請求找到Handler即處理器,springmvc提供了不同的映射器實(shí)現(xiàn)不同的映射方式,例如:配置文件方式,實(shí)現(xiàn)接口方式,注解方式等。
有如下幾種常見的HandlerMapping
- BeanNameUrlHandlerMapping:根據(jù)請求的url與容器中定義的bean的name進(jìn)行匹配,從而從spring容器中找到bean實(shí)例。。
- SimpleUrlHandlerMapping:BeanNameUrlHandlerMapping的增強(qiáng)版,可以映射一個url到一個Handler的id。
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/url1">controller的bean id</prop>
<prop key="/url2">controller的bean id</prop>
</props>
</property>
</bean>
- DefaultAnnotationHandlerMapping:從spring3.1版本開始,廢除使用。
- RequestMappingHandlerMapping:掃描RequestMapping注解,根據(jù)相關(guān)配置,綁定URL到一個Handler。
RequestMappingHandlerMapping是開發(fā)中最常用的HandlerMapping。
- Handler:處理器
Handler就是Controller,它是繼DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler對具體的用戶請求進(jìn)行處理。
由于Handler涉及到具體的用戶業(yè)務(wù)請求,所以一般情況需要程序員根據(jù)業(yè)務(wù)需求開發(fā)Handler。
- HandlAdapter:處理器適配器
SpringMVC最終是通過HandlerAdapter來調(diào)用實(shí)際的Controller方法的。通過擴(kuò)展適配器可以對更多類型的處理器進(jìn)行執(zhí)行。常用的有如下幾個:
- SimpleControllerHandlerAdapter:處理實(shí)現(xiàn)了
org.springframework.web.servlet.mvc. Controller接口的Controller。 - AnnotationMethodHandlerAdapter:從spring3.1版本開始,廢除使用,推薦使用
RequestHandlerAdapter來代替注解形式的處理器適配。 - RequestHandlerAdapter:處理類型為HandlerMethod的Handler(使用@RequestMapping注解的方法就是一種HandlerMethod)。
- HttpRequestHandlerAdapter:所有實(shí)現(xiàn)了
org.springframework.web.HttpRequestHandler接口的Controller通過此適配器進(jìn)行適配和執(zhí)行。
springmvc使用
<mvc:annotation-driven>自動加載RequestMappingHandlerMapping和RequestMappingHandlerAdapter,可用在springmvc.xml配置文件中使用<mvc:annotation-driven>替代注解處理器和適配器的配置。
- ViewResolver:視圖解析器
View Resolver負(fù)責(zé)將處理結(jié)果生成View視圖,View Resolver首先根據(jù)邏輯視圖名解析成物理視圖名即具體的頁面地址,再生成View視圖對象,最后對View進(jìn)行渲染將處理結(jié)果通過頁面展示給用戶。 springmvc框架提供了很多的View視圖類型,包括:jstlView、freemarkerView、pdfView等。
一般情況下需要通過頁面標(biāo)簽或頁面模版技術(shù)將模型數(shù)據(jù)通過頁面展示給用戶,需要由程序員根據(jù)業(yè)務(wù)需求開發(fā)具體的頁面。
- HandlerExceptionResolver:異常處理器
我們可以實(shí)現(xiàn)自己的處理器在全局層面攔截Handler拋出的Exception,再做進(jìn)一步的處理。SpringMVC自帶了以下幾個異常處理器:
- SimpleMappingExceptionResolver:可以將不同的異常映射到不同的JSP頁面。
- ExceptionHandlerExceptionResolver:解析使用了@ExceptionHandler注解的方法來處理異常。
- ResponseStatusExceptionResolver:處理@ResponseStatus注解的異常。
- DefaultHandlerExceptionResolver:默認(rèn)的處理器,包括不支持的method、不支持的mediaType等。
- HandlerInterceptor:Hanler攔截器
請求路徑上的攔截器,需要自己實(shí)現(xiàn)這個接口以攔截請求,做一些對Handler的前置和后置的處理工作。