当前位置:   article > 正文

feign - 异常处理_feignclient异常处理

feignclient异常处理

feign异常处理。

1. 前言

本文针对feign调用过程中,发生异常情况时的逻辑处理流程进行梳理,为之后遇到相关异常情况时,心里有底。

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);
	}
}
  • 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

以上测试用例中,调用端没有任何需要特别说的地方,重点还是在第一步的配置中:

  1. 我们首先定义了hystrix的异常处理实现类。
  2. 然后我们又扩展了feign提供的异常处理接口,并注入到Spring容器中,以让其参与feign的异常处理流程。

所以,本文所要解决的问题就是:

  1. 这两个自定义异常处理的扩展分别会在什么情况下触发?
  2. 以及两者触发的先后顺序是什么样的?

3. 源码解析

跟随以上这两个问题,在我们之前feign源码阅读1feign源码阅读2基础上,看看以上两个配置项是如何生效的。

3.1 源码 - 初始化

根据之前的博客feign源码解析 - 初始化,我们可以很快定位出如下代码:

// 在前面博客中我们介绍了, 针对每个@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();
		}

	}

}
  • 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
3.2 源码 - 运行时

根据之前的博客feign源码解析 - 运行时,我们可以很快定位出如下代码:

  // ===================== 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();
  }
  • 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
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;
    }

  }
}
  • 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

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 fallback —— FallbackFactory自定义实现类

5. 相关

  1. feign源码解析 - 初始化
  2. feign源码解析 - 运行时
  3. feign调用异常处理
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/84661
推荐阅读
  

闽ICP备14008679号