SpringBoot之统一包装响应
SpringBoot 中可以基于 ControllerAdvice
和 HttpMessageConverter
实现对数据返回的包装。
实现如下,先来写一个 POJO 来定义一下返回格式:
import com.example.demo.common.exception.base.ErrorCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;
@Getter
@AllArgsConstructor
public class Response<T> {
private int code = HttpStatus.OK.value();
private String msg = "success";
private T data;
public Response(T data) {
this.data = data;
}
public Response(int code, String msg) {
this.code = code;
this.msg = msg;
}
public Response(int code, T data) {
this.code = code;
this.data = data;
}
public Response(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.msg = errorCode.getMessage();
}
public Response(ErrorCode errorCode, T data) {
this.code = errorCode.getCode();
this.msg = errorCode.getMessage();
this.data = data;
}
}
这里用到了lombok,lombok的使用介绍不在本文范围内。
用一个 ResponseBodyAdvice 类的实现包装 Controller 的返回值:
以下是我以前的实现方式:
import com.example.demo.common.RequestContextHolder;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice
public class FormatResponseBodyAdvice implements ResponseBodyAdvice {
private static Logger logger = LoggerFactory.getLogger(FormatResponseBodyAdvice.class);
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
Object wrapperBody = body;
try {
if (!(body instanceof Response)) {
if (body instanceof String) {
wrapperBody = objectMapper.writeValueAsString(new Response<>(body));
} else {
wrapperBody = new Response<>(body);
}
}
} catch (Exception e) {
logger.error("request uri path: {}, format response body error", request.getURI().getPath(), e);
}
return wrapperBody;
}
}
今天找到了导致这个异常的原因:
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new MappingJackson2HttpMessageConverter());
}
}
然后 FormatResponseBodyAdvice 就可以修改为如下实现:
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice
public class FormatResponseBodyAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (!(body instanceof Response)) {
return new Response<>(body);
}
return body;
}
}
比之前的实现方式优雅了很多而且不用再处理 jackson 的异常了。
写一个 Controller 来尝试一下:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello world!";
}
}
请求这个端点得到结果:
{
"code": 200,
"msg": "success",
"data": "hello world!"
}
说明我们的配置是成功的,同时可以在相应头中看到:
content-type: application/json;charset=UTF-8
如果是之前的实现方式,这里的值就是:
content-type: html/text
也不太符合 restful
规范.