今天做项目的时候,遇到一个问题,如果我调用某个服务的接口,但是这个服务挂了,同时业务要求这个接口的结果是必须的,那我该怎么办呢,答案是通过hystrix,但是又有一点,服务不是平白无故挂的(排除服务器停电等问题),也就是说有可能是timeout or wrong argument 等等,那么我该如何越过hystrix的同时又能将异常成功抛出呢
第一点:先总结一下异常处理的方式:
1):通过在controller中编写@ExceptionHandler 方法
直接在controller中编写异常处理器方法
1
2
3
4
5
6
7
8
9
10
|
@RequestMapping ( "/test" ) public ModelAndView test() { throw new TmallBaseException(); } @ExceptionHandler (TmallBaseException. class ) public ModelAndView handleBaseException() { return new ModelAndView( "error" ); } |
但是呢这种方法只能在这个controller中有效,如果其他的controller也抛出了这个异常,是不会执行的
2):全局异常处理:
1
2
3
4
5
6
7
8
9
10
|
@ControllerAdvice public class AdminExceptionHandler { @ExceptionHandler (TmallBaseException. class ) public ModelAndView hAndView(Exception exception) { //logic return null ; } } |
本质是aop代理,如名字所言,全局异常处理,可以处理任意方法抛出的异常
3)通过实现SpringMVC的HandlerExceptionResolver接口
1
2
3
4
5
6
7
8
9
10
|
public static class Tt implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { //logic return null ; } } |
然后在mvc配置中添加即可
1
2
3
4
5
6
7
8
|
@Configuration public class MyConfiguration extends WebMvcConfigurerAdapter { @Override public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) { //初始化异常处理器链 exceptionResolvers.add( new Tt()); } } |
接下来就是Fegin ,如果想自定义异常需要了解1个接口:ErrorDecoder
先来看下rmi调用结束后是如果进行decode的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
Object executeAndDecode(RequestTemplate template) throws Throwable { Request request = targetRequest(template); //代码省略 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(); } //从此处可以发现,如果状态码不再200-300,或是404的时候,意味着非正常响应就会对内部异常进行解析 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()); } } } |
默认的解析方式是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public static class Default implements ErrorDecoder { private final RetryAfterDecoder retryAfterDecoder = new RetryAfterDecoder(); @Override public Exception decode(String methodKey, Response response) { //获取错误状态码,生成fegin自定义的exception FeignException exception = errorStatus(methodKey, response); Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER)); if (retryAfter != null ) { //如果重试多次失败,则抛出相应的exception return new RetryableException(exception.getMessage(), exception, retryAfter); } //否则抛出默认的exception return exception; } |
我们可以发现,做了2件事,第一获取状态码,第二重新抛出异常,额外的判断是否存在多次失败依然error的异常,并没有封装太多的异常,既然如此那我们就可以封装我们自定义的异常了
但是注意,这块并没有涉及hystrix,也就意味着对异常进行处理还是会触发熔断机制,具体避免方法最后讲
首先我们编写一个BaseException 用于扩展:省略getter/setter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
public class TmallBaseException extends RuntimeException { /** * * @author joker * @date 创建时间:2018年8月18日 下午4:46:54 */ private static final long serialVersionUID = -5076254306303975358L; // 未认证 public static final int UNAUTHENTICATED_EXCEPTION = 0 ; // 未授权 public static final int FORBIDDEN_EXCEPTION = 1 ; // 超时 public static final int TIMEOUT_EXCEPTION = 2 ; // 业务逻辑异常 public static final int BIZ_EXCEPTION = 3 ; // 未知异常->系统异常 public static final int UNKNOWN_EXCEPTION = 4 ; // 异常码 private int code; // 异常信息 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,我们定义好了基类之后可以先进行测试一番:服务接口controller:
1
2
3
4
5
6
7
|
//显示某个商家合作的店铺 @RequestMapping (value= "/store" ) public ResultDTO<Collection<BrandDTO>>findStoreOperatedBrands( @RequestParam ( "storeId" )Long storeId) { 为了测试,先直接抛出异常 throw new TmallBaseException(TmallBaseException.BIZ_EXCEPTION, "ceshi" ); } |
接口:
1
2
|
@RequestMapping (value= "/auth/brand/store" ,method=RequestMethod.POST,produces=MediaType.APPLICATION_JSON_UTF8_VALUE) ResultDTO<List<BrandDTO>>findStoreOperatedBrands( @RequestParam ( "storeId" )Long storeId); |
其余的先不贴了,然后我们发起rest调用的时候发现,抛出异常之后并没有被异常处理器处理,这是因为我们是通过fegin,而我又配置了feign的fallback类,抛出异常的时候会自动调用这个类中的方法.
有两种解决方法:
1.直接撤除hystrix ,很明显its not a good idea
2.再封装一层异常类,具体为何,如下
AbstractCommand#handleFallback 函数是处理异常的函数,从方法后缀名可以得知,当exception 是HystrixBadRequestException的时候是直接抛出的,不会触发fallback,也就意味着不会触发降级
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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); } } }; |
既然如此,那一切都明了了,修改类的继承结构即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public class TmallBaseException extends HystrixBadRequestException { /** * * @author joker * @date 创建时间:2018年8月18日 下午4:46:54 */ private static final long serialVersionUID = -5076254306303975358L; // 未认证 public static final int UNAUTHENTICATED_EXCEPTION = 0 ; // 未授权 public static final int FORBIDDEN_EXCEPTION = 1 ; // 超时 public static final int TIMEOUT_EXCEPTION = 2 ; // 业务逻辑异常 public static final int BIZ_EXCEPTION = 3 ; // 未知异常->系统异常 public static final int UNKNOWN_EXCEPTION = 4 ; // 异常码 private int code; // 异常信息 private String message; } |
至于怎么从服务器中获取异常然后进行转换,就是通过上面所讲的ErrorHandler:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class TmallErrorDecoder implements ErrorDecoder { @Override public Exception decode(String methodKey, Response response) { System.out.println(methodKey); Exception exception= null ; try { String json = Util.toString(response.body().asReader()); exception=JsonUtils.json2Object(json,TmallBaseException. class ); } catch (IOException e) { e.printStackTrace(); } return exception!= null ?exception: new TmallBaseException(TmallBaseException.UNKNOWN_EXCEPTION, "系统运行异常" ); } } |
最后微服务下的全局异常处理就ok了,当然这个ErrorDdecoder 和BaseException推荐放在common模块下,所有其它模块都会使用到它。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/Coder_Joker/article/details/81811567