Hello, I’m Brother Tongo.

Today I’ll talk about how Spring Boot implements elegant data response: unified result response format, simple data encapsulation.

The premise

Regardless of the size of the system, most Spring Boot projects are to provide Restful + JSON interface for front-end or other services to call, the format is uniform and standard, is the symbol of the program monkey to treat each other, but also to reduce the joint criticism of the basic guarantee.

The response result usually contains the service status code, response description, response timestamp, and response content, for example:

{" code ": 200," desc ":" query "success," timestamp ":" the 2020-08-12 14:37:11 ", "data" : {" uid ":" 1597242780874 ", "name" : "Test 1"}} Copy the codeCopy the code

The service status code is divided into two factions: one is recommended to use HTTP response code as interface service return; The other is that the HTTP response codes all return 200, and the response status is represented by a separate field in the response body. The two methods have their own advantages and disadvantages. I recommend using the second method, because many Web servers have interception and processing functions for HTTP status codes, and the number of status codes is limited and not flexible enough. For example, if 200 is returned, the interface successfully processed and responded normally. Now, a status code is required to indicate that the interface successfully processed and responded normally, but the request data status is incorrect. You can return 2001 to indicate that the interface successfully processed and responded normally.

Customize the response body

Defining a data response body is the first step in returning a uniform response format, and the structure returned to the caller should remain the same whether the interface returns normally or if an exception occurs. To give an example:

@apiModel@data public class Response<T> {@apiModelProperty (value = "return code ", example = "200") private Integer code; @apiModelProperty (value = "return code description ", example = "ok") private String desc; @apiModelProperty (value = "timestamp ", example = "2020-08-12 14:37:11") private Date timestamp = new Date(); @apiModelProperty (value = "return result ") private T data; } Duplicate codeCopy the code

This way, just return Response in the Controller method, and the interface Response will be consistent, but this will result in a lot of fixed-format code templates, such as the following:

@RequestMapping("hello1") public Response<String> hello1() { final Response<String> response = new Response<>(); response.setCode(200); SetDesc (" Return success "); response.setdesc (" Return success "); response.setData("Hello, World!" ); return response; } Duplicate codeCopy the code

The result of calling the interface is:

{" code ": 200," desc ":" return to success ", "timestamp" : "the 2020-08-12 14:37:11", "data" : "Hello, World!" } Duplicate codeCopy the code

How can such repetitive, untechnical code be worthy of a LAN duo? It is best to subtract duplicate code, such as:

@RequestMapping("hello2") public String hello2() { return "Hello, World!" ; } Duplicate codeCopy the code

This is done with the ResponseBodyAdvice provided by Spring.

Global processing of response data

Code first:

/** * <br> Created at 2020/8/12 ** @author www.howardliu.cn * @since 1.0.0 */ @restControllerAdvice public class ResultResponseAdvice implements ResponseBodyAdvice<Object> { @Override public boolean supports(final MethodParameter returnType, final Class<? extends HttpMessageConverter<?>> converterType) { return ! returnType.getGenericParameterType().equals(Response.class); // 1 } @Override public Object beforeBodyWrite(final Object body, final MethodParameter returnType, final MediaType selectedContentType, final Class<? extends HttpMessageConverter<? >> selectedConverterType, final ServerHttpRequest request, final ServerHttpResponse response) { if (body == null || body instanceof Response) { return body; } final Response<Object> result = new Response<>(); result.setCode(200); Result.setdesc (" Query succeeded "); result.setData(body); if (returnType.getGenericParameterType().equals(String.class)) {// 2 ObjectMapper objectMapper = new ObjectMapper(); try { return objectMapper.writeValueAsString(result); } catch (JsonProcessingException e) {throw new RuntimeException(" An exception occurred while serializing the Response object into a JSON string ", e); } } return result; }} /** * <br> Created at 2020/8/12 ** @author www.howardliu.cn * @since 1.0.0 */ @restController public class HelloWorldController { @RequestMapping("hello2") public String hello2() { return "Hello, World!" ; } @RequestMapping("user1") public User user1() { User u = new User(); u.setUid(System.currentTimeMillis() + ""); U.s etName (" 1 "); return u; }} Copy the codeCopy the code

This code implements Spring ResponseBodyAdvice as a template. There are only two areas that need special attention, and these are the ones marked 1 and 2 in the code.

The supports method checks whether the beforeBodyWrite method needs to be called. If it returns true, the beforeBodyWrite method is executed. Determine whether to execute beforeBodyWrite according to the return type of the Controller method. You can also return true all the time and determine whether to perform type conversion later.

Then I will focus on line 2, which is a pit and a pit. If you are not familiar with the Spring structure, you will definitely linger here for a long time without any magic tricks.

The line in Code 2 determines whether the Controller’s method returns a String, and if so, serializes the returned object.

This is because the Spring response type separate processing of type String, use StringHttpMessageConverter classes for data conversion. Protected Long getContentLength(T T, @nullable MediaType contentType); And StringHttpMessageConverter method will be rewritten as protected Long getContentLength (String STR, @ Nullable MediaType contentType), The first argument is the Response object, which is typed as String, and if we force a Response object back, the ClassCastException will be reported.

Of course, there are only a few cases where a String is returned directly, and this hole may pop up in a special interface one day.

added

This is just the simplest application of the ResponseBodyAdvice class, but we can implement many more extensions. Such as:

  1. Return the request ID: this is associated with the RequestBodyAdvice, after the request ID is obtained, the response is placed in the body of the response;

  2. Result data encryption: ResponseBodyAdvice is used to encrypt the response data, which will not invade the business code and can handle the encryption level of the interface flexibly through annotations;

  3. Optionally wrap the response body: define the annotation IgnoreResponseWrap, on interfaces that do not wrap the response body, and then judge the annotation of the method on the SUPPORTS method, for example:

    @Override public boolean supports(final MethodParameter returnType, final Class> converterType) { final IgnoreResponseWrap[] declaredAnnotationsByType = returnType.getExecutable().getDeclaredAnnotationsByType(IgnoreResponseWrap.class); return ! (declaredAnnotationsByType.length > 0 || returnType.getGenericParameterType().equals(Response.class)); } Duplicate code

There are many other ways of playing that are not listed.

conclusion

If you want to be complete, you also need to consider the exception of the interface. You can’t have a big try/catch/finally to cover the business logic. A follow-up article will focus on how the interface can return a uniform result response even in the event of an exception.

Recommended reading

  • Free access to 100 computer classics

Hello, I’m Tongo. Swim in the code, play to enjoy life. If this article is helpful to you, please like, bookmark, follow. Welcome to follow the public account “Code farmers attack”, discover a different world.