例外処理を装う。
1 はじめに
この記事では、今後、関連する異常事態に遭遇した際にボトムアップ思考で対応できるよう、擬似通話処理中に異常事態が発生した場合の論理的な処理の流れを整理します。
2. ユースケース
まず背景としてテスト ケースを紹介します。
// ==================================================================
// ================================================== 配置 ==========
// ==================================================================
// ========================== 配置1
// 使用 fallbackFactory属性配置了自定义hystrix异常处理逻辑
@FeignClient(value = "projectB", fallbackFactory = FeignCallServiceFallbackFactory.class)
public interface FeignSpringAnnotationCallService {
@RequestMapping(value = "/projectB/{name}", method = RequestMethod.GET)
String call(@PathVariable(value = "name") String name);
}
// ========================== 配置2
// 定义feign的全局异常处理接口ErrorDecoder实现类
// override {@code FeignClientsConfiguration} 和 {@code FeignClientFactoryBean.getInheritedAwareOptional(context, ErrorDecoder.class)}
@Bean
public FeignErrorDecoder feignErrorDecoder() {
// 在 AsyncResponseHandler 中应用
return new FeignErrorDecoder();
}
public static class FeignErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
int status = response.status();
if (methodKey.contains("#callFail2")) {
// 如果这里抛出异常, 将被转给hystrix异常处理
throw new RuntimeCryptoException("callFail2-LQ");
}
if (status >= HttpStatus.BAD_REQUEST.value() && status <= HttpStatus.INTERNAL_SERVER_ERROR.value()) {
String message = response.reason();
// 获取原始错误信息
try (Reader reader = response.body().asReader(Charset.defaultCharset())) {
message = CharStreams.toString(reader);
} catch (Exception ignored) {
status = HttpStatus.EXPECTATION_FAILED.value();
log.error(HttpStatus.EXPECTATION_FAILED.getReasonPhrase(), ignored);
}
// return new KqHystrixBadRequestException(status,methodKey + " " + message);
return new KqHystrixBadRequestException(status, message);
}
return errorStatus(methodKey, response);
}
}
// ==================================================================
// ================================================== 调用 ==========
// ==================================================================
@Slf4j
@RestController
public class HelloController {
@Autowired
private FeignSpringAnnotationCallService projectBServiceCall;
@PostMapping("/hello/{name}")
public String feignCall(@PathVariable String name) {
return projectBServiceCall.call(name);
}
}
上記のテスト ケースでは、呼び出し側で特別なことは何もありません。重要な点は依然として最初のステップの構成にあります。
- まず、hystrix の例外処理実装クラスを定義します。
- 次に、feign が提供する例外処理インターフェイスを拡張し、それを Spring コンテナに挿入して、feign の例外処理プロセスに参加できるようにしました。
したがって、この論文で解決すべき問題は次のとおりです。
- これら 2 つのカスタム例外処理拡張機能はどのような状況でトリガーされますか?
- そして、この 2 つはどのような順序でトリガーされるのでしょうか?
3. ソースコード分析
上記 2 つの質問に続いて、以前のhttps://fulizhe.blog.csdn.net/article/details/127186374およびhttps://fulizhe.blog.csdn.net/article/details/127189134に基づいて、上記 2 つの構成項目がどのように有効になるかを見てみましょう。
3.1 ソースコード - 初期化
以前のブログ [https://fulizhe.blog.csdn.net/article/details/127186374] (偽のソース コード分析 - 初期化) によると、次のコードをすぐに見つけることができます。
// 在前面博客中我们介绍了, 针对每个@FeignClient注解的接口, 会创建一个对应的FeignClientFactoryBean实例用来生成@FeignClient注解所修饰的接口的代理类.
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
/***********************************
* WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some
* lifecycle race condition.
***********************************/
......
// FeignClientsRegistrar.registerFeignClient(...)实现中会读取注解@FeignClient中配置的 fallbackFactory, fallback属性值。赋值给FeignClientFactoryBean类型实例对应属性
private Class<?> fallback = void.class;
private Class<?> fallbackFactory = void.class;
......
protected void configureUsingConfiguration(FeignContext context,
Feign.Builder builder) {
......
// 从Spring容器中查找是否有用户自定义的ErrorDecoder实现类
ErrorDecoder errorDecoder = getInheritedAwareOptional(context,
ErrorDecoder.class);
if (errorDecoder != null) {
builder.errorDecoder(errorDecoder);
}
else {
FeignErrorDecoderFactory errorDecoderFactory = getOptional(context,
FeignErrorDecoderFactory.class);
if (errorDecoderFactory != null) {
ErrorDecoder factoryErrorDecoder = errorDecoderFactory.create(type);
builder.errorDecoder(factoryErrorDecoder);
}
}
......
if (decode404) {
builder.decode404();
}
}
}
3.2 ソースコード - ランタイム
以前のブログ [https://fulizhe.blog.csdn.net/article/details/127189134] (偽のソース コード分析 - ランタイム) によると、次のコードをすぐに見つけることができます。
// ===================== HystrixInvocationHandler.java
// 因为我们启用了 feign.hystrix.enabled=true(在 HystrixFeignConfiguration 中生效), 所以最终代理类为HystrixInvocationHandler, 而非默认的ReflectiveFeign.FeignInvocationHandler.
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
// early exit if the invoked method is from java.lang.Object
// code is the same as ReflectiveFeign.FeignInvocationHandler
// 如果调用的是Object定义的系类方法, 诸如toString()等
......
// 对于每个定义的feign方法,都重新构建一个新的自定义HystrixCommand类型的实例.
// HystrixCommand类型覆写了基类AbstractCommand的isFallbackUserDefined()方法,用来托底fallback —— 即判断当前的自定义HystrixCommand类型中是否定义了"getFallback"方法(这里采用的是反射方式,估计是为了兼容)
// 这里自定义的HystrixCommand则正好会覆写基类的getFallback(),和上面一行呼应上了. 齿轮严丝合缝地咬合上了.
HystrixCommand<Object> hystrixCommand =
new HystrixCommand<Object>(setterMethodMap.get(method)) {
@Override
protected Object run() throws Exception {
try {
// 跨线程调用, 这里最终调用的还是 SynchronousMethodHandler
return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
} catch (Exception e) {
throw e;
} catch (Throwable t) {
throw (Error) t;
}
}
@Override
protected Object getFallback() {
if (fallbackFactory == null) {
return super.getFallback();
}
try {
Object fallback = fallbackFactory.create(getExecutionException());
Object result = fallbackMethodMap.get(method).invoke(fallback, args);
......
} catch (Exception e) {
......
}
}
};
......
return hystrixCommand.execute();
}
final class SynchronousMethodHandler implements MethodHandler {
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
// 默认是"永不重试", 直接进入接下来的catch()块, 然后抛出异常.没希望进入下面的 continue. 异常处理流程进入hystrix体系下.
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
// 只接受IOException异常
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
// 封装为 RetryableException 再次抛出, 供上层的invoke(...)捕获,用作重试条件
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
if (decoder != null)
return decoder.decode(response, metadata.returnType());
CompletableFuture<Object> resultFuture = new CompletableFuture<>();
// 自定义ErrorDecoder实现类在这里生效
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
metadata.returnType(),
elapsedTime);
try {
if (!resultFuture.isDone())
throw new IllegalStateException("Response handling not done");
return resultFuture.join();
} catch (CompletionException e) {
Throwable cause = e.getCause();
if (cause != null)
throw cause;
throw e;
}
}
}
4. まとめ
細かいことは無視して要約を作成し、全体像を形成しましょう。
終わり | に従い、 | 失敗時のハンドラ |
---|---|---|
サーバーエラー | HTTP 戻りステータス コードが 200 ~ 300 の間ではありません (リクエストは正常に送信され、期待どおりに受信されました) |
失敗した場合は、ErrorDecoder カスタム実装クラスに移動します。 |
クライアントエラー | タイムアウト、ヒューズ条件などの一般的な条件がトリガーされます。 1. 対応するサービスが見つからない例外 com.netflix.client.ClientException: Load balancer does not have available server for client: projectB 2. タイムアウト例外 java.net.SocketTimeoutException: connect timed out 3. 対象サービスが見つからない例外 feign.RetryableException: Failed to connect to /127.0.0.1:80 executing GET http://127.0.0.1/xxx/user/select/getPersonnelById |
常に hystrix フォールバックに従ってください -FallbackFactory カスタム実装クラス |