The global exception handling in Spring Cloud Gateway cannot be handled by @ControllerAdvice directly. By tracking the exception information thrown, find the corresponding source code and customize some processing logic to meet the needs of the business.
The gateway does proxy forwarding to the interface, and the back end corresponds to REST API, and the returned data format is JSON. If you do not handle exceptions, the Gateway provides error information as a page by default, which is inconvenient for the front-end to handle exceptions.
To handle the exception information, return data in JSON format to the client. Let’s look at the implementation code first, and then tell you what you need to pay attention to.
Custom exception handling logic:
package com.cxytiandi.gateway.exception; import java.util.HashMap; import java.util.Map; import org.springframework.boot.autoconfigure.web.ErrorProperties; import org.springframework.boot.autoconfigure.web.ResourceProperties; 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.HttpStatus; import org.springframework.web.reactive.function.server.RequestPredicates; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunctions; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; <p> ** @author yinjihuan ** / public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler { public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) { super(errorAttributes, resourceProperties, errorProperties, applicationContext); } @override protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) { int code = 500; Throwable error = super.getError(request);if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
code = 404;
}
returnresponse(code, this.buildMessage(request, error)); } @override protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {returnRouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse); } /** * Override protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) { int statusCode = (int) errorAttributes.get("code");
returnHttpStatus.valueOf(statusCode); } /** * build exception message * @param request * @param ex * @return
*/
private String buildMessage(ServerRequest request, Throwable ex) {
StringBuilder message = new StringBuilder("Failed to handle request [");
message.append(request.methodName());
message.append("");
message.append(request.uri());
message.append("]");
if(ex ! = null) { message.append(":");
message.append(ex.getMessage());
}
returnmessage.toString(); } /** * Build the returned JSON data format * @param status status code * @param errorMessage exception information * @return
*/
public static Map<String, Object> response(int status, String errorMessage) {
Map<String, Object> map = new HashMap<>();
map.put("code", status);
map.put("message", errorMessage);
map.put("data", null);
returnmap; }}Copy the code
Override the default configuration:
package com.cxytiandi.gateway.exception; import java.util.Collections; import java.util.List; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.web.ResourceProperties; import org.springframework.boot.autoconfigure.web.ServerProperties; 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.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.result.view.ViewResolver; Override the default exception handling / * * * * * @ author yinjihuan * * / @ Configuration @ EnableConfigurationProperties ({ServerProperties. Class, ResourceProperties.class}) public class ErrorHandlerConfiguration { private final ServerProperties serverProperties; private final ApplicationContext applicationContext; private final ResourceProperties resourceProperties; private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public ErrorHandlerConfiguration(ServerProperties serverProperties, ResourceProperties resourceProperties, ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext) { this.serverProperties = serverProperties; this.applicationContext = applicationContext; this.resourceProperties = resourceProperties; this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) { JsonExceptionHandler exceptionHandler = new JsonExceptionHandler( errorAttributes, this.resourceProperties, this.serverProperties.getError(), this.applicationContext); exceptionHandler.setViewResolvers(this.viewResolvers); exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters()); exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());returnexceptionHandler; }}Copy the code
Pay attention to the point
- How to return JSON instead of HTML when an exception occurs?
In org. Springframework. Boot. Autoconfigure. Web. Reactive. Error. The getRoutingFunction DefaultErrorWebExceptionHandler () method is to control returns Format, the original code is as follows:
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(
ErrorAttributes errorAttributes) {
return RouterFunctions.route(acceptsTextHtml(), this::renderErrorView)
.andRoute(RequestPredicates.all(), this::renderErrorResponse);
}
Copy the code
This side is preferred to use HTML to display, want to use JSON can be changed, as follows:
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
Copy the code
- GetHttpStatus needs to be rewritten
The original method is to obtain the corresponding HttpStatus via status, the code is as follows:
protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
int statusCode = (int) errorAttributes.get("status");
return HttpStatus.valueOf(statusCode);
}
Copy the code
If there is no status field in the format we define, then an error will be reported and the corresponding response code cannot be found. Either add status subparagraph to the data format or rewrite it. I return code, so rewrite it, and the code is as follows:
@Override
protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
int statusCode = (int) errorAttributes.get("code");
return HttpStatus.valueOf(statusCode);
}
Copy the code