Welcome to my GitHub

Here is a classification and summary of all original (including supporting source code): 161a6b40270c86 https://github.com/zq2599/blog_demos

Overview of this article

  • In the "Spring Cloud Gateway Modifying the Content of Request and Response Body" , we successfully modified the content of the request body through the filter. At that time, we left a problem: if an exception occurs in the filter (for example, the request parameter is invalid), throw In the case of exception information, the return code and body received by the caller are processed by the Spring Cloud Gateway framework, and the caller cannot know the real cause of the error based on these contents, as shown in the following figure:

在这里插入图片描述

  • The task of this article is to analyze the reasons for the above phenomenon and figure out the specific logic of the return code and response body generation by reading the source code

Summary in advance

  • Here is a summary of the analysis results in advance. If you are very busy and do not have much time but want to know the final cause, just pay attention to the following summary:
  1. In the Spring Cloud Gateway application, there is a bean of ErrorAttributes type, and its getErrorAttributes method returns a map
  2. When the application throws an exception, the return code comes from the value of the status of the above map, and the return body is the result of the serialization of the entire map
  3. By default, the implementation class of ErrorAttributes is DefaultErrorAttributes
  • Look at the status value of the above map (that is, the return code of response), how it is generated in DefaultErrorAttributes:
  1. First look at whether the exception object is of type ResponseStatusException
  2. If it is of type ResponseStatusException, call the getStatus method of the exception object as the return value
  3. If it is not of type ResponseStatusException, look at whether the exception class has ResponseStatus annotations,
  4. If so, take the code attribute of the annotation as the return value
  5. If the exception object is neither a ResponseStatusException type nor a ResponseStatus annotation, 500 is returned
  • Finally, look at how the message field of the map (that is, the message field of the response body) is generated in DefaultErrorAttributes:
  1. Is the exception object of type BindingResult?
  2. If it is not of type BindingResult, it depends on whether it is of type ResponseStatusException
  3. If it is, use getReason as the return value
  4. If it is not ResponseStatusException type, it depends on whether the exception class has ResponseStatus annotation, if there is, take the reason attribute of the annotation as the return value
  5. If the reason obtained through the annotation is also invalid, return the abnormal getMessage field
  • The above content is the essence of this article, but it does not include the analysis process. If you are interested in the Spring Cloud source code, please allow Xin Chen to accompany you on a short source code reading journey

Spring Cloud Gateway error handling source code

  • The first thing to look at is the configuration class ErrorWebFluxAutoConfiguration.java, where two instances are registered for spring, <font color="red"> each is very important </font>, let’s focus on the first one, that is to say The implementation class of ErrorWebExceptionHandler is DefaultErrorWebExceptionHandler:

在这里插入图片描述

  • When handling an exception, the handle method of this ErrorWebExceptionHandler will be called through FluxOnErrorResume. This method is in its parent class AbstractErrorWebExceptionHandler.java, as shown in the figure below. The code in the red box is the key, and the content of the exception return is determined here:

在这里插入图片描述

  • Expanding the getRoutingFunction method, you can see that renderErrorResponse is called to process the response:
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return route(acceptsTextHtml(), this::renderErrorView).andRoute(all(), this::renderErrorResponse);
    }
  • Open the renderErrorResponse method, as shown below, the truth is out!
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
  // 取出所有错误信息
  Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
  
  // 构造返回的所有信息 
  return ServerResponse
           // 控制返回码
           .status(getHttpStatus(error))
           // 控制返回ContentType
           .contentType(MediaType.APPLICATION_JSON)
           // 控制返回内容
           .body(BodyInserters.fromValue(error));
}
  • Through the above code, we get two important conclusions:
  1. The status code returned to the caller depends on the return value of the getHttpStatus method
  2. The body returned to the caller depends on the content of the error
  • I have already read this, so naturally I have to look at the internals of getHttpStatus, as shown below, the status comes from the input parameters:
