javascript
Spring MVC-03循序渐进之Spring MVC
概述
前面兩篇介紹了模型2架構的優勢以及如何構建一個模型2應用。但是Spring MVC框架可以幫助我們快速的開發MVC應用。
Spring MVC體系概述
若基于某個框架來開發一個模型2的應用程序,我們要負責編寫一個Dispatcher servlet和控制類。 其中Dispatcher servlet必須能夠做到如下事情:
Spring MVC框架圍繞DispatcherServlet這個核心展開,DispatcherServlet負責截獲請求并分派給相應的處理器處理。 SpringMVC框架包括注解驅動控制器、請求及響應的信息處理、視圖解析、本地化解析、上傳文件解析、異常處理及表單標簽綁定等內容。
由于SpringMVC是基于Model2實現的框架,所以它底層的機制也是MVC,我們來看下SpringMVC的整體架構
從接收請求到返回相應,Spring MVC框架的眾多組件有條不紊的完成內部的分工,在整個框架中,DispatcherServlet處于核心的位置,負責協調和組織不同組件以完成請求處理并返回響應的工作。
下面我們來分步解析下SpringMVC處理請求的整體過程
整個過程始于客戶端發出的一個HTTP請求,Web應用服務器收到這個請求后,如果匹配DispatcherServlet的請求映射路徑(web.xml中指定),則web容器將該請求交給DispatcherServlet處理
DispatcherServlet接收到這請求后,將根據請求的信息(包括url,HTTP方法、請求報文頭、請求參數、Cookie等)及HandlerMapping的配置找到處理請求的處理器(Handler)。 可將HandlerMapping看做是路由控制器,將Handler看做目標主機。值得注意的是,SpringMVC中并沒有定義一個Handler接口,實際上任何一個Object都可以稱為請求處理器。
當DispatcherServlet根據HandlerMapping得到對應請求的Handler后,通過HandlerAdpapter對Handler進行封裝再以統一的適配器接口調用Handler.
處理器完成業務邏輯的處理后將返回一個ModelAndView給DispatcherServlet,ModelAndView包含了視圖邏輯名和模型數據信息
ModelAndView并非真正的視圖對象,DispatcherServlet通過ViewResolver完成邏輯視圖和真實視圖對象的解析工作
當得到真實的視圖對象View后,DispatcherServlet就使用這個View對ModelAndView中的模型數據進行視圖渲染
最終客戶端得到相應消息可能是一個普通的html頁面,也可能是要給XML或者JSON串,甚至是一張圖片或者PDF文檔等不同的媒體形式。
Spring MVC的DispatcherServlet
我們在前面兩篇博文的例子中,servlet需要我們自己編寫,基于Spring MVC ,則無需如此。 SpringMVC中自帶了一個開箱即用的DispatcherServlet,全限定名為org.springframework.web.servlet.DispatcherServlet
使用DispatcherServlet
要想使用這個servlet,同樣的也需要把它配置在部署描述符(web.xml)、應用servlet和servlet-mapping。如下所示
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0"xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"><servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><!-- map all requests to DispatcherServlet --><url-pattern>/</url-pattern></servlet-mapping> </web-app>servlet元素內的on-startup是可選項,如果它存在,則它將在應用程序啟動時裝載servlet并調用他的init方法,若不存在,則該servlet的第一個請求時加載。
DispatcherServlet將使用Spring MVC諸多默認的組件,此外,初始化的時候,它會尋找一個在應用程序的WEB-INF目錄下的配置文件,該配置文件的命名規則 servletName-servlet.xml 。 其中servletName是在部署描述中的DispatcherServlet的名稱,比如我們上述的配置文件 <servlet-name>springmvc</servlet-name>,則在WEB-INF下對應的文件為springmvc-servlet.xml.
此外,也可以把SpringMVC的配置文件放在應用程序目錄中的任何地方,用servlet定義的init-param元素,以便DispatcherServlet加載到該文件。 init-param元素擁有一個contextConfigLocation的param-name元素,其param-value元素則包含配置文件的路徑。如下所示
Controller接口
Spring2.5之前,開發一個控制器的唯一方法是實現org.springframework.web.servlet.mvc.Controller接口,該接口中有一個方法
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;其實現類可以訪問對應請求的HttpServletRequest 和HttpServletResponse ,同時還必須返回包含視圖路徑或者視圖路徑和模的ModelAndView 對象。
Controller接口實現類只能處理一個單一動作,而一個基于注解的控制器可以同時支持多個請求處理動作,并且無需實現任何接口(后續介紹這種主流的開發方式,這里先演示下實現接口的方式)
簡單示例(實現接口的方式)
在這里我們只是簡單的演示一下這種方式的用法,實際開發中并不推薦這樣做。基于注解的方式后續介紹。
maven工程結構如下:
pom.xml添加依賴
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.artisan</groupId><artifactId>chapter03a</artifactId><packaging>war</packaging><version>0.0.1-SNAPSHOT</version><name>chapter03a Maven Webapp</name><url>http://maven.apache.org</url><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope><!-- provided 依賴只有在當JDK 或者一個容器已提供該依賴之后才使用。 例如, 如果你開發了一個web 應用,你可能在編譯 classpath 中需要可用的Servlet API 來編譯一個servlet,但是你不會想要在打包好的WAR 中包含這個Servlet API;這個Servlet API JAR 由你的應用服務器或者servlet 容器提供。已提供范圍的依賴在編譯classpath (不是運行時)可用。它們不是傳遞性的,也不會被打包。 --></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>4.3.9.RELEASE</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency></dependencies><build><finalName>chapter03a</finalName><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>2.5.1</version><configuration><source>1.7</source><target>1.7</target><compilerArgument>-Xlint:all</compilerArgument><showWarnings>true</showWarnings><showDeprecation>true</showDeprecation></configuration></plugin></plugins></build> </project>部署描述文件和Spring MVC 的配置文件
部署描述文件web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0"xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"><servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- load servlet file by contextConfigLocation --><init-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/config/artisan-servlet.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><!-- map *.action requests to DispatcherServlet --><url-pattern>*.action</url-pattern></servlet-mapping> </web-app>這里告訴Servlet/JSP容器,我們將使用SpringMVC的DispatcherServlet,并通過配置url-pattern原始來匹配.action結尾的URL映射到該servlet。 并通過init-param元素,加載特定目錄下的Spring MVC 配置文件人,如果不配置的話,則SpringMVC的配置文件將在默認的/WEB-INF目錄下,并且按照約定的命名規范。
Spring MVC配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean name="/product_input.action" class="com.artisan.springmvc.controller.InputProductController"/><bean name="/product_save.action" class="com.artisan.springmvc.controller.SaveProductController"/></beans>這里聲明兩個控制器類InputProductController和SaveProductController,并分別映射到/product_input.action和/product_save.action。
Controller
下面來編寫“傳統”風格的控制器,分別實現Spring提供的Controller接口
package com.artisan.springmvc.controller;import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;import org.apache.log4j.Logger; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; /*** * @author Mr.Yang* @Desc 實現Spring自身的Controller**/ public class InputProductController implements Controller{private static final Logger logger = Logger.getLogger(InputProductController.class);@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {logger.info("InputProductController called");return new ModelAndView("/WEB-INF/jsp/ProductForm.jsp");}}InputProductController 類的handleRequest方法只返回一個ModelAndView對象,包含一個視圖,并沒有模型。因此,該請求將被轉發到/WEB-INF/jsp/ProductForm.jsp頁面
package com.artisan.springmvc.controller;import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;import org.apache.log4j.Logger; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller;import com.artisan.springmvc.form.ProductForm; import com.artisan.springmvc.model.Product; /*** * @author Mr.Yang* @Desc 實現Spring自身的Controller**/ public class SaveProductController implements Controller{private static final Logger logger = Logger.getLogger(SaveProductController.class);@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {logger.info("SaveProductController called");ProductForm productForm = new ProductForm();// populate action propertiesproductForm.setName(request.getParameter("name"));productForm.setDescription(request.getParameter("description"));productForm.setPrice(request.getParameter("price"));// create modelProduct product = new Product();product.setName(productForm.getName());product.setDescription(productForm.getDescription());try {product.setPrice(Float.parseFloat(productForm.getPrice()));} catch (NumberFormatException e) {}// insert code to save Productreturn new ModelAndView("/WEB-INF/jsp/ProductDetails.jsp", "product",product);}}SaveProductController類的handleRequest方法中,首先用請求創建了一個ProductForm對象,然后它根據ProductForm對象創建Product對象 。 由于ProductForm的price是一個字符串,而在Product類中是一個float類型,因此需要轉型(后面會介紹通過數據綁定省去ProductForm,這里暫不講解)
SaveProductController的handleRequest方法返回最后的ModelAndView模型包含了視圖的路徑、模型名稱和模型(Product對象),該模型將提供給目標視圖,用于界面顯示。
視圖
兩個JSP,綁定了CSS樣式。
我們在web.xml配置url-pattern來匹配.action ,沒有配置 / (所有請求)是因為如果配置了/,而沒有配置靜態資源過濾,這個CSS也會被攔截,因此這里暫時配置了攔截所有action結尾的請求。
ProductForm.jsp
<!DOCTYPE HTML> <html> <head> <title>Add Product Form</title> <style type="text/css">@import url(css/main.css);</style> </head> <body><div id="global"><form action="product_save.action" method="post"><fieldset><legend>Add a product</legend><p><label for="name">Product Name: </label><input type="text" id="name" name="name" tabindex="1"></p><p><label for="description">Description: </label><input type="text" id="description" name="description" tabindex="2"></p><p><label for="price">Price: </label><input type="text" id="price" name="price" tabindex="3"></p><p id="buttons"><input id="reset" type="reset" tabindex="4"><input id="submit" type="submit" tabindex="5" value="Add Product"></p></fieldset> </form> </div> </body> </html>ProductDetails.jsp
<!DOCTYPE HTML> <html> <head> <title>Save Product</title> <style type="text/css">@import url(css/main.css);</style> </head> <body> <div id="global"><h4>The product has been saved.</h4><p><h5>Details:</h5>Product Name: ${product.name}<br/>Description: ${product.description}<br/>Price: ${product.price}</p> </div> </body> </html>ProductDetail.jsp頁面通過模型屬性名“product”來訪問由SaveProductController傳入的Product對象。這里用JSP表達式來顯示Product對象的各種屬性,后續會詳解JSP 的EL表達式。
測試應用
輸入URL:
http://localhost:8080/chapter03a/product_input.action
輸入相應的數據
提交數據后,url跳轉到
http://localhost:8080/chapter03a/product_save.action
View Resolver
上個案例中,頁面的跳轉通過指定頁面的路徑來完成的,比如
new ModelAndView("/WEB-INF/jsp/ProductDetails.jsp", "product",product);其實,Spring MVC為我們提供了視圖解析器,負責解析視圖,現在我們來改造下。
在SpringMVC的配置文件中配置視圖解析器
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/WEB-INF/jsp/"/><property name="suffix" value=".jsp"/></bean>如上視圖解析器前綴和后綴兩個屬性,這樣一來,view的路徑就縮短了。 比如僅需要ProductDetails,而無需設置/WEB-INF/jsp/ProductDetails.jsp,視圖解析器就會自動增加前綴和后綴。
緊接著我們調整下控制器中的頁面跳轉
InputProductController 修改為
return new ModelAndView("ProductForm");SaveProductController修改為
return new ModelAndView("ProductDetails", "product",product);重新運行測試,結果同上。
總結
本博文是Spring MVC的入門介紹,我們知道了使用SpringMVC,我們無需編寫自己的DispatcherServlet,其傳統風格的控制器開發方式是實現控制器接口。 從Spring2.5版本開始,Spring提供了基于注解的方式開發控制器,下篇博文介紹。
源碼
代碼已提交到github
https://github.com/yangshangwei/SpringMvcTutorialArtisan
總結
以上是生活随笔為你收集整理的Spring MVC-03循序渐进之Spring MVC的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring MVC-02循序渐进之解耦
- 下一篇: Spring MVC-04循序渐进之基于