本文为Spring Boot2.1系列的第二篇,代码可以从github下载 https://github.com/yinww/demo-springboot2.git
对数据验证的工作经常由前端工程师完成,但是代码重复繁琐,并且即使有前端的验证也不能放松后端对数据的验证工作,否则可能存在数据安全性的问题,代码示意如下:
function checkForm() {
var username = $('#name').val();
if (null == username || '' == username) {
$('#errMsg').html(getI18n('userCantEmpty'));
return false;
}
var code = $('#code').val();
if (null == code || '' == code) {
$('#errMsg').html(getI18n('codeCantEmpty'));
return false;
}
return true;
}
本文介绍Spring Boot的后台验证技术hibernate validator,内容包括:
1、hibernate validator验证
2、hibernate validator自定义验证
3、hibernate validator验证的国际化
一、hibernate validator验证
Spring Boot2.1自带hibernate validator包,不需要额外单独引入。
参考 上一篇文章 访问https://start.spring.io/ 创建工程demo002,Group输入com.yinww.demo.springboot2.demo002 , Artifact输入demo0022,Dependencies选择Web和Thymeleaf,然后点击Generate Project按钮下载zip包,解压缩并将其作为Maven Project引入到Eclipse环境中。
新建JavaBean类User,写明对每个属性的验证要求,如不能为空等等
package com.yinww.demo.springboot2.demo002.domain;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
public class User {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
@Email(message = "邮箱格式错误")
private String email;
// getter and setter ...
}
新建一个返回值的类ResultEntity
package com.yinww.demo.springboot2.demo002.domain;
public class ResultEntity {
private int status;
private Object data;
private String msg;
public ResultEntity() {
}
public ResultEntity(int status) {
this.status = status;
}
public ResultEntity(int status, Object data) {
this.status = status;
this.data = data;
}
// getter and setter...
}
创建一个Controller类TestController,其中save方法的@Validated注解就是给user对象做数据校验的
package com.yinww.demo.springboot2.demo002.controller;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.yinww.demo.springboot2.demo002.domain.ResultEntity;
import com.yinww.demo.springboot2.demo002.domain.User;
@Controller
public class TestController {
@RequestMapping(value={"/", "index.html"})
public String index() {
return "index";
}
@RequestMapping(value="save")
@ResponseBody
public Object save(@Validated User user) {
return new ResultEntity(HttpStatus.OK.value(), user);
}
}
编写一个全局的异常处理类TestExceptionHandler 以捕捉异常并返回到前端
package com.yinww.demo.springboot2.demo002.handler;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.yinww.demo.springboot2.demo002.domain.ResultEntity;
@ControllerAdvice
public class TestExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(TestExceptionHandler.class);
@ExceptionHandler(Exception.class)
@ResponseBody
public ResultEntity handleException(Exception e){
log.error(e.getMessage(), e);
BindingResult result = null;
if (e instanceof MethodArgumentNotValidException){
result = ((MethodArgumentNotValidException) e).getBindingResult();
} else if (e instanceof BindException){
result = ((BindException) e).getBindingResult();
} else if (e instanceof ConstraintViolationException){
Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException) e).getConstraintViolations();
List<String> errors = new ArrayList<>();
for (ConstraintViolation<?> violation : constraintViolations) {
errors.add(violation.getMessage());
}
return new ResultEntity(HttpStatus.OK.value(), errors);
}
if (result != null) {
List<String> errors = new ArrayList<>();
for (ObjectError error : result.getAllErrors()) {
errors.add(error.getDefaultMessage());
}
return new ResultEntity(HttpStatus.BAD_REQUEST.value(), errors);
}
return new ResultEntity(HttpStatus.BAD_REQUEST.value());
}
}
创建一个Thymeleaf模板文件index.html
<form action="save" method="post">
<table>
<tr>
<td>用户名</td>
<td><input name="username"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td>邮箱</td>
<td><input name="email"></td>
</tr>
<tr>
<td><input type="submit"> </td>
</tr>
</table>
</form>
运行Spring Boot工程demo002,在浏览器中访问 http://localhost:8080/
点击提交按钮,如果验证结果有问题,那么页面返回验证出错的内容
{"status":400,"data":["密码不能为空","用户名不能为空"],"msg":null}
前端工程师可以对上面的json内容进行解析,展示成想要的效果。
二、自定义验证
给User类添加一个身份证号码的属性,验证身份证号码的格式是否正确,required用于说明是否必填,相关的逻辑都需要在后面的自定义验证类中实现
@IDConstraint(required = false, message = "身份证号码格式错误")
private String idCard;
创建注解验证约束类 IDConstraint
package com.yinww.demo.springboot2.demo002.validator;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Constraint(validatedBy=IDValidator.class)
public @interface IDConstraint {
boolean required();
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
创建验证的逻辑类IDValidator
package com.yinww.demo.springboot2.demo002.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class IDValidator implements ConstraintValidator<IDConstraint, String> {
private boolean required;
@Override
public void initialize(IDConstraint idConstraint) {
this.required = idConstraint.required();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if(value == null || value.trim().length() == 0) {
return !required;
}
String regIdcard = "^\\d{6}(19|20)\\d{2}((1[0-2])|0\\d)([0-2]\\d|30|31)\\d{3}[\\d|X|x]$";
return value.matches(regIdcard);
}
}
修改Thymeleaf模板index.html form表单中加上身份证号码输入框
<form action="save" method="post">
<table>
<tr>
<td>用户名</td>
<td><input name="username"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td>邮箱</td>
<td><input name="email"></td>
</tr>
<tr>
<td>身份证号码</td>
<td><input name="idCard"></td>
</tr>
<tr>
<td><input type="submit"> </td>
</tr>
</table>
</form>
运行Spring Boot工程demo002,在浏览器中访问 http://localhost:8080/
身份证号码输入框中输入1234,点击提交按钮,如果验证结果有问题,那么页面返回验证出错的内容
{"status":400,"data":["密码不能为空","用户名不能为空","身份证号码格式错误"],"msg":null}
前端工程师可以对上面的json内容进行解析,展示成想要的效果。
三、hibernate validator的国际化
国际化,简单说就是需要将内容抽取到多语言的properties文件中。
修改application.properties文件,添加内容:
spring.messages.basename=i18n/messages
在resources目录下创建i18n目录,在i18n目录下创建messages.properties、messages_zh_CN.properties和messages_en_US.properties 三个文件。messages_zh_CN.properties文件中输入:
error.username=用户名不能为空
error.password=密码不能为空
error.email=邮箱格式错误
error.idcard=身份证号码格式错误
messages_en_US.properties文件中输入:
error.username=username cannot be empty
error.password=password cannot be empty
error.email=email invalid
error.idcard=idcard invalid
messages.properties内容空着就行,但是不能没有这个文件。
User类修改为:
public class User {
@NotBlank(message = "{error.username}")
private String username;
@NotBlank(message = "{error.password}")
private String password;
@Email(message = "{error.email}")
private String email;
@IDConstraint(required = false, message = "{error.idcard}")
private String idCard;
// getter and setter
}
Demo002Application类修改为
@SpringBootApplication
public class Demo002Application {
@Autowired
private MessageSource messageSource;
public static void main(String[] args) {
SpringApplication.run(Demo002Application.class, args);
}
@Bean
public Validator getValidator() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource);
return validator;
}
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
slr.setDefaultLocale(Locale.CHINA);
// slr.setDefaultLocale(Locale.US);
return slr;
}
}
运行Spring Boot工程demo002,在浏览器中访问 http://localhost:8080/ 也能正常验证。将上面代码中的Locale设置为Locale.US即可显示messages_en_US.properties文件中定义的英文的验证错误内容。
本文内容到此结束,更多内容可关注公众号: