Welcome to my GitHub
Here we classify and summarize all the original works of Xinchen (including supporting source code): https://github.com/zq2599/blog_demos
Overview of this article
-
In the Spring Cloud Gateway application, if an exception is not caught when processing a request, the response received by the requester is the default content of the system and cannot meet the actual business needs
-
Therefore, the previous article Spring Cloud Gateway filter accurately controls exception return (analysis) At the beginning, we deeply analyzed the relevant source code of Spring Cloud Gateway to understand the details of global exception handling. Then, through the previous article Spring Cloud Gateway filter precisely controls exception return (actual combat, control http return code and message field) In practice, we have been able to set the http return code and the message field in the body at will, that is, to control the contents in the two red boxes in the figure below:
- As shown in the figure above, when an exception occurs, the system returns 8 fields, which is not flexible enough. In some scenarios with strict requirements on format and content, we need to be able to fully control the return code and the content of the return body. As shown below, only three fields are returned, and each field is fully used for business:
{ # This is a return code with specific business meaning "code": "010020003", # This is a text message that accurately describes the cause of the error "message": "Make sure that the user-id Field is valid", # This is normal business data. When an exception occurs, this field is empty "data": null }
- Today, our goal is to customize the return information when an exception occurs through coding. The specific content is the above JSON data: there are only three fields: code, message and data
Source download
- The complete source code in this actual combat can be downloaded from GitHub. The address and link information are shown in the table below( https://github.com/zq2599/blog_demos):
name | link | remarks |
---|---|---|
Project Home | https://github.com/zq2599/blog_demos | The project is on the GitHub home page |
git warehouse address (https) | https://github.com/zq2599/blog_demos.git | The warehouse address of the source code of the project, https protocol |
git warehouse address (ssh) | git@github.com:zq2599/blog_demos.git | The project source code warehouse address, ssh protocol |
- There are multiple folders in this git project. The source code of this article is in the spring cloud tutorials folder, as shown in the red box below:
- There are several sub projects under the spring cloud tutorials folder. The code of this chapter is gateway change body, as shown in the red box below:
Why not use conventional means
- When it comes to global exception handling, experienced people should think of the global exception handling classes modified by the commonly used ControllerAdvice and ExceptionHandler annotations. However, the Spring Cloud Gateway is based on WebFlux. The HttpServletRequest we used to handle exceptions is not applicable in the Spring Cloud Gateway. Therefore, the means of ControllerAdvice and ExceptionHandler cannot be used To handle global exceptions
Basic ideas
-
Do enough theoretical analysis before you start, and the code you write can work normally
-
Open DefaultErrorWebExceptionHandler.java and find the renderErrorResponse method to see how the Spring Cloud Gateway originally constructs the exception return content:
- At the moment, smart people should think of what to do: create a new class to inherit DefaultErrorWebExceptionHandler and override its renderErrorResponse method. In the new renderErrorResponse method, set the return content according to the actual business needs. Yes, that's our idea, but we need to refine it. The final specific steps are as follows:
-
Add an exception class, customeinforexception.java, which has three fields: http return code, business return code and business description information
-
At the code location where the exception is returned, use the customizeindexception class to throw an exception, and set the fields of the customizeindexception instance according to the actual business scenario
-
MyErrorWebExceptionHandler.java is added, which inherits from DefaultErrorWebExceptionHandler and overrides the renderErrorResponse method, which checks whether the exception instance is of the type of customeindexception. If so, take out the http return code, business return code, business description information and other fields from it, and construct the content of the returned body. If the exception instance is not of the type of customeindexception Ion type, the previous processing logic remains unchanged;
-
A new configuration class is added to register the MyErrorWebExceptionHandler instance with the spring environment
- After analysis, start coding. For simplicity, this article will not add maven sub project, but based on Above Create the sub project gateway change body and continue to write code in it;
code
- Add an exception class, customeinforexception.java:
package com.bolingcavalry.changebody.exception; import lombok.Data; import org.springframework.http.HttpStatus; @Data public class CustomizeInfoException extends Exception { /** * http Return code */ private HttpStatus httpStatus; /** * body Code field in (business return code) */ private String code; /** * body Message field in (business return message) */ private String message; }
- Modify the apply method of RequestBodyRewrite.java, which is processing the request body. If it is found that there is no user ID field, the request will not be forwarded to the service provider provider provider Hello, but an error will be returned. The error here is handled with the customeindexception class:
@Override public Publisher<String> apply(ServerWebExchange exchange, String body) { try { Map<String, Object> map = objectMapper.readValue(body, Map.class); // If the request parameter does not contain user ID, an exception is returned if (!map.containsKey("user-id")) { CustomizeInfoException customizeInfoException = new CustomizeInfoException(); // 406 is returned here. You can adjust it according to your business needs customizeInfoException.setHttpStatus(HttpStatus.NOT_ACCEPTABLE); // Here, you can set your own code according to your business needs customizeInfoException.setCode("010020003"); // Here, you can set the returned message according to your business needs customizeInfoException.setMessage("Make sure that the user-id Field is valid"); return Mono.error(customizeInfoException); } // Get id int userId = (Integer)map.get("user-id"); // Write the map after obtaining the nanme map.put("user-name", mockUserName(userId)); return Mono.just(objectMapper.writeValueAsString(map)); } catch (Exception ex) { log.error("1. json process fail", ex); return Mono.error(new Exception("1. json process fail", ex)); } }
- For the exception handling class MyErrorWebExceptionHandler.java, one thing to pay attention to here is: the following code is only a reference. You don't need to stick to the logic related to customizeindexception, and you can freely set the returned status code and body according to business requirements:
package com.bolingcavalry.changebody.handler; import com.bolingcavalry.changebody.exception.CustomizeInfoException; import org.springframework.boot.autoconfigure.web.ErrorProperties; import org.springframework.boot.autoconfigure.web.WebProperties; import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler; import org.springframework.boot.web.reactive.error.ErrorAttributes; import org.springframework.context.ApplicationContext; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; import java.util.HashMap; import java.util.Map; public class MyErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler { public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, WebProperties.Resources resources, ErrorProperties errorProperties, ApplicationContext applicationContext) { super(errorAttributes, resources, errorProperties, applicationContext); } @Override protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) { // Return code int status; // Finally, the responseBodyMap is used to generate the response body Map<String, Object> responseBodyMap = new HashMap<>(); // Like the parent class, get all exception information sorted out by DefaultErrorAttributes Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); // The original exception information can be obtained by getError method Throwable throwable = getError(request); // If the exception class is customized by us, we can customize it if (throwable instanceof CustomizeInfoException) { CustomizeInfoException myGatewayException = (CustomizeInfoException) throwable; // The http return code, the code field of the body, and the message field of the body are all obtained from the customeindexception instance status = myGatewayException.getHttpStatus().value(); responseBodyMap.put("code", myGatewayException.getCode()); responseBodyMap.put("message", myGatewayException.getMessage()); responseBodyMap.put("data", null); } else { // If it is not our custom exception, maintain the same logic as the parent class // Return code status = getHttpStatus(error); // body content responseBodyMap.putAll(error); } return ServerResponse // http return code .status(status) // The type is the same as before .contentType(MediaType.APPLICATION_JSON) // Content of response body .body(BodyInserters.fromValue(responseBodyMap)); } }
- Finally, the configuration class MyErrorWebFluxAutoConfiguration.java:
package com.bolingcavalry.changebody.config; import com.bolingcavalry.changebody.handler.MyErrorWebExceptionHandler; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.WebProperties; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.reactive.error.ErrorAttributes; import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.web.reactive.result.view.ViewResolver; import java.util.stream.Collectors; @Configuration(proxyBeanMethods = false) @AutoConfigureBefore(WebFluxAutoConfiguration.class) public class MyErrorWebFluxAutoConfiguration { private final ServerProperties serverProperties; public MyErrorWebFluxAutoConfiguration(ServerProperties serverProperties) { this.serverProperties = serverProperties; } @Bean @Order(-1) public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes, org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties, WebProperties webProperties, ObjectProvider<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext) { MyErrorWebExceptionHandler exceptionHandler = new MyErrorWebExceptionHandler(errorAttributes, resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources(), this.serverProperties.getError(), applicationContext); exceptionHandler.setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList())); exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters()); exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders()); return exceptionHandler; } }
- After coding, it's time to run the program to verify the effect;
verification
-
Start the application gateway change body
-
Send a POST request with postman. The address is http://localhost:8081/hello/change As shown in the following figure, the http return code in red box 2 is set in our code, and the returned content in red box 3 is the three fields we customized:
- So far, the actual combat of controlling the abnormal return of Spring Cloud Gateway application has been completed. From the source code analysis and combined with the actual combat drill, I hope Xinchen's article can accompany you to deeply understand Spring Cloud Gateway and create a more powerful gateway application;
You're not alone. Xinchen's original accompanies you all the way
- Java series
- Spring collection
- Docker series
- kubernetes series
- Database + middleware series
- DevOps series
Welcome to the official account: programmer Xin Chen
Wechat search "programmer Xinchen", I'm Xinchen, looking forward to traveling with you in the Java World
https://github.com/zq2599/blog_demos