功能开发中,我们会有这种需求,希望所有的数据返回统一的格式,包含状态码
,数据
,错误信息
,当前时间
,比如:
{"code":200,"data":null,"message":"OK","timestamp":1559292578716}
首先我们需要有这么一个类
@Data
public class Result<T> {
private long timestamp;
private String message;
private int code=SUCCESS;
private T data;
@Data
public static class Builder<T> {
//部分代码略
}
}
但是我们不能在每一个Controller
上的返回格式都定义为Result
Spring Boot
如何在不改变原有的Controller
的基础上实现呢?
我们可以借助Spring
提供的ResponseBodyAdvice
实现
public interface ResponseBodyAdvice<T> {
boolean supports(MethodParameter var1, Class<? extends HttpMessageConverter<?>> var2);
@Nullable
T beforeBodyWrite(@Nullable T var1, MethodParameter var2, MediaType var3, Class<? extends HttpMessageConverter<?>> var4, ServerHttpRequest var5, ServerHttpResponse var6);
}
该接口只有两个方法supports
和beforeBodyWrite
- supports 方法返回
boolean
,判断是否支持Controller
的返回类型 - beforeBodyWrite 写出之前的处理,此文中主要依靠此方法,
T var1
就是Controller
返回的对象
写一个我们自己的实现
@ControllerAdvice()
public class ResponseAdvisor implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
return new Result.Builder<>().data(body).build();
}
}
系统中原有的Controller
@RestController
public class DemoController {
@GetMapping("/echo/{name}")
public String echo(@PathVariable("name") String name){
return "hello:"+name;
}
@GetMapping("/user/{id}")
public User getUserById(@PathVariable("id") Long id){
User user=new User(id,"Lucy",18);
return user;
}
@GetMapping("/download")
public ResponseEntity<FileSystemResource> download() {
File file = new File("d:/1.jpg");
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
try {
headers.add("Content-Disposition", "attachment; filename=" + new String(file.getName().getBytes("GB2312"),"ISO-8859-1"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return ResponseEntity
.ok()
.headers(headers)
.contentLength(file.length())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new FileSystemResource(file));
}
}
分别进行测试
{
"timestamp": 1559293805111,
"message": "OK",
"code": 200,
"data": {
"id": 1,
"name": "Lucy",
"age": 18
}
}
java.lang.ClassCastException: com.podigua.demo.core.Result cannot be cast to java.lang.String
java.lang.ClassCastException: com.podigua.demo.core.Result cannot be cast to org.springframework.core.io.Resource
三个测试.发现有两个出错, 出错的原因是找不到对应的HttpMessageConverter
,HttpMessageConverter
是一组消息转换器
,以下列出部分MessageConverter
StringHttpMessageConverter
负责字符串类型的数据的读取或者写出ByteArrayHttpMessageConverter
负责二进制格式数据的读取或者写出ResourceHttpMessageConverter
负责资源文件的读取或者写出FormHttpMessageConverter
负责读取form提交的数据application/x-www-form-urlencoded
MappingJacksonHttpMessageConverter
负责json格式数据的读取或者写出
原因就是缺少 MappingJackson2HttpMessageConverter
和ResourceHttpMessageConverter
我们调整下代码,增加这两个消息转换器
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new MappingJackson2HttpMessageConverter());
converters.add(1, new ResourceHttpMessageConverter());
}
}
资源类数据,不能按照这种格式返回,最终返回的是文件流,我们调整下代码ResponseAdvisor
的beforeBodyWrite
方法,若返回值已经是Result
类型也不需要处理
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
if (body instanceof Resource) {
return body;
}
if (body instanceof Result) {
return body;
}
return new Result.Builder<>().data(body).build();
}
若Coltroller
抛出了异常呢? 需要AOP
捕获异常,并将异常信息与状态码封装为Result
的数据即可
项目github地址