Bean Validation數據校驗和分組校驗

前端校驗後,爲什麼需要後端校驗

在前面完成數據參數綁定到Controller時,我們可以在@RequestParam註解中做簡單的空校驗,就是設置required屬性爲true,以此來指定Controller方法中形參是否必須傳入。數據校驗是很常見的操作,有前端校驗,即用戶在前端頁面上填寫表單時,檢查數據的合法性,再傳入到後端。到了後端,還可以有後端的數據校驗,後端校驗通常是在業務邏輯方法,也就是我們的Controller方法裏完成,例如在參數列表裏使用註解完成數據校驗。後端校驗的原因是爲了安全性考慮,防止別人通過接口亂調用後臺的業務邏輯方法,假設在業務方法中不進行數據校驗,有可能會被別讓繞過前端校驗,直接通過後端的接口方法調用,傳入大量的髒數據到數據庫。

在這篇日誌中,總結自己使用集成Hibernate的校驗框架, 和Spring MVC的Bean Validation數據校驗。Bean Validation數據校驗可以檢查JavaBean中的數據,接下來先看看校驗器的搭建。

 

配置validation校驗器

validator聲明和屬性配置

上面說到,後端校驗通常放在業務邏輯方法中,在Spring MVC裏也就是我們的處理器適配器中具體實現方法,通過在形參列表中使用註解,完成數據校驗。我的處理器映射器和適配器都是用註解的方式配置,即在Spring MVC的配置文件裏使用的annotation-driven標籤對,既然後端數據校驗發生在處理器適配器中,當然校驗器也要在annotation-driven標籤裏聲明瞭:

<!-- 簡寫方式 配置註解的處理器映射器和適配器 -->
<!-- 添加名爲"validator的校驗器" -->
<mvc:annotation-driven  validator="validator"></mvc:annotation-driven>

在annotation-driven標籤中我們聲明一個validator屬性,指定了一個id爲validator校驗器。然後我們就繼續在下面配置這個id爲“validator”的校驗器屬性:

<!-- 配置校驗器 -->
<bean id="validator" 
			class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
	<property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
	<property name="validationMessageSource" ref="messageSource" />
</bean>

LocalValidatorFactoryBean是默認的校驗器實現,下面配置的providerClass爲繼承的Hibernate校驗框架,最後validationMessageSource表示的是校驗使用的資源文件,裏面可以自定義編寫當校驗出數據不合法時,給出的錯誤提示。如果不配置這個校驗資源文件,會默認使用ValidationMessages.properties。

校驗資源文件配置

當我們聲明瞭校驗資源文件後,就可以使用自己編寫的文件實現自定義錯誤提示信息。首先在Spring MVC配置文件中配置這個資源文件:

<!-- 配置校驗資源文件 -->
		<bean id="messageSource" 
			class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
			<property name="basenames">
				<list>
					<value>classpath:UserValidationMessages</value>
				</list>
			</property>
			<property name="fileEncodings" value="utf-8" /> <!-- 資源文件編碼格式 -->
			<property name="cacheSeconds" value="120" /> <!-- 資源文件內容的緩存事件1 -->
		</bean>


ReloadableResourceBundleMessageSource類用提供讀取資源屬性文件,也就是我們的校驗資源文件,它需要指定一些屬性,資源文件名basenames和資源文件的編碼格式fileEncodings和資源文件內容的緩存時間cacheSeconds,在這裏我們配置校驗資源文件爲UserValidationMessages,這是我們自己編寫的properties文件:

# 設置校驗錯誤提示信息
user.name.length.error=Please enter a name with a length of 1-20 characters.
user.gender.isEmpty=Please enter gender

在下面具體校驗註解中我們纔會看到它的使用。

前端數據類型轉換

