环境信息: Java 8
、Spring Boot 2.x
、Spring Cloud 2021.x
引言
微服务中的异常传递
在使用Feign进行远程调用、开启了熔断降级且存在全局异常处理时,异常的传递就变得尤为重要。
在微服务架构中,服务之间的调用是通过网络进行的,因此在调用过程中可能会出现各种异常,如网络超时、服务不可用等。
为了保证服务的稳定性,我们需要对这些异常进行处理,以避免异常在服务之间传递,导致整个系统的不稳定。
调用的几种情况
假设此时A服务正在调用B服务,通常会涉及以下几种情况:
- B服务成功返回
- B服务抛出异常,被全局异常处理器拦截后正常返回,因为全局异常拦截会将异常封装为统一结果对象返回,此时是不会走降级逻辑的
- B服务抛出异常,被全局异常处理器拦截后,继续抛出异常,此时会走降级逻辑并返回结果
详细分析
基础准备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
@SuppressWarnings("unused") @Getter @AllArgsConstructor public enum ReturnCode {
RC200(200, "成功"),
RC500(500, "网络异常"); }
|
- 创建统一返回结果类,用于对返回结果进行封装,这里使用
R
类
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
|
@Data @Builder @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) @SuppressWarnings("unused") public class R<T> {
private String message;
private Integer code;
private T data;
private long timestamp;
public static <T> R<T> success() { return R.<T>builder() .code(ReturnCode.RC200.getCode()) .message(ReturnCode.RC200.getMessage()) .timestamp(System.currentTimeMillis()) .build(); }
public static <T> R<T> success(T data) { return R.<T>builder() .code(ReturnCode.RC200.getCode()) .message(ReturnCode.RC200.getMessage()) .data(data) .timestamp(System.currentTimeMillis()) .build(); }
public static <T> R<T> success(T data, String message) { return R.<T>builder() .code(ReturnCode.RC200.getCode()) .message(message) .data(data) .timestamp(System.currentTimeMillis()) .build(); }
public static <T> R<T> fail(Integer code, String message) { return R.<T>builder() .code(code) .message(message) .timestamp(System.currentTimeMillis()) .build(); }
public static <T> R<T> fail(ReturnCode returnCode) { return R.<T>builder() .code(returnCode.getCode()) .message(returnCode.getMessage()) .timestamp(System.currentTimeMillis()) .build(); }
public static <T> R<T> fail(ReturnCode returnCode, String message) { return R.<T>builder() .code(returnCode.getCode()) .message(message) .timestamp(System.currentTimeMillis()) .build(); }
}
|
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
|
@SuppressWarnings("unused") @Data @EqualsAndHashCode(callSuper = true) public class ServiceException extends RuntimeException {
private Integer code;
private String message;
public ServiceException(ReturnCode codeEnum) { super(codeEnum.getMessage()); this.code = codeEnum.getCode(); this.message = codeEnum.getMessage(); }
public ServiceException(Integer code, String message) { super(message); this.code = code; this.message = message; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@Slf4j @RestControllerAdvice() public class GlobalExceptionHandler {
@ExceptionHandler(ServiceException.class) public R<String> serviceExceptionHandler(ServiceException e) { log.error("业务异常: {} -> {}", Arrays.stream(e.getStackTrace()).findFirst().orElse(null), e.getMessage()); return R.fail(e.getCode(), e.getMessage()); } }
|
情况一
B服务成功返回
无须特殊处理,正常返回即可。
情况二
B服务抛出异常,被全局异常处理器拦截后正常返回
当B服务抛出异常时,全局异常处理器会拦截异常,并将异常封装为统一结果对象返回,此时是不会走降级逻辑的。
此时我们可以直接通过R
类中的状态码来判断是否成功返回,如下:
1 2 3 4 5 6 7 8 9
| R<String> result = service.method(); boolean isSuccess = ReturnCode.RC200.getCode().equals(result.getCode());
if (isSuccess) { } else { throw new ServiceException(result.getCode(), result.getMessage()); }
|
在上述代码中,我们先判断了是否成功返回,如果成功则继续执行,否则抛出异常。
抛出异常的操作可能会在多个方法中用到,我们不妨将其封装到R
类中,以便于调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class R<T> {
@JsonIgnore public T getCheckData() { checkError(); return data; }
private void checkError() { if (isSuccess()) { return; } throw new ServiceException(code, message); }
private boolean isSuccess() { return ReturnCode.RC200.getCode().equals(code); } }
|
在加入上述代码后,我们就可以直接通过getCheckData()
方法来隐藏异常处理逻辑,使代码更加简洁。
1 2 3 4 5
| public void getDataFromOtherService() { R<String> result = service.method(); String data = result.getCheckData(); }
|
情况三
B服务抛出异常,被全局异常处理器拦截后,继续抛出异常
在情况二中,如果异常被全局异常处理器拦截,那么异常会被封装为统一结果对象返回,此时是不会走降级逻辑的。
所以我们可以定义一个异常来单独处理这种情况,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
@SuppressWarnings("unused") @Data @EqualsAndHashCode(callSuper = true) public class FeignClientException extends RuntimeException {
private String message;
public FeignClientException(String message) { super(message); this.message = message; } }
|
并且在全局异常处理器中对该异常进行处理,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class GlobalExceptionHandler {
@ExceptionHandler(value = FeignClientException.class) public void defaultExceptionHandler(FeignClientException e) { throw e; } }
|
此时我们在B服务中可以通过如下方式抛出异常:
1 2 3 4 5 6
| public Service { public String method() { throw new FeignClientException("Feign调用异常, 该异常会被继续抛出!"); } }
|
此时异常会被全局异常处理器拦截,然后直接抛出,从而走降级逻辑。
总结
在本文中,我们探讨了在使用Spring Cloud中的OpenFeign进行远程服务调用时如何处理异常,特别是当这些异常被全局异常处理器捕获时的情况。
通过实际代码示例,我们演示了如何在微服务架构中适当地传递和封装异常,以保持系统的稳定性和响应性。