protected int getHttpStatus(Map<String, Object> errorAttributes) {
  return (int) errorAttributes.get("status");
}
  • At this point, we can draw a conclusion: the return value of the getErrorAttributes method is the key to determining the return code and returning the body!
  • Let's take a look at the real face of this getErrorAttributes method, in DefaultErrorAttributes.java (recall that when I looked at ErrorWebFluxAutoConfiguration.java just now, I mentioned that the things inside are very important, including the errorAttributes method):
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = this.getErrorAttributes(request, options.isIncluded(Include.STACK_TRACE));
        if (Boolean.TRUE.equals(this.includeException)) {
            options = options.including(new Include[]{Include.EXCEPTION});
        }

        if (!options.isIncluded(Include.EXCEPTION)) {
            errorAttributes.remove("exception");
        }

        if (!options.isIncluded(Include.STACK_TRACE)) {
            errorAttributes.remove("trace");
        }

        if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
            errorAttributes.put("message", "");
        }

        if (!options.isIncluded(Include.BINDING_ERRORS)) {
            errorAttributes.remove("errors");
        }

        return errorAttributes;
    }
  • Due to space limitations, we won't expand the above code anymore, just go to the result:
  1. The return code comes from the return of determineHttpStatus
  2. The message field comes from the return of determineMessage
  • Open the determineHttpStatus method, the final answer is announced, please pay attention to the Chinese comment:
private HttpStatus determineHttpStatus(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) {
        // 异常对象是不是ResponseStatusException类型
        return error instanceof ResponseStatusException 
        // 如果是ResponseStatusException类型,就调用异常对象的getStatus方法作为返回值
        ? ((ResponseStatusException)error).getStatus() 
        // 如果不是ResponseStatusException类型,再看异常类有没有ResponseStatus注解,
        // 如果有,就取注解的code属性作为返回值
        : (HttpStatus)responseStatusAnnotation.getValue("code", HttpStatus.class)
        // 如果异常对象既不是ResponseStatusException类型,也没有ResponseStatus注解,就返回500
        .orElse(HttpStatus.INTERNAL_SERVER_ERROR);
    }
  • In addition, the content of the message field is also determined:
    private String determineMessage(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) {
        // 异常对象是不是BindingResult类型
        if (error instanceof BindingResult) {
            // 如果是,就用getMessage作为返回值
            return error.getMessage();
        } 
        // 如果不是BindingResult类型,就看是不是ResponseStatusException类型
        else if (error instanceof ResponseStatusException) {
            // 如果是,就用getReason作为返回值
            return ((ResponseStatusException)error).getReason();
        } else {
            // 如果也不是ResponseStatusException类型,
            // 就看异常类有没有ResponseStatus注解,如果有就取该注解的reason属性作为返回值
            String reason = (String)responseStatusAnnotation.getValue("reason", String.class).orElse("");
            if (StringUtils.hasText(reason)) {
                return reason;
            } else {
                // 如果通过注解取得的reason也无效,就返回异常的getMessage字段
                return error.getMessage() != null ? error.getMessage() : "";
            }
        }
    }
  • At this point, the source code analysis has been completed. How to control the final return code and return content, I believe you should know how to control it. In the next "Practice", let’s strike while the iron is hot, write the code and try to accurately control the return code and return content.
  • Spoilers ahead of time, the following "Practice" will be presented:
  1. Directly, control the return code and the error field in the body
  2. Little stumbling block, see the trick
  3. Simple and easy to use, return information through annotation control
  4. The ultimate solution, fully customized return content
  • Please look forward to the above content, Xinchen original will not disappoint you

You are not alone, Xinchen and original are with you all the way

  1. Java series
  2. Spring series
  3. Docker series
  4. kubernetes series
  5. database + middleware series
  6. DevOps series

Welcome to pay attention to the public account: programmer Xin Chen

Search for "Programmer Xin Chen" on WeChat, I am Xin Chen, and I look forward to traveling the Java world with you...
https://github.com/zq2599/blog_demos

程序员欣宸
147 声望24 粉丝

热爱Java和Docker