当前位置: 代码迷 >> JavaScript >> jquery<json->spring(3.0)以后台校验
  详细解决方案

jquery<json->spring(3.0)以后台校验

热度:466   发布时间:2012-10-30 16:13:35.0
jquery<--json-->spring(3.0)之后台校验

前一段试了一下前台用jquery(1.3.2),后台用spring(3.0),之间用json交换数据,
然后写了篇总结jquery(1.3.2)<--json-->spring(3.0),有几位大侠提出了后台校验
的问题,我也觉得这是很普遍的问题,就参考一些资料做了个小demo,现在总结一下,
欢迎各位拍砖。

我是这样考虑的,在后台接收数据时,首先使用一个所有属性均为String的对象,在
这个对象上,使用Bean Validation(JSR-303)进行数据校验,校验通过后,再将该对象
转换为一个VO然后进行后续处理,在校验充分的情况下,转换步骤是不会出现例外的。
在校验失败的时候,返回由属性名(也即页面元素名)、错误信息对组成的数组,前端
根据元素名自动突出显示对应元素、并在其旁边显示错误信息,下面详细说明。

1. 前端提交
这和前文一样,没什么可说的。

2. 后台校验

2.1 属性全为String的对象,及其校验
spring 3开始全面支持JSR-303,并以hibernate validator作为默认实现,该技术可以
用声明(Annotaion)的方式定义校验,而且标记可以自由扩充,我觉得这很方便,所以
用的这一技术做的校验。
后台所有接收的对象都为全String类型属性对象,因为对于json数据传输我们无法在接收
数据之前进行校验,属性全用String可以避免在Controller获取数据前发生数据转换错误。
JSR-303内建了一些校验规则,例如Min,Max,Pattern(正则表达式,有了这个就可以
处理绝大部分校验了),hibernate validtor还扩充了NotEmpty,Email等,一下是我的
例子中客户信息的对象:
package json;

import javax.validation.constraints.Digits;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Email;

import constraints.IsDate;

public class JSONCustomer {
	@NotEmpty
	private String name;

	@NotEmpty
	private String addr;

	@NotEmpty
	@IsDate(format="yyyy-MM-dd")
	private String birthday;

	@NotEmpty
	private String hukou;

	@Email
	private String email;

	@Pattern(regexp="(((\\w+)\\.)+(\\w+))?")
	private String url;

	@Digits(integer = 3, fraction = 0)
	@Min(value = 100)
	@Max(value = 230)
	private String height;

	//setter, getter略
}


其中IsDate是我自己扩充的校验Annotation,这个后面再说。
这样,校验就定义好了,简单吧。

2.2 校验
下面来看看Controller中的校验。首先要在Controller的构造函数中传入validator,
spring有默认的validator,就是hibennate validator 4(所以需要这个jar包)。
然后在处理函数中使用validator.validate()就可以了,代码如下:
package controller;

import java.lang.reflect.InvocationTargetException;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;

import org.apache.commons.beanutils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import json.JSONCustomer;
import json.JSONResponse;
import vo.Customer;

@Controller
@RequestMapping("/customerInfo")
public class CustomerInfoController extends JSONController {

	@Autowired
	public CustomerInfoController(Validator validator) {
		super(validator);
	}
	
	@RequestMapping(value = "/new", method = RequestMethod.POST)
	@ResponseBody
	public JSONResponse newCustomer(@RequestBody JSONCustomer jsoncustomer) throws IllegalAccessException, InvocationTargetException {
		Set<ConstraintViolation<JSONCustomer>> failures = validator.validate(jsoncustomer);
		
		if (failures.isEmpty()) {
			//校验通过
			Customer customer = new Customer();
			//将接收的全String属性对象转成vo
			BeanUtils.copyProperties(jsoncustomer, customer);
			
			return successed(jsoncustomer);
		} else {
			//校验失败
			return failed(failures);
		}
	}
}


其中JSONController是我自己的Controller基类,后面再说。根据validate()的返回值failures
可以知道是否通过了校验,successed()和failed()是JSONController的方法,为了统一成功与
失败时的返回数据格式,这个在后面说明。

2.3 自定义校验Annotation
这部分请参阅相关的文档,我就不多说了,这里只贴出代码:
package constraints;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target( { METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = IsDateValidator.class)
@Documented
public @interface IsDate {
	String message() default "日期格式不正确";
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
	String format();
}


package constraints;

import java.text.ParseException;
import java.text.SimpleDateFormat;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class IsDateValidator implements ConstraintValidator<IsDate, String> {
	private String format;
	
	public void initialize(IsDate constraintAnnotation) {
		this.format = constraintAnnotation.format();
	}

	public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
		try {
			SimpleDateFormat sf = new SimpleDateFormat(format);
			sf.setLenient(false);
			sf.parse((String)object);
			
			return true;
		} catch (ParseException pe) {
			return false;
		}
	}

}


