The premise
@ControllerAdvice and @ExceptionHandler can customize the handling logic of different types of exceptions as long as the request is handled by DispatcherServlet. Specific can consult ResponseEntityExceptionHandler and DefaultHandlerExceptionResolver, the underlying principle is very simple, there is an exception occurs when the search container has the exception handler and matching corresponding exception type, If no default exception handler can be found, the default exception handler will be used for backstop. (Personally, Spring has such an elegant idea of “if there is a custom exception handler, if there is no default exception handler” when designing many functions.)
The custom exception architecture provided in SpringMVC does not work in Spring-WebFlux for the simple reason that the underlying running containers are different. WebExceptionHandler is Spring – WebFlux exception handler layer interface, so back to the subclasses can be traced to DefaultErrorWebExceptionHandler is Spring Cloud Gateway’s global exception handler, The configuration class is ErrorWebFluxAutoConfiguration.
Why custom exception handling
Start by drawing a hypothetical but realistic architecture diagram to identify the role of the gateway:
The role of the gateway in the overall architecture is:
- Route requests from the server application to the back-end application.
- (Aggregation) The response from the back-end application is forwarded to the server application.
Assuming that the gateway service is always normal:
For the first point, assuming that the back-end application cannot come online smoothly and without damage, there will be a certain probability that gateway route requests to some backend “zombie nodes” (when the request route passes, the application is better restarted or just stopped). At this time, the route will fail and an exception will be thrown, which is usually Connection Refuse.
For point 2, assuming the exception is not handled correctly by the back-end application, the exception information should be forwarded back to the server application through the gateway, in which case there should theoretically be no exception.
In fact, there is the third hidden problem. If the gateway not only takes on the functions of routing, but also includes functions such as authentication and traffic limiting, if these functions are not fully handled during the development of exception capture or even the logic itself has bugs, exceptions may not be captured and processed normally. Walked the default exception handler DefaultErrorWebExceptionHandler, default exception handler processing logic may not conform to our expected result.
How do I customize exception handling
We can see the default exception handler configuration first class ErrorWebFluxAutoConfiguration:
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class })
public class ErrorWebFluxAutoConfiguration {
private final ServerProperties serverProperties;
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public ErrorWebFluxAutoConfiguration(ServerProperties serverProperties, ResourceProperties resourceProperties, ObjectProvider
viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext)
{
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.viewResolvers = viewResolversProvider.orderedStream()
.collect(Collectors.toList());
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class,
search = SearchStrategy.CURRENT)
@Order(-1)
public ErrorWebExceptionHandler errorWebExceptionHandler( ErrorAttributes errorAttributes) {
DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(
errorAttributes, this.resourceProperties,
this.serverProperties.getError(), this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class,
search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes(a) {
return new DefaultErrorAttributes(
this.serverProperties.getError().isIncludeException()); }}Copy the code
Note that the two Bean instances ErrorWebExceptionHandler and TerrorAttributes both use the @conditionalonMissingBean annotation, which means we can override them with a custom implementation. To customize a CustomErrorWebFluxAutoConfiguration (besides ErrorWebExceptionHandler custom implementations, other direct copy ErrorWebFluxAutoConfiguration) :
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class CustomErrorWebFluxAutoConfiguration {
private final ServerProperties serverProperties;
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public CustomErrorWebFluxAutoConfiguration(ServerProperties serverProperties, ResourceProperties resourceProperties, ObjectProvider
viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext)
{
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.viewResolvers = viewResolversProvider.orderedStream()
.collect(Collectors.toList());
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class,
search = SearchStrategy.CURRENT)
@Order(-1)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
// TODO implements custom ErrorWebExceptionHandler logic
return null;
}
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes(a) {
return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException()); }}Copy the code
ErrorWebExceptionHandler implementation, can reference DefaultErrorWebExceptionHandler directly, or even directly inherit DefaultErrorWebExceptionHandler, covering the corresponding method. Here, the exception information is directly encapsulated as a Response in the following format, and finally rendered in JSON format:
{
"code": 200."message": "Description"."path" : "Request path"."method": "Request method"
}
Copy the code
We need to analyse some of DefaultErrorWebExceptionHandler source:
// Encapsulate the exception attribute
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
return this.errorAttributes.getErrorAttributes(request, includeStackTrace);
}
// Render exception Response
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace);
return ServerResponse.status(getHttpStatus(error))
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(error));
}
// Return the object whose routing method is based on ServerResponse
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return route(acceptsTextHtml(), this::renderErrorView).andRoute(all(), this::renderErrorResponse);
}
// HTTP response status code encapsulation, originally based on the exception attribute status attribute parsing
protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
int statusCode = (int) errorAttributes.get("status");
return HttpStatus.valueOf(statusCode);
}
Copy the code
Identify three points:
- The object that is finally encapsulated in the response body comes from
DefaultErrorWebExceptionHandler#getErrorAttributes()
And it turns out to be oneMap<String, Object>
The sequence of bytes converted by the instance. - The original
RouterFunction
The implementation only supports HTML returns, so we need to change it to JSON (or all formats). DefaultErrorWebExceptionHandler#getHttpStatus()
The response status code encapsulates the original logic based on exception attributesgetErrorAttributes()
The status property of the.
Custom JsonErrorWebExceptionHandler is as follows:
public class JsonErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {
public JsonErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
@Override
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
// The logic can be customized based on the exception type
Throwable error = super.getError(request);
Map<String, Object> errorAttributes = new HashMap<>(8);
errorAttributes.put("message", error.getMessage());
errorAttributes.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
errorAttributes.put("method", request.methodName());
errorAttributes.put("path", request.path());
return errorAttributes;
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
@Override
protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
// You can customize the HTTP response code according to the errorAttributes
returnHttpStatus.INTERNAL_SERVER_ERROR; }}Copy the code
The configuration class CustomErrorWebFluxAutoConfiguration add JsonErrorWebExceptionHandler:
@Bean
@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class, search = SearchStrategy.CURRENT)
@Order(-1)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
JsonErrorWebExceptionHandler exceptionHandler = new JsonErrorWebExceptionHandler(
errorAttributes,
resourceProperties,
this.serverProperties.getError(),
applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
Copy the code
INTERNAL_SERVER_ERROR(500) is the exception’s HTTP response status code. This is not a big change, as long as you understand the context logic of the original exception handling.
test
Test scenario 1: Directly invoke the downstream service when only the gateway is started and the downstream service is not started:
The curl http://localhost:9090/order/host / {/ response results"path":"/order/host"."code": 500,"message":"Connection refused: no further information: localhost/127.0.0.1:9091"."method":"GET"}
Copy the code
Test scenario 2: The downstream service starts and invokes normally, but the gateway throws an exception.
Apply a custom global filter to the gateway and deliberately throw an exception:
@Component
public class ErrorGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
int i = 1/0;
returnchain.filter(exchange); }}Copy the code
The curl http://localhost:9090/order/host / {/ response results"path":"/order/host"."code": 500,"message":"/ by zero"."method":"GET"}
Copy the code
The response results are consistent with the custom logic, and the background log also prints the corresponding exception stack.
summary
The author always thinks that it is very important to classify and deal with anomalies according to classification in engineering. In the system in charge of my company, the author insists on realizing the classification capture of anomalies, mainly to distinguish the anomalies that can be compensated by retry and those that cannot be retried and need timely warning. In this way, self-healing logic can be customized for recoverable anomalies, and timely warning and human intervention can be made for unrecoverable anomalies. Therefore, the Spring Cloud Gateway technology stack must also investigate its custom exception handling logic.
The original link
- Making Page: www.throwable.club/2019/05/11/…
- Coding Page: throwable. Coding. Me / 2019/05/11 /…
(C-1-D E-A-20190511)
Technical official account (Throwable Digest), push the author’s original technical articles from time to time (never plagiarize or reprint) :