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的解决方案,就不重复造轮子了
  • 24
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Cloud 2021.0.5.0 是 Spring Cloud 的一个版本,它可以和 Sentinel-datasource-nacos 模块一起使用。下面是使用 Spring Cloud 2021.0.5.0 和 Sentinel-datasource-nacos 的步骤: 1. 在 pom.xml 文件中引入以下依赖: ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2021.0.5.0</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-nacos-config</artifactId> <version>2.2.5.RELEASE</version> </dependency> ``` 2. 配置 Nacos 作为 Sentinel 的数据源,例如: ```yaml spring: cloud: sentinel: transport: dashboard: localhost:8080 port: 8719 nacos: server-addr: localhost:8848 groupId: DEFAULT_GROUP dataId: sentinel rule-type: flow ``` 3. 在 Nacos 中创建对应的命名空间和配置文件,例如: ```yaml spring: cloud: sentinel: transport: dashboard: localhost:8080 port: 8719 nacos: server-addr: localhost:8848 groupId: DEFAULT_GROUP dataId: sentinel rule-type: flow data-type: json namespace: 7e1d4f1f-8a31-4f0d-9f8d-0d4a563beba4 ``` 4. 在应用程序中添加 Sentinel 规则,例如: ```java @PostConstruct public void init() throws Exception { String appName = env.getProperty("spring.application.name"); ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(nacosServerAddr, groupId, appName + "-flow-rule", source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {})); FlowRuleManager.register2Property(flowRuleDataSource.getProperty()); } ``` 以上是 Spring Cloud 2021.0.5.0 使用 Sentinel-datasource-nacos 的基本步骤,具体实现可以根据项目需要进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值