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

The effect