feign.Contract.BaseContract.parseAndValidatateMetadata
拋出的異常, 代碼如下:
if (targetType.getInterfaces().length == 1) {
Util.checkState(targetType.getInterfaces()[0].getInterfaces().length == 0, "Only single-level inheritance supported: %s", new Object[]{targetType.getSimpleName()});
}
爲何會做這個限制目前未知,感覺很奇怪,如果有人知道的話麻煩告訴一聲。
解決辦法
spring boot 默認會使用 SpringMvcContract 去實現
代碼位置:org.springframework.cloud.openfeign.FeignClientsConfiguration
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
通過代碼我們可以知道,如果我們自己提供一個實現,就可以替換掉
拷貝源碼實現自己的Contract,然後把上面的驗證註釋掉;
編寫一個 Configuration,註冊自己實現的 Contract;
代碼大概這樣:
@Configuration
public class FeignConfig {
@Autowired(
required = false
)
private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList();
@Autowired(
required = false
)
private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList();
@Bean
public Contract feignContract( ConversionService feignConversionService) {
return new MContract(this.parameterProcessors, feignConversionService);
}
@Bean
public FormattingConversionService feignConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService();
Iterator var2 = this.feignFormatterRegistrars.iterator();
while(var2.hasNext()) {
FeignFormatterRegistrar feignFormatterRegistrar = (FeignFormatterRegistrar)var2.next();
feignFormatterRegistrar.registerFormatters(conversionService);
}
return conversionService;
}
}
解決方法-補充
按照上面的思路,只能保證你的代碼能運行,但無法調用到具體的服務
因爲會出現路徑不正確的問題,本來應該是 /api/get 會變成 /get
源碼位置,邏輯大概是:如果你有繼承,使用繼承類上的@RequestMapping
protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
if (targetType.getInterfaces().length == 1) {
this.processAnnotationOnClass(data, targetType.getInterfaces()[0]);
}
this.processAnnotationOnClass(data, targetType);
而 processAnnotationOnClass 方法的實現,邏輯大概是 : 如果該類有繼承就不處理。
protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
if (clz.getInterfaces().length == 0) {
RequestMapping classAnnotation = (RequestMapping)AnnotatedElementUtils.findMergedAnnotation(clz, RequestMapping.class);
if (classAnnotation != null && classAnnotation.value().length > 0) {
String pathValue = Util.emptyToNull(classAnnotation.value()[0]);
pathValue = this.resolve(pathValue);
if (!pathValue.startsWith("/")) {
pathValue = "/" + pathValue;
}
data.template().insert(0, pathValue);
}
}
}
與上面的邏輯進行對應,結果就是 如果接口有繼承,只會使用父類的@RequestMapping
所以,最後只需要在自己實現的 MContract 中將該邏輯改變就好