使用 Java Bean 来体现统一返回数据这个结构,可以解决接口返回格式一致性,但也有几个新问题诞生了:
- 接口返回值不明显,不能一眼看出来该接口的返回值
- 每一个接口都需要增加额外的代码量
- 不利于swagger对接口的返回类型的支持
所幸Spring Boot已经为我们提供了更好的解决办法,只需要在项目中加上以下代码,就可以无感知的为我们统一全局返回值。
package com.crownboot.web.global.response;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpStatus;
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.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import com.crownboot.web.properties.CrownBoot;
@ControllerAdvice(annotations = { RestController.class, Controller.class })
public class GlobalResponseHandleAdvice implements RequestBodyAdvice, ResponseBodyAdvice<Object> {
@Autowired
private CrownBoot crownBoot;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
Controller controller = returnType.getDeclaringClass().getAnnotation(Controller.class);
RestController restController = returnType.getDeclaringClass().getAnnotation(RestController.class);
ResponseBody responseBody = returnType.getMethodAnnotation(ResponseBody.class);
boolean wrapper = true;
UnifiedReturn unifiedReturn = returnType.getMethodAnnotation(UnifiedReturn.class);
if (Objects.nonNull(unifiedReturn)) {
wrapper = unifiedReturn.wrapper();
}
return (Objects.nonNull(restController) || (Objects.nonNull(controller) && Objects.nonNull(responseBody)))
&& wrapper;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
UnifiedReturn unifiedReturn = returnType.getExecutable().getDeclaringClass().getAnnotation(UnifiedReturn.class);
if (Objects.isNull(unifiedReturn)) {
unifiedReturn = returnType.getMethodAnnotation(UnifiedReturn.class);
}
HttpStatus status = HttpStatus.OK;
if (Objects.nonNull(unifiedReturn)) {
status = unifiedReturn.status();
}
// ResponseStatus
ResponseStatus responseStatus = returnType.getMethodAnnotation(ResponseStatus.class);
if (Objects.nonNull(responseStatus)) {
status = responseStatus.code();
}
if (Objects.nonNull(unifiedReturn) && !unifiedReturn.wrapper()) {
return body;
}
// swagger
URI uri = request.getURI();
if (Objects.nonNull(uri) && StringUtils.containsAny(uri.getPath(), "swagger-resources", "api-docs")) {
return body;
}
return body instanceof ApiResponse ? body : ApiResponse.<Object>success(response, status.value(), body).build();
}
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return inputMessage;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
// RequestUtils.getRequest().setAttribute(APICons.API_REQUEST_BODY, body);
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
有时针对特殊应用场景,无需对返回数据进行包装,可以自定义一个注解进行标识
package com.crownboot.web.global.response;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.http.HttpStatus;
/**
* 包装API返回注解
*
* @author luopeng
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UnifiedReturn {
/**
* 是否包装返回
*/
boolean wrapper() default false;
/**
* 正常返回httpcode码
*/
HttpStatus status() default HttpStatus.OK;
}
此时请求 hello接口,会报ApiResponses不能转为string的异常
{
"timestamp": "2019-12-09T12:22:53.776+0000",
"status": 500,
"error": "Internal Server Error",
"message": "com.gitee.web.api.ApiResponses cannot be cast to java.lang.String",
"path": "/users"
}
java.lang.ClassCastException: com.gitee.web.api.ApiResponses cannot be cast to java.lang.String
at org.springframework.http.converter.StringHttpMessageConverter.getContentLength(StringHttpMessageConverter.java:44) ~[spring-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.http.converter.AbstractHttpMessageConverter.addDefaultHeaders(AbstractHttpMessageConverter.java:260) ~[spring-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:211)
解决
package com.gitee.config;
import java.util.List;
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;
@Configuration
public class MyConfig implements WebMvcConfigurer{
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.set(0, new MappingJackson2HttpMessageConverter());
}
}
重写WebMvcConfigurerAdapter,覆盖了原有的HttpMessageConverters,此处采用:Jackson
epom-web工程pom添加jackson依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>