背景知识
JSR是Java Specification Requests
的缩写,意思是Java 规范提案。关于数据校验,最新的是JSR380
,也就是我们常说的Bean Validation 2.0(2017年8月)
。
Bean Validation
是一个通过配置注解来验证参数的框架,它包含两部分Bean Validation API
(规范)和Hibernate Validator
(实现)。
Bean Validation | Hibernate Validation | JDK | Spring Boot |
---|---|---|---|
1.1 | 5.4 + | 6+ | 1.5.x |
2.0 | 6.0 + | 8+ | 2.0.x |
依赖
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
</dependency>
使用
1、controller
给controller的请求参数req加上@Valid注解,表示要对req进行按规则校验(具体规则见Req实体类)。
import javax.annotation.Nonnull;
import javax.validation.Valid;
... ...
@ApiOperation("测试参数校验")
@PostMapping("/xxx")
public void xxx(@Nonnull @Valid @RequestBody Req req) {
commonService.xxx(req);
}
2、Req实体类
在实体类属性上加上@NotBlank等注解,当有请求服务controller时,就会自动进行参数校验。后面有常用注解及含义描述。如果业务场景需要有复杂的校验逻辑,,还可以自定义扩展注解(如示例中的@OrganizationExist)。
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ApiModel("请求实体")
public class Req {
@ApiModelProperty(value = "目标使用权企业id")
@OrganizationExist // 自定义注解
private String targetUsingCompanyId;
@NotBlank(message = "其他属性不能为空")
@ApiModelProperty(value = "其他属性")
private String otherProperty;
... ...
}
3、service
如果你的目标代码不一定是从controller直接调用,即调用controller时执行不到相应的校验时,可以考虑手动触发参数校验。
@Override
public void xxx(Req req) {
//防止不是从controller访问到目标代码,这里增加了手动触发参数校验。
//存在的问题:如果是controller访问目标代码,会触发两次参数校验。
//由于笔者这里的目标代码是一个提供给其他service调用的公共方法,所以在这里手动触发了一次参数校验。
Set<ConstraintViolation<Req>> validationParamResult =
Validation.buildDefaultValidatorFactory().getValidator().validate(req);
if (!CollectionUtils.isEmpty(validationParamResult)) {
StringBuilder message = new StringBuilder("参数校验不通过:");
validationParamResult.stream().forEach(r -> {
message.append(r.getMessage()).append(",").append(r.getInvalidValue()).append(";");
});
return;
}
... 参数校验通过后需要执行的代码(目标代码) ...
}
4、常用的参数校验的注解
注解 | 描述 |
---|---|
@Valid | 对po实体进行校验 |
@AssertFalse | 所注解的元素必须是Boolean类型,且值为false |
@AssertTrue | 所注解的元素必须是Boolean类型,且值为true |
@DecimalMax | 所注解的元素必须是数字,且值小于等于给定的值 |
@DecimalMin | 所注解的元素必须是数字,且值大于等于给定的值 |
@Digits | 所注解的元素必须是数字,且值必须是指定的位数 |
@Future | 所注解的元素必须是将来某个日期 |
@Max | 所注解的元素必须是数字,且值小于等于给定的值 |
@Min | 所注解的元素必须是数字,且值大于等于给定的值 |
@Range | 所注解的元素需在指定范围区间内 |
@NotNull | 所注解的元素值不能为null |
@NotBlank | 所注解的元素值有内容 |
@Null | 所注解的元素值为null |
@Past | 所注解的元素必须是某个过去的日期 |
@PastOrPresent | 所注解的元素必须是过去某个或现在日期 |
@Pattern | 所注解的元素必须满足给定的正则表达式 |
@Size | 所注解的元素必须是String、集合或数组,且长度大小需保证在给定范围之内 |
所注解的元素需满足Email格式 |
扩展
1、OrganizationExist
自定义OrganizationExist注解,用于校验该机构是否存在,及其状态是否正常。注解的使用方法与自带的校验注解没有区别(见2、Req实体类的targetUsingCompanyId属性)。
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @author tianxiang
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { OrganizationExistValidator.class})
public @interface OrganizationExist {
String message() default "机构不存在或者机构状态不是normal";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default {};
}
2、OrganizationExistValidator
编写OrganizationExistValidator类实现ConstraintValidator<OrganizationExist, String>接口。其中OrganizationExist是你自定义的注解,String是这个注解加在什么类型的属性上。isValid方法就是具体的校验方法,在这里书写你的校验逻辑,参数中String organizationId就是前端传过来的值。
这里我还实现了ApplicationContextAware接口,通过上下文获取到organizationService。因为手动触发参数校验时通过自动注入获取不到organizationService。
import com.xx.OrganizationService;
import com.xx.OrganizationStatus;
import com.xx.dto.OrganizationDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* @author tianxiang
*/
@Slf4j
@Component
public class OrganizationExistValidator implements ConstraintValidator<OrganizationExist, String>, ApplicationContextAware {
private OrganizationService organizationService;
private static ApplicationContext applicationContext;
@Override
public void initialize(OrganizationExist constraintAnnotation) {
}
//具体的校验逻辑
@Override
public boolean isValid(String organizationId, ConstraintValidatorContext constraintValidatorContext) {
//通过上下文获取organizationService ,是为了防止不是通过controller服务到目标方法。
organizationService = applicationContext.getBean(OrganizationService.class);
if (StringUtils.isBlank(organizationId)) {
log.error("organizationId不能为空");
return false;
}
OrganizationDTO organization = organizationService.getOrganizationById(organizationId);
if (organization == null) {
log.error("企业不存在");
return false;
}
String organizationStatus = organization.getOrganizationStatus();
if (StringUtils.isBlank(organizationStatus) || !OrganizationStatus.NORMAL.eq(organizationStatus)) {
//企业状态 != 正常
log.error("企业状态不是:正常");
return false;
}
return true;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}