天天看點

springmvc使用JSR-303進行校驗

下面提供一種springmvc的校驗方案,一般沒有校驗或者手動寫validator的話都要寫好多代碼好多if判斷,使用JSR-303規範校驗隻需要在Pojo字段上加上相應的注解就可以實作校驗了

1.依賴的jar包,我直接貼pom了

<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<spring.version>4.1.1.RELEASE</spring.version>
	</properties>
	
	<dependencies>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-core</artifactId>
			<version>2.4.0</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.4.0</version>
		</dependency>
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>1.3.1</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
		</dependency>
		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>1.1.0.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>5.1.3.Final</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.7</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.11</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.8.4</version>
		</dependency>
		<dependency>
			<groupId>javax.el</groupId>
			<artifactId>javax.el-api</artifactId>
			<version>3.0.0</version>
		</dependency>
	</dependencies>
           

2.springmvc配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
		http://www.springframework.org/schema/tx 
		http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-3.0.xsd
		http://www.springframework.org/schema/mvc
		http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- 預設的注解映射的支援 -->
	<mvc:annotation-driven/>
	<context:annotation-config/>
	<aop:aspectj-autoproxy/>

	<!-- 自動掃描的包名 -->
	<context:component-scan base-package="spring.test.web.controller"/>
	
	<bean id="mappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
	
</beans>
           

3.寫java代碼了

先建一個User類

public class User {
	@NotNull
	private String username;
	
	@NotNull(message = "密碼不能為空")
	private String password;
	
	public String getUsername() {
		return username;
	}
	
	public void setUsername(String username) {
		this.username = username;
	}
	
	public String getPassword() {
		return password;
	}
	
	public void setPassword(String password) {
		this.password = password;
	}
	
}
           

然後建一個controller試試

@ResponseBody
@RequestMapping("/test1")
public AjaxResponse validateTest1(@Valid User user, BindingResult result){
	AjaxResponse ajaxResponse = new AjaxResponse();
	Map<String, Object> map = new HashMap<>();
	if(result.hasErrors()){
		ajaxResponse.setSuccess(false);
		ajaxResponse.setHasErrors(true);
		map.put("errors", result.getAllErrors());
	}
	else {
		map.put("now", new Date());
		map.put("user", user);
	}
	ajaxResponse.setData(map);
	return ajaxResponse;
}
           

這個方法第一個參數上加了@Valid标注,第二個參數是必須的而且必須緊跟着@Valid參數之後,校驗結果都在這個result裡面

方法邏輯比較簡單,調用result.hasErrors()看校驗有沒有錯誤,有錯誤的話就把所有的錯誤放進結果傳回給用戶端,如果沒有錯誤就傳回目前時間跟user對象

然後寫個測試方法試試

@Test
public void test1() throws Exception{
	this.mockMvc.perform(post("/validator/test1")).andDo(print());
	this.mockMvc.perform(post("/validator/test1").param("username", "testusername").param("password", "testpassword")).andDo(print());
}
           

第一個請求的結果是

{"success":false,"message":null,"hasErrors":true,"data":[{"name":"user","message":"may not be null"},{"name":"user","message":"密碼不能為空"}]}
           

第二個請求結果是

{"success":true,"message":null,"hasErrors":false,"data":{"now":1420788232169,"user":{"username":"testusername","password":"testpassword"}}}
           

很明顯第一次請求沒有使用者名和密碼兩個字段校驗都沒有通過,第二次是成功的

用起來還是很簡單的,但是如果我不想每次都在controller方法裡寫if(result.hasErrors())怎麼辦呢,寫個切面,把if寫在切面裡,如果有errors直接就傳回了,不用再執行controller方法了

@Aspect
public class ValidAspect {
	
	@Autowired private Validator validator;
	
	@Around("@annotation(org.springframework.web.bind.annotation.ResponseBody)")
	public Object doTest(ProceedingJoinPoint pjp) throws Throwable{
		MethodSignature signature = (MethodSignature) pjp.getSignature();
		Method method = signature.getMethod();
		if(!AjaxResponse.class.equals(method.getReturnType())){
			pjp.proceed();
		}
		Object[] args = pjp.getArgs();
		Annotation[][] annotations = method.getParameterAnnotations();
		for(int i = 0; i < annotations.length; i++){
			if(!hasValidAnnotation(annotations[i])){
				continue;
			}
			if(!(i < annotations.length-1 && args[i+1] instanceof BindingResult)){
				//驗證對象後面沒有跟bindingResult,事實上如果沒有應該到不了這一步
				continue;
			}
			BindingResult result = (BindingResult) args[i+1];
			if(result.hasErrors()){
				AjaxResponse ajaxResponse = new AjaxResponse();
				ajaxResponse.setSuccess(false);
				ajaxResponse.setHasErrors(true);
				ajaxResponse.setData(processErrors(result));
				return ajaxResponse;
			}
		}
		return pjp.proceed();
	}

	private boolean hasValidAnnotation(Annotation[] annotations){
		if(annotations == null){
			return false;
		}
		for(Annotation annotation : annotations){
			if(annotation instanceof Valid){
				return true;
			}
		}
		return false;
	}
	
	private List<BindingError> processErrors(BindingResult result){
		if(result != null && result.hasErrors()){
			List<BindingError> list = new ArrayList<BindingError>();
			for(ObjectError error : result.getAllErrors()){
				BindingError be = new BindingError();
				be.setMessage(error.getDefaultMessage());
				be.setName(error.getObjectName());
				list.add(be);
			}
			return list;
		}
		return null;
	}
}
           

注意要在springmvc配置檔案加一行<bean class="spring.test.web.aop.ValidAspect"/>

然後再寫個方法邏輯跟上面的類似

@ResponseBody
@RequestMapping("/test2")
public AjaxResponse validateTest2(@Valid User user, BindingResult result){
	AjaxResponse ajaxResponse = new AjaxResponse();
	Map<String, Object> map = new HashMap<>();
	map.put("now", new Date());
	map.put("user", user);
	ajaxResponse.setData(map);
	return ajaxResponse;
}
           

這裡沒有再去判斷hasErrors()了,然後測試一下

寫測試方式試一下

@Test
public void test2() throws Exception{
	this.mockMvc.perform(post("/validator/test2")).andDo(print());
	this.mockMvc.perform(post("/validator/test2").param("username", "testusername").param("password", "testpassword")).andDo(print());
}
           

第一個請求結果是

{"success":false,"message":null,"hasErrors":true,"data":[{"name":"user","message":"密碼不能為空"},{"name":"user","message":"may not be null"}]}
           

第二個請求結果是

{"success":true,"message":null,"hasErrors":false,"data":{"now":1420788479105,"user":{"username":"testusername","password":"testpassword"}}}
           

效果跟上面是一樣的

當然我這個切面僅僅是個示範,我攔截的是帶有ResponseBody注解的方法,我這些方法會傳回一個統一的格式,如果是要傳回别的視圖就要自己定義注解加其他參數了

JSR-303原生支援的校驗注解也是有限的,如果要實作其他的驗證,可以自己拓展,拓展方法就下次再說了,我也才剛接觸。