之前有参与过一个缴费相关的SpringCloud项目,开发的过程中,也踩过不少的坑。总会遇到几次,fegin调用失败直接走了fallback。但是只是看fallback,根本看不出什么有用的信息。很多时候只能再去看看哪里没有对应上,特别耽误时间。然后就自己追源码找到了一个可行的方案。
本地测试的时候,遇到fegin调用失败,能打断点发现真正的错误原因么?
当然可以!
但是首先我们需要先简单的了解feign
1.Feign的简单基础知识
FeginClient注解的方法,会通过Proxy代理生成代理类执行。即通过Proxy的newProxyInstance方法执行得到了代理类,newProxyInstance方法有三个参数ClassLoader、Class<?>[]和InvocationHandler。生成的代理类主要执行的方法就是通过代理的执行器InvocationHandler的invoke方法来执行的。
对于想了解更多关于Fegin原理的小伙伴,可以先去看看亦山的文章,这里就不详细说明原理了。
在OpenFegin中,执行器InvocationHandler对应的其中一个实现类是HystrixInvocationHandler(io.github.openfeign.feign-hystrix)
final class HystrixInvocationHandler implements InvocationHandler {
/*只粘贴了核心方法,详情参数看源码*/
@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
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
HystrixCommand<Object> hystrixCommand =
new HystrixCommand<Object>(setterMethodMap.get(method)) {
@Override
protected Object run() throws Exception {
try {
/*
* hystrixCommand.execute()执行后会调用该方法,此时会通过method获取对应的
* MethodHandler,并执行invoke方法
*/
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);
if (isReturnsHystrixCommand(method)) {
return ((HystrixCommand) result).execute();
} else if (isReturnsObservable(method)) {
// Create a cold Observable
return ((Observable) result).toBlocking().first();
} else if (isReturnsSingle(method)) {
// Create a cold Observable as a Single
return ((Single) result).toObservable().toBlocking().first();
} else if (isReturnsCompletable(method)) {
((Completable) result).await();
return null;
} else {
return result;
}
} catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an interface
throw new AssertionError(e);
} catch (InvocationTargetException e) {
// Exceptions on fallback are tossed by Hystrix
throw new AssertionError(e.getCause());
}
}
};
if (Util.isDefault(method)) {
return hystrixCommand.execute();
} else if (isReturnsHystrixCommand(method)) {
return hystrixCommand;
} else if (isReturnsObservable(method)) {
// Create a cold Observable
return hystrixCommand.toObservable();
} else if (isReturnsSingle(method)) {
// Create a cold Observable as a Single
return hystrixCommand.toObservable().toSingle();
} else if (isReturnsCompletable(method)) {
return hystrixCommand.toObservable().toCompletable();
}
/*正常执行此方法*/
return hystrixCommand.execute();
}
}
通过 HystrixInvocationHandler.this.dispatch.get(method)方法获取到了对应的MethodHandler, 其中对应的实现类为SynchronousMethodHandler.
final class SynchronousMethodHandler implements MethodHandler {
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
try {
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) 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就行*/
response = client.execute(request, options);
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
boolean shouldClose = true;
try {
if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
}
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();
}
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
} 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());
}
}
}
Object decode(Response response) throws Throwable {
try {
return decoder.decode(response, metadata.returnType());
} catch (FeignException e) {
throw e;
} catch (RuntimeException e) {
throw new DecodeException(e.getMessage(), e);
}
}
}
断点可以打到返回response的后面。通过response的返回就可以获取fegin调用之后的状态了。
2.断点实操
通过response不同状态的返回,以及response对象,我们能知道更多的信息。
(1) 正常200的返回
(2)错误404的返回
(3)错误500的返回
500的错误还是比较特殊的,只是这么看的话,也很难知道具体的报错信息,这个时候就可以在另外一个地方打断点来获取详细的报错信息了。其实就是HystrixInvocationHandler的异常抛出的位置(如下图)。
以上就是判断Fegin报错并断点调试的方法。方法知道了,那么有的小伙伴就想问,实际使用的时候如何快速找到SynchronousMethodHandler和HystrixInvocationHandler呢?毕竟实践才是检验真理的唯一标准!
3.我在哪里?
首先确认项目中已经引用了OpenFegin的依赖,然后就可以在依赖中直接找到对应的类,话不多说直接上图。
然后在Libraries里面找io.github.openfeign:feign-core包,点开后就可以找到SynchronousMethodHandler。
在io.github.openfeign:feign-core包则可以找到HystrixInvocationHandler
当然也可以在github上看源码
https://github.com/OpenFeign/feign
4.结语
就跟搞笑版《XX宝典》一样,不必看上面的也可练此神功。
根本没必要这么麻烦 ,如果想把这样的异常直接抛出或者打印出来,只需要把fallback类实现FallbackFactory接口,并且将默认的create方法中将Throwable抛出或者打印即可。。很多时候象生产环境等,不开放对应端口,想在服务器上打断点并不容易,并且会影响其他用户的使用。所以还是这个方法更有效。