有了内建的校验和自定义校验的功能,我们就可以实现所有的校验了。而且校验的错误信息
是可以自定义的。校验施加的对象也可以是属性、方法、甚至对象整体,就是说可以根据对
象的多个属性值判断进行校验,详细的请参考jsr-303和其推荐实现hibernate validator的
相关文档。

3. 返回值

3.1 返回的对象结构
为了统一页面端的处理,后台返回值具有统一的结构,定义如下:
package json;

import java.util.ArrayList;

/**
 * 服务器返回对象
 * 所有服务器处理返回的统一对象
 */
public class JSONResponse {
	//成功、失败标志
	private boolean successed = false;
	//错误信息
	private ArrayList<JSONError> errors = null;
	//成功时返回的对象
	private Object returnObject = null;
	
	//setter, getter略
}

其中JSONError定义如下:
package json;

/**
 * 错误信息
 *
 */
public class JSONError {
	//元素名,与页面元素名一致
	private String element;
	//错误信息
	private String message;
	
	//setter, getter略
}


成功时,successed=true,返回的对象在returnObject中,对于不同页面,这个
对象是不同的,但各个页面知道自己要得到一个什么结构的数据。校验失败时
successed=false,errors是一个数组,其中每个元素是一个<元素名-错误信息>对。

3.2 返回方法
返回的方法是统一的,定义在JSONController里,代码如下:
package controller;

import java.util.ArrayList;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;

import json.JSONError;
import json.JSONResponse;


public class JSONController {
	protected Validator validator;

	public JSONController(Validator validator) {
		this.validator = validator;
	}
	
	public JSONResponse successed(Object obj) {
		JSONResponse ret = new JSONResponse();
		ret.setSuccessed(true);
		ret.setReturnObject(obj);
		
		return ret;
	}
	
	public JSONResponse failed(Set failures) {
		Set<ConstraintViolation<?>> failureSet = (Set<ConstraintViolation<?>>)failures;
		ArrayList<JSONError> errors = new ArrayList<JSONError>();
		for (ConstraintViolation<?> failure : failureSet) {
			errors.add(new JSONError(failure.getPropertyPath().toString(), failure.getMessage()));
		}
		
		JSONResponse ret = new JSONResponse();
		ret.setSuccessed(false);
		ret.setErrors(errors);
		
		return ret;
	}
}


4. 页面处理
页面的html主要就是一个包含姓名、地址、生日、Email、身高等输入项的form,这里就
不贴出来丢人了,需要说明的是每个输入项后都有一个<div class="errorMessage"></div>
准备放错误信息。
页面以json格式接收后台的处理结果,如果结果标识成功,则对返回的对象进行处理(显
示之类的,这里没有涉及),如果标识失败,则根据错误信息进行处理。
其中提交数据的处理代码如下:
function save() {
	var elemUserinfo = $('#customerInfo');
	var userinfo = elemUserinfo.serializeObject();
	var jsonuserinfo = JSON.stringify(userinfo);

	jQuery.ajax( {
		type : 'POST',
		contentType : 'application/json',
		url : 'customerInfo/new.do',
		data : jsonuserinfo,
		dataType : 'json',
		success : function(resp) {
			if (resp.successed) {
				alert("数据已成功保存" );
				//清除错误信息
				elemUserinfo.applyErrors({});
			} else {
				elemUserinfo.applyErrors(resp.errors);
			}
		},
		error : failed
	});
};

其中的applyErrors是我自己写的一段函数,就是根据错误信息的元素名,给元素画个红框,然后
把错误信息写到该元素后面的<div class="errorMessage"></div>里。该函数的参数为空对象时
清除所有错误信息。代码如下:

/**
 * 突出显示错误的输入域,写错误信息
 */
$.fn.applyErrors = function(es) {
	alert('applyErrors - ' + es);
	this.map(function() {
		$.each(this.elements, function() {
			var currElem = this;
			$(currElem).css('border-color', ''); //清除突出显示
			$(currElem).nextAll('div').text(''); //清除错误信息
			$.each(es, function() {
				if (this.element == currElem.name) {
					$(currElem).css('border-color', 'red'); //突出显示
					$(currElem).nextAll('div').text(this.message); //错误信息
				}
			})
		})
	});
};

我对javascript不太熟悉,这段代码是仿照serializeObject写的,哪位大侠有空,帮着改改,在下
不胜感激。

最后贴一张校验结果的图,我对美工不在行,画面比较丑,见笑了。


1 楼 Angel_Night 2010-05-05  
楼主又更新了啊

