Springboot中使用ajax提交复杂对象+校验
ajax提交复杂对象
1. 提交的对象类UserDetail:
public class User {
@NotBlank
private String id;
@NotBlank
private String userName;
}
public class UserDetail {
@NotNull
private User user;
private String details;
}
2. controller的代码:
@RequestMapping(value = "api/user/add", method = { RequestMethod.PUT, RequestMethod.POST })
@ResponseBody
public String add(@RequestBodyUserDetail userDetail) {
System.out.print(userDetail);
return "success";
}
3. ajax提交的代码,红色部分是关键代码,①处不写对会报400错;②处不这样写,后端无法接收
var user = {
userName : $("#userName").val()
}
var userDetail = {
user : user,
detail : $("#detail").val()
};
$.ajax({
type : 'POST',
dataType : "JSON",
contentType:"application/json", //①
cache : false,
url : url,
data : JSON.stringify(datas),
async : false,
success : function(result) {
alert("成功");
},
error : function(result) {
alert("失败");
}
});
后端校验
在做校验的时候踩了几个坑,这里主要是把坑记录下来
坑1:
springboot的校验,首先考虑用@Valid做,于是在Controller代码里加入注解
public String add(@Valid@RequestBodyUserDetail userDetail) {
System.out.print(userDetail);
return "success";
}
很快发现问题,此处Valid的校验只会对按照UserDetail类里的校验规则做校验,而不会对其引用对象user校验,如User类里要求userName不为空,但此时校验不会校验userName。即没有对嵌套的内层对象做校验。
解决:在UserDetail类的user对象上加上@Valid注解
public class UserDetail {
@NotNull
@Valid
private User user;
private String details;
}
写个测试方法,测试正常:
publicclass ValidationTest {
privatestaticfinal Validator validator;
// 获得验证器实例
static {
Configuration<?> config = Validation.byDefaultProvider().configure();
ValidatorFactory factory = config.buildValidatorFactory();
validator = factory.getValidator();
factory.close();
}
publicstaticvoid main(String[] args) throws ParseException {
User user = new User();
UserDetail userDetail = new UserDetail ();
user.setSysUser(user);
Set<ConstraintViolation<UserDetail>> violations = validator.validate(userDetail);
if (violations.size() == 0) {
System.out.println("No violations.");
} else {
System.out.printf("%s violations:%n", violations.size());
violations.stream().forEach(ValidationTest::printError);
}
}
privatestaticvoid printError(ConstraintViolation<?> violation) {
System.out.println(violation.getPropertyPath() + " " + violation.getMessage());
}
}
坑2:
加上@Valid之后,前台直接报400错误,后台没有任何异常打印信息,userDetails也没有打印出来。推测此时验证已经生效,但绑定过程异常,并且该异常没有被BindException捕获(简单对象的验证错误是可以用这个异常捕获的)。
几经周折,最后发现这里报的异常是MethodArgumentNotValidException,于是添加对这个异常的全局处理,问题解决
改进:考虑了一下,在Controller层使用@Valid来做校验体验不好,直接手动校验逻辑会更加清楚
写一个校验工具类BeanValidators
publicclass BeanValidators {
/**
* 调用JSR303的validate方法, 验证失败时抛出ConstraintViolationException.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
publicstaticvoid validateWithException(Validator validator, Object object, Class<?>... groups)
throws ConstraintViolationException {
Set constraintViolations = validator.validate(object, groups);
if (!constraintViolations.isEmpty()) {
thrownew ConstraintViolationException(constraintViolations);
}
}
/**
* 辅助方法,
* 转换ConstraintViolationException中的Set<ConstraintViolations>中为List<message>.
*/
publicstatic List<String> extractMessage(ConstraintViolationException e) {
return extractMessage(e.getConstraintViolations());
}
/**
* 辅助方法, 转换Set<ConstraintViolation>为List<message>
*/
@SuppressWarnings("rawtypes")
publicstatic List<String> extractMessage(Set<? extends ConstraintViolation> constraintViolations) {
List<String> errorMessages = Lists.newArrayList();
for (ConstraintViolation violation : constraintViolations) {
errorMessages.add(violation.getMessage());
}
returnerrorMessages;
}
/**
* 辅助方法,
* 转换ConstraintViolationException中的Set<ConstraintViolations>为Map<property,
* message>.
*/
publicstatic Map<String, String> extractPropertyAndMessage(ConstraintViolationException e) {
return extractPropertyAndMessage(e.getConstraintViolations());
}
/**
* 辅助方法, 转换Set<ConstraintViolation>为Map<property, message>.
*/
@SuppressWarnings("rawtypes")
publicstatic Map<String, String> extractPropertyAndMessage(
Set<? extends ConstraintViolation> constraintViolations) {
Map<String, String> errorMessages = Maps.newHashMap();
for (ConstraintViolation violation : constraintViolations) {
errorMessages.put(violation.getPropertyPath().toString(), violation.getMessage());
}
returnerrorMessages;
}
/**
* 辅助方法,
* 转换ConstraintViolationException中的Set<ConstraintViolations>为List<propertyPath
* message>.
*/
publicstatic List<String> extractPropertyAndMessageAsList(ConstraintViolationException e) {
return extractPropertyAndMessageAsList(e.getConstraintViolations(), " ");
}
/**
* 辅助方法, 转换Set<ConstraintViolations>为List<propertyPath message>.
*/
@SuppressWarnings("rawtypes")
publicstatic List<String> extractPropertyAndMessageAsList(
Set<? extends ConstraintViolation> constraintViolations) {
return extractPropertyAndMessageAsList(constraintViolations, " ");
}
/**
* 辅助方法,
* 转换ConstraintViolationException中的Set<ConstraintViolations>为List<propertyPath
* +separator+ message>.
*/
publicstatic List<String> extractPropertyAndMessageAsList(ConstraintViolationException e, String separator) {
return extractPropertyAndMessageAsList(e.getConstraintViolations(), separator);
}
/**
* 辅助方法, 转换Set<ConstraintViolation>为List<propertyPath +separator+ message>.
*/
@SuppressWarnings("rawtypes")
publicstatic List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations,
String separator) {
List<String> errorMessages = Lists.newArrayList();
for (ConstraintViolation violation : constraintViolations) {
errorMessages.add(violation.getPropertyPath() + separator + violation.getMessage());
}
returnerrorMessages;
}
}
Controller层调用代码(object是需要验证的类):
protected Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
try {
BeanValidators.validateWithException(validator, user);
} catch (ConstraintViolationException e) {
List<String> list = BeanValidators.extractPropertyAndMessageAsList(e, ": ");
list.add(0, "数据验证失败:");
//处理逻辑
}