https://blog.csdn.net/_/article/details/
1 实现一个Validator
这种方法不需要我们的bean里面有任何注解之类的东西
实现Validator 重写两个方法 @Component public class UserValidator implements Validator {
@Override public boolean supports(Class clazz) {
return User2.class.equals(clazz); } @Override public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "name", "name.empty"); User2 p = (User2) target; if (p.getId() == 0) {
errors.rejectValue("id", "can not be zero"); } } }
2 修改Controller代码
@InitBinder 注解添加在initBinder 方法 initBinder 方法 binder 加入 validator 注入上面的UserValidator实例 并给Controller的方法参数加上@Validated注解 @RestController public class UserController {
@Autowired UserValidator validator; @InitBinder public void initBinder(WebDataBinder binder) {
binder.setValidator(validator); } @RequestMapping(value = "/user/post", method = RequestMethod.POST) public ServiceResponse handValidatePost(@Validated @RequestBody User user) {
ServiceResponse serviceResponse = new ServiceResponse(); serviceResponse.setCode(0); serviceResponse.setMessage("test"); return serviceResponse; } }
1 对于简单类型参数(非Bean)
,直接在参数前,使用注解添加约束规则
。比如 @NotNull @Length 等
2 在类名前追加 @Validated 注解,否则添加的约束规则不生效
。
3 方法被调用时,如果传入的实际参数与约束规则不符
,会直接抛出 ConstraintViolationException ,表明参数校验失败
4 对于Bean类型的参数
,在Bean内部的各个字段上面追加约束注解
,然后在方法的参数前面添加 @Valid 注解
即可。
5 对于Bean里面套Bean的,同样在外层Bean里面写@Valid即可
。
public class CreateProjectReqVO extends BaseVO {
/** * 请求序列号 */ @NotNull(message = "请求序列号不可为空") private Integer requestNo; /** * 项目名称 */ @NotNull(message = "项目名称不可为空") private String projectName; …… }
注意 参数前面的 @Valid 注解 public CreateProjectRespVO createProject(@Valid CreateProjectReqVO reqVO) {
……
@AssertTrue / @AssertFalse 验证适用字段:boolean 注解说明:验证值是否为true / false @DecimalMax / @DecimalMin 验证适用字段:BigDecimal,BigInteger,String,byte,short,int,long 注解说明:验证值是否小于或者等于指定的小数值,要注意小数存在精度问题 @Digits 验证适用字段:BigDecimal,BigInteger,String,byte,short,int,long 注解说明:验证值的数字构成是否合法 属性说明:integer:指定整数部分的数字的位数。fraction: 指定小数部分的数字的位数。 @Future / @Past 验证适用字段:Date,Calendar 注解说明:验证值是否在当前时间之后 / 之前 属性说明:公共 @Max / @Min 验证适用字段:BigDecimal,BigInteger,String,byte,short,int,long 注解说明:验证值是否小于或者等于指定的整数值 属性说明:公共 注意事项:建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单提交的值为“”时无法转换为int @NotNull / @Null 验证适用字段:引用数据类型 注解说明:验证值是否为非空 / 空 属性说明:公共 @NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格. @NotEmpty 检查约束元素是否为Null或者是EMPTY. @NotBlank 与 @NotEmpty 的区别:空格(" ")对于 NotEmpty 是合法的,而 NotBlank 会抛出校验异常 @Pattern 验证适用字段:String 注解说明:验证值是否配备正则表达式 属性说明:regexp:正则表达式flags: 指定Pattern.Flag 的数组,表示正则表达式的相关选项。 @Size 验证适用字段:String,Collection,Map,数组 注解说明:验证值是否满足长度要求 属性说明:max:指定最大长度,min:指定最小长度。 @Length(min=, max=):专门应用于String类型 @Valid 验证适用字段:递归的对关联对象进行校验 注解说明:如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验(是否进行递归验证) 属性说明:无 @Range(min=, max=) 被指定的元素必须在合适的范围内 @CreditCardNumber信用卡验证 @Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。 @URL(protocol=,host=, port=,regexp=, flags=)
1 约束用的注解,一般需要带上message参数
,方便自定义错误信息
,比如前面例子中的“@NotNull(message = “项目名称不可为空”)
”。而这个message参数是支持EL表达式的
@NotNull(message = "${member.id.null}") 再定义一个比如叫做 messages.properties 的配置文件来统一管理错误信息 member.id.null=用户编号不能为空
2 约束规则支持正则表达式
3 注解不能(只)放在实现类上
public interface IProjectService {
/** * 项目创建 */ CreateProjectRespVO createProject(CreateProjectReqVO reqVO); } @Service public class ProjectServiceImpl implements IProjectService {
/** * 项目创建 */ @Override public CreateProjectRespVO createProject(@Valid CreateProjectReqVO reqVO) {
…… }
在进行校验时会发生“javax.validation.ConstraintDeclarationException”异常(注意跟校验不通过发生的异常不是一个)
解决方法:
@Override父类/接口的方法,入参约束只能写在父类/接口上面。 或者两边都写上也可(但是这样维护时容易出问题,不推荐)。 另外 @Validated 这个注解写在哪边都可以。
使用spring validation完成数据后端校验
public class Foo {
@NotBlank private String name; @Min(18) private Integer age; @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误") @NotBlank(message = "手机号码不能为空") private String phone; @Email(message = "邮箱格式错误") private String email; //... getter setter }
@Controller public class FooController {
@RequestMapping("/foo") public String foo(@Validated Foo foo <1>, BindingResult bindingResult <2>) {
if(bindingResult.hasErrors()){
for (FieldError fieldError : bindingResult.getFieldErrors()) {
//... } return "fail"; } return "success"; } }
<1> 参数Foo前需要加上 @Validated 注解,表明需要spring对其进行校验,而校验的信息会存放到其后的BindingResult中。 注意,必须相邻,如果有多个参数需要校验,形式可以如下 foo(@Validated Foo foo, BindingResult fooBindingResult ,@Validated Bar bar, BindingResult barBindingResult); 即一个校验类对应一个校验结果。 <2> 校验结果会被自动填充,在controller中可以根据业务逻辑来决定具体的操作,如跳转到错误页面。
访问 http://localhost:8080/foo?name=xujingfeng&email=000&age=19
可以得到如下的debug信息:
当发生多个错误,spring validation不会在第一个错误发生后立即停止,而是继续试错,告诉我们所有的错误
Not注解
groups
是验证分组
,比如我有的验证只有更新的时候做
,有的只有添加的时候做
,就用这个
javax.validate
里有提供一个默认分组Default.class是个接口
,不指定分组时都会执行这个
分组 class
是个空的接口
在字段上面指定他们的分组
@NotNull(message = "primary is not null",groups = {
GroupInterface1.class}) private Long id; public @interface GroupInterface1(){
}
此时controller应该要加上@Valid
, 否则不会验证
@RequestMapping(value = "/test") public void test(@Validated(GroupInterface1.class) User user) {
}
Class Foo{
//只有在Adult分组下,18岁的限制才会起作用 @Min(value = 18,groups = {
Adult.class}) private Integer age; public interface Adult{
} public interface Minor{
} }
@RequestMapping("/drink") public String drink(@Validated({
Foo.Adult.class}) Foo foo, BindingResult bindingResult) {
if(bindingResult.hasErrors()){
for (FieldError fieldError : bindingResult.getFieldErrors()) {
//... } return "fail"; } return "success"; } @RequestMapping("/live") public String live(@Validated Foo foo, BindingResult bindingResult) {
if(bindingResult.hasErrors()){
for (FieldError fieldError : bindingResult.getFieldErrors()) {
//... } return "fail"; } return "success"; }
drink方法
限定需要进行Adult校验
,而live方法则不做限制
上面是Hibernate Validation
提供的能力,而不是JSR标准提供
的。@GroupSequence
它是JSR标准提供的
注解(只是没有provider强大而已,但也有很适合它的使用场景)
// Defines group sequence. 定义组序列(序列:顺序执行的) @Target({
TYPE }) @Retention(RUNTIME) @Documented public @interface GroupSequence {
Class<?>[] value(); }
顾名思义,它表示Group组序列
。默认情况下,不同组别的约束验证是无序的
,在某些情况下,约束验证的顺序是非常的重要的,比如如下两个场景:
第二个组的约束
验证依赖于
第一个约束执行完成的结果
(必须第一个约束正确了,第二个约束执行才有意义
)Group组的校验非常耗时
,并且会消耗比较大的CPU/内存。那么我们的做法应该是把这种校验放到最后
,所以对顺序提出了要求一个组可以定义为其他组的序列
,使用它进行验证的时候必须符合该序列规定的顺序
。在使用组序列验证的时候,如果序列前边的组验证失败
,则后面的组将不再给予验证
。
public class User {
@NotEmpty(message = "firstname may be empty") private String firstname; @NotEmpty(message = "middlename may be empty", groups = Default.class) private String middlename; @NotEmpty(message = "lastname may be empty", groups = GroupA.class) private String lastname; @NotEmpty(message = "country may be empty", groups = GroupB.class) private String country; public interface GroupA {
} public interface GroupB {
} // 组序列 @GroupSequence({
Default.class, GroupA.class, GroupB.class}) public interface Group {
} }
测试一
public static void main(String[] args) {
User user = new User(); // 此处指定了校验组是:User.Group.class Set<ConstraintViolation<User>> result = Validation.buildDefaultValidatorFactory().getValidator().validate(user, User.Group.class); // 对结果进行遍历输出 result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue()).forEach(System.out::println); }
middlename middlename may be empty: null firstname firstname may be empty: null 只有Default这个Group的校验了,序列上其它组并没有执行校验
测试二
User user = new User(); user.setFirstname("f"); user.setMiddlename("s");
lastname lastname may be empty: null Default组都校验通过后,执行了GroupA组的校验。 但GroupA组校验木有通过,GroupB组的校验也就不执行了 @GroupSequence提供的组序列顺序执行以及短路能力,在很多场景下是非常非常好用的
注意
顺序
只能控制在分组级别
,无法控制
在约束注解级别
。因为一个类内的约束
(同一分组内),它的顺序是<Set> metaConstraints来保证
的,所以可以认为同一分组内的校验器
是木有执行的先后顺序的
(不管是类、属性、方法、构造器…)
DefaultGroupSequenceProvider
用来做多字段联合校验
某个嘉年华俱乐部门票对不同人群存在不同的定价
年龄必须大于 12 岁; 12 ~ 18 岁半价; 18 岁以上全价; coser 则可以免费;
换句话来说,任何年龄大于 12 岁的人都可以购买全价,对于半价票需要校验年龄是否满足未成年,对于免费票需要校验是否是 coser。
@Getter @Setter @GroupSequenceProvider(Person.FareTypeGroupSequenceProvider.class) public class Person {
/** * 年龄 */ @Min(value = 12, message = "年龄最小不低于 12 岁") @Max(value = 18, message = "年龄大于 18 岁需要购买全价门票", groups=WhenIsHalf.class) private Integer age; /** * 票价[0=免费,1=半价,2=全价] */ @NotNull(message = "票价类型不能为空") private Integer fareType; /** * 是否是 coser */ @AssertTrue(message = "只有 coser 才可以免费进去", groups = WhenIsFree.class) private Boolean coser; public interface WhenIsFree {
} public interface WhenIsHalf {
} /** * 校验分组处理器 */ public static class FareTypeGroupSequenceProvider implements DefaultGroupSequenceProvider<Person> {
@Override public List<Class<?>> getValidationGroups(Person person) {
ArrayList<Class<?>> list = Lists.newArrayList(); list.add(Person.class); // 判空 if (Objects.nonNull(person)) {
// 当 fareType = 0 时 if (Objects.equals(person.getFareType(), 0)) {
list.add(WhenIsFree.class); } else (Objects.equals(person.getFareType(), 1)) {
list.add(WhenIsHalf.class); } } return list; } } }
分开写
```java @Getter @Setter @GroupSequenceProvider(FareTypeGroupSequenceProvider.class) public class Person {
/** * 年龄 */ @Min(value = 12, message = "年龄最小不低于 12 岁") @Max(value = 18, message = "年龄大于 18 岁需要购买全价门票", groups=WhenIsHalf.class) private Integer age; /** * 票价[0=免费,1=半价,2=全价] */ @NotNull(message = "票价类型不能为空") private Integer fareType; /** * 是否是 coser */ @AssertTrue(message = "只有 coser 才可以免费进去", groups = WhenIsFree.class) private Boolean coser; public interface WhenIsFree {
} public interface WhenIsHalf {
} }
/** * 校验分组处理器 */ public class FareTypeGroupSequenceProvider implements DefaultGroupSequenceProvider<Person> {
@Override public List<Class<?>> getValidationGroups(Person person) {
ArrayList<Class<?>> list = Lists.newArrayList(); list.add(Person.class); // 判空 if (Objects.nonNull(person)) {
// 当 fareType = 0 时 if (Objects.equals(person.getFareType(), 0)) {
list.add(WhenIsFree.class); } else (Objects.equals(person.getFareType(), 1)) {
list.add(WhenIsHalf.class); } } return list; } }
注意
DefaultGroupSequenceProvider 类型是泛型类,必须指定为需要参数校验的 Bean 类型; DefaultGroupSequenceProvider.getValidationGroups 方法返回的 list 中必须包含 Bean 类型; DefaultGroupSequenceProvider 只能增强 Default.class 分组;
添加一个“字符串不能包含空格”的限制
@Target({
METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented @Constraint(validatedBy = {
CannotHaveBlankValidator.class})<1> public @interface CannotHaveBlank {
//默认错误消息 String message() default "不能包含空格"; //分组 Class<?>[] groups() default {
}; //负载 Class<? extends Payload>[] payload() default {
}; //指定多个时使用 @Target({
FIELD, METHOD, PARAMETER, ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @interface List {
CannotHaveBlank[] value(); } }
我们不需要关注太多东西,使用spring validation的原则便是便捷我们的开发,例如payload,List ,groups,都可以忽略。
<1> 自定义注解中
指定了这个注解真正的验证者类
public class CannotHaveBlankValidator implements <1> ConstraintValidator<CannotHaveBlank, String> {
@Override public void initialize(CannotHaveBlank constraintAnnotation) {
} @Override public boolean isValid(String value, ConstraintValidatorContext context <2>) {
//null时不进行校验 if (value != null && value.contains(" ")) {
<3> //获取默认提示信息 String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate(); System.out.println("default message :" + defaultConstraintMessageTemplate); //禁用默认提示信息 context.disableDefaultConstraintViolation(); //设置提示语 context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation(); return false; } return true; } }
也可以合并起来写
@Target({
METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented @Constraint(validatedBy = {
CannotHaveBlank.CannotHaveBlankValidator.class})<1> public @interface CannotHaveBlank {
//默认错误消息 String message() default "不能包含空格"; //分组 Class<?>[] groups() default {
}; //负载 Class<? extends Payload>[] payload() default {
}; class CannotHaveBlankValidator implements <1> ConstraintValidator<CannotHaveBlank, String> {
@Override public void initialize(CannotHaveBlank constraintAnnotation) {
} @Override public boolean isValid(String value, ConstraintValidatorContext context <2>) {
... } } }
public class User{
@CannotHaveBlank(groups=ValidCannotHaveBlank.class) private String name; public interface ValidCannotHaveBlank {
} }
自定义效验的验证者编写规则
所有的验证者都需要实现ConstraintValidator接口
,它的接口也很形象,包含一个初始化事件方法,和一个判断是否合法的方法
public interface ConstraintValidator<A extends Annotation, T> {
void initialize(A constraintAnnotation); boolean isValid(T value, ConstraintValidatorContext context); }
<2> ConstraintValidatorContext
这个上下文
包含了认证中所有的信息
,我们可以利用这个上下文实现获取默认错误提示信息
,禁用错误提示信息
,改写错误提示信息等操作
。
<3> 一些典型校验操作
,或许可以对你产生启示作用。
值得注意的一点是,自定义注解
可以用在METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER
之上,ConstraintValidator的第二个泛型参数T,是需要被校验的类型
解决多字段联合逻辑校验问题【享学Spring MVC】
见 百度网盘 AopDemo
1 外部调用类
没用@Valid 或@Validated注解
(比如controller方法参数中没加
)
2 尝试hibnerate的包
@javax.validation.constraints.NotBlank 尝试下面 @org.hibernate.validator.constraints.NotBlank
注解效验的类 使用时 controller 接口,要效验的参数前 @Valid
@PostMapping("/register") public String handlePostRequest(@RequestBody @Valid UserInfo user, BindingResult bindingResult) {
... }
controller
上加@Validated
就能报错
@Slf4j @RestController @Api(description = "用户管理") @Validated @RequestMapping("user") public class UserController {
@PostMapping("/register") public String handlePostRequest(@RequestBody @Valid UserInfo user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "register fail"; } log.info("user: {}", user.toString()); return "register success"; } }