一、对所有Controller的response做统一格式封装
我们的接口会返回空、返回字符串、返回对象、返回异常(这个后续讲),所以需要统一返回格式
二、方法一
在接口文档,规定外层的格式,比如需要返回4个字段:data、sign、repCode、repMsg
1、建立包com.example.message
2、建立CommonResponse.java
package com.example.message;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("通用返回对象")
@Data
public class CommonResponse {
@ApiModelProperty(value = "数据")
private String data; //数据
@ApiModelProperty(value = "签名")
private String sign; //签名
@ApiModelProperty(value = "应答码")
private String repCode; //应答码
@ApiModelProperty(value = "应答信息")
private String repMsg; //应答信息
public static CommonResponse succ() {
CommonResponse response = new CommonResponse();
response.setRepCode("000000");
response.setRepMsg("成功");
return response;
}
public static CommonResponse succ(String data) {
CommonResponse response = new CommonResponse();
response.setData(data);
response.setRepCode("000000");
response.setRepMsg("成功");
return response;
}
public static CommonResponse fail(String repCode, String repMsg) {
CommonResponse response = new CommonResponse();
response.setRepCode(repCode);
response.setRepMsg(repMsg);
return response;
}
}
3、代码中Controller统一返回值为CommonResponse类型,建立ResponseController.java
package com.example.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.domain.Teacher;
import com.example.message.CommonResponse;
import io.swagger.annotations.Api;
@Api(description = "返回值格式测试")
@RestController
@RequestMapping("/response")
public class ResponseController {
/**
* 方法一
* @return
*/
@GetMapping("/succ")
public CommonResponse getSucc() {
return CommonResponse.succ(); //这样写会被其他程序员鄙视
}
}
4、缺点,每一个接口都要手工指定成功、失败
三、方法二
1、使用ResponseBodyAdvice接口和@ControllerAdvice注解
2、ResponseBodyAdvice接口是在Controller执行return之后,response消息转换为http消息返回给浏览器之前,执行的对response的一些处理
3、@ControllerAdvice注解增强Controller的扩展功能
具体是在执行之前还是执行之后的增强,需要实现不同的接口和注解
4、修改ResponseController.java
package com.example.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.domain.Teacher;
import com.example.message.CommonResponse;
import io.swagger.annotations.Api;
@Api(description = "返回值格式测试")
@RestController
@RequestMapping("/response")
public class ResponseController {
/**
* 方法一
* @return
*/
@GetMapping("/succ")
public CommonResponse getSucc() {
return CommonResponse.succ(); //这样写会被其他程序员鄙视
}
/**
* 方法二:返回String
* @return
*/
@GetMapping("/string")
public String getString() {
return "abc";
}
/**
* 方法二:无返回
*/
@GetMapping("/empty")
public void getEmpty() {
}
/**
* 方法二:返回对象
* @return
*/
@GetMapping("/teacher")
public Teacher getTercher() {
Teacher teacher = new Teacher();
teacher.setName("张三");
teacher.setAge("18");
teacher.setBeginTime("2020");
teacher.setEndTime("2021");
return teacher;
}
}
5、在包com.example.utils下添加JsonUtil.java
package com.example.utils;
import java.io.IOException;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonUtil {
private static ObjectMapper objectMapper = new ObjectMapper();
// 对象转字符串
public static <T> String obj2String(T obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
// 字符串转对象
public static <T> T string2Obj(String str, Class<T> clazz) {
if (StringUtils.isEmpty(str) || clazz == null) {
return null;
}
try {
return objectMapper.readValue(str, clazz);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
6、在包com.example.utils下添加ResponseHandler.java
package com.example.utils;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
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;
import com.example.message.CommonResponse;
@ControllerAdvice(basePackages = "com.example.web")
public class ResponseHandler implements ResponseBodyAdvice<Object> {
/**
* 是否支持advice功能
* true=支持,false=不支持
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
/**
* 处理response的具体方法
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
if (body instanceof String) {
//需要返回的类型是String
return JsonUtil.obj2String(CommonResponse.succ((String)body));
} else if (body == null) {
return CommonResponse.succ();
} else {
//将对象转成json串
return CommonResponse.succ(JsonUtil.obj2String(body));
}
//返回失败由抛异常捕获
}
}
7、说明
1)@ControllerAdvice(basePackages = "com.example.web")标识了增强功能,basePackages指定了Controller的路径
2)ResponseBodyAdvice拦截Controller方法的返回值,统一处理返回值
3)supports方法,定义了返回值是否需要执行beforeBodyWrite
4)beforeBodyWrite方法,具体对response的操作
5)有response自然也有RequestBodyAdvice接口
6)执行机制:
RequestBodyAdvice:需在Controller层中的方法上加上@RequestMapping和@RequestBody;若是没有@RequestBody,RequestBodyAdvice不会执行
ResponseBodyAdvice:需在controller层中的方法上加上@RequestMapping和ResponseBody;若是没有@ResponseBody,ResponseBodyAdvice不会执行
四、测试
1、执行getEmpty
{
"data": null,
"sign": null,
"repCode": "000000",
"repMsg": "成功"
}
2、执行getString
{"data":"abc","sign":null,"repCode":"000000","repMsg":"成功"}
在ResponseHandler.java中这样写:
if (body instanceof String) {
return CommonResponse.succ((String)body);
}
会报错:
{
"timestamp": "2020-08-30T18:02:20.325+0000",
"status": 500,
"error": "Internal Server Error",
"message": "com.example.message.CommonResponse cannot be cast to java.lang.String",
"path": "/response/string"
}
因为方法的返回值是String,在增强里面返回的是CommonResponse对象,所以报错了。原因是,返回值类型为String的时候,springboot会默认定义一个StringHttpMessageConverter消息转换器。而body进来是abc,经过拦截处理后返回是CommonResponse对象,无法转换消息
而返回为空和返回对象,消息转换器均为MappingJackson2HttpMessageConverter
3、执行getTercher
{
"data": "{\"name\":\"张三\",\"age\":\"18\",\"beginTime\":\"2020\",\"endTime\":\"2021\"}",
"sign": null,
"repCode": "000000",
"repMsg": "成功"
}
注:最新代码上传至https://github.com/csj50/myboot