This is an original article. Welcome any form of reprint, but please be sure to note the source cold https://lltx.github.io.

Why global exception handling

In traditional Spring Boot applications, we @ControllerAdvice handle global exceptions and uniformly wrap returns


// Pick the spring Cloud Alibaba console module for processing
@ControllerAdvice
public class ConsoleExceptionHandler {

    @ExceptionHandler(AccessException.class)
    private ResponseEntity<String> handleAccessException(AccessException e) {
        returnResponseEntity.status(HttpStatus.FORBIDDEN).body(e.getErrMsg()); }}Copy the code

For example: (3) An exception occurs when the application calls the database, and the @controllerAdvice package is sent to the client

However, in the microservice architecture, for example, if the (2) gateway fails to invoke the service microservice (forwarding failure, invocation exception, forwarding failure), the @ControllerAdvice set in the application will be invalid because the traffic is not forwarded to the application for processing.

As shown in the figure above: Simulate an error output page where all route assertions do not match 404, consistent with the Spring Boot default. Obviously we can’t solve the problem by configuring @ControllerAdvice on the gateway as well, because Spring Cloud Gateway is based on WebFlux reactive programming.

The solution

Default processing flow

  • ExceptionHandlingWebHandler as a spring cloud gateway will be carried out in most part of the core WebHandler filtering of exception handling
public class ExceptionHandlingWebHandler extends WebHandlerDecorator {
	@Override
	public Mono<Void> handle(ServerWebExchange exchange) {
		Mono<Void> completion;
		try {
			completion = super.handle(exchange);
		}
		catch (Throwable ex) {
			completion = Mono.error(ex);
		}

     // Get the global WebExceptionHandler execution
		for (WebExceptionHandler handler : this.exceptionHandlers) {
			completion = completion.onErrorResume(ex -> handler.handle(exchange, ex));
		}
		returncompletion; }}Copy the code
  • The default implementation DefaultErrorWebExceptionHandler

public class DefaultErrorWebExceptionHandler  {

	@Override
	protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
     // Depending on the client's 'accPET' request header to decide what resources to return, as shown in the page returned by the browser
		return route(acceptsTextHtml(), this::renderErrorView).andRoute(all(), this::renderErrorResponse); }}Copy the code
/ / simulation specified ` accpet ` situation curl, the location, request GET 'http://localhost:9999/adminx/xx' \ 18:09:23 - headers' Accept: application/json' {"timestamp":"2020-05-24 18:09:24","path":"/adminx/xx","status":404,"error":"Not Found ", "message", null, "requestId" : "083 c48e3-2"} ⏎Copy the code

Rewrite ErrorWebExceptionHandler

/ * * *@author lengleng
 * @date2020/5/23 * <p> * Gateway exception General-purpose processor, which only works in WebFlux environment and has a lower priority than {@linkResponseStatusExceptionHandler} * /
@Slf4j
@Order(-1)
@RequiredArgsConstructor
public class GlobalExceptionConfiguration implements ErrorWebExceptionHandler {
	private final ObjectMapper objectMapper;

	@Override
	public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
		ServerHttpResponse response = exchange.getResponse();

		if (response.isCommitted()) {
			return Mono.error(ex);
		}

		// header set
		response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
		if (ex instanceof ResponseStatusException) {
			response.setStatusCode(((ResponseStatusException) ex).getStatus());
		}

		return response
				.writeWith(Mono.fromSupplier(() -> {
					DataBufferFactory bufferFactory = response.bufferFactory();
					try {
						return bufferFactory.wrap(objectMapper.writeValueAsBytes(R.failed(ex.getMessage())));
					} catch (JsonProcessingException e) {
						log.warn("Error writing response", ex);
						return bufferFactory.wrap(new byte[0]); }})); }}Copy the code

conclusion

  • Rewrite DefaultErrorWebExceptionHandler priority must be less than the built-in ResponseStatusExceptionHandler after it processes to obtain the corresponding error response code of a class
  • Other extensions can reference SentinelBlockExceptionHandler sentinel integrated gateway processing, but as a whole and the default exception handling is not what’s the difference
  • Spring Cloud Hoxton.SR4 & Spring Boot 2.3.0
  • Specific implementation code reference:gitee.com/log4j/pig