SpringBoot 中可以基于 ControllerAdvice
和 HttpMessageConverter
实现对数据返回的包装。
实现如下,先来写一个 POJO
来定义一下返回格式:
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
| 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
的返回值:
以下是我以前的实现方式:
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
| 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; }
}
|
为什么要对返回类型是 String
时进行特殊处理呢?因为如果直接返回 new Response<>(body)
的话,在使用时返回 String
类型的话,会报类型转换异常,当时也没有理解什么原因导致的,所以最后使用了 jackson
对 Response
又做了一次序列化。
今天找到了导致这个异常的原因:
因为在所有的 HttpMessageConverter
实例集合中,StringHttpMessageConverter
要比其它的 Converter
排得靠前一些。我们需要将处理 Object
类型的 HttpMessageConverter
放得靠前一些,这可以在 Configuration
类中完成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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
就可以修改为如下实现:
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
| 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
来尝试一下:
1 2 3 4 5 6 7 8 9
| @RestController public class HelloController {
@GetMapping("/hello") public String hello() { return "hello world!"; }
}
|
请求这个端点得到结果:
1 2 3 4 5
| { "code": 200, "msg": "success", "data": "hello world!" }
|
说明我们的配置是成功的,同时可以在相应头中看到:
1
| content-type: application/json;charset=UTF-8
|
如果是之前的实现方式,这里的值就是:
也不太符合 restful
规范。