环境
springclcoud: 2023.0.0
springboot: 3.2.1
springcloud-alibaba: 2022.0.0.0
问题
升级springcloud版本后, 发现sentinel 整合 openfeign相关项目启动失败
开启feign对sentinel的支持就会报错 Caused by: java.lang.IllegalStateException: Method PaymentService#disablePaymentLog() not annotated with HTTP method type (ex. GET, POST)
Warnings:
- Class PaymentService has annotations [Service, FeignClient] that are not used by contract Default
- Method disablePaymentLog has an annotation GetMapping that is not used by contract Default
配置代码
feign:
sentinel:
enabled: true
@FeignClient(value = "payment", configuration = OpenFeignConfig.class)
public interface PaymentService {
@GetMapping(value = "/payment/log/disable")
void disablePaymentLog();
...
}
error stack
Caused by: java.lang.IllegalStateException: Method PaymentService#disablePaymentLog() not annotated with HTTP method type (ex. GET, POST)
Warnings:
- Class PaymentService has annotations [Service, FeignClient] that are not used by contract Default
- Method disablePaymentLog has an annotation GetMapping that is not used by contract Default
at feign.Util.checkState(Util.java:140) ~[feign-core-13.1.jar:na]
at feign.Contract$BaseContract.parseAndValidateMetadata(Contract.java:115) ~[feign-core-13.1.jar:na]
at feign.Contract$BaseContract.parseAndValidateMetadata(Contract.java:65) ~[feign-core-13.1.jar:na]
at feign.DeclarativeContract.parseAndValidateMetadata(DeclarativeContract.java:38) ~[feign-core-13.1.jar:na]
at feign.ReflectiveFeign$ParseHandlersByName.apply(ReflectiveFeign.java:137) ~[feign-core-13.1.jar:na]
at feign.ReflectiveFeign.newInstance(ReflectiveFeign.java:56) ~[feign-core-13.1.jar:na]
at feign.ReflectiveFeign.newInstance(ReflectiveFeign.java:48) ~[feign-core-13.1.jar:na]
at feign.Feign$Builder.target(Feign.java:201) ~[feign-core-13.1.jar:na]
at org.springframework.cloud.openfeign.DefaultTargeter.target(DefaultTargeter.java:30) ~[spring-cloud-openfeign-core-4.1.0.jar:4.1.0]
at org.springframework.cloud.openfeign.FeignClientFactoryBean.loadBalance(FeignClientFactoryBean.java:399) ~[spring-cloud-openfeign-core-4.1.0.jar:4.1.0]
at org.springframework.cloud.openfeign.FeignClientFactoryBean.getTarget(FeignClientFactoryBean.java:447) ~[spring-cloud-openfeign-core-4.1.0.jar:4.1.0]
at org.springframework.cloud.openfeign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:422) ~[spring-cloud-openfeign-core-4.1.0.jar:4.1.0]
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:182) ~[spring-beans-6.1.2.jar:6.1.2]
... 32 common frames omitted
分析
spring-cloud-starter-alibaba-sentinel对openfeign的适配由自动配置载入
其为我们自动注入了FeignBuiler的sentinel实现SentinelFeign.Builder
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ SphU.class, Feign.class })
public class SentinelFeignAutoConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.sentinel.enabled")
public Feign.Builder feignSentinelBuilder() {
return SentinelFeign.builder();
}
}
看一下SentinelFeign
public final class SentinelFeign {
private static final String FEIGN_LAZY_ATTR_RESOLUTION = "spring.cloud.openfeign.lazy-attributes-resolution";
private SentinelFeign() {
}
public static Builder builder() {
return new Builder();
}
public static final class Builder extends Feign.Builder
implements ApplicationContextAware {
private Contract contract = new Contract.Default();
private ApplicationContext applicationContext;
private FeignClientFactory feignClientFactory;
@Override
public Feign.Builder invocationHandlerFactory(
InvocationHandlerFactory invocationHandlerFactory) {
throw new UnsupportedOperationException();
}
@Override
public Builder contract(Contract contract) {
this.contract = contract;
return this;
}
// 重写Feign.Builder#build() 来构建Feign(ReflectiveFeign)
@Override
public Feign build() {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override
public InvocationHandler create(Target target,
Map<Method, MethodHandler> dispatch) {
GenericApplicationContext gctx = (GenericApplicationContext) Builder.this.applicationContext;
BeanDefinition def = gctx.getBeanDefinition(target.type().getName());
FeignClientFactoryBean feignClientFactoryBean;
// If you need the attributes to be resolved lazily, set the property value to true.
Boolean isLazyInit = applicationContext.getEnvironment()
.getProperty(FEIGN_LAZY_ATTR_RESOLUTION, Boolean.class, false);
if (isLazyInit) {
/*
* Due to the change of the initialization sequence,
* BeanFactory.getBean will cause a circular dependency. So
* FeignClientFactoryBean can only be obtained from BeanDefinition
*/
feignClientFactoryBean = (FeignClientFactoryBean) def
.getAttribute("feignClientsRegistrarFactoryBean");
}
else {
feignClientFactoryBean = (FeignClientFactoryBean) applicationContext
.getBean("&" + target.type().getName());
}
Class fallback = feignClientFactoryBean.getFallback();
Class fallbackFactory = feignClientFactoryBean.getFallbackFactory();
String beanName = feignClientFactoryBean.getContextId();
if (!StringUtils.hasText(beanName)) {
beanName = (String) getFieldValue(feignClientFactoryBean, "name");
}
Object fallbackInstance;
FallbackFactory fallbackFactoryInstance;
// check fallback and fallbackFactory properties
if (void.class != fallback) {
fallbackInstance = getFromContext(beanName, "fallback", fallback,
target.type());
return new SentinelInvocationHandler(target, dispatch,
new FallbackFactory.Default(fallbackInstance));
}
if (void.class != fallbackFactory) {
fallbackFactoryInstance = (FallbackFactory) getFromContext(
beanName, "fallbackFactory", fallbackFactory,
FallbackFactory.class);
return new SentinelInvocationHandler(target, dispatch,
fallbackFactoryInstance);
}
return new SentinelInvocationHandler(target, dispatch);
}
private Object getFromContext(String name, String type,
Class fallbackType, Class targetType) {
Object fallbackInstance = feignClientFactory.getInstance(name,
fallbackType);
if (fallbackInstance == null) {
throw new IllegalStateException(String.format(
"No %s instance of type %s found for feign client %s",
type, fallbackType, name));
}
// when fallback is a FactoryBean, should determine the type of instance
if (fallbackInstance instanceof FactoryBean<?> factoryBean) {
try {
fallbackInstance = factoryBean.getObject();
}
catch (Exception e) {
throw new IllegalStateException(type + " create fail", e);
}
fallbackType = fallbackInstance.getClass();
}
if (!targetType.isAssignableFrom(fallbackType)) {
throw new IllegalStateException(String.format(
"Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
type, fallbackType, targetType, name));
}
return fallbackInstance;
}
});
super.contract(new SentinelContractHolder(contract));
return super.build();
}
...... 省略
}
}
再看Feign.Builder源码
public static class Builder extends BaseBuilder<Builder, Feign> {
... 省略
public <T> T target(Class<T> apiType, String url) {
return target(new HardCodedTarget<>(apiType, url));
}
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
@Override
public Feign internalBuild() {
final ResponseHandler responseHandler =
new ResponseHandler(logLevel, logger, decoder, errorDecoder,
dismiss404, closeAfterDecode, decodeVoid, responseInterceptorChain());
MethodHandler.Factory<Object> methodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors,
responseHandler, logger, logLevel, propagationPolicy,
new RequestTemplateFactoryResolver(encoder, queryMapEncoder),
options);
return new ReflectiveFeign<>(contract, methodHandlerFactory, invocationHandlerFactory,
() -> null);
}
}
build()方法在父类中
public abstract class BaseBuilder<B extends BaseBuilder<B, T>, T> implements Cloneable {
... 省略
// fixme spirngcloud build()方法final 不能被重写
public final T build() {
return enrich().internalBuild();
}
protected abstract T internalBuild();
protected ResponseInterceptor.Chain responseInterceptorChain() {
ResponseInterceptor.Chain endOfChain =
ResponseInterceptor.Chain.DEFAULT;
ResponseInterceptor.Chain executionChain = this.responseInterceptors.stream()
.reduce(ResponseInterceptor::andThen)
.map(interceptor -> interceptor.apply(endOfChain))
.orElse(endOfChain);
return (ResponseInterceptor.Chain) Capability.enrich(executionChain,
ResponseInterceptor.Chain.class, capabilities);
}
}
总结
至此, 因为feign源码中build方法是final,无法被重写,springcloud-starter-alibaba-sentinel中对其实现还没有适配, 所以导致出现该问题
解决
- 等springcloud-alibaba官方实现
- 对SentinelFeign自己重新实现一下, build()方法为final,但是给了抽象模板方法 internalBuild(), 重写它即可,然后把自己的SentinelFeign注入到自动配置到spring容器中. 这里可以参考 issue#3514的解决方案,就不重复造轮子了