springboot 参数校验

本贴最后更新于 2667 天前,其中的信息可能已经时移世改

本文项目已发布到 github,后续学习项目也会添加到此工程下,欢迎 fork 点赞。
https://github.com/wangyuheng/spring-boot-sample

http 接口开发过程中的常用场景为,根据提交的表单数据进行格式校验,包括字段长度、数据类型、范围等等。。如果每次都写一堆 if...else if... 太傻了,所以 java 提供了一套标准化校验方案 JSR 303,而标准的最佳实践为 Hibernate Validator

一句话为,通过注解对 bean 进行校验,并返回标准文案。

依赖

spring-boot-starter-web 已经集成了 hibernate-validator, 无需重复引用。

注解

JSR303 定义了一套,在 javax.validation.constraints 包目录下,hibernate-validator 扩展了一套,在 org.hibernate.validator.constraints 包下,下文会介绍如何自定义注解。

在 Bean 的字段中增加注解,代码如下:

public class User implements Serializable { @Min(1) private int id; @Email private String username; @NotBlank @Length(min = 6, max = 36) private String password; private Integer age; public int getId() { return id; } public void setId(int id) { this.id = id; } 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; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }

Controller

  1. @Validated 声明对参数进行校验
  2. MessageSource 用来分离错误提示,也可以实现国际化
  3. BindingResult 绑定参数校验结果
@RestController @RequestMapping("/user") public class UserApi { @Autowired private MessageSource messageSource; @GetMapping("/{id}") public Object getInfo(@Validated User user, BindingResult bindingResult) { Map<String, Object> result = new HashMap<>(); if (bindingResult.hasErrors()) { List<FieldError> fieldErrors = bindingResult.getFieldErrors(); Locale locale = LocaleContextHolder.getLocale(); StringBuilder errorMessage = new StringBuilder(); fieldErrors.forEach(fieldError -> { errorMessage.append(fieldError.getField()) .append(":") .append(messageSource.getMessage(fieldError, locale)) .append(","); }); result.put("code", 10001); result.put("message", errorMessage); } else { result.put("code", 10000); result.put("data", user); } return result; } }

自定义错误提示

框架本身做了错误提示,但是为了友好,通常会自定义提示。

硬编码

可以在 注解中硬编码提示语,如

@Email(message = "用户名必须是邮箱") private String username;

ValidationMessages.properties

不过不够灵活。在使用 spring-boot 的过程中,我们都熟悉了约定大于配置。可以在 resources 目录下增加 ValidationMessages.properties 文件,并在其中复写

javax.validation.constraints.Min.message=参数最小为{1}

可以实现自定义提示,注意 properties 中文编码问题。@ 注 1 propertiesEditor

messages.properties

springboot 提供的消息文件默认路径为 resources 下 messages.properties,可以把 ValidationMessages.properties 和 messages.properties 指定为自定义配置文件

  • application.properties 中配置属性
spring.messages.basename=valid
  • 在 resources 目录下创建校验提示文件 valid.properties
org.hibernate.validator.constraints.NotBlank.message={0} can't be blank user.id.error={0} error
  • 配置 messageSource
@Configuration public class ValidatorConf { @Autowired private MessageSource messageSource; @Bean @ConditionalOnClass(MessageSource.class) public LocalValidatorFactoryBean localValidatorFactoryBean() { LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean(); bean.setProviderClass(HibernateValidator.class); bean.setValidationMessageSource(messageSource); return bean; } }

简化

需要的校验功能实现了,但是每个 restful 接口都需要增加这些校验代码?每个参数后面都加一个 BindingResult? 显然不合理。懒惰是美德,试着做一次简化。

通过 @RestControllerAdvice@ExceptionHandler 全局捕获 restapi 的 BindException 异常,在 controller 代码中,在需要校验的参数前增加 @Validated

@RestControllerAdvice public class GlobalValidator { @Autowired private MessageSource messageSource; @ExceptionHandler(BindException.class) public Object bindError(BindException e) { Map<String, Object> result = new HashMap<>(); List<FieldError> fieldErrors = e.getFieldErrors(); Locale locale = LocaleContextHolder.getLocale(); StringBuilder errorMessage = new StringBuilder(); fieldErrors.forEach(fieldError -> { errorMessage.append(fieldError.getField()) .append(":") .append(messageSource.getMessage(fieldError, locale)) .append(","); }); result.put("code", 10001); result.put("message", errorMessage); return result; } }
@RestController @RequestMapping("/simpleuser") public class SimpleUserApi { @GetMapping("/{uid}") public Object getInfo(@Validated User user) { Map<String, Object> result = new HashMap<>(); result.put("code", 10000); result.put("data", user); return result; } }

测试

mockMvc 请求 api 接口,判断返回值 code

@RunWith(SpringRunner.class) @SpringBootTest public class ValidatorApiTest { private MockMvc mockMvc; @Autowired private WebApplicationContext context; @Before public void setup() { mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); } private JSONObject requestApi(String path) throws Exception { return new JSONObject(mockMvc.perform(MockMvcRequestBuilders.get(path)) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcResultHandlers.print()) .andReturn() .getResponse() .getContentAsString()); } @Test public void test_user_param_valid() throws Exception { String rightPath = "/user/" + "12?id=123" + "&password=123456"; assertTrue(10000 == requestApi(rightPath).getInt("code")); String errorPath = "/user/" + "abc"; assertTrue(10001 == requestApi(errorPath).getInt("code")); } @Test public void test_simpleuser_param_valid() throws Exception { String rightPath = "/simpleuser/" + "12?id=123" + "&password=123456"; assertTrue(10000 == requestApi(rightPath).getInt("code")); String errorPath = "/simpleuser/" + "abc"; assertTrue(10001 == requestApi(errorPath).getInt("code")); } }
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3194 引用 • 8214 回帖
  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    946 引用 • 1460 回帖 • 1 关注
  • valid
    1 引用

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...