background
In the development of microservices architecture project, the API interface uniformly uses the response structure.
The HTTP status code is returned as 200. The status code is obtained according to the structure code.
{
"code": 0,
"message": "success",
"data": {
"name": "kent"
}
}
Copy the code
The service invokes the process when the user requests it.
The problem
In microservices architecture, under normal circumstances, the returned data structure is returned as the response structure, but when an exception occurs in the service invocation, no code is returned.
{
"timestamp": "2020-08-11 13:25:03",
"status": 500,
"error": "Internal Server Error",
"exception": "tech.xproject.common.core.exception.BusinessException",
"message": "not enough stock",
"trace": "tech.xproject.common.core.exception.BusinessException: not enough stock"
}
Copy the code
To solve
Customize FeignErrorDecoder, TerrorAttributes and BusinessException to handle exceptions.
1. Customize FeignErrorDecoder
Code location: service-API
package tech.xproject.order.config; import com.alibaba.fastjson.JSON; import feign.FeignException; import feign.Response; import feign.RetryableException; import feign.codec.ErrorDecoder; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import tech.xproject.common.core.entity.ExceptionInfo; import tech.xproject.common.core.enums.ResultCodeEnum; import tech.xproject.common.core.exception.BusinessException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Optional; /** * @author kent */ @Slf4j @Configuration public class FeignErrorDecoder extends ErrorDecoder.Default { @Override public Exception decode(String methodKey, Response response) { Exception exception = super.decode(methodKey, response); If (exception instanceof RetryableException) {return exception; } try {// if FeignException is used, If (Exception Instanceof FeignException && ((FeignException) exception).responseBody().isPresent()) { ByteBuffer responseBody = ((FeignException) exception).responseBody().get(); String bodyText = StandardCharsets.UTF_8.newDecoder().decode(responseBody.asReadOnlyBuffer()).toString(); ExceptionInfo ExceptionInfo = json.parseObject (bodyText, ExceptionInfo.class); // If code in Excepiton is not empty, use this code, Otherwise the ERROR code using the default Integer code = Optional. OfNullable (exceptionInfo. GetCode ()). OrElse (ResultCodeEnum. ERROR. GetCode ()); // If message in Excepiton is not empty, the message is used, Otherwise, the default error message String message = is used Optional.ofNullable(exceptionInfo.getMessage()).orElse(ResultCodeEnum.ERROR.getMessage()); return new BusinessException(code, message); } } catch (Exception ex) { log.error(ex.getMessage(), ex); } return exception; }}Copy the code
2. Use FeignErrorDecoder in FeignClient
Code location: service-API
@FeignClient(name = ServiceNameConstant.ORDER_SERVICE, configuration = {FeignErrorDecoder.class})
Copy the code
Complete code example
package tech.xproject.order.feign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import tech.xproject.common.core.constant.ServiceNameConstant; import tech.xproject.order.config.FeignErrorDecoder; import tech.xproject.order.pojo.dto.CreateOrderReqDTO; import tech.xproject.order.pojo.entity.Order; /** * @author kent */ @FeignClient(name = ServiceNameConstant.ORDER_SERVICE, Configuration = {feignErrorDecoder.class}) public interface RemoteOrderService {/** * @param createOrderReqDTO createOrderReqDTO * @return Order */ @PostMapping("/order/create") Order create(@RequestBody CreateOrderReqDTO createOrderReqDTO); }Copy the code
Customization terrorAttributes
Code location: service
Terrorattributes that are not customised do not return with a code and need to add customised parameters to the returned object
package tech.xproject.product.handler; import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import org.springframework.web.context.request.WebRequest; import tech.xproject.common.core.exception.BusinessException; import java.util.Map; /** * @author kent */ @Component @Primary public class CustomErrorAttributes extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace); Throwable error = this.getError(webRequest); if (error instanceof BusinessException) { errorAttributes.put("code", ((BusinessException) error).getCode()); } return errorAttributes; }}Copy the code
4. Unified handling of global exceptions
Code location: Web
package tech.xproject.web.manager.handler; import feign.FeignException; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestControllerAdvice; import tech.xproject.common.core.entity.R; import tech.xproject.common.core.enums.ResultCodeEnum; import tech.xproject.common.core.exception.BusinessException; import java.util.List; /** * @author kent */ @Slf4j @RestControllerAdvice public class WebGlobalExceptionHandler { @ExceptionHandler({FeignException.class}) @ResponseBody public R<? > feignExceptionHandler(FeignException exception) { log.error(exception.getMessage(), exception); return R.error(exception.getMessage()); } @ExceptionHandler({RuntimeException.class}) @ResponseBody public R<? > runtimeExceptionHandler(RuntimeException exception) { log.error(exception.getMessage(), exception); return R.error(exception.getMessage()); } @ExceptionHandler({Exception.class}) @ResponseBody public R<? > exceptionHandler(Exception exception) { log.error(exception.getMessage(), exception); return R.error(exception.getMessage()); } @ExceptionHandler({BusinessException.class}) public R<? > businessExceptionHandler(BusinessException exception) { log.error(exception.getMessage(), exception); return R.error(exception.getCode(), exception.getMessage()); } @ExceptionHandler({BindException.class}) @ResponseBody public R<? > bindExceptionHandler(BindException exception) { List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors(); String errorMessage = fieldErrors.get(0).getDefaultMessage(); log.error(errorMessage, exception); return R.error(ResultCodeEnum.ERROR_PARAMETER.getCode(), errorMessage); } @ExceptionHandler({MethodArgumentNotValidException.class}) public R<? > validateExceptionHandler(MethodArgumentNotValidException exception) { List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors(); String errorMessage = fieldErrors.get(0).getDefaultMessage(); log.error(errorMessage, exception); return R.error(ResultCodeEnum.ERROR_PARAMETER.getCode(), errorMessage); }}Copy the code
5, custom ErrorHandlerConfiguration [gateway]
Code location: gateway
@Bean @Order(Ordered.HIGHEST_PRECEDENCE) public ErrorWebExceptionHandler errorWebExceptionHandler(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()); return exceptionHandler; }Copy the code
Complete code example
package tech.xproject.gateway.config; 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; import tech.xproject.gateway.handler.JsonExceptionHandler; import java.util.Collections; import java.util.List; /** * @author kent */ @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()); return exceptionHandler; }}Copy the code
6. [gateway] Customize JsonExceptionHandler
Code location: gateway
Returns code, message, data using a custom structure
/** * get error attributes */ @Override protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) { Throwable error = super.getError(request); String exMessage = error ! = null ? error.getMessage() : ResultCodeEnum.ERROR.getMessage(); Format (" Request error [%s %s], exception: %s", request.methodName(), request.uri(), exMessage); Map<String, Object> map = new HashMap<>(3); map.put("code", ResultCodeEnum.ERROR.getCode()); map.put("message", message); map.put("data", null); return map; }Copy the code
Override the getHttpStatus method to return the HTTP status code 200
/**
* response http code 200
* the error code need use the code in response content
*
* @param errorAttributes
*/
@Override
protected int getHttpStatus(Map<String, Object> errorAttributes) {
return HttpStatus.OK.value();
}
Copy the code
Complete code example
package tech.xproject.gateway.handler; 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.*; import tech.xproject.common.core.enums.ResultCodeEnum; import java.util.HashMap; import java.util.Map; /** * @author kent */ public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler { public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) { super(errorAttributes, resourceProperties, errorProperties, applicationContext); } /** * get error attributes */ @Override protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) { Throwable error = super.getError(request); String exMessage = error ! = null ? error.getMessage() : ResultCodeEnum.ERROR.getMessage(); Format (" Request error [%s %s], exception: %s", request.methodName(), request.uri(), exMessage); Map<String, Object> map = new HashMap<>(3); map.put("code", ResultCodeEnum.ERROR.getCode()); map.put("message", message); map.put("data", null); return map; } /** * render with json * * @param errorAttributes */ @Override protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse); } /** * response http code 200 * the error code need use the code in response content * * @param errorAttributes */ @Override protected int getHttpStatus(Map<String, Object> errorAttributes) { return HttpStatus.OK.value(); }}Copy the code
summary
Looking up all kinds of articles on the Internet, I never found a solution, only various scattered solutions, and finally through my own code, breakpoint debugging, and combined with the relevant articles finally solved.
Use articles and code solutions to record them for reference to those who need them.
Full code Demo
Github.com/new9xgh/fei…
reference
Resolve FeignException to return a status code thrown by the underlying service
Feign Returns a unified error object for custom ErrorDecoder errors
Global exception handling for the Spring Cloud Gateway