Using Java beans to embody the uniform return data structure solves the problem of interface return format consistency, but several new problems arise:
- The return value of the interface is not obvious and cannot be seen at a glance
- Each interface requires an additional amount of code
- Adverse to Swagger’s support for interface return types
Fortunately, Spring Boot has provided us with a better solution, just add the following code to the project, and we can unify the global return value for us without any awareness.
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
> 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
> 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
> converterType) {
return true;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class
> converterType) {
return inputMessage;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class
> converterType) {
// RequestUtils.getRequest().setAttribute(APICons.API_REQUEST_BODY, body);
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class
> converterType) {
returnbody; }}Copy the code
Sometimes, for special application scenarios, there is no need to wrap the returned data and a custom annotation can be used to identify it
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;
/** * The wrapper API returns the annotation **@author luopeng
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UnifiedReturn {
/** * whether the package returns */
boolean wrapper(a) default false;
/** * return httpcode */
HttpStatus status(a) default HttpStatus.OK;
}
Copy the code
When the Hello interface is requested, an exception is reported that the ApiResponses cannot be converted to string
{
"timestamp": "The 2019-12-09 T12: so. 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.21..RELEASE.jar:5.21..RELEASE]
at org.springframework.http.converter.AbstractHttpMessageConverter.addDefaultHeaders(AbstractHttpMessageConverter.java:260) ~[spring-web-5.21..RELEASE.jar:5.21..RELEASE]
at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:211)
Copy the code
To solve
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
> converters)
> {
converters.set(0.newMappingJackson2HttpMessageConverter()); }}Copy the code
Rewrite the WebMvcConfigurerAdapter to override the original HttpMessageConverters, using Jackson
Epom – Web Project POM Adds a Jackson dependency
<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>
Copy the code