问题背景
在实际生产环境,服务A通过feign调用服务Consumer,FeignClient配置如下
@FeignClient(value = "sleuthConsumer2" , fallbackFactory = Consumer2Factory.class)
public interface Consumer2Client {
@RequestMapping("/consumer2")
String consumer2();
}
一眼看上去这个配置没有任何问题,在实际调用中也可以正常调用,一切都很OK的,直到有一天,由于网络原因, 服务A调用Consumer的consumer2方法操作了了两次。
这个很尴尬了。
问题分析
feign的重试机制和ribbon的重试机制,之前有了解过,feign默认重试五次,但是没有开启的,ribbon默认重试一次,是开启的,这个重试机制也是分场景的,
之前有开过一篇文章讲解过,大家可以去看看feign和ribbon的重试机制 , 我们值得只有GET请求在默认情况下不管啥情况,只要出现网络异常,比如请求超时,都是会进行重试的, 然后我们发现上面的代码并没有指定请求方式,feign默认不会直接给我当做GET请求去做了呢?
代码调试
feign容器启动的时候,对于每个feignClient都会生成一个代理对象,我们直接找到这个代理对象的代码,查看他请求的发起过程,
代码入口: feign.SynchronousMethodHandler
从上面的调试图上,可以看到,由于我们没有指定请求的方式,feign在构建RequestTemplate的时候读取到的配置,默认就是直接使用了GET请求。
源码解析
此处的源码不会贴的很详细,仅贴的大概
//feign.ReflectiveFeign
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
// 省略代码。。。
return proxy;
}
解析target
//
public Map<String, MethodHandler> apply(Target key) {
// 主要看这一行代码,解析FeignClient上面的Metadata信息
List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
// 省略代码。。。
}
return result;
}
下面的调用链就比较深了,我就不一一贴了有兴趣的可以自己去看看
Contract > BaseContract > SpringMvcContract
// org.springframework.cloud.netflix.feign.support.SpringMvcContract
protected void processAnnotationOnMethod(MethodMetadata data,
Annotation methodAnnotation, Method method) {
if (!RequestMapping.class.isInstance(methodAnnotation) && !methodAnnotation
.annotationType().isAnnotationPresent(RequestMapping.class)) {
return;
}
// 获取方法上面的RequestMapping注解
RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
// 获取设置的请求方式
RequestMethod[] methods = methodMapping.method();
if (methods.length == 0) { // 如果为空,那么直接设置为GET请求
methods = new RequestMethod[] { RequestMethod.GET };
}
//省略代码。。。。
}
总结
在实际的项目当中,使用RequestMapping用作方法上面的映射时,一定要指定 请求方式,或者直接使用PostMapping或者GetMapping之类的,否则出了啥问题,真是一脸懵逼