還有一個配置,使用conversionService接口完成將前端客戶端的一些數據進行轉換後,再傳入後端服務器端。爲什麼需要數據類型轉換?你看再前端頁面中用戶輸入的數據通常是基本數據類型,整數或者字符串,但傳到後端Controller進行業務邏輯處理時,通常我們是用Java類來保存數據。前面的數據參數綁定我們就可以看到,我們的查詢用戶頁面,用戶名和性別在前端頁面都是字符串類型,傳到後端進行查詢時,是要用Java類型保存,再傳入Controller方法中。所以有時候需要對前端數據進行轉換後,再傳入後端。我們可以在annotation-driven標籤中,配置conversionService屬性:

<mvc:annotation-driven  conversion-service="conversionService" 
    validator="validator">
</mvc:annotation-driven>

然後配置conversionService使用FromattingConversionServiceFactoryBean類,該類可以完成字符串和Date類型的轉換。

<!-- 字符串轉爲Date類型的Java類 -->
		<bean id="conversionService" 
			class="org.springframework.format.support.FormattingConversionService-FactoryBean">
		</bean>

      其實,如果我們不在annotation-driven標籤中配置conversion-service屬性,還是會默認加載一個ConversionService,而且使用的就是FromattingConversionServiceFactoryBean類,在這裏我是想展示出有這麼一個配置,可以完成前端的數據類型轉換。

測試用例

添加校驗註解

來到校驗註解配置,在這裏就要指定我們要對哪些數據進行校驗了,例如我們的Controller方法完成對用戶數據的查詢,Java實體類是User,要求前端填入的查詢數據,姓名長度範圍在1-20個字符之間,性別不能爲空。那麼在User類中我們就可以這麼配置:

public class User {
    
    private Integer id;
    @Size(min=1, max=20, message="{user.name.length.error}")
    private String username;

    private String password;
    @NotEmpty(message="{user.gender.isEmpty}")
    private String gender;

    private String email;

    private String province;

    private String city;

    private Date birthday;

    private Integer age;

    // 省略get()和set()方法
}

在username屬性上面,我麼使用@Size註解,指定min=1和max=20,校驗的錯誤信息爲user.name.length.error。在gender屬性上,使用@NotEmpty註解完成非空校驗,錯誤信息爲user.gender.isEmpty。由於我們在前面配置了校驗資源文件,所以輸出的會是我們的自定義錯誤提示。

      最後的配置,就是在我們的Controller方法形參列表中,使用@Validated註解,指定哪些參數數據需要進行校驗:

@Controller
@RequestMapping("user")
public class validationControllerTest {
	private UserServiceImpl userService = new UserServiceImpl();
	@RequestMapping("validationByCondition")
	public String queryUserByCondition(Model model, 
			@Validated User user, 
			BindingResult bindingResult) {
		// 保存校驗錯誤信息
		List<ObjectError> ErrorList = null;
		if (bindingResult.hasErrors()) {
			ErrorList = bindingResult.getAllErrors();
			for (ObjectError objectError : ErrorList) {
				System.out.println(objectError.getDefaultMessage());
			}
		}
		
		List<User> userList = null;
		
		if(user == null || (user.getUsername() == null && user.getGender() == null)) {
			// 如果查詢框中兩個查詢條件都爲空,則默認查詢所有顧客數據
			userList = userService.queryUserList();
		} else { 
			// 否則進行條件查詢
			userList = userService.queryUserByCondition(user);
		}
		
		// model數據傳到頁面
		model.addAttribute("userList", userList);
		model.addAttribute("ErrorList", ErrorList);
		
		return "users/validateUser"; // 返回視圖
	}
}

可以看到,我們要對傳入方法的User類實例進行數據校驗,並且傳入BindingResult類對象,它存放了校驗錯誤信息。第9行首先定義一個ErrorList來存放校驗錯誤信息。第11行獲取所有的錯誤信息放到ErrorList中,然後做輸出,如果只是這樣輸出,錯誤信息是顯示在了控制檯上,所以第29行我們通過model對象,將這個ErrorList傳到前端頁面去顯示。

前端視圖

