SpringBoot-2.7.12
请求的body参数无法转换,服务端没有报错信息打印,而是响应的状态码是400
java
@PostMapping("/static/user")
public User userInfo(@RequestBody(required = false) User user){
user.setAge(19);
return user;
}
java
@PostMapping("/static/requiredUser")
public User requiredUserInfo(@RequestBody() User user){
log.info(gson.toJson(user));
user.setAge(19);
return user;
}
SpringBoot-2.1.14.RELEASE
对应springframe一系列版本spring-web、spring-webmvc...版本5.1.15.RELEASE
请求体参数无法接受,没有报错
java
@PostMapping("/static/requiredUser")
public User requiredUserInfo(@RequestBody() User user){
log.info(gson.toJson(user));
user.setAge(19);
return user;
}
无body及日志输出原因
是因为我这里有一段全局异常处理,但是这里的异常处理返回信息的结果和我上面接口的返回结果不一致。将断点打在这里,也会发现异常并没有进来(进来就会有错误日志了)。是因为我的全局异常类继承了ResponseEntityExceptionHandler这个类导致的。
发现去掉这个全局异常处理,响应结果如下:
java
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
private final Log log = LogFactory.getLog(this.getClass());
@ExceptionHandler(Exception.class)
@ResponseBody
ReturnInfo handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
//return new ReturnInfo(status.value(), ex.getMessage());
log.error(status.value(),ex);
return ReturnInfo.buildErrorInfo(ex.getMessage());
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
}
}
全局异常处理修改
java
// 只监听这个的下面的异常处理
@ControllerAdvice(assignableTypes = {ExampleController.class})
ResponseEntityExceptionHandler
这个类里面有定义处理HttpMessageNotReadableException异常,这个指定特定异常的优先级可能更高,所以先在这里处理了,但是因为返回的结果类型不匹配,所以最终的响应体是空。
请求体参数属性首字母小写
当出现属性变量名首字母小写时,idea自动生成的get/set格式是set/get[a-z][A-Z].*,首字母会小写,此时你的json的key是属性名,参数是能正常接受的。但是如果将set/get格式换成set/get[A-Z][A-Z].*格式,就会接收不到参数,说明Spring的自动解析参数是set或get方法来的。测试发现,参数接收/响应结果会根据类的get/set中的一个来确定属性的key,例如:
存在一个类中有属性iName,启中只有满足setiName/getiName一项,这个时候使用下面格式就能接收参数
json
{"iName" : "iName"}
如果都不满从setiName/getiName,而是setIName;getIName,则只能使用下面格式接收参数(并且响应的key和传递一样):
json
{"iname" : "iname"}
dto1
java
public class Goods {
private String name;
private String iName;
private String description;
private String iDescription;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIName() {
return iName;
}
public void setIName(String iName) {
this.iName = iName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getIDescription() {
return iDescription;
}
public void setIDescription(String iDescription) {
this.iDescription = iDescription;
}
}
传参格式:
json
{
"name": "name",
"description": "description",
"iname": "iname",
"idescription": "idescription"
}
响应结果
json
{
"name": "name",
"description": "description",
"iname": "iname",
"idescription": "idescription"
}
dto2
java
public class Goods {
private String name;
private String iName;
private String description;
private String iDescription;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getiName() {
return iName;
}
public void setiName(String iName) {
this.iName = iName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getiDescription() {
return iDescription;
}
public void setiDescription(String iDescription) {
this.iDescription = iDescription;
}
}
接收参数:
json
{
"name": "name",
"description": "description",
"iName": "iName",
"iDescription": "iDescription"
}
响应结果:
json
{
"name": "name",
"description": "description",
"iName": "iName",
"iDescription": "iDescription"
}
dto3
两种方式都存在,没有属性结果都被返回
java
public class Goods {
private String name;
private String iName;
private String description;
private String iDescription;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIName() {
return iName;
}
public void setIName(String iName) {
this.iName = iName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getIDescription() {
return iDescription;
}
public void setIDescription(String iDescription) {
this.iDescription = iDescription;
}
public String getiName() {
return iName;
}
public void setiName(String iName) {
this.iName = iName;
}
public String getiDescription() {
return iDescription;
}
public void setiDescription(String iDescription) {
this.iDescription = iDescription;
}
}
请求体解析
在AbstractJackson2HttpMessageConverter调用read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)方法解析请求体
- type:@RequestBody注解的类
- contextClass:被代理处理的controller接口类
- inputMessage:HTTP请求体的消息
获取要转换的请求的参数类型Type进行解析
TypeVariable处理逻辑:
- 根据这个泛型参数所在的类,new ResolvableType(clazz)对象;
- 最终是要获取泛型所代表的具体类型
ParameterizedType支持嵌套处理
根据前面获取到的Type类型,是要ObjectMapper对象构建JavaType对象。
最终使用ObjectMapper结合JavaType和输入流得到对象。默认的解析和转换都是以Jackson的规则来的
Jackson使用测试
java
public class JacksonUtil {
private static final ObjectMapper objectMapper = new ObjectMapper();
public static JavaType getJavaType(Type type){
return objectMapper.getTypeFactory().constructType(type);
}
/**
* 字符串转Java对象
* @param json json字符串
* @param javaType 最终解析的Java对象类Type对应的JavaType
* @return 泛型对象
* @param <T>
*/
public static <T> T stringToObject(String json, JavaType javaType){
try {
return objectMapper.readValue(json, javaType);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 输入流json转Java对象
* @param inputStream json输入流
* @param javaType 最终解析的Java对象类Type对应的JavaType
* @return
*/
public static Object inputStreamToObject(InputStream inputStream, JavaType javaType){
try {
return objectMapper.readValue(inputStream, javaType);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
*
* @param json json字符串
* @param clazz 结果类
* @return json解析结果对象
* @param <T>
*/
public static <T> T stringToClass(String json, Class<T> clazz){
try {
return objectMapper.readValue(json, clazz);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 对象转json字符串
* @param object 要转成json的对象
* @param javaType 对象的Type对应的JavaType
* @return
*/
public static String objectToJson(Object object, JavaType javaType){
try {
return objectMapper.writerFor(javaType).writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
/**
* 普通对象转输出流
* @param object 对象
* @param out 输出流
* @throws IOException
*/
public static void objectToPrintStream(Object object, OutputStream out) throws IOException {
JsonGenerator generator = objectMapper.getFactory().createGenerator(out, JsonEncoding.UTF8);
ObjectWriter writer = objectMapper.writer();
writer.writeValue(generator, object);
generator.flush();
}
}
java
public class JacksonExample {
public static void main(String[] args) throws IOException {
String json = "{\n" +
" \"name\": \"name\",\n" +
" \"description\": \"description\",\n" +
" \"iName\": \"iName\",\n" +
" \"iDescription\": \"iDescription\"\n" +
"}";
JavaType javaType = JacksonUtil.getJavaType(Goods.class);
Goods goods = JacksonUtil.stringToObject(json, javaType);
Goods goods1 = JacksonUtil.stringToClass(json, Goods.class);
System.out.println(JacksonUtil.objectToJson(goods, javaType));
JacksonUtil.objectToPrintStream(goods1,System.out);
}
}
在json的解析和转换成json的过程中,以对象的set/get方法作为属性的重要依据
可以从下面的测试看出,get方法在解析和转换的过程中占主要地位