I. scene
Recently, the microservice architecture encapsulated by the company has been used to build microservice projects. There are not many packages, and most of them can be used according to the use mode of native spring cloud components. The components involved include Nacos, Ribbon, OpenFeign, Hystrix and Sentinel. The leader asked to build a micro service project without using Hystrix and Sentinel for service fusing and degradation, that is, the client calls the server service through OpenFeign. If the server exception is directly returned to the client, the client can capture and print the exception information.
Two, simulation server side exception
First, let the server generate an arithmetic exception and throw it to see what happens to the client under normal circumstances.
Client code
TestErrorController
@RestController @RequestMapping("/test") public class TestErrorController { @Autowired private TestErrorFeignService testErrorFeignService; /** * Test exception */ @RequestMapping("/testError") public ResultBody testError() { String result; try { result = testErrorFeignService.testError(); } catch (Exception e) { logger.error("TestService testError e :{}.", e); return ResultBody.fail500(e.getMessage()); } return ResultBody.success(result); } }
TestErrorFeignService
@FeignClient(name = "service-provider",path = "/test") public interface TestErrorFeignService { /** * Test exception */ @RequestMapping(value = "/testError") public String testError(); }
Server code
TestErrorController
@RestController @RequestMapping("/test") public class TestErrorController { private static HLogger logger = HLoggerFactory.getLogger(TestErrorController.class); /** * Test exception */ @RequestMapping("/testError") public String testError() { int i; try { i = 16 / 0; } catch (Exception e) { logger.error("TestErrorService testError Exception, the exception information is:{}.", e); throw e; } return "provider" + i; } }
View log after running
Server log
Client log
As can be seen from the above log, the server log shows an arithmetic exception and tells us that it is because of / by zero. However, looking at the log printed by the client, it is found that the exception information captured and printed is Feign FeignException$InternalServerError: status 500 reading TestErrorFeignService#testError(). The client calls the server exception through OpenFeign. The feignexception with status code 500 generated by the client tells us the exception generated when calling the testerror() method of the Feign interface of testerrorfeigneservice. Obviously, this does not meet my needs, and the specific exception causes of the server are not printed out.
Solution
Server
First, the server needs global exception capture, and the following classes are added to the server. This class mainly captures two kinds of exceptions: one is user-defined business exception, and the other is other exceptions. Of course, you can also subdivide which exceptions to catch. After the exception is caught, the exception information (e.getMessage()) is added to the response.
Global exception capture class (GlobalExceptionHandler)
@RestController @ControllerAdvice public class GlobalExceptionHandler { private static HLogger log = HLoggerFactory.getLogger(GlobalExceptionHandler.class); /** * @description: Capture custom business exceptions */ @ResponseBody @ExceptionHandler(BusinessException.class) public ResultBody processServerBusinessException(HttpServletResponse response, BusinessException e) { // Custom error code response.setStatus(e.getCode()); response.setContentType("application/json;charset=UTF-8"); ResultBody result = new ResultBody(); result.setCode(e.getCode()); result.setDetailMessage(e.getMessage()); log.info("e :{}, result:{}.", e, result); return result; } /** * Catch other exceptions */ @ResponseBody @ExceptionHandler({Exception.class}) public ResultBody processDefaultException(HttpServletResponse response, Exception e) { // The status codes of other exceptions are uniformly set to 500 response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); response.setContentType("application/json;charset=UTF-8"); ResultBody result = new ResultBody(); result.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value()); result.setDetailMessage(e.getMessage()); return result; } }
client
When the client calls the server method through OpenFeign, if the server is abnormal, that is, when a response in a non-200 state is returned, in fact, the client will decode the error information through the decode method of ErrorDecoder.
By Default, the decode of the inner class Default is used.
If we want to achieve the effect that the client can print the exception information of the server, we need to implement our own error decoder.
Client added custom error decoder
Custom error decoder (FeignClientErrorDecoder)
@Configuration public class FeignClientErrorDecoder implements ErrorDecoder { private static HLogger log = HLoggerFactory.getLogger(FeignClientErrorDecoder.class); private static ThreadLocal<Gson> gson = ThreadLocal.withInitial(() -> new Gson()); @Override public Exception decode(String methodKey, Response response) { if (response.status() != HttpStatus.OK.value()) { try { String errorContent; errorContent = Util.toString(response.body().asReader()); if (response.status() != HttpStatus.INTERNAL_SERVER_ERROR.value()) { return gson.get().fromJson(errorContent, BusinessException.class); } else { return gson.get().fromJson(errorContent, RuntimeException.class); } } catch (IOException e) { log.error("FeignClientErrorDecoder decode exception:{}.", e); return e; } } return new Exception("server unknown error"); } }
Effect display
After adding the above two classes, we can achieve the desired effect. Restart the client and server projects and view the effect. The client log is as follows.
It can be seen that the client prints the exception information of the server.
be careful:
1. In the error code parser, if the exception information String is converted to an exception class, gson get(). fromJson(errorContent, Exception.class); The caught exception is undeclaredtowableexception. The exception information is 1 but not 2. At this time, the printed exception information e is null.
If the exception information String is converted to IOException class, gson get(). fromJson(errorContent, IOException.class); FeignException caught.
I don't know why this happens. There is no research. Therefore, if it is not converted to a custom exception, it is uniformly converted to RuntimeException.
2. The status of the response in the error decoder is the status of the HttpServletResponse set in the global exception capture, that is, response setStatus(e.getCode()); The content of the custom response ResultBody returned in the global exception capture is the string content of the body that entered the response in the error decoder. Because the attribute of RuntimeException is detailMessage, in order to prevent problems in Gson conversion, it is best to uniformly set the information attribute in custom exception and response body to detailMessage.
If there is any error, please correct it, thank you!