SpringMVC是一個基於MVC的Web框架,是spring框架的一個模塊,使用了MVC架構模式的思想,將web層進行職責解耦。首先讓我們整體看一下SpringMVC處理請求的流程:
- 發起請求到前端控制器(DispatcherServlet)
- 前端控制器請求HandlerMapping查找Handler,可以根據xml配置、註解進行查找
- 處理器映射器HandlerMapping向前端控制器返回Handler
- 前端控制器調用處理器適配器去執行Handler
- 處理器適配器去執行Handler
- Handler執行完成給適配器返回ModelAndView
- 處理器適配器向前端控制器返回ModelAndView(springmvc框架的一個底層對象,包括Model和view)
- 前端控制器請求視圖解析器去進行視圖解析,根據邏輯視圖名解析成真正的視圖(jsp)
- 視圖解析器向前端控制器返回View
- 前端控制器進行視圖渲染,視圖渲染將模型數據(在ModelAndView對象中)填充到request域
- 前端控制器向用戶響應結果
【源碼分析】
首先是SpringMVC的入口類DispatcherServlet,該類其實是一個servlet類,(可以從web.xml文件的配置中直接點擊進去查看類源碼),前端控制器接收到請求之後,會調用它的doService方法,然後調用doDispatch方法,重點就是doDispatch方法,源碼如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
// 如果請求方式爲multipart,則通過multipart進行解析
multipartRequestParsed = processedRequest != request;
// Determine handler for the current request.(上圖中的步驟2:通過調用getHandler方法,調用處理器映射器HandlerMapping查找Handler,詳見方法二)
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.(根據處理器得到相應的適配器)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//執行預處理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// Actually invoke the handler.(上圖中步驟4:請求處理器適配器HandlerAdapter執行Handler)
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv);
// 執行相應的後處理
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
//該方法詳情見方法三(上圖中的步驟8、9、10)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
②方法二(getHandler方法源碼)
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
③方法三(processDispatchResult方法源碼)
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?(上圖中的步驟8:通過render方法,請求視圖解析器view Resolver解析視圖,具體源碼如方法四)
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
④方法四(render方法源碼)
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
if (mv.isReference()) {
// We need to resolve the view name.
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException(
"Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" +
getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
view.render(mv.getModelInternal(), request, response);
}
【優點】
1、清晰的角色劃分:前端控制器(DispatcherServlet)、請求到處理器映射(HandlerMapping)、處理器適配器(HandlerAdapter)、視圖解析器(ViewResolver)、處理器或頁面控制器(Controller)、驗證器( Validator)、命令對象(Command 請求參數綁定到的對象就叫命令對象)、表單對象(Form Object提供給表單展示和提交到的對象就叫表單對象)。
2、和Spring其他框架無縫集成,是其它Web框架所不具備的;
3、可適配,通過HandlerAdapter可以支持任意的類作爲處理器;
4、可定製性,HandlerMapping、ViewResolver等能夠非常簡單的定製;
5、功能強大的數據驗證、格式化、綁定機制;
6、強大的JSP標籤庫,使JSP編寫更容易。
【缺點】
1.SpringMVC與servlet API耦合,難以脫離servlet容器獨立運行,降低了SpringMVC框架的可擴展性。
2.太過細化的角色劃分,太過繁瑣,降低了應用的開發效率。
【入門小程序】
開發環境:eclipse、MySQL、JDK、tomcat、引入如下圖所示jar包(點擊鏈接下載jar包)
程序結構如下:
①Items類:
package cn.itcast.ssm.po;
import java.util.Date;
public class Items {
private Integer id;
private String name;
private Float price;
private String pic;
private Date createtime;
private String detail;
// get和set方法略
}
②Web.xml代碼:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>mySpringMVC</display-name>
<!-- springmvc前端控制器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- contextConfigLocation配置springmvc加載的配置文件(配置處理器映射器、適配器等等)如果不配置,默認加載的是WEB-INF/servlet名稱-servlet.xml -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<!-- 第一種:*.action,訪問以.action結尾 由DispatcherServlet進行解析 第二種:/,所以訪問的地址都由DispatcherServlet進行解析,對於靜態文件的解析需要配置不讓DispatcherServlet進行解析
使用此種方式可以實現 RESTful風格的url 第三種:/*,這樣配置不對,使用這種配置,最終要轉發到一個jsp頁面時, 仍然會由DispatcherServlet解析jsp地址,不能根據jsp頁面找到handler,會報錯。 -->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>
③Springmvc.xml代碼:
<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"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- **************************************************************************************** -->
<!-- 配置Handler -->
<bean name="/queryItems.action" class="cn.itcast.ssm.controller.ItemsController1" />
<!-- **************************************************************************************** -->
<!-- **************************************************************************************** -->
<!-- 處理器映射器 -->
<!-- 將bean的name作爲 URL進行查詢,需要在配置Handler時指定beanname(就是url) -->
<bean
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
<!-- **************************************************************************************** -->
<!-- **************************************************************************************** -->
<!-- 處理器適配器 -->
<!-- 所有的處理器適配器都實現HandlerAdapter接口,該接口中有boolean supports(Object handler);
方法,通過這個supports方法來判斷尋找相應的Handler -->
<bean
class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
<!-- **************************************************************************************** -->
<!-- **************************************************************************************** -->
<!-- 視圖解析器 -->
<!--解析jsp解析,默認使用jstl標籤,classpath下的得有jstl的包 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
</bean>
<!-- **************************************************************************************** -->
</beans>
④controller代碼:
package cn.itcast.ssm.controller;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import cn.itcast.ssm.po.Items;
/**
* 實現controller接口的處理器
*
* @author happy
*
*/
public class ItemsController1 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
// 調用Services查找數據庫,查詢商品列表,這裏使用靜態數據模擬
List<Items> itemsList = new ArrayList<Items>();
// 想List中填充靜態數據
Items items_1 = new Items();
items_1.setName("聯想筆記本");
items_1.setPrice(6000f);
items_1.setDetail("ThinkPad T430 聯想筆記本電腦!");
Items items_2 = new Items();
items_2.setName("蘋果手機");
items_2.setPrice(5000f);
items_2.setDetail("iphone6蘋果手機!");
itemsList.add(items_1);
itemsList.add(items_2);
// 返回ModelAndView
ModelAndView modelAndView = new ModelAndView();
// 相當於request的setAttribute,在jsp頁面中通過itemsList取數據
modelAndView.addObject("itemsList", itemsList);
// 指定視圖
modelAndView.setViewName("/WEB-INF/jsp/items/itemsList.jsp");
return modelAndView;
}
}
⑤itemsList.jsp代碼:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>查詢商品列表</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/item/queryItem.action" method="post">
查詢條件:
<table width="100%" border=1>
<tr>
<td><input type="submit" value="查詢"/></td>
</tr>
</table>
商品列表:
<table width="100%" border=1>
<tr>
<td>商品名稱</td>
<td>商品價格</td>
<td>生產日期</td>
<td>商品描述</td>
<td>操作</td>
</tr>
<c:forEach items="${itemsList }" var="item">
<tr>
<td>${item.name }</td>
<td>${item.price }</td>
<td><fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
<td>${item.detail }</td>
<td><a href="${pageContext.request.contextPath }/item/editItem.action?id=${item.id}">修改</a></td>
</tr>
</c:forEach>
</table>
</form>
</body>
</html>