Welcome to my GitHub
Here is a classification and summary of all original (including supporting source code): 161a8139bd3c79 https://github.com/zq2599/blog_demos
Overview of this article
- As shown in the figure above, the system always returns 8 fields when an exception occurs, which is not flexible enough. In some scenarios with strict requirements on the format and content, we need to be able to fully control the return code and the content of the returned body, as follows As shown, only three fields are returned, and each field is completely for business services:
{
# 这是有具体业务含义的返回码
"code": "010020003",
# 这是能精确描述错误原因的文本信息
"message": "请确保请求参数中的user-id字段是有效的",
# 这是常规的业务数据,发生异常时该字段为空
"data": null
}
- Today our goal is to customize the return information when an exception occurs through encoding. The specific content is the above JSON data: only three fields of code, message, and data.
Source download
- There are multiple folders in this git project. The source code of this article is under the <font color="blue">spring-cloud-tutorials</font> folder, as shown in the red box in the following figure:
- There are multiple sub-projects under the <font color="blue">spring-cloud-tutorials</font> folder. The code for this article is <font color="red">gateway-change-body</font>, as shown below The red box shows:
Why not use conventional means
- When it comes to global exception handling, experienced you should think of the commonly used ControllerAdvice and ExceptionHandler annotations to modify the global exception handling classes, but Spring Cloud Gateway is based on WebFlux. The HttpServletRequest we used to handle exceptions before is not included in Spring Cloud Gateway. Not applicable, therefore, you cannot use ControllerAdvice and ExceptionHandler to handle global exceptions
The basic idea
- Do a sufficient theoretical analysis before you start, and the code you write can work normally
- Open DefaultErrorWebExceptionHandler.java and find the renderErrorResponse method to see how Spring Cloud Gateway originally constructed the exception return content:
- At this moment, you should be smart about how to do it: make a new class inherit DefaultErrorWebExceptionHandler and override its renderErrorResponse method. In the new renderErrorResponse method, set the return content according to actual business needs. That’s right, this is our idea, but we still need In detail, the final specific steps are as follows:
- Add a new exception class <font color="blue">CustomizeInfoException.java</font>, this class has three fields: http return code, business return code, business description information
- In the code position where the exception is returned, use the CustomizeInfoException class to throw an exception, and set the various fields of the CustomizeInfoException instance according to the actual business scenario
- Added MyErrorWebExceptionHandler.java, inherited from DefaultErrorWebExceptionHandler, and rewritten the renderErrorResponse method. This checks whether the exception instance is of the CustomizeInfoException type. If it is, extract the http return code, business return code, business description information and other fields from it, and construct the return body If the exception instance is not of type CustomizeInfoException, keep the previous processing logic unchanged;
- A new configuration class is added to register the MyErrorWebExceptionHandler instance to the spring environment
coding
- New exception class <font color="blue">CustomizeInfoException.java</font>:
package com.bolingcavalry.changebody.exception;
import lombok.Data;
import org.springframework.http.HttpStatus;
@Data
public class CustomizeInfoException extends Exception {
/**
* http返回码
*/
private HttpStatus httpStatus;
/**
* body中的code字段(业务返回码)
*/
private String code;
/**
* body中的message字段(业务返回信息)
*/
private String message;
}
- Modify the apply method of RequestBodyRewrite.java, which is processing the request body. If it is checked that there is no <font color="blue">user-id</font> field, the request will not be forwarded to the service provider <font color= "blue">provider-hello</font>, but returns an error, the error here is handled by the CustomizeInfoException class:
@Override
public Publisher<String> apply(ServerWebExchange exchange, String body) {
try {
Map<String, Object> map = objectMapper.readValue(body, Map.class);
// 如果请求参数中不含user-id,就返回异常
if (!map.containsKey("user-id")) {
CustomizeInfoException customizeInfoException = new CustomizeInfoException();
// 这里返回406,您可以按照业务需要自行调整
customizeInfoException.setHttpStatus(HttpStatus.NOT_ACCEPTABLE);
// 这里按照业务需要自行设置code
customizeInfoException.setCode("010020003");
// 这里按照业务需要自行设置返回的message
customizeInfoException.setMessage("请确保请求参数中的user-id字段是有效的");
return Mono.error(customizeInfoException);
}
// 取得id
int userId = (Integer)map.get("user-id");
// 得到nanme后写入map
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));
}
}
- Exception handling class MyErrorWebExceptionHandler.java, here is a place that needs <font color="red"> focus on: </font> The code below is only for reference, you don’t need to stick to the logic related to CustomizeInfoException, you can follow Business requirements freely set the returned status code and body:
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) {
// 返回码
int status;
// 最终是用responseBodyMap来生成响应body的
Map<String, Object> responseBodyMap = new HashMap<>();
// 这里和父类的做法一样,取得DefaultErrorAttributes整理出来的所有异常信息
Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
// 原始的异常信息可以用getError方法取得
Throwable throwable = getError(request);
// 如果异常类是咱们定制的,就定制
if (throwable instanceof CustomizeInfoException) {
CustomizeInfoException myGatewayException = (CustomizeInfoException) throwable;
// http返回码、body的code字段、body的message字段,这三个信息都从CustomizeInfoException实例中获取
status = myGatewayException.getHttpStatus().value();
responseBodyMap.put("code", myGatewayException.getCode());
responseBodyMap.put("message", myGatewayException.getMessage());
responseBodyMap.put("data", null);
} else {
// 如果不是咱们定制的异常,就维持和父类一样的逻辑
// 返回码
status = getHttpStatus(error);
// body内容
responseBodyMap.putAll(error);
}
return ServerResponse
// http返回码
.status(status)
// 类型和以前一样
.contentType(MediaType.APPLICATION_JSON)
// 响应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 the coding is completed, it is time to run the program to verify the effect;
verify
- Start the application gateway-change-body
- Use postman to initiate a POST request, the address is <font color="blue"> http://localhost:8081/hello/change </font>, as shown in the figure below, the http return code in red box 2 is set in our code Yes, the content returned by the red box 3 shows that we have customized the three fields:
- At this point, the actual combat to control the abnormal return of the Spring Cloud Gateway application has been completed. From the source code analysis combined with the actual combat exercise, I hope that Xinchen's article can accompany you to understand Spring Cloud Gateway in depth and create a more powerful gateway application;
You are not lonely, Xinchen is with you all the way
- Java series
- Spring series
- Docker series
- kubernetes series
- database + middleware series
- DevOps series
Welcome to pay attention to the public account: programmer Xin Chen
Search "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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。