1. 程式人生 > >【SpringMVC】SpringMVC初學詳解篇

【SpringMVC】SpringMVC初學詳解篇

      SpringMVC是一個基於MVCWeb框架,是spring框架的一個模組,使用了MVC架構模式的思想,將web層進行職責解耦。首先讓我們整體看一下SpringMVC處理請求的流程:


  1. 發起請求到前端控制器(DispatcherServlet)
  2. 前端控制器請求HandlerMapping查詢Handler,可以根據xml配置、註解進行查詢
  3. 處理器對映器HandlerMapping向前端控制器返回Handler
  4. 前端控制器呼叫處理器介面卡去執行Handler
  5. 處理器介面卡去執行Handler
  6. Handler執行完成給介面卡返回ModelAndView
  7. 處理器介面卡向前端控制器返回ModelAndView(springmvc框架的一個底層物件,包括Model和view)
  8. 前端控制器請求檢視解析器去進行檢視解析,根據邏輯檢視名解析成真正的檢視(jsp)
  9. 檢視解析器向前端控制器返回View
  10. 前端控制器進行檢視渲染,檢視渲染將模型資料(在ModelAndView物件中)填充到request域
  11. 前端控制器向用戶響應結果

【原始碼分析】

      首先是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.SpringMVCservlet API耦合,難以脫離servlet容器獨立執行,降低了SpringMVC框架的可擴充套件性。

      2.太過細化的角色劃分,太過繁瑣,降低了應用的開發效率。

【入門小程式】

開發環境:eclipseMySQLJDKtomcat、引入如下圖所示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>