本系列示例与胶水代码地址: https://github.com/HashZhang/spring-cloud-scaffold
入口类注解修改
之前的项目,我们也许会用@SpringCloudApplication
作为我们入口类的注解。这个注解包括:
@SpringBootApplication @EnableDiscoveryClient @EnableCircuitBreaker public @interface SpringCloudApplication { }
其中的@EnableDiscoveryClient
会启动服务发现的客户端,我们这里继续用Eureka,但是EurekaClient不需要这个注解,只要加上spring-cloud-starter-eureka-client
的依赖,就会启动EurekaClient。对于@EnableCircuitBreaker
这个注解,就比较麻烦了。我们引入了spring-cloud-starter-netflix-eureka-client
依赖,这个依赖,包含了hystrix
依赖,导致会自动启用hystrix
实现CircuitBreaker
接口,而我们并不想启用hystrix。并且,使用SpringCloud
的CircuitBreaker
的抽象接口,并不能完全使用resilience4j
的所有功能,spring-cloud
社区维护的resilience4j
的starter
功能还有适用性不如resilience4j
自己维护的starter
。
所以,** 我们这里改为使用@SpringBootApplication
作为入口类注解 **
open-feign 的兼容
升级后,多 FeignClient 类同一微服务,导致 feign 配置 bean 名称重复。当你的项目中存在多个不同的FeignClient
类使用同一个微服务名称的时候:
@FeignClient(value = "service-provider") public interface UserService { } @FeignClient(value = "service-provider") public interface RetailerService { }
升级后,就会报错:
Spring Boot 2.1.x 版本之后默认不支持同名 bean,需要增加配置 spring.main.allow-bean-definition-overriding=true
。但是这是一个非常危险的配置,bean 覆盖开启,如果你定义了重复 Bean,你并不知情,这样可能导致你不确定用的是哪个 bean 导致业务问题。所以不推荐开启。
另一个解决办法是参考:Support Multiple Clients Using The Same Service
使用FeignClient
中的contextId
配置:
@FeignClient(value = "service-provider", contextId = "UserService") public interface UserService { } @FeignClient(value = "service-provider", contextId = "RetailerService") public interface RetailerService { }
但是如果你这样的的FeignClient
非常的多,那么改起来也是比较麻烦。我们这里可以考虑使用类的全限定名作为 contextId。考虑在项目中创建两个和框架中的代码同名同路径的类,分别是org.springframework.cloud.openfeign.FeignClientFactoryBean
还有FeignClientsRegistrar
,相当于改了这两个类的源代码。
要实现的目的是:使用类的全限定名作为 contextId,这样不同类的 contextId 肯定不一样,实现了配置 bean 的名称唯一。同时修改feign配置读取,用name作为key去读取,而不是contextId,也就是能这样配置不同微服务的feign
配置:
feign.client.config.微服务名称.connectTimeout=1000
对于org.springframework.cloud.openfeign.FeignClientFactoryBean
,修改configureFeign方法:
protected void configureFeign(FeignContext context, Feign.Builder builder) { FeignClientProperties properties = this.applicationContext .getBean(FeignClientProperties.class); if (properties != null) { if (properties.isDefaultToProperties()) { configureUsingConfiguration(context, builder); configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder); //使用name,而不用原来的contextId,我们要实现的是多个FeignClient通过contextId指定bean名称实现多个同微服务的FeignClient共存 //但是配置上,同一个微服务的FeignClient统一配置 configureUsingProperties(properties.getConfig().get(this.name), builder); } else { configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder); //使用name,而不用原来的contextId,我们要实现的是多个FeignClient通过contextId指定bean名称实现多个同微服务的FeignClient共存 //但是配置上,同一个微服务的FeignClient统一配置 configureUsingProperties(properties.getConfig().get(this.name), builder); configureUsingConfiguration(context, builder); } } else { configureUsingConfiguration(context, builder); } }
对于org.springframework.cloud.openfeign.FeignClientsRegistrar
,修改registerFeignClients
:
for (String basePackage : basePackages) { Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { // verify annotated class is an interface AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); Map<String, Object> attributes = annotationMetadata .getAnnotationAttributes( FeignClient.class.getCanonicalName()); //使用类的名字作为contextId,实现不同类context不同名称 //这样就不用每个同微服务的feignclient都要填写不同的contextId了 String contextId = candidateComponent.getBeanClassName(); registerClientConfiguration(registry, contextId, attributes.get("configuration")); registerFeignClient(registry, annotationMetadata, attributes, contextId); } } }
详细源码,请参考: