【SpringMVC】SpringMVC初學詳解篇
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檔案的配置中直接點選進去檢視類原始碼),前端控制器接收到請求之後,會呼叫它的
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>