springcloud2023.0.0 openfeign开启sentinel 启动报错 GetMapping that is not used by contract Default

环境

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中对其实现还没有适配, 所以导致出现该问题

解决

  1. 等springcloud-alibaba官方实现
  2. 对SentinelFeign自己重新实现一下, build()方法为final,但是给了抽象模板方法 internalBuild(), 重写它即可,然后把自己的SentinelFeign注入到自动配置到spring容器中. 这里可以参考 issue#3514的解决方案,就不重复造轮子了
### SpringCloud 2023.x 和 SpringCloud Alibaba 的兼容性分析 Spring Cloud 2023.x 是 Spring Cloud 的一个较新的版本,主要用于支持最新的 Spring Boot 版本(如 3.x 系列)。Spring Cloud Alibaba 则是一个由阿里巴巴维护的扩展项目,旨在为 Spring Cloud 提供额外的功能支持,例如 Nacos、Sentinel 和 Seata 等组件的支持。以下是关于 Spring Cloud 2023.x 和 Spring Cloud Alibaba 兼容性的详细分析: #### 1. Spring Cloud 2023.x 的版本特性 Spring Cloud 2023.x 是基于 Spring Boot 3.x 的版本,因此它需要与 Spring Boot 3.x 系列保持兼容性。根据引用内容,Spring Cloud Alibaba 的最新版本(如 2023.0.1.0)也声明了对 Spring Boot 3.2.x 的支持[^4]。这意味着 Spring Cloud Alibaba 的最新版本理论上可以与 Spring Cloud 2023.x 配合使用。 #### 2. Spring Cloud Alibaba 的版本特性 Spring Cloud Alibaba 的版本设计通常会考虑与 Spring Boot 和 Spring Cloud 的兼容性。例如: - **Spring Cloud Alibaba 2023.0.1.0** 声明支持 Spring Boot 3.2.x 和 Spring Cloud 2023.x[^4]。 - 在选择 Spring Cloud Alibaba 版本时,必须确保其对应的 Spring Boot 和 Spring Cloud 版本与项目需求一致[^1]。 #### 3. 兼容性矩阵 以下是 Spring Cloud Alibaba 和 Spring Cloud 2023.x 的兼容性矩阵: | Spring Cloud Alibaba | Spring Boot | Spring Cloud | |----------------------|-------------|--------------| | 2023.0.1.0 | 3.2.x | 2023.x | 从上述矩阵可以看出,Spring Cloud Alibaba 2023.0.1.0 完全支持 Spring Boot 3.2.x 和 Spring Cloud 2023.x[^4]。 #### 4. 实际配置示例 以下是一个 Maven 配置示例,展示如何在项目中引入 Spring Cloud 2023.x 和 Spring Cloud Alibaba 2023.0.1.0: ```xml <dependencyManagement> <dependencies> <!-- Spring Cloud BOM --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2023.0.0</version> <type>pom</type> <scope>import</scope> </dependency> <!-- Spring Cloud Alibaba BOM --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2023.0.1.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- Spring Cloud Alibaba Nacos Discovery Starter --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- Spring Cloud Alibaba Nacos Config Starter --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> </dependencies> ``` #### 5. 注意事项 - 在引入 Spring Cloud Alibaba 的依赖时,应避免与其他不兼容的 Spring Cloud 或 Spring Boot 版本混用[^3]。 - 如果需要使用特定功能(如 Nacos、Sentinel),需单独引入对应的 starter 模块。 --- ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值