【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>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章