lombok@NonNull 注解无法对 SpringMVC 封装请求的对象的 Field 进行非空校验
1 | If put on a parameter, lombok will insert a null -check at the start of the method / constructor 's body, throwing a {@code NullPointerException} with the parameter' s name as message. <br> If put on a field, any generated method assigning a value to this field will also produce these null -checks. |
可见 @NonNull 注解用于标注Field时,并非是直接校验所标注字段不能为空,而是对赋值该字段的方法加 null 校验。
如该对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Data @AllArgsConstructor public class ReqIn { @NonNull private Byte name; private String id; public ReqIn(){ System.out.println( "无参构造" ); } } |
编译生成 .class 文件如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | public class ReqIn { @NonNull private String name; private String id; public ReqIn() { System.out.println( "无参构造" ); } @NonNull public String getName() { return this .name; } public String getId() { return this .id; } public void setName( @NonNull final String name) { if (name == null ) { throw new NullPointerException( "name is marked non-null but is null" ); } else { this .name = name; } } public void setId( final String id) { this .id = id; } public boolean equals( final Object o) { if (o == this ) { return true ; } else if (!(o instanceof ReqIn)) { return false ; } else { ReqIn other = (ReqIn)o; if (!other.canEqual( this )) { return false ; } else { Object this $name = this .getName(); Object other$name = other.getName(); if ( this $name == null ) { if (other$name != null ) { return false ; } } else if (! this $name.equals(other$name)) { return false ; } Object this $id = this .getId(); Object other$id = other.getId(); if ( this $id == null ) { if (other$id != null ) { return false ; } } else if (! this $id.equals(other$id)) { return false ; } return true ; } } } protected boolean canEqual( final Object other) { return other instanceof ReqIn; } public int hashCode() { int PRIME = true ; int result = 1 ; Object $name = this .getName(); int result = result * 59 + ($name == null ? 43 : $name.hashCode()); Object $id = this .getId(); result = result * 59 + ($id == null ? 43 : $id.hashCode()); return result; } public String toString() { return "ReqIn(name=" + this .getName() + ", id=" + this .getId() + ")" ; } public ReqIn( @NonNull final String name, final String id) { if (name == null ) { throw new NullPointerException( "name is marked non-null but is null" ); } else { this .name = name; this .id = id; } } } |
可见,对 setName() 和 @AllArgsConstructor 注解生成的全参数构造器,均对 name 入参做非 null 校验。
但当 @Controller 内 @RequestMapping 注解的接口,若以 ReqIn 作为封装的请求对象,则接口调用时若没有传 name 字段,则会发现接口接收到的入参里,虽然字段被标注了 @NonNull,但 name 确实为 null。
原因简单总述为:SpringMVC对参数解析和封装对象时,对于有无参构造器的对象,会使用无参构造器初始化,随后对传入的 Field 进行赋值,对没有传的 Field 则会忽略。因此,虽然带有 name 参数的构造器和 setName()均对 name 做了 null 校验,但实际上根本没有被调用。
A)初始化被封装对象
详述:
1、SpringMVC对参数解析和封装对象时
如果目标对象仅有一个 Constructor,则会调用该 Constructor 封装对象;
如果对象有多于一个 Constructor,则会取无参构造器来封装对象,本例中无参构造器为手动生成,使用 @NoArgsConstructor 同样效果。
如果对象没有指定构造器,这种情况在有 @NonNull 注解时不存在,因为 @NonNull 注解会对指定了该注解的 Field 生成 Constructor,参数与被注解字段数量一致。
2、如果被 @NonNull 注解的字段为 String,则注解不会生效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Data @AllArgsConstructor public class ReqIn { @NonNull private String name; @NonNull private String id; public ReqIn(){ System.out.println( "无参构造" ); } }<br><br> @RequestMapping ( "/req" )<br> @Controller <br> public class ReqInController {<br><br><br> @GetMapping ( "/testAnnotation" )<br> public String testAnnotation(ReqIn reqIn){<br> System.out.println(reqIn);<br> return "index" ;<br> }<br>}<br> |
2.1、如果请求时,带有 name 字段,则 name 字段会被设置为空字符串
1 | 请求:<br>http: //localhost:1234/req/testAnnotation?id=133&name= |
1 2 | 输出:<br>无参构造 ReqIn(name=, id= 133 ) |
2.2、如果请求时,不带有 name 字段,则 name 字段会被设置为 null
1 | 请求:<br>http: //localhost:1234/req/testAnnotation?id=133 |
1 2 | 输出:<br>无参构造 ReqIn(name= null , id= 133 ) |
可见,对于 String 字段,在 SpringMVC 封装对象时,@NonNull 注解无法阻止字段被设置为null。
原因在于,调用无参构造器之后,由于没有传入 name,封装对象的 parameterTypes 仅有 id,进而没有调用过 setName()方法,@NonNull 注解对 name 字段自然无法约束非 null。
结论
要使 @NonNull 注解对于指定 Field 生效,则需要
1、被封装对象没有无参构造器
2、如果被注解 Field 是 String 类型,则如果该参数为空时,不要作为请求的 key-value 对的 key 传到后端接口。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· 因为Apifox不支持离线,我果断选择了Apipost!