直接上源代码吧  跑上次那个demo 我花了1个小时...
2 楼 yiyu 2010-05-05  
对不起,代码在这里
3 楼 godson_2003 2010-05-05  
看起来感觉不错啊 明天好好研究研究
4 楼 SSailYang 2010-05-06  
在 Spring 出到 3.0.0 的时候,它内置的 Json 格式转换器还不支持 @DateTimeFormat 和 @NumberFormat,在 Ajax Simplifications in Spring 3.0 的这篇文章里作者自己写了一个 BeanPostProcessor 去解决这个问题。不知道 3.0.2 怎么样了
5 楼 oakeye 2010-05-06  
试试新的入例校验,之前都没作 只是在前端考虑了
6 楼 hyl1234 2010-05-06  
还没怎么看过spring3.0 下来看看效果 谢谢lz
7 楼 java_doc 2010-05-06  
不错,但是在Chrome下不能用哦,还有在后台处理Json的请求和响应、验证也太繁琐了,为什么不像 Ajax Simplifications in Spring 3.0 那个sample那样处理呢?
8 楼 upup1000 2010-05-06  
我发现java注解还是挺有用的,哈哈。
9 楼 yiyu 2010-05-06  
java_doc 写道
不错,但是在Chrome下不能用哦,还有在后台处理Json的请求和响应、验证也太繁琐了,为什么不像 Ajax Simplifications in Spring 3.0 那个sample那样处理呢?


chrome里不行啊?我看看,我一直在firefox里试的

我觉得Ajax Simplifications in Spring 3.0里有个问题,如果前台的数据转不成Account会怎么样?例如一个需要数字的地方输入了字符,我觉得会在开始校验前就出例外了,结果会是导致ajax通讯失败,而这不是我们想要的,我们想要的是ajax通讯正常结束,但是接收到的是校验错误信息
10 楼 yiyu 2010-05-06  
java_doc 写道
不错,但是在Chrome下不能用哦,还有在后台处理Json的请求和响应、验证也太繁琐了,为什么不像 Ajax Simplifications in Spring 3.0 那个sample那样处理呢?


chrome里不能执行的原因知道了,inputCustomerInfo.html里head里的style和script加载时好像给过滤掉了,把这两部分移到body里就行了。
不过感觉不太好,也许不该用这种页面加载的方式,哪位大侠有经验给出出主意吧
11 楼 dmhorse 2010-05-06  
我没用过json,但感觉老要创建 jsoncustomer之类的object有种代码重复的感觉.
12 楼 prowl 2010-05-06  
Annotation的方式很新颖
13 楼 java_doc 2010-05-06  
yiyu 写道
java_doc 写道
不错,但是在Chrome下不能用哦,还有在后台处理Json的请求和响应、验证也太繁琐了,为什么不像 Ajax Simplifications in Spring 3.0 那个sample那样处理呢?


chrome里不行啊?我看看,我一直在firefox里试的

我觉得Ajax Simplifications in Spring 3.0里有个问题,如果前台的数据转不成Account会怎么样?例如一个需要数字的地方输入了字符,我觉得会在开始校验前就出例外了,结果会是导致ajax通讯失败,而这不是我们想要的,我们想要的是ajax通讯正常结束,但是接收到的是校验错误信息

这确实是一个问题,在网上查了一下也有其他人遇到过,但是没有比较好的解决办法,简单的就是在客户端验证,这样就不会出现这种情况了,感觉这样也不是很好。
14 楼 yiyu 2010-05-06  
java_doc 写道
yiyu 写道
java_doc 写道
不错,但是在Chrome下不能用哦,还有在后台处理Json的请求和响应、验证也太繁琐了,为什么不像 Ajax Simplifications in Spring 3.0 那个sample那样处理呢?


chrome里不行啊?我看看,我一直在firefox里试的

我觉得Ajax Simplifications in Spring 3.0里有个问题,如果前台的数据转不成Account会怎么样?例如一个需要数字的地方输入了字符,我觉得会在开始校验前就出例外了,结果会是导致ajax通讯失败,而这不是我们想要的,我们想要的是ajax通讯正常结束,但是接收到的是校验错误信息

这确实是一个问题,在网上查了一下也有其他人遇到过,但是没有比较好的解决办法,简单的就是在客户端验证,这样就不会出现这种情况了,感觉这样也不是很好。


其实我一直觉得那些简单的数据合法性校验就在前台做就行了,后台只做业务合法性校验就行了,不知大家怎么看?
15 楼 caoyangx 2010-05-07  
呵呵,去年10月时,我同事也做了一个类似的,后台实体用验证注解,前台验证,效果很好,很方便。
16 楼 Angel_Night 2010-05-07  
yiyu 写道

其实我一直觉得那些简单的数据合法性校验就在前台做就行了,后台只做业务合法性校验就行了,不知大家怎么看?


这东西 按情况划分 最合适

如果给一个公司做oa 那没有必要做太多的数据校验..
一个员工 是不会输入边界数据来玩极限的
最多做一些简单的前台验证就够了 当然 增加用户体验的ajax逻辑验证还是要有的..

如果是放在 天朝大局域网 上跑的东西
把验证做好...都不一定够..
17 楼 bhzln 2010-05-12  
这种校验不过是增加用户体验了,看起来效果好,实际上服务器端的提交校验不一样是不能省么,个人观点用个服务器端校验就行了,其他的都是等项目完成了有时间的时候装饰一下
18 楼 lym6520 2010-05-14  
感觉很不错,呵,jquery都1.4.1了,楼主可以更新了,在性能上有了很大提高。
  相关解决方案