爲了在前端頁面中顯示錯誤信息提示,我們通過model對象把錯誤信息傳到了前端視圖中顯示:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>用戶查詢列表</title>
</head>
<body>
	<form action="validationByCondition.action" method="post">
		用戶名:<input type="text" name="username" />&nbsp;&nbsp;
		性別:<input type="text" name="gender" />
		<input type="submit" value="查找" />
	</form>
	<!-- 顯示錯誤信息 -->
	<c:if test="${ErrorList != null}">
		<c:forEach items="${ErrorList}" var="error">
			<font color="red">${error.defaultMessage}</font><br/>
		</c:forEach>
	</c:if>
	<hr/>
	<h2>搜索結果</h2>
	<table width="300px;" border=1>
		<tr>
			<td>顧客名</td>
			<td>性別</td>
			<td>電子郵箱</td>
			<td>省會</td>
			<td>城市</td>
		</tr>
		
		<c:forEach items="${userList }" var="user">
			<tr>
				<td>${user.username }</td>
				<td>${user.gender }</td>
				<td>${user.email }</td>
				<td>${user.province }</td>
				<td>${user.city }</td>
			</tr>
		</c:forEach>	
	</table>
</body>
</html>

第15行我們接收傳來的ErrorList,並遍歷錯誤信息,將其顯示出來:

 

Bean Validation分組校驗

使用場景

分組校驗的使用場景是這樣的,假設我們的多個Controller方法都用到了同一個Java類作爲數據存儲並傳入,我們需要對類中哪些屬性進行校驗,是在實體類裏面使用註解標識的,但是如果我們一個Controller方法只需對類中的某一個屬性做校驗,而另一個Controller方法需要對類中的兩個屬性做校驗,怎麼辦?方法是使用分組校驗,在Java實體類中的屬性裏標註分組信息,然後在不同的Controller方法中形參列表裏的@Validated註解添加分組屬性,根據不同的分組,實現不同的校驗。

配置分組信息

首先創建兩個空接口,用來標識不同的分組:

然後在Java實體類中,爲需要校驗的屬性username和gender,在其註解裏添加分組:

public class User {
    
    private Integer id;
    @Size(min=1, max=20, message="{user.name.length.error}", 
    		groups= {UserGroup1.class})
    private String username;

    private String password;
    @NotEmpty(message="{user.gender.isEmpty}", groups= {UserGroup2.class})
    private String gender;

    private String email;

    private String province;

    private String city;

    private Date birthday;

    private Integer age;

    //省略get()和set()方法
}

我們將username屬性的校驗放在了UserGroup1,gender屬性的校驗放在了UserGroup2。

最後,我們在Controller方法裏,根據不同的業務邏輯,配置校驗分組,實現對共用數據類型的不同校驗方式:

@Controller
@RequestMapping("user")
public class validationControllerTest {
	private UserServiceImpl userService = new UserServiceImpl();
	@RequestMapping("validationByCondition")
	public String queryUserByCondition(Model model, 
			@Validated(value=UserGroup1.class) User user, 
			BindingResult bindingResult) {
		// 保存校驗錯誤信息
		List<ObjectError> ErrorList = null;
		if (bindingResult.hasErrors()) {
			ErrorList = bindingResult.getAllErrors();
			for (ObjectError objectError : ErrorList) {
				System.out.println(objectError.getDefaultMessage());
			}
		}
		
		List<User> userList = null;
		
		if(user == null || (user.getUsername() == null && user.getGender() == null)) {
			// 如果查詢框中兩個查詢條件都爲空,則默認查詢所有顧客數據
			userList = userService.queryUserList();
		} else { 
			// 否則進行條件查詢
			userList = userService.queryUserByCondition(user);
		}
		
		// model數據傳到頁面
		model.addAttribute("userList", userList);
		model.addAttribute("ErrorList", ErrorList);
		
		return "users/validateUser"; // 返回視圖
	}
}

在@Validated註解中,添加UserGroup1分組,這樣,在調用該方法進行業務邏輯處理時,只會對該分組標識的username屬性進行校驗:

 

完整實現GitHub已上傳:

https://github.com/justinzengtm/SSM-Framework/tree/master/SpringMVC_Project

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