在前面的文章中,我们已经学习了如何使用 hibernate-validator 进行表单校验。
FluentValidator是一个工具类库,使用流式(Fluent Interface)调用风格让校验跑起来更优雅,代码更简洁,同时验证器(Validator)可以做到开闭原则,实现最大程度的复用。
github地址:https://github.com/neoremind/fluent-validator
中文使用手册:http://neoremind.com/2016/02/java的业务逻辑验证框架fluent-validator/
替换 hibernate-validator 为 fluent-validator,实现对入参的校验及异常输出
引入 Spring Boot Starter 父工程
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.5.RELEASEversion>
parent>
添加 fluent-validator
的依赖
<dependency>
<groupId>com.baidu.unbizgroupId>
<artifactId>fluent-validatorartifactId>
<version>1.0.9version>
dependency>
因为 spring-boot 默认使用 logback 作为日志输出组件,所以在引入的时候需要去掉 fluent-validate 自身的 slf4j
<dependency>
<groupId>com.baidu.unbizgroupId>
<artifactId>fluent-validatorartifactId>
<version>1.0.9version>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
exclusions>
dependency>
fluent-validator 集成 hibernate-validator 需要添加依赖
<dependency>
<groupId>com.baidu.unbizgroupId>
<artifactId>fluent-validator-jsr303artifactId>
<version>1.0.9version>
dependency>
fluent-validator 集成 spring 需要添加依赖
<dependency>
<groupId>com.baidu.unbizgroupId>
<artifactId>fluent-validator-springartifactId>
<version>1.0.9version>
dependency>
添加后的整体依赖如下
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-undertowartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.baidu.unbizgroupId>
<artifactId>fluent-validatorartifactId>
<version>1.0.9version>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>com.baidu.unbizgroupId>
<artifactId>fluent-validator-jsr303artifactId>
<version>1.0.9version>
dependency>
<dependency>
<groupId>com.baidu.unbizgroupId>
<artifactId>fluent-validator-springartifactId>
<version>1.0.9version>
dependency>
dependencies>
fluent-validate 与 spring 结合使用 annotation 方式进行参数校验,需要借助于 spring 的 AOP,fluent-validate 提供了处理类 FluentValidateInterceptor,但是 fluent-validate 提供的默认验证回调类 DefaultValidateCallback 对校验失败的情况并没有处理,所以需要自行实现一个
public static class HussarValidateCallBack extends DefaultValidateCallback implements ValidateCallback {
@Override
public void onSuccess(ValidatorElementList validatorElementList) {
super.onSuccess(validatorElementList);
}
@Override
public void onFail(ValidatorElementList validatorElementList, List<ValidationError> errors) {
throw new RuntimeException(errors.get(0).getErrorMsg());
}
@Override
public void onUncaughtException(Validator validator, Exception e, Object target) throws Exception {
throw new RuntimeException(e);
}
}
注册 ValidateCallback 及 FluentValidateInterceptor,并且配置一个 AOP 规则
@Configuration
public class ValidateConfiguartion {
@Bean
public FluentValidateInterceptor fluentValidateInterceptor() {
FluentValidateInterceptor fluentValidateInterceptor = new FluentValidateInterceptor();
fluentValidateInterceptor.setCallback(validateCallback());
return fluentValidateInterceptor;
}
@Bean
public ValidateCallback validateCallback() {
return new HussarValidateCallBack();
}
@Bean
public BeanNameAutoProxyCreator beanNameAutoProxyCreator(){
BeanNameAutoProxyCreator proxyCreator = new BeanNameAutoProxyCreator();
proxyCreator.setBeanNames("*Controller");
proxyCreator.setInterceptorNames("fluentValidateInterceptor");
return proxyCreator;
}
}
@Slf4j
@RestControllerAdvice
public class RestExceptionHandler {
/**
* 校验错误拦截处理
* 使用 @RequestBody 接收入参时,校验失败抛 MethodArgumentNotValidException 异常
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public RestData handler(MethodArgumentNotValidException e) {
log.error("MethodArgumentNotValidException handler", e);
BindingResult bindingResult = e.getBindingResult();
if (bindingResult.hasFieldErrors()) {
return RestData.build().error(bindingResult.getFieldError().getDefaultMessage());
}
return RestData.build().error("parameter is not valid");
}
/**
* 校验错误拦截处理
* 使用 @RequestBody 接收入参时,数据类型转换失败抛 HttpMessageConversionException 异常
*/
@ExceptionHandler(value = HttpMessageConversionException.class)
public RestData handler(HttpMessageConversionException e) {
log.error("HttpMessageConversionException handler", e);
return RestData.build().error(e.getMessage());
}
/**
* 全局异常处理
*/
@ExceptionHandler(value = Exception.class)
public RestData handler(Exception e) {
log.error("exception handler", e);
return RestData.build().error(e.getMessage());
}
}
为业务类添加校验规则,此处,并没有添加 fluent-validate 注解,而是保持了原来的 hibernate-validate
@Getter
@Setter
public class UserBO {
@NotBlank(message = "手机号不能为空")
private String mobile;
@NotBlank(message = "密码不能为空")
@Length(min = 6, max = 16, message = "密码长度必须在6-16位之间")
private String upwd;
}
在 Controller 接口的参数前面添加 @FluentValid 注解,替换掉 hibernate-validate 的 @Valid 注解
@RestController
@Slf4j
public class UserController {
@PostMapping("/register")
public RestData<UserBO> register(@FluentValid @RequestBody UserBO userBo) {
return new RestData<UserBO>().success("", userBo);
}
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
编写测试用例
@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = Application.class)
public class UserTest {
private MockMvc mvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Before
public void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@Test
public void test1() throws Exception {
MvcResult mvcResult = mvc.perform(
MockMvcRequestBuilders
.post("/register")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content("{\"upwd\":\"123456\"}")
)
// .andExpect(status().isOk());
// .andExpect(content().string("hello"))
.andDo(MockMvcResultHandlers.print())
.andReturn();
Assert.assertEquals(200, mvcResult.getResponse().getStatus());
}
}
返回结果为
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {Content-Type=[application/json;charset=UTF-8]}
Content type = application/json;charset=UTF-8
Body = {"status":false,"info":"手机号不能为空","data":null,"success":false}
本章源码 : https://gitee.com/gongm_24/spring-boot-tutorial.git
fluent-validate 可以全方位兼容 hibernate-validate,基于 spring 的 AOP 可以提供基于注解的方法入参校验,同时也可以提供流式编程的工具类业务校验,替代 hibernate-validate 的同时提供了更多扩展性。
public class SexValidator extends ValidatorHandler<Integer> implements Validator<Integer> {
@Override
public boolean validate(ValidatorContext context, Integer t) {
if (t < 0 || t > 2) {
// 构建详细错误信息
context.addError(ValidationError.create("sex值不正确").setErrorCode(100).setField("sex").setInvalidValue(t));
// 简单处理,直接放入错误消息
// context.addErrorMsg("sex值不正确");
return false;
}
return true;
}
}
Validator 接口定义了三个方法,解释如下
public interface Validator<T> {
/**
* 判断在该对象上是否接受或者需要验证
*
* 如果返回true,那么则调用{@link #validate(ValidatorContext, Object)},否则跳过该验证器
*
* @param context 验证上下文
* @param t 待验证对象
*
* @return 是否接受验证
*/
boolean accept(ValidatorContext context, T t);
/**
* 执行验证
*
* 如果发生错误内部需要调用{@link ValidatorContext#addErrorMsg(String)}方法,也即context.addErrorMsg(String)
*
来添加错误,该错误会被添加到结果存根{@link Result}的错误消息列表中。
*
* @param context 验证上下文
* @param t 待验证对象
*
* @return 是否验证通过
*/
boolean validate(ValidatorContext context, T t);
/**
* 异常回调
*
* 当执行{@link #accept(ValidatorContext, Object)}或者{@link #validate(ValidatorContext, Object)}发生异常时的如何处理
*
* @param e 异常
* @param context 验证上下文
* @param t 待验证对象
*/
void onException(Exception e, ValidatorContext context, T t);
}
添加 sex 属性,使用 @FluentValidate 注解标记使用自定义校验
@Getter
@Setter
public class UserBO {
@NotBlank(message = "手机号不能为空")
private String mobile;
@NotBlank(message = "密码不能为空")
@Length(min = 6, max = 16, message = "密码长度必须在6-16位之间")
private String upwd;
@FluentValidate({SexValidator.class})
private Integer sex;
}
在对象属性上添加 @FluentValid 注解,代替 hibernate-validate 的 @Valid。
public class Garage {
@FluentValidate({CarNotExceedLimitValidator.class})
@FluentValid
private List<Car> carList;
}