feign - 异常处理

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

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

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

3. 源码解析

跟随以上这两个问题,在我们之前https://fulizhe.blog.csdn.net/article/details/127186374https://fulizhe.blog.csdn.net/article/details/127189134基础上,看看以上两个配置项是如何生效的。

3.1 源码 - 初始化

根据之前的博客[https://fulizhe.blog.csdn.net/article/details/127186374](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();
		}

	}

}

3.2 源码 - 运行时

根据之前的博客[https://fulizhe.blog.csdn.net/article/details/127189134](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();
  }
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 fallback —— FallbackFactory自定义实现类

5. 相关

  1. feign源码解析 - 初始化
  2. feign源码解析 - 运行时
  3. feign调用异常处理

猜你喜欢

转载自blog.csdn.net/lqzkcx3/article/details/130392174
今日推荐