Exception handling operation under SpringCloud feign service fuse
- 2021-10-13 07:36:28
- OfStack
When I was working on the project today, Encountered a problem, If I call the interface of a service, But this service hangs up, and the result of this interface is required by the business. What should I do? The answer is through hystrix, but there is another point. The service is not hung up for no reason (excluding problems such as server power failure), that is to say, it may be timeout, or, wrong, argument, etc., so how can I cross hystrix and throw an exception successfully?
Point 1: First summarize the exception handling methods under 1:
1): By writing the @ ExceptionHandler method in controller
Write exception handler methods directly in controller
@RequestMapping("/test")
public ModelAndView test()
{
throw new TmallBaseException();
}
@ExceptionHandler(TmallBaseException.class)
public ModelAndView handleBaseException()
{
return new ModelAndView("error");
}
However, this method can only be used in this controller. If other controller also throws this exception, it will not be executed
2): Global exception handling:
@ControllerAdvice
public class AdminExceptionHandler
{
@ExceptionHandler(TmallBaseException.class)
public ModelAndView hAndView(Exception exception)
{
//logic
return null;
}
}
aop proxy in essence, as the name says, global exception handling, can handle exceptions thrown by any method
3) By implementing the HandlerExceptionResolver interface of SpringMVC
public static class Tt implements HandlerExceptionResolver
{
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex)
{
//logic
return null;
}
}
Then add it in the mvc configuration
@Configuration
public class MyConfiguration extends WebMvcConfigurerAdapter {
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
// Initialize the exception handler chain
exceptionResolvers.add(new Tt());
}
}
Next is Fegin. If you want to customize exceptions, you need to know one interface: ErrorDecoder
First, let's look at if decode is carried out after rmi call is finished
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = targetRequest(template);
// Code omission
try {
if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
response.toBuilder().request(request).build();
}
if (Response.class == metadata.returnType()) {
if (response.body() == null) {
return response;
}
if (response.body().length() == null ||
response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false;
return response;
}
// Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
}
// It can be found from here that , If the status code is no longer 200-300, Or 404 At the time of , Means that the abnormal response will resolve the internal exception
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
return decode(response);
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
return decode(response);
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
}
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
The default parsing method is:
public static class Default implements ErrorDecoder {
private final RetryAfterDecoder retryAfterDecoder = new RetryAfterDecoder();
@Override
public Exception decode(String methodKey, Response response) {
// Get the error status code , Generate fegin Custom exception
FeignException exception = errorStatus(methodKey, response);
Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER));
if (retryAfter != null) {
// If the retry fails multiple times , The corresponding exception
return new RetryableException(exception.getMessage(), exception, retryAfter);
}
// Otherwise, the default exception
return exception;
}
We can find that we have done two things, first get the status code, second throw the exception again, extra judgment whether there are many failures and still error exceptions, and did not encapsulate too many exceptions, so we can encapsulate our custom exceptions
However, note that this piece does not involve hystrix, which means that handling exceptions will still trigger the fuse mechanism. The specific avoidance method will be discussed finally
First we write an BaseException for extension: omit getter/setter
public class TmallBaseException extends RuntimeException
{
/**
*
* @author joker
* @date Creation time: 2018 Year 8 Month 18 Day Afternoon 4:46:54
*/
private static final long serialVersionUID = -5076254306303975358L;
// Unauthenticated
public static final int UNAUTHENTICATED_EXCEPTION = 0;
// Unauthorized
public static final int FORBIDDEN_EXCEPTION = 1;
// Timeout
public static final int TIMEOUT_EXCEPTION = 2;
// Business logic exception
public static final int BIZ_EXCEPTION = 3;
// Unknown exception -> System anomaly
public static final int UNKNOWN_EXCEPTION = 4;
// Exception code
private int code;
// Exception information
private String message;
public TmallBaseException(int code, String message)
{
super(message);
this.code = code;
this.message = message;
}
public TmallBaseException(String message, Throwable cause)
{
super(message, cause);
this.message = message;
}
public TmallBaseException(int code, String message, Throwable cause)
{
super(message, cause);
this.code = code;
this.message = message;
}
}
OK, after we define the base class, we can test it once: service interface controller:
// Show stores that a merchant cooperates with
@RequestMapping(value="/store")
public ResultDTO<Collection<BrandDTO>>findStoreOperatedBrands(@RequestParam("storeId")Long storeId)
{
To test , Throw the exception directly first
throw new TmallBaseException(TmallBaseException.BIZ_EXCEPTION, "ceshi");
}
Interface:
@RequestMapping(value="/auth/brand/store",method=RequestMethod.POST,produces=MediaType.APPLICATION_JSON_UTF8_VALUE)
ResultDTO<List<BrandDTO>>findStoreOperatedBrands(@RequestParam("storeId")Long storeId);
The rest will not be posted first, and then when we initiated the rest call, we found that the exception was not handled by the exception handler after throwing the exception, because we passed fegin, and I configured the fallback class of feign, and the methods in this class will be automatically called when throwing the exception.
There are two solutions:
1. Remove hystrix directly, obviously its not a good idea
2. Encapsulate the Layer 1 exception class, as follows
The AbstractCommand # handleFallback function is a function for handling exceptions. From the method suffix name, it can be known that when exception is HystrixBadRequestException, it is thrown directly and will not trigger fallback, which means that it will not trigger demotion
final Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {
@Override
public Observable<R> call(Throwable t) {
circuitBreaker.markNonSuccess();
Exception e = getExceptionFromThrowable(t);
executionResult = executionResult.setExecutionException(e);
if (e instanceof RejectedExecutionException) {
return handleThreadPoolRejectionViaFallback(e);
} else if (t instanceof HystrixTimeoutException) {
return handleTimeoutViaFallback();
} else if (t instanceof HystrixBadRequestException) {
return handleBadRequestByEmittingError(e);
} else {
/*
* Treat HystrixBadRequestException from ExecutionHook like a plain HystrixBadRequestException.
*/
if (e instanceof HystrixBadRequestException) {
eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);
return Observable.error(e);
}
return handleFailureViaFallback(e);
}
}
};
In this case, all the 1 cuts are clear, and you can modify the inheritance structure of the class:
@ControllerAdvice
public class AdminExceptionHandler
{
@ExceptionHandler(TmallBaseException.class)
public ModelAndView hAndView(Exception exception)
{
//logic
return null;
}
}
0
As for how to get the exception from the server and then convert it, it is through ErrorHandler mentioned above:
@ControllerAdvice
public class AdminExceptionHandler
{
@ExceptionHandler(TmallBaseException.class)
public ModelAndView hAndView(Exception exception)
{
//logic
return null;
}
}
1
Finally, the global exception handling under microservices is ok. Of course, this ErrorDdecoder and BaseException are recommended to be placed under common module, and all other modules will use it.