一、前言
本文的目的是摸清jsr303注解的坑。
我对jsr303的态度是:能不用就不用。
原因:
1、坑多。不是所有开发人员都会花时间去摸细节
2、满足不了复杂校验。花拳绣腿,不能满足实际开发中复杂的校验场景,与其部分校验用jsr303,部分自写,还不如全部自写。
本文基于 6.0.17.Final 进行研究。其他版本是否会跟我的版本有出入? 未知(不过推测不可能有大的变化,不然以前用这个注解在升级后校验规则的行为变了,那就坑了)
二、使用注意
-
@Valid 和 @Validated,用@Valid,别用hibernate的注解,用javax的
有些开发偶尔用@Valid,偶尔用@Validated,一个是javax的,一个是spring的注解。别这样,虽然@Validated有它独特的group功能,但是
-
如下注意点
// 不用@valid不检查 @PostMapping("/test1") public String test1(T01_NoMessage dto) { return "ok"; } // 用了@Valid才检查 @PostMapping("/test1") public String test1(@Valid T01_NoMessage dto) { return "ok"; } // 传JSON 可以生效 @PostMapping("/2/test0") public Object test0(@Valid @RequestBody T50_Json dto) { return dto; } // 不是dto类不生效(其他奇奇怪怪的入参不生效) @PostMapping("/test1") public String test1(@Valid List<T01_NoMessage> dto) { return "ok"; } // 不生效 @PostMapping("/3/test1") public Object test1(@Valid @NotBlank String name) { return "name:" + name; } // 生效:除了dto外还有其他字段、路径参数 @PostMapping("/3/test4/{id}") public Object test4( @PathVariable("id") int id, String other, @Valid T66_NotOnly dto ) { return dto; } // 生效:传JSON,除了dto外,还有other。(PS:这个other怎么传参的? 通过`?other=xxx`即可) @PostMapping("/3/test5") public Object test5( String other, @Valid @RequestBody T66_NotOnly dto ) { return dto; }
-
嵌套类必须也用@Valid,否则不生效。详细的嵌套问题在后面单独讨论
// A类中有B类,这就叫做内嵌
-
正则表达式单和双斜杠都可以
// 可以,两个等价的(建议用一个斜杠的,因为感觉比较正统) @Pattern(regexp = "^[\u4e00-\u9fa5a-zA-Z0-9_]{4,64}$") @Pattern(regexp = "^[\\u4e00-\\u9fa5a-zA-Z0-9_]{4,64}$")
-
Mockmvc测试controller的方法,jsr303正常生效;但是如果仅仅方法调用,不生效
// 被校验 @Test public void test2() throws Exception { mockMvc.perform(MockMvcRequestBuilders.post("/3/test9").param("name", "stone")).andDo(MockMvcResultHandlers.print()); } // 不被校验 @Test public void test() { T69_SpringTest dto = new T69_SpringTest(); dto.setName(null); t03_TestController.test9(dto); } // IDEA 的bug:IDEA自动补全时,会在变量前面生成@Valid注解 @Test public void test() { // 这里的 @Valid 是代码自动补全的(IDEA 提示 `Create local variable 'dto'`),为什么会提示这个? // 实际上这个 @Valid 的注解,并不会触发拦截。实际上可能是 IDEA 的一个bug @Valid T69_SpringTest dto = new T69_SpringTest(); //dto.setName(""); dto.setName(null); t03_TestController.test9(dto); }
嵌套问题
注意,x-www-form-urlencorded的传参方式的dto是没有内嵌结构的,因为它是键值对传参,而键值对传参非常扁平,不会有嵌套结构。前端根本是不可能传这么复杂的、有层次结构的参数的。比如对于
public class A{
private String name;
private B b;
private List<C> cList;
}
@PostMapping("/test")
public void post(@Valid A a) {
....
}
要怎么传参?
http://xxx?name=stone&b.name=cassie&cList.name=Pg1&cList.name=Pg2
这样吗? 没见过这么奇怪的传参
所以针对传JSON才有嵌套的说法
嵌套是什么?
嵌套是类中有类。比如A类有成员变量B类,或者B类的集合类,如
public class A {
private B b;
private List<C> cList;
}
以下是爬坑,注意点
-
如何校验嵌套的类
// 只用@NotNull只能管必填,但不会校验b或cList public class A { @NotNull private B b; @NotNull private List<C> cList; } // 之用@Valid只管校验,就是传了就校验,不传也行(因为允许null) public class A { @Valid private B b; @Valid private List<C> cList; } // 如果要校验 "必填且必须校验",则@Notnull和@Valid一起用 public class A { @NotNull @Valid private B b; @NotNull @Valid private List<C> cList; }
顺序
Jsr303 注解了Controller的方法后,当发起http请求,如果检查不通过,会进入Controller方法吗? 会进入@ControllerAdvice的捕获方法吗?
-
检测是在调用Controller方法之前,若不通过,是不会进入的。即打在controller方法的断点是不会执行的
-
jsr303检查抛出异常, 是能够在@ControllerAdvice里捕获的
(打消疑惑:既然检查没进入Controller方法,那抛出异常时也就不会进入@ControllerAdvice的捕获方法里)
三、常用JSR303注解速查
注解 | 可以用在什么类型的字段上 | 作用 |
---|---|---|
@NotBlank | 只接受CharSequence | 必填。非空串 “”,非trim后是空串的如 ” “ |
@NotEmpty | 只接受CharSequence,Collection,Map,Array | 必填。字串类型时不能是空串 “”,但可以是 ” “;集合类型时必须size大于0 |
@Notnull | any types(即接受所有类型) | 必填 |
@Max | 只接受BigDecimal、BigInteger、byte、short、int、long、Byte、Short、Integer、Long;不支持double/float以及它的包装类 | 不必填,但填了就得符合 |
@Min | 同@Max | 不必填,但填了就得符合 |
@Size | 只接受CharSequence,Collection,Map,Array | 不必填,但填了就得符合 |
@Pattern | 只接受CharSequence | 不必填,但填了就得符合 |
只接受CharSequence | 不必填,但填了就得符合 | |
@Future | 一系列日期的格式,详见补充1 |
不必填,但填了就得符合 |
@FutureOrPresent | 同@Future | 不必填,但填了就得符合 |
@Past | 同@Future | 不必填,但填了就得符合 |
@PastOrPresent | 同@Future | 不必填,但填了就得符合 |
-
补充1:
* java.util.Date * java.util.Calendar * java.time.Instant * java.time.LocalDate * java.time.LocalDateTime * java.time.LocalTime * java.time.MonthDay * java.time.OffsetDateTime * java.time.OffsetTime * java.time.Year * java.time.YearMonth * java.time.ZonedDateTime * java.time.chrono.HijrahDate * java.time.chrono.JapaneseDate * java.time.chrono.MinguoDate * java.time.chrono.ThaiBuddhistDate
四、一次生产爬坑和思考
记录一次生产中使用jsr303的疑惑和爬坑事件。
有一次产品说有个字段以前必填的,要改成选填。代码如下
@PostMapping("/2/test66")
public Object test66(@Valid @RequestBody T200_ProdSizeProblem dto) {
return dto;
}
@Data
public class T200_ProdSizeProblem {
@NotBlank
private String name;
@Valid
private List<T200_ProdSizeProblemSub> subList;
}
@Data
public class T200_ProdSizeProblemSub {
@NotBlank(message = "参数列描述不能为空")
@Size(min = 1, max = 50, message = "最大长度为50")
private String descr;
}
产品要的需求就是 descr
字段可选,于是我就把 @NotBlank(message = "参数列描述不能为空")
去掉,因为对于 @Size
,我是知道它是 “非必填,但是如果填了长度就得符合”。所以我想你可以不填,但是填了就得按照我的长度要求,这样比较好。
但是出bug啦,界面上不填描述,但还是收到后端爆出的错误。
为什么啦? 之前不是说@Size
可以不填,但是填了才会进行size的校验的吗?
一排查,原来是描述不填的时候,desc还是传了空串。
作为后端很无奈,又不能要求前端同学 “在用户不填描述时不要出现descr字段,别传空串”,因为前端这部分逻辑比较复杂了所以不要求前端改,所以只能后端去掉@Size校验
PS:上述代码暴露出一个问题,以前的程序员根本就不知道要加上@NotNull,这个字段是一定要传的,并且size肯定会大于0,所以改成
@Data
public class T200_ProdSizeProblem {
@NotBlank
private String name;
@NotEmpty
@Valid
private List<T200_ProdSizeProblemSub> subList;
}
这就是我说的,很多同事根本就不清楚这些jsr303注释背后的细节和坑 !!!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/135292.html