一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情
前言
SpringBoot实际项目中, 为了保证接口的健壮性和可用性接口参数验证是非常必须的,新来实习员工完成一个注册用户功能相关代码如下:
这段代码业务逻辑实现没有任何问题,但是代码过于臃肿,我们能否采用优雅的方式进行参数验证呢?
Spring Validation
简介
Java API规范 (JSR303) 定义了Bean校验的标准validation-api,但没有提供具体实现。 hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。 Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。
集成步骤
引入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
注意:Spring Boot2.2之前版本需要引入spring-boot-starter-validation,而2.2之后的版本无需引入,引文spring-boot-starter-web中已经添加了此引用。
编写参数验证规则
我们需要在实体类中,定义验证规则
public class User implements Serializable
{
private static final long serialVersionUID = -6160884495447867892L;
@NotBlank(message = "名字必填")
@Length(min = 1, max = 20, message = "用户名长度需要在20个字以内")
private String name;
@Email(message = "请填写正确的邮箱地址")
@Pattern(regexp = "^([a-zA-Z]|[0-9])(\\w|\\-)+@[a-zA-Z0-9]+\\.([a-zA-Z]{2,4})$",message = "邮箱格式不正确")
@Length(min = 5, max = 50, message = "邮箱长度需要在50个字符以内")
private String email;
@NotEmpty(message = "could not be empty")
@Pattern(regexp = "^(\\d{6})(\\d{4})(\\d{2})(\\d{2})(\\d{3})([0-9]|X)$", message = "invalid ID")
private String cardNo;
@NotNull
@Range(min = 0, max = 1, message = "sex should be 0-1")
private Integer sex;
@NotEmpty(message = "telphone could not be empty")
@TelephoneNumber(message= "invalid telphone invalid")
private String telphone;
//省略get、set方法
}
业务层添加验证
//接口定义
User addUser(@Valid User user);
//实现类
@Service
@Validated
@Transactional(rollbackFor=Exception.class)
public class TUserServiceImpl implements TUserService
{
public User addUser(@Valid User user)
{
return user;
}
}
//controller调用
@Autowired
private TUserService userService;
@RequestMapping("/addUser")
public User addUser(@RequestBody User user)
{
return userService.addUser(user);
}
注意: Validation用于Service验证时,我们需要在定义接口时添加@Valid注解,否则测试时会报如下错误:
HV000151: A method overriding another method must not redefine the parameter constraint configuration, but method TUserServiceImpl#addUser(User) redefines the configuration of TUserService#addUser(User).
测试验证
POST请求新增用户接口:
统一接口返回和全局异常处理,可以参考:juejin.cn/post/708603… 。
错误信息如果需要实现国际化,可以参考juejin.cn/post/708646… 。
@Validate和@Valid什么区别?
上述的例子中分别用了@validate和@Valid两个注解,那么他们有什么区别呢?
-
@Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制
-
@Valid 可以用在方法、构造函数、方法参数和成员属(字段)上,而@Validated 只能用在类型、方法和方法参数上,不能用在成员属性(字段)上。
-
@Valid 可以作用于嵌套的属性上,而@Validated不能作用于嵌套类属性上。
public class User implements Serializable {
@Valid // 这里只能用@Valid
private Address address;
}
Java的JSR-349规范提供了常用的验证规则
Spring Validation 新增验证规则
自定义Validation
虽然Spring Boot Validation提供了很多默认的验证规则,但是有些特殊的验证还是无法满足,所以我们需要自定义Validation 规则.
定义验证注解
@Target({ ElementType.METHOD,ElementType.FIELD,ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR,ElementType.PARAMETER,ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {TelephoneValidator.class}) // 指定校验器
public @interface Telephone
{
String message() default "Invalid telephone number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
定义验证器
public class TelephoneValidator implements ConstraintValidator<Telephone , String>
{
private Logger logger =LoggerFactory.getLogger(Logger.class);
private static final String REGEX_TELEPHONE = "0\\d{2,3}[-]?\\d{7,8}|0\\d{2,3}\\s?\\d{7,8}|13[0-9]\\d{8}|15[1089]\\d{8}";
public boolean isValid(String value, ConstraintValidatorContext context)
{
if(StringUtil.isEmpty(value))
{
logger.error("telphone param is empty");
return false;
}
return Pattern.matches(REGEX_TELEPHONE, value);
}
}
使用
@Telephone(message= "telphone invalid")
private String telphone;
验证自定义规则
总结
本来讲解了采用Spring Validation实现参数验证,大家一定要记住的是Validation只针对字段来进行相关的验证,切莫用于实现业务的复杂验证,这样违背了架构单一原则。