一、spring3.1 mvc核心思想介紹
網上下載實例:SpringMVC實例
Spring MVC是spring的一個web組件,它爲構建穩健的web應用提供了豐富的功能。
Spring MVC是基於每個邏輯和功能是高可配置的這樣架構和設計的。當然springMVC可以與其它流行的web框架像struts、webwork、javaserverface及tapestry實現無縫集成。我們看一下springMVC的核心思想。
Spring請求的生命週期
總結一下springMVC幾個關鍵的步驟,總共可以分爲六個步驟,分別爲:
(1) 客戶端向spring容器發起一個http請求
(2) 發起的請求被前端控制起所攔截(DispatcherServlet),前端控制器會去找恰當的映射處理器來處理這次請求。
(3) 根據處理器映射(Handler Mapping)來選擇並決定將請求發送給那一個控制器。
(4) 在控制器中處理所發送的請求,並以modeandView(屬性值和返回的頁面)的形式返回給向前端控制器。
(5) 前端控制器通過查詢viewResolver對象來試着解決從控制返回的視圖。
(6) 如果前端控制找到對應的視圖,則將視圖返回給客戶端,否則拋異常。
通過上面的圖和springMVC生命週期的六個步驟,想必大家對springMVC的核心思想有個了大概的瞭解了,下面我們以實例爲主,帶領大家慢慢熟悉整個springMVC及如何使用springMVC。(本教程基於maven實現springMVC中的例子,所以大家得對maven需要有大概的瞭解)。
二、spring3.1 mvc 框架的特點
如果僅僅關注於web方面的支持,Spring有下面一些特點:
· 清晰的角色劃分:控制器,驗證器,命令對象,表單對象和模型對象;分發器,處理器映射和視圖解析器;等等。
· 直接將框架類和應用類都作爲JavaBean配置,包括通過應用上下文配置中間層引用,例如,從web控制器到業務對象和驗證器的引用。
· 可適應性,但不具有強制性:根據不同的情況,使用任何你需要的控制器子類(普通控制器,命令,表單,嚮導,多個行爲,或者自定義的),而不是要求任何東西都要從Action/ActionForm繼承。
可重用的業務代碼,而不需要代碼重複:你可以使用現有的業務對象作爲命令對象或表單對象,而不需要在ActionForm的子類中重複它們的定義
· 可定製的綁定和驗證:將類型不匹配作爲應用級的驗證錯誤,這可以保存錯誤的值,以及本地化的日期和數字綁定等,而不是隻能使用字符串表單對象,手動解析它並轉換到業務對象。
· 可定製的處理器映射,可定製的視圖解析:靈活的模型可以根據名字/值映射,處理器映射和視圖解析使應用策略從簡單過渡到複雜,而不是隻有一種單一的方法。
· 可定製的本地化和主題解析,支持JSP,無論有沒有使用Spring標籤庫,支持JSTL,支持不需要額外過渡的Velocity,等等。
簡單而強大的標籤庫,它儘可能地避免在HTML生成時的開銷,提供在標記方面的最大靈活性。三、spring 3.1 MVC入門列子HelloWorld
(1)在WEB-INF/web.xml中加入如下代碼:
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:spring-servlet.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
上述的配置的就是前段控制器,在servlet-mapping配置了*.html,意味着所有以.html結尾的請求多會通過這個servlet,當dispatcherServlet啓動時,他默認會在web-info目錄下查找一個spring-servlet.xml的配置文件。上面我們通過顯示指定了這個文件的位置,即在類路徑底下的spring-servlet.xml.這個文件我們會在第二步點給他家做詳細介紹。
(2)在類路徑底下添加spring-servlet.xml文件,其內容如下:
<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context=http://www.springframework.org/schema/context
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan
base-package="com.pango.spring.helloworld.controller"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix"value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp"/>
</bean>
</beans>
上面這個文件,我們定義了一個<context:component-scan
base-package="com.pango.spring.helloworld.controller"/>
這樣的標籤,定義了這個後,當spring在啓動時,會加載com.pango.spring.helloworld.controller這個包底下及子包底下的所有的組件(這就包的自動掃描機制,即spring會將標有@Controller@Component等類加載到spring容器管理中),後面我們還定義了<beanid="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix"value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp"/>
</bean>
ViewResolver是一個試圖解析器,就是我們第一部分提到的springMVC生命週期中的第五步,上面這段的配置的意思就是,當我們從後端控制器中返回的視圖時,前端控制器就根據這一段配置來返回一個具體的視圖,如後端控制返回的是一個hello,根據上面的配置,最後前端控制器會組併成這樣的一個地址:/web-inf/jsp/hello.jsp,然後從/web-inf/jsp/這個目錄下面查找一個hello.jsp返回客戶端。第三部分我們看我們寫得HelloworldController後臺控制器。
(3)在包底下寫一個HelloWorldController的類,其內容如下:
@Controller
public classHelloWorldController {
@RequestMapping(value="/hello")
public String sayHello(ModelMapmodelMap){
modelMap.put("sayHello", "hello world");
return "/hello";
}
}
在這裏簡單介紹下上面的配置,後面我們會詳細講解各個參數:
Ø Controller即聲明這個類是一個控制器,上面第二部分我們說明了,只要加了@Controller標示的,spring會通過自動掃描機制,將這個類納入spring容器管理中。
Ø @RequestMapping(value="/hello"),這個定義的就是一個請求路徑,只要符合/hello路徑的多會交給這個控制器的sayhello方法來處理。
Ø 最後我們返回/hello的視圖給客戶端。
(4)好了,大功告成,我們再在web-info/jsp/目錄下添加一個hello.jsp文件,就可以啓動運行我們的第一個程序了。hello.jsp的內容如下:
<%@ pagelanguage="java" contentType="text/html;charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ tagliburi="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE htmlPUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<metahttp-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Insert titlehere</title>
</head>
<body>
<span>${sayHello}</span>
</body>
</html>
運行後訪問ip;port/project/hello.html就可以看到我們預期的結果了。
四、springMVC參數傳遞mvc結構中,v層不斷有數據和c層交互,所以弄明白在springMVC中如何與後臺進行數據交互是極其重要的,當然在下面我不會介紹每一個方法,只是對常用的方法,對於我這裏沒有涉及的方法大家可以參考spring官方的文檔中springMVC這個章節。下面我們來看一幅圖。
當我們向springMVC發起請求到視圖返回前,springMVC幫我們做了主要是上面幾個步驟,通過數據綁定、數據類型轉換、驗證、結果綁定這幾個步驟。
讓我們看下實例:
@RequestMapping("/user/find")
public String findUserById(@RequestParam("userId") intuserId,ModelMap modelMap){
modelMap.put("userId", userId);
return "/user";
}
Ø @RequestMapping("/user/find"),是對請求映射的說明,這個註解中主要包含四個屬性,分別value、method、params、header,這四個參數分別表示:
Value:指定路徑
Method:請求方式
Params:參數
Headers:請求頭
後面三個就是對請求路徑的一個限制條件。
SpringMVC對於路徑的定義非常的靈活
以下URL都是合法的:
l /usercreateUser
匹配/user/createUser、/user/aaa/bbb/createUser等URL。
l /user/createUser??
匹配/user/createUseraa、/user/createUserbb等URL。
l /user/{userId}
匹配user/123、user/abc等URL。
l /user{userId}
l company/{companyId}/user/{userId}/detail
匹配company/123/user/456/detail等的URL。
Ø 對RequestParam的介紹
@RequestParam有以下三個參數。
l value:參數名。
l required:是否必需,默認爲true,表示請求中必須包含對應的參數名,如果不存在將拋出異常。
l defaultValue:默認參數名,設置該參數時,自動將required設爲false。極少情況需要使用該參數,也不推薦使用該參數。
當發送請求時,請求參數中必須要包含userId這個參數,當不包含這個參數,請求將找不到這個映射。當屬性required=true時,不包含這個參數將會拋異常,如果不能確定是否需要這個參數是我們可以寫成,@RequestParam(value= "userId", required = false) 。
Ø 直接將屬性映射到對象中
@RequestMapping("/user/find2")
public String find2UserById(User user,ModelMapmodelMap){
modelMap.put("user", user);
return "/user";
}
SpringMVC按:
“HTTP請求參數名 = 命令/表單對象的屬性名”
的規則,自動綁定請求數據,支持“級聯屬性名”,自動進行基本類型數據轉換。
如:發起下面這個請求,springMVC會自動將id、name、password屬性的值填充到user對象中。
http://localhost:8080/springParam/user/save.html?id=12&name=marcle&password=123
Ø SpringMVC以rest技術向springMVC傳遞參數
通過 REST 風格體系架構,請求和響應都是基於資源表示的傳輸來構建的。資源是通過全局 ID 來標識的,這些 ID一般使用的是一個統一資源標識符(URI)。客戶端應用使用 HTTP 方法(如,GET、POST、PUT 或DELETE)來操作一個或多個資源。通常,GET 是用於獲取或列出一個或多個資源,POST 用於創建,PUT 用於更新或替換,而DELETE 則用於刪除資源。
例如,GET http://host/context/employees/12345
將獲取 ID 爲12345 的員工的表示。這個響應表示可以是包含詳細的員工信息的 XML 或 ATOM,或者是具有更好 UI 的JSP/HTML 頁面。您看到哪種表示方式取決於服務器端實現和您的客戶端請求的 MIME 類型。
RESTful Web Service 是一個使用 HTTP 和 REST 原理實現的 Web Service。通常,一個RESTful Web Service 將定義基本資源 URI、它所支持的表示/響應 MIME,以及它所支持的操作。
Spring 3.0之後引入了對rest風格的支持。我們看實例
@RequestMapping("/user/find/{id}")
public String rest(@PathVariable int id,ModelMapmodelMap){
User user = new User();user.setName("marcle");
user.setPassword("123");
user.setId(id);
modelMap.put("user", user);
return "/user";
}
這裏需要注意的地方時@RequestMapping("/user/find/{id}")和@PathVariable intid名稱必須一樣,否則會出現異常。
Ø 簡單介紹返回視圖的方式
u ModelAndView 形式返回
@RequestMapping("/user/save2")
public ModelAndView save2(User user,ModelMap modelMap){
ModelAndView mav = new ModelAndView();
mav.setViewName("/user");
mav.addObject("user", user);
return mav;
}
ModelAndView就是對返回到頁面的值和視圖進行封裝。
u 直接字符串的形式返回,如”return“/user””,再把屬性通過modelMap進行封裝,modelMap存儲的值屬於request範圍內,如果要發送服務器端請求,springMVC非常方便,你只要這樣寫即可return”direct:user”.
還有一種傳遞參數的方法,我放在springMVC中的rest技術介紹
下面我們看看springMVC返回的過程SpringMVC簡單沒幾個標籤,用起來還是非常好用的,在使用springMVC中的標籤之前需要向每個jsp的頭部引入標籤支持<%@taglib prefix="form"uri="http://www.springframework.org/tags/form"%>
1) form標籤
這個標籤會生成一個HTML的form標籤,同時爲內部標籤的綁定暴露了一個綁定路徑,它把命令對象(commandobject)放在pageContext中,這樣內部的標籤就可以訪問這個對象,這個庫中的其它標籤都是這個標籤的嵌套標籤。
如我們有個user的域對象,包含id、name、password屬性,我們將把它當作返回index.jsp表單控制器的對象,如下面的代碼:
<form:formaction="${ctx}/user/save.${ext}"method="post"commandName="user">
<table>
<tr>
<td>Id:</td>
<td><form:input path="id"/></td>
</tr>
<tr>
<td>name:</td>
<td><form:input path="name"/></td>
</tr>
<tr>
<td>password:</td>
<td><form:input path="password"/></td>
</tr>
<tr>
<tdcolspan="2"><input type="submit" value="Save"/></td>
</tr>
</table>
</form:form>
上述的id、name、password由頁面控制器放置在pageContext中,即在內部控制器方法中需要做這樣的聲明:
@RequestMapping(value="/user/save",method=RequestMethod.GET)
public String forSave(@ModelAttribute User user){
return "/index";
}
後臺控制器中必須綁定這個@ModelAttribute Useruser命令行對象,而form下面的屬性需要於這個user中的屬性對應起來,否則將會拋異常。標籤經過解析後生成的代碼如下:
<form id="user"action="/springTag/user/save.html"method="post">
<table>
<tr>
<td>Id:</td>
<td><input id="id"name="id" type="text"value="0"/></td>
</tr>
<tr>
<td>name:</td>
<td><input id="name"name="name" type="text"value=""/></td>
</tr>
<tr>
<td>password:</td>
<td><inputid="password" name="password" type="text"value=""/></td>
</tr>
<tr>
<td colspan="2"><inputtype ="submit" value="Save Changes"/></td>
</tr>
</table>
</form>
(2) input標籤
使用時如上面的表達式,<form:input path="id" />解析後會變成<input id="name"name="name" type="text"value=""/>可見用spring標籤比傳統的html簡潔很多。
(3)checkbox 標籤
這個標籤解析之後會變成html’中的type爲checkbox的input元素,我們假設我們的用戶有很多的參考東西,如信息的訂閱、愛好、格言等,即如下面的域模型:
public class Preferences {
private boolean receiveNewsletter;
private String[] interests;
private String favouriteWord;
public boolean isReceiveNewsletter() {
return receiveNewsletter;
}
public void setReceiveNewsletter(boolean receiveNewsletter) {
this.receiveNewsletter = receiveNewsletter;
}
public String[] getInterests() {
return interests;
}
public void setInterests(String[] interests) {
this.interests = interests;
}
public String getFavouriteWord() {
return favouriteWord;
}
public void setFavouriteWord(String favouriteWord) {
this.favouriteWord = favouriteWord;
}
}
我們的相應的jsp文件可以寫成:
<form:formaction="${ctx}/pre/save.${ext}"method="post"commandName="preferences">
<table>
<tr>
<td>Subscribe tonewsletter?:</td>
<%-- Approach 1: Property is of typejava.lang.Boolean --%>
<td><form:checkboxpath="receiveNewsletter"/></td>
<td> </td>
</tr>
<tr>
<td>Interests:</td>
<td>
<%-- Approach 2: Property is of an array or of typejava.util.Collection --%>
Quidditch: <form:checkbox path="interests"value="Quidditch"/>
Herbology: <form:checkbox path="interests"value="Herbology"/>
Defence Against the Dark Arts: <form:checkboxpath="interests"
value="Defence Against the DarkArts"/>
</td>
<td> </td>
</tr>
<tr>
<td>FavouriteWord:</td>
<td>
<%-- Approach 3: Property is of typejava.lang.Object --%>
Magic: <form:checkbox path="favouriteWord"value="Magic"/>
</td>
<td> </td>
</tr>
<tr>
<td colspan="2">
<input type="submit"value="submit"/>
</td>
</tr>
</table>
</form:form>
如果有多個供選擇的,在後臺我們以數組的形式存儲。
(4)radiobutton 標籤
解析後會變成html元素中type爲radio的input元素
如下面的情況:
<tr>
<td>Sex:</td>
<td>Male: <form:radiobutton path="sex" value="M"/> <br/>
Female: <form:radiobutton path="sex" value="F"/> </td>
<td> </td>
</tr>
(5)password標籤
解析後變成html元素中type爲password的input元素,即爲密碼框。
<tr>
<td>Password:</td>
<td>
<form:password path="password" />
</td>
</tr>
(6)select標籤
這個標籤對應於html元素中的下拉框,即爲select元素。
<tr>
<td>Skills:</td>
<td><form:selectpath="skills" items="${skills}"/></td>
<td></td>
</tr>
(7)option標籤
<form:select path="house">
<form:option value="Gryffindor"/>
<form:option value="Hufflepuff"/>
<form:option value="Ravenclaw"/>
<form:option value="Slytherin"/>
</form:select>
(8)options標籤
<form:select path="country">
<form:option value="-" label="--Please Select"/>
<form:options items="${countryList}" itemValue="code" itemLabel="name"/>
</form:select>
(9)textarea標籤
<td><form:textarea path="notes" rows="3" cols="20" /></td>
(10)hidden標籤
<form:hidden path="house" />
(11)errors標籤
<form:form>
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName" /></td>
<%-- Show errors for firstName field --%>
<td><form:errors path="firstName" /></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName" /></td>
<%-- Show errors for lastName field --%>
<td><form:errors path="lastName" /></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes" />
</td>
</tr>
</table>
</form:form>
六、springMVC攔截器
和Struts2一樣,SpringMVC也可以使用攔截器對請求進行攔截處理,用戶可以自定義攔截器來實現特定的功能,自定義的攔截器必須實現HandlerInterceptor接口。這個接口中定義了三個方法:preHandle()、postHandle()、afterCompletion()。
下面對代碼中的三個方法進行解釋。
preHandle():這個方法在業務處理器處理請求之前被調用,在該方法中對用戶請求request進行處理。如果程序員決定該攔截器對請求進行攔截處理後還要調用其他的攔截器,或者是業務處理器去進行處理,則返回true;如果程序員決定不需要再調用其他的組件去處理請求,則返回false。
postHandle():這個方法在業務處理器處理完請求後,但是DispatcherServlet向客戶端返回請求前被調用,在該方法中對用戶請求request進行處理。
afterCompletion():這個方法在DispatcherServlet完全處理完請求後被調用,可以在該方法中進行一些資源清理的操作。
下面通過一個例子來說明如何使用SpringMVC框架的攔截器。
要求編寫一個攔截器,攔截所有不在工作時間的請求,把這些請求轉發到一個特定的靜態頁面,而不對它們的請求進行處理。
首先編寫TimeInterceptor.Java,代碼如下:
public classTimeInterceptor extends HandlerInterceptorAdapter {
private int openingTime; // openingTime 屬性指定上班時間
private int closingTime; // closingTime屬性指定下班時間
private String outsideOfficeHoursPage; //outsideOfficeHoursPage屬性指定錯誤
public void setOpeningTime(int openingTime) {
this.openingTime = openingTime;
}
public void setClosingTime(int closingTime) {
this.closingTime = closingTime;
}
public void setOutsideOfficeHoursPage(StringoutsideOfficeHoursPage) {
this.outsideOfficeHoursPage = outsideOfficeHoursPage;
}
// 重寫 preHandle()方法,在業務處理器處理請求之前對該請求進行攔截處理
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception{
Calendar cal = Calendar.getInstance();
int hour = cal.get(Calendar.HOUR_OF_DAY); //獲取當前時間
if (openingTime <= hour&& hour <closingTime) { // 判斷當前是否處於工作 時間段內
return true;
} else {
response.sendRedirect(outsideOfficeHoursPage); // 返回提示頁面
return false;
}
}
}
可以看出,上面的代碼重載了preHandle()方法,該方法在業務處理器處理請求之前被調用。在該方法中,首先獲得當前的時間,判斷其是否在openingTime和closingTime之間,如果在,返回true,這樣纔會調用業務控制器去處理該請求;否則直接轉向一個靜態頁面,返回false,這樣該請求就不會被處理。
下面是在dispatcherServlet-servlet.xml中對攔截器進行的配置,代碼如下:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/user/*"/>
<beanclass="com.pango.spring.interceptor.TimeInterceptor">
<property name="openingTime"value="12"></property>
<property name="closingTime"value="24"></property>
<property name="outsideOfficeHoursPage"value="outTime.html"></property>
</bean>
</mvc:interceptor>
</mvc:interceptors>
可以看出,上面代碼用bean標籤去定義TimeInterceptor,令其id爲officeHoursInterceptor,並給它的3個屬性賦值。在urlMapping中通過<propertyname="interceptors">去指定officeHoursInterceptor爲一個攔截器,讀者可以在<list>和</list>之間定義多個攔截器
outsideOfficeHours.html的代碼很簡單,只是輸出一句提示語。
運行程序,在瀏覽器中隨便訪問一個頁面,如果請求的時間在9點~18點之間,則該請求可以被處理;否則,返回一句提示語,如圖23-5所示
說 明:在第22章中介紹過控制反轉是Spring框架的核心思想,即用一個接口去定義一些操作,在接口的實現類中去重寫這些操作,然後在Spring的配置文件中去把該接口的實現類注入到應有框架中,這樣就可以通過調用接口去調用接口的實現類。本節講的攔截器就體現了這種思想,即實現HandlerInterceptorAdapter接口,重寫preHandle()方法並在配置文件中實現TimeInterceptor的注入。這 樣當框架調用HandlerInterceptorAdapter時,就可以調用到TimeInterceptor類的preHandle()方法
七、spring3MVC 類型轉換
Servlet中的輸入參數爲都是string類型,而spring mvc通過databind機制將這些string 類型的輸入參數轉換爲相應的commandobject(根據view和controller之間傳輸數據的具體邏輯,也可稱爲model attributes, domainmodel objects)。在這個轉換過程中,spring實際是先利用java.beans.PropertyEditor中的setAdText方法來把string格式的輸入轉換爲bean屬性,亦可通過繼承java.beans.PropertyEditorSupport來實現自定義的PropertyEditors。
自定義完畢propertyEditor後,有以下幾種方式來註冊自定義的customerpropertyEditor. (我只實現了第二種轉換方式,至於其它方法大家可以自己嘗試)
Ø 直接將自定義的propertyEditor放到需要處理的java bean相同的目錄下
名稱和java Bean相同但後面帶Editor後綴。
例如需要轉換的java bean名爲User,則在相同的包中存在UserEditor類可實現customer propertyEditor的自動註冊。
Ø 利用@InitBinder來註冊customer propertyEditor
這個在之前的筆記中已經介紹過了,即在controller類中增加一個使用@InitBinder標註的方法,在其中註冊customerEditor
Java代碼
public class BaseController {
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class, newCustomDateEditor(true));
}
}
Ø 繼承 WebBindingInitializer 接口來實現全局註冊
使用@InitBinder只能對特定的controller類生效,爲註冊一個全局的customerEditor,可以實現接口WebBindingInitializer 。
Java代碼
public class CustomerBindingimplements WebBindingInitializer {
public void initBinder(WebDataBinder binder,WebRequest request) {
// TODO Auto-generated method stub
SimpleDateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class,new CustomDateEditor(dateFormat, false));
}
}
並修改 spring-servlet xml配置文件
Xml代碼
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="webBindingInitializer">
<bean
class="net.zhepu.web.customerBinding.CustomerBinding" />
</property>
</bean>
但這樣一來就無法使用mvc:annotation-driven 了。
使用conversion-service來註冊自定義的converter
DataBinder實現了PropertyEditorRegistry,TypeConverter這兩個interface,而在spring mvc實際處理時,返回值都是returnbinder.convertIfNecessary(見HandlerMethodInvoker中的具體處理邏輯)。因此可以使用customerconversionService來實現自定義的類型轉換。
Xml代碼
<beanid="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<propertyname="converters">
<list>
<beanclass="net.zhepu.web.customerBinding.CustomerConverter"/>
</list>
</property>
</bean>
需要修改spring-servletxml配置文件中的annotation-driven,增加屬性conversion-service指向新增的conversionServicebean。
Xml代碼
<mvc:annotation-driven validator="validator"
conversion-service="conversionService" />
對於第二種方式實現如下
Date類型編輯器
public class CustomDateEditorextends PropertyEditorSupport {
private static finalMap<String, String>dateMap;
static {
dateMap = new HashMap<String,String>();
dateMap.put("yyyy-MM-dd", "\\d{4}-\\d{2}-\\d{2}");
dateMap.put("yyyy-MM-dd hh:mm:ss", "\\d{4}-\\d{2}-\\d{2}\\d{2}:\\d{2}:\\d{2}");
dateMap.put("yyyy年MM月dd日", "\\d{4}年\\d{2}月\\d{2}日");
}
private final boolean allowEmpty;
public CustomDateEditor(boolean allowEmpty) {
this.allowEmpty = allowEmpty;
}
@Override
public void setAsText(String text) throwsIllegalArgumentException {
if (this.allowEmpty&&!StringUtils.hasText(text)) {
// Treat empty String as null value.
setValue(null);
} else {
try {
boolean flag = false;
for (String dateFormatStr : dateMap.keySet()) {
if (text.matches(dateMap.get(dateFormatStr))) {
flag = true;
System.out.println(text);
DateFormat dateFormat = newSimpleDateFormat(dateFormatStr);
setValue(dateFormat.parse(text));
break;
}
}
if (!flag) {
//throw new IllegalArgumentException("Could not parse date: " +text);
}
} catch (ParseException ex) {
//throw new IllegalArgumentException("Could not parse date: " +ex.getMessage(), ex);
}
}
}
@Override
public String getAsText() {
Date value = (Date) getValue();
DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd");
return value != null ? dateFormat.format(value) :"";
}
}
@InitBinder來註冊customer propertyEditor
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class, newCustomDateEditor(true));
}
最後講講對於requestBody或httpEntity中數據的類型轉換
SpringMVC中對於requestBody中發送的數據轉換不是通過databind來實現,而是使用HttpMessageConverter來實現具體的類型轉換。
例如,之前提到的json格式的輸入,在將json格式的輸入轉換爲具體的model的過程中,springmvc首先找出requestheader中的contenttype,再遍歷當前所註冊的所有的HttpMessageConverter子類,根據子類中的canRead()方法來決定調用哪個具體的子類來實現對requestBody中的數據的解析。如果當前所註冊的httpMessageConverter中都無法解析對應contexttype類型,則拋出HttpMediaTypeNotSupportedException (http 415錯誤)。
那麼需要如何註冊自定義的messageConverter呢,很不幸,在spring3.0.5中如果使用annotation-driven的配置方式的話,無法實現自定義的messageConverter的配置,必須老老實實的自己定義AnnotationMethodHandlerAdapter的bean定義,再設置其messageConverters以註冊自定義的messageConverter。
Xml代碼
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
<bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
八、json格式數據的輸入和輸出
Springmvc處理json需要使用jackson的類庫,因此爲支持json格式的輸入輸出需要先修改pom.xml增加jackson包的引用
Xml代碼
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-lgpl</artifactId>
<version>1.8.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.8.0</version>
</dependency>
在spring-servlet.xml中必須加入這段代碼:<mvc:annotation-driven/>
根據前面的分析,在spring mvc中解析輸入爲json格式的數據有兩種方式
1:使用@RequestBody來設置輸入
Java代碼
@RequestMapping("/json1")
@ResponseBody
public JsonResult testJson1(@RequestBody User u){
log.info("get json input from request body annotation");
log.info(u.getUserName());
return new JsonResult(true,"return ok");
}
2:使用HttpEntity來實現輸入綁定
Java代碼
@RequestMapping("/json2")
publicResponseEntity<JsonResult>testJson2(HttpEntity<User> u){
log.info("get json input from HttpEntity annotation");
log.info(u.getBody().getUserName());
ResponseEntity<JsonResult>responseResult = newResponseEntity<JsonResult>(new JsonResult(true,"returnok"),HttpStatus.OK);
return responseResult;
對應Json格式的輸出也對應有兩種方式
1:使用@responseBody來設置輸出內容爲context body
@RequestMapping(value="/kfc/brands/{name}", method= RequestMethod.GET)
public @ResponseBodyList<Shop>getShopInJSON(@PathVariable String name) {
List<Shop> shops = newArrayList<Shop>();
Shop shop = new Shop();
shop.setName(name);
shop.setStaffName(new String[]{"mkyong1", "mkyong2"});
shops.add(shop);
Shop shop2 = new Shop();
shop2.setName(name);
shop2.setStaffName(new String[]{"mktong1", "mktong2"});
shops.add(shop2);
return shops;
}
當我們在地址欄中輸入:http://localhost:8080/springJson/kfc/brands/kfc_name.html
服務器端會返回給我們jason格式的數據,這樣我們就可以省去手工繁瑣的組並了
2:返回值設置爲ResponseEntity<?>類型,以返回contextbody
@RequestMapping("/json2")
publicResponseEntity<JsonResult>testJson2(HttpEntity<User>u){
log.info("get json input from HttpEntity annotation");
log.info(u.getBody().getUserName());
ResponseEntity<JsonResult>responseResult = newResponseEntity<JsonResult>(new JsonResult(true,"returnok"),HttpStatus.OK);
return responseResult;
}
九、spring3mvc文件上傳
Spring mvc使用jakarta的commonsfileupload來支持文件上傳,因此我們需要在pom.xml中導入所依賴的兩個包:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.2</version>
</dependency>
在spring-servlet.xml中加入以下這段代碼:
<beanid="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- one of the properties available; the maximumfile size in bytes -->
<property name="maxUploadSize"value="100000" />
</bean>
其中的property中可以限制最大和最小文件上傳。
在客戶端的代碼如下:
<formmethod="post"action="${ctx}/user/upload.${ext}"enctype="multipart/form-data">
<inputtype="text" name="name"/>
<inputtype="file" name="file"/>
<inputtype="submit"/>
</form>
服務器端的代碼如下:
@RequestMapping(value= "/user/upload", method = RequestMethod.POST)
public String handleFormUpload(@RequestParam("name") Stringname,
@RequestParam("file") MultipartFile file, HttpServletRequestrequest)
throws IOException {
String filePath =request.getRealPath("/");
if (!file.isEmpty()) {
String fileName = file.getOriginalFilename();
System.out.println(filePath + "/" + fileName);
byte[] bytes = file.getBytes();
FileOutputStream output = new FileOutputStream(newFile(filePath
+ fileName));
output.write(bytes);
output.close();
return "redirect:/success.jsp";
} else {
return "redirect:/failure.jsp";
}
}
十、springmvc國際化和本地化
何爲國際化,簡單來說就是在那個國家顯示哪個國家的語言,在計算機中,國際化和本地化意味着計算機軟件要適應不同的語言和地區的差異。國際化就是設計爲了適應不同地區和語言的差異而工程不需要做任何改動。
這一節的目的就是在springMVC中增加國際化和本地化的應用,我們將在這一節實現三種語言可以相互切換的國際化和本地化。
(1)我們在resources下面添加三個property文件,分別爲:messages_de.properties、messages_en.properties、messages_zh.properties,文件的命名規則:messages_語言.properties
三個文件的內容如下:
Ø messages_de.properties
label.firstname=Vorname
label.lastname=Familiename
label.email=Email
label.telephone=Telefon
label.addcontact=AddierenKontakt
label.title=spring mvcInternationalization (i18n) / Localization
Ø messages_en.properties
label.firstname=FirstName
label.lastname=LastName
label.email=Email
label.telephone=Telephone
label.addcontact=AddContact
label.title=springmvc Internationalization (i18n) / Localization
Ø messages_zh.properties(經過轉換後的中文)
label.firstname=\u59D3
label.lastname=\u540D\u5B57
label.email=\u7535\u5B50\u90AE\u4EF6
label.telephone=\u7535\u8BDD
label.addcontact=\u8054\u7CFB\u65B9\u5F0F
label.title=springmvc\u56FD\u9645\u5316\u548C\u672C\u5730\u5316\u652F\u6301
(2)spring-servet.xml文件的配置
<!-- 爲了使用國際化信息源,SpringMVC必須實現MessageSource接口。當然框架內部有許多內置的實現類。我們需要做的是註冊一個MessageSource類型的Bean。Bean的名稱必須爲messageSource,從而方便DispatcherServlet自動檢測它。每個DispatcherServlet只能註冊一個信息源-->
<beanid="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename"value="classpath:messages" />
<property name="defaultEncoding"value="UTF-8" />
</bean>
<!—session解析區域 -->
<beanid="localeResolver"
class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<!--property name="defaultLocale" value="en"/>-->
</bean>
<!-- 修改用戶的區域需要調用區域修改攔截器 LocaleChangeInterceptor。如下所設定設定paramName屬性來設定攔截請求中的特定參數(這裏是language)確定區域。既然是攔截器那就需要註冊到攔截器Bean 中,這裏是註冊到了DefaultAnnotationHandlerMappingBean中 -->
<beanid="localeChangeInterceptor"
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName"value="lang" />
</bean>
<!--
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="defaultLocale" value="en"/>
</bean>
-->
<bean id="handlerMapping"
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<propertyname="interceptors">
<ref bean="localeChangeInterceptor"/>
</property>
</bean>
(3)在jsp目錄下面創建一個contact.jsp文件
<%@ page language="java"contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@tagliburi="http://www.springframework.org/tags"prefix="spring"%>
<%@tagliburi="http://www.springframework.org/tags/form"prefix="form"%>
<%@taglib prefix="c"uri="http://java.sun.com/jsp/jstl/core"%>
<c:setvar="ctx"value="${pageContext.request.contextPath}"/>
<c:setvar="ext" value="html" />
<html>
<head>
<title>Spring 3 MVC Series - ContactManager</title>
</head>
<body>
<h3><spring:messagecode="label.title"/></h3>
<spanstyle="float: right">
<ahref="${ctx}/language.${ext}?local=en">英文</a>
|
<ahref="${ctx}/language.${ext}?local=de">德文</a>
<ahref="${ctx}/language.${ext}?local=zh">中文</a>
</span>
<form:form method="post"action="addContact.html"commandName="contact">
<table>
<tr>
<td><form:labelpath="firstname"><spring:messagecode="label.firstname"/></form:label></td>
<td><form:inputpath="firstname"/></td>
</tr>
<tr>
<td><form:labelpath="lastname"><spring:messagecode="label.lastname"/></form:label></td>
<td><form:inputpath="lastname"/></td>
</tr>
<tr>
<td><form:labelpath="lastname"><spring:messagecode="label.email"/></form:label></td>
<td><form:inputpath="email"/></td>
</tr>
<tr>
<td><form:labelpath="lastname"><spring:messagecode="label.telephone"/></form:label></td>
<td><form:inputpath="telephone"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit"value="<spring:messagecode="label.addcontact"/>"/>
</td>
</tr>
</table>
</form:form>
</body>
</html>
其中<spring:message>標籤結合ResourceBundleMessageSource 的功能,在網頁上顯示 messages.properties中的文字訊息。
(4)創建LanguageController
@Controller
public class LanguageController{
@Autowired
private SessionLocaleResolver localeResolver;
@RequestMapping("/forLanguage")
public String forLanguage(@ModelAttribute Contactcontact){
return "/contact";
}
@RequestMapping(value="/language",method=RequestMethod.GET)
public ModelAndViewchangeLocal(@ModelAttribute Contact contact,HttpServletRequestrequest,@RequestParam String local,HttpServletResponseresponse){
if("zh".equals(local)){
localeResolver.setLocale(request, response,Locale.CHINA);
}else if("en".equals(local)) {
localeResolver.setLocale(request, response,Locale.ENGLISH);
}else if("de".equals(local)){
localeResolver.setLocale(request, response,Locale.GERMAN);
}
return newModelAndView("/contact");
}
}
其中紅色部分就是對語言的設置
效果如下圖:
JSR 303 – Bean Validation是一個數據驗證的規範,2009 年 11 月確定最終方案。2009 年 12 月 Java EE 6 發佈,BeanValidation 作爲一個重要特性被包含其中,SpringMVC在使用了<mvc:annotation-driven>後,如果路徑中有jsr 303的實現,將自動提供對jsr 303驗證方式的支持。
Ø 引入hibernate-validator,hibernate-validator對jsr 303做了實現
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.2.0.Final</version>
</dependency>
Ø 新增一個pojo bean ,增加jsr 303格式的驗證annotation
public class Contact{
private Long id = 0L;
@Size(min = 1)
private String firstName;
@NotNull
@Size(min = 1)
private String lastName;
@Past
private Date dateOfBirth;
private boolean married;
@Min(0)
@Max(20)
private int children;
}
Ø 在controller 類中的handler method中,對需要驗證的對象前增加@Valid 標誌
@RequestMapping(value="/add",method=RequestMethod.POST)
public String addContact(@ModelAttribute @Valid Contactcontact,BindingResult result){
if(result.hasErrors()){
return "/contact";
}
return "/contact";
}
Ø 在jsp頁面中添加contact.jsp
<form:form action="${ctx}/contact/add.${ext}"method="post"
commandName="contact">
<table border="1">
<tr>
<th> </th>
<th><spring:messagecode="editcontact.heading"/></th>
</tr>
<tr>
<tdbgcolor="cyan"><spring:message
code="editcontact.label.firstname"/></td>
<td><form:inputpath="firstName" size="40"/><font
color="#FF0000"><form:errorspath="firstName*"/></font></td>
</tr>
<tr>
<tdbgcolor="cyan"><spring:message
code="editcontact.label.lastname"/></td>
<td><form:inputpath="lastName" size="40"/><font
color="#FF0000"><form:errorspath="lastName*"/></font></td>
</tr>
<tr>
<tdbgcolor="cyan"><spring:messagecode="editcontact.label.dob"/></td>
<td><form:inputpath="dateOfBirth" size="40"/><font
color="#FF0000"><form:errorspath="dateOfBirth*"/></font></td>
</tr>
<tr>
<tdbgcolor="cyan"><spring:message
code="editcontact.label.married"/></td>
<td><form:checkboxpath="married" /><fontcolor="#FF0000"><form:errors
path="married"/></font></td>
</tr>
<tr>
<tdbgcolor="cyan"><spring:message
code="editcontact.label.children"/></td>
<td><form:inputpath="children" size="5"/><font
color="#FF0000"><form:errorspath="children*"/></font></td>
</tr>
<tr>
<td><inputtype="submit"
value="<spring:messagecode="editcontact.button.save"/>"/></td>
<td><inputtype="reset"
value="<spring:messagecode="editcontact.button.reset"/>"/></td>
</tr>
</table>
</form:form>
Ø 結果
使用jsr303非常簡單吧,有些人就問了,可以不可以自定義錯誤信息,當然是可以的,下面我就通過自定義錯誤來實現對contact的校驗。
@Size(min = 1,message = "Contact first name is required.")
private String firstName;
@NotNull(message = "Contact cannot be left empty.")
@Size(min = 1, message = "Contact last name isrequired.")
private String lastName;
@Past(message = "Contact date of birth must be a date in thepast.")
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date dateOfBirth;
private boolean married;
@Min(value = 0, message = "A contact cannot have fewer than 0children")
@Max(value = 20, message = "A contact cannot have more than 20children")
private int children;
只要將錯誤信息寫到註解後面的message中即可,簡單吧,我們再來看看jsr 303主要的註解有哪些?
表 1. BeanValidation 中內置的constraint
注 解 |
功能說明 |
@Null |
被註釋的元素必須爲null |
@NotNull |
被註釋的元素必須不爲null |
@AssertTrue |
被註釋的元素必須爲true |
@AssertFalse |
被註釋的元素必須爲false |
@Min(value) |
被註釋的元素必須是一個數字,其值必須大於等於指定的最小值 |
@Max(value) |
被註釋的元素必須是一個數字,其值必須小於等於指定的最大值 |
@DecimalMin(value) |
被註釋的元素必須是一個數字,其值必須大於等於指定的最小值 |
@DecimalMax(value) |
被註釋的元素必須是一個數字,其值必須小於等於指定的最大值 |
@Size(max, min) |
被註釋的元素的大小必須在指定的範圍內 |
@Digits (integer,fraction) |
被註釋的元素必須是一個數字,其值必須在可接受的範圍內 |
@Past |
被註釋的元素必須是一個過去的日期 |
@Future |
被註釋的元素必須是一個將來的日期 |
|
被註釋的元素必須符合指定的正則表達式 |
表2. Hibernate Validator附加的constraint
|
|
|
|
有人寫就問了,那麼可以不可以自定義註釋類型呢?答案當然是可以的。
Ø 自定義jsr303註釋類型
(1)@Age是一個定製化的 constraint,由兩個內置的constraint 組合而成。代碼如下
@Max(130)
@Min(1)
@Constraint(validatedBy = {})
@Documented
@Target( {ElementType.ANNOTATION_TYPE, ElementType.METHOD,ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Age{
String message() default "年齡填寫不正確";
Class<?>[] groups()default {};
Class<? extendsPayload>[] payload() default {};
}
(2)status是一個重新寫得註釋類型
@Status 的annotation 部分
@Constraint(validatedBy ={StatusValidator.class})
@Documented
@Target( {ElementType.ANNOTATION_TYPE, ElementType.METHOD,ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Status{
String message() default"狀態選擇不正確";
Class<?>[] groups()default {};
Class<? extendsPayload>[] payload() default {};
}
@Status 的constraint validator部分
public class StatusValidatorimplements ConstraintValidator<Status,Integer> {
private final int[] ALL_STATUS = {1, 2,3};
public void initialize(Status arg0) {
// TODO Auto-generated method stub
}
public boolean isValid(Integer value,ConstraintValidatorContext arg1) {
if(Arrays.asList(ALL_STATUS).contains(value)){
return true;
} else{
return false;
}
}
}
如果大家對於註釋怎麼寫不夠了解,請參考其它相關文檔。
在屬性上增加age、status
@Age
private int age;
@Status
private intstatus;
運行結果如下