本系列示例與膠水代碼地址: 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);
}
}
}
詳細源碼,請參考: