SpringMVC接收参数详解—自定义对象篇
相信小伙伴们都会有给controller传参的经历,但是springmvc内部是如何处理这些参数的。今天就带大家来看一看自定义对象情况下的实现。
一、普通实体类参数
@GetMapping("/test1")
public String test1(LoginParam loginParam){
System.out.println(loginParam);
return "test1";
}
@Data
@AllArgsConstructor
//@NoArgsConstructor
public class LoginParam{
String username;
String password;
}
框架行为详解:
在无参和有参构造都有的情况下,会先调用LoginParam的 无参构造 方法,若传入的参数符合条件,再调用set方法。
如果没有无参构造方法(即只有有参构造,有参构造会覆盖默认的无参构造方法),则先调用 有参构造 方法,若传入的参数符合条件,再调用set方法
下面我们分几类情况来讨论
1.不传或传入的参数名和形参对象属性名不匹配
-
有 无参构造(也有有参构造,下述同):调用无参构造方法,类似于
new LoginParam()
,因为没有传入username和password,所以不调用set方法 -
只有 有参构造:调用有参构造方法,
new LoginParam(null,null)
,因为没有传入username和password,所以不调用set方法
2.传入正确username、password(即使是username=&password=,即不写参数值)
-
有 无参构造:调用无参构造方法,
new LoginParam()
,因为传入了username和password,所以调用set方法void setUsername(username) void setPassword(password)
-
只有 有参构造:调用有参构造方法,
new LoginParam(username,password)
,因为传入了username和password,所以调用set方法void setUsername(username) void setPassword(password)
3.只传入username,不传password
-
有 无参构造:调用无参构造方法,
new LoginParam()
,因为传入了username,所以调用set方法void setUsername(username)
-
只有 有参构造:调用有参构造方法,
new LoginParam(username,null)
,因为传入了username,所以调用set方法void setUsername(username)
4.总结
通过上述我们发现,当LoginParam有 无参构造 方法但没有set方法时,即使我们传入username、password,它也不能正确接受。所以我们要杜绝这种现象。
所以可以避免错误发生的解决方案是:只提供有参构造,这样就不用担心有没有写set方法,如果参数传对了可以接受参数,如果传错了也可以构造空的参数
二、@RequestBody实体类参数
@GetMapping("/test2")
public String test2(@RequestBody LoginParam loginParam)
{
System.out.println(loginParam);
return "test2";
}
@Data
@AllArgsConstructor
//@NoArgsConstructor
public class LoginParam{
String username;
String password;
}
框架行为详解:
若请求体没数据或不满足json格式,则直接抛异常
若请求体满足json格式,会先调用LoginParam的 无参构造 方法,若传入的参数符合条件,再调用set方法
如果没有无参构造方法,则先调用 有参构造 方法,不会再调用set方法
下面我们分几类情况来讨论
0.不传或不满足json格式
抛异常
1.传入的参数名和形参对象属性名不匹配(但json格式正确)
-
有 无参构造:调用无参构造方法,类似于
new LoginParam()
,因为没有传入username和password,所以不调用set方法 -
只有 有参构造:调用有参构造方法,
new LoginParam(null,null)
2.传入正确username、password
-
有 无参构造:调用无参构造方法,
new LoginParam()
,因为传入了username和password,所以调用set方法void setUsername(username) void setPassword(password)
-
只有 有参构造:调用有参构造方法,
new LoginParam(username,password)
3.只传入username,不传password
-
有 无参构造:调用无参构造方法,
new LoginParam()
,因为传入了username,所以调用set方法void setUsername(username)
-
只有 有参构造:调用有参构造方法,
new LoginParam(username,null)
4.总结
同理,当有 无参构造 方法但没有set方法时,即使我们传入username、password,它也不能正确接受
我们可以同样:只提供有参构造,这样就不用担心有没有写set方法,如果参数传对了可以接受参数,如果传错了也可以构造空的参数
三、结合SpringValidation框架
按照上述思路,我们只提供 有参构造,java中帮我们封装好了一个类:Record,它默认只包含全参构造。在这种情况下又分下列几种错误传参的情况:
1.普通实体类参数
- 不传或传入的参数名和形参对象属性名不匹配,则调用有参构造方法
new LoginParam(null,null)
2.@RequestBody
- 不传或不满足json格式,则框架抛异常
- 传入的参数名和形参对象属性名不匹配(但json格式正确,例如空json),则调用有参构造方法
new LoginParam(null,null)
3.路径参数
- 不可能为null,因为如果为null,url都不匹配
4.普通参数
- 可能为null
- @Max,@Size注解无法校验是否为 null,因此前面要配合@NotNull或@NotBlank注解
注意:@Validated/@Valid 的验证逻辑是:先检查 obj 不为空,再逐个检查 obj 中各个字段的约束。只有加上@Validated/@Valid,实体类中的@NotNull等注解才会生效。
在默认配置下,Spring MVC并不会自动验证简单类型的方法参数(如String
、int
等)。若要让这些约束注解生效,需要在控制器方法参数前使用@Valid
或@Validated
注解来触发验证
@Validated/@Valid是等构造函数和set方法完了以后,再开始检查各个字段是否符合注解约束
四、特殊情况
@Data
public class PageRequest{
@NotNull
@Min(1)
private Integer current=1;
@NotNull
@Min(1)
private Integer size=10;
}
如果在字段中直接给出了值,那么字段在调用构造方法前,默认就是这个值。否则默认是为:对象则为null,基本类型则为0 etc…
下面也来分情况讨论
1.不传或传入的参数名和形参对象属性名不匹配
-
有 无参构造:调用无参构造方法,类似于
new PageRequest()
,因为没有传入current和size,所以不调用set方法。
此时current=1,size=10 -
只有 有参构造:调用有参构造方法,
new PageRequest(null,null)
,因为没有传入current和size,所以不调用set方法。此时current=null,size=null
2.传入正确current、size(即使是current=&size=,即不写参数值)
-
有 无参构造:调用无参构造方法,
new PageRequest()
,因为传入了current和size,所以调用set方法void setCurrent(current) void setSize(size)
此时current=传入的值,size=传入的值(若不写参数值,则传入的值为null,下述同) -
只有 有参构造:调用有参构造方法,
new PageRequest(current,size)
,因为传入了current和size,所以调用set方法void setCurrent(current) void setSize(size)
此时current=传入的值,size=传入的值
3.只传入current,不传size
-
有 无参构造:调用无参构造方法,
new PageRequest()
,因为传入了current,所以调用set方法void setCurrent(current)
此时current=传入的值,size=10 -
只有 有参构造:调用有参构造方法,
new PageRequest(current,null)
,因为传入了current,所以调用set方法void setCurrent(current)
此时current=传入的值,size=null
4.总结
如果在字段中给出了值,那么不要只写全参构造方法,要给出无参构造。
无参构造中,如果没有传入current和size,那么则为字段中给出的值。如果传入了current和size,那么会调用set方法,set成传入的值。
有参构造中,如果如果没有传入current和size,那么调用构造函数后current和size都为null。如果传入了current和size,则为传入的值。所以字段中提前给出的值不会起作用。
五、正确使用方式
使用java record类来充当dto和vo,因为它默认只包含全参构造。所以无论是错误还是正确的参数都能应对。同时加上@Validated/@Valid注解,这样如果参数不对还能进行校验。
如果要提前给字段赋值,那么用class类(record中不允许给属性赋值,因为属性值默认修饰符为final,默认给出的全参构造赋值时就会报错),给出无参构造。如果没有传入参数,那么则为字段中给出的值。如果传入了参数,那么会调用set方法,set成传入的值。