Spring Cloud升級之路 - Hoxton - 2.入口類註解修改與OpenFeign的改造

本系列示例與膠水代碼地址: 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。並且,使用SpringCloudCircuitBreaker的抽象接口,並不能完全使用resilience4j的所有功能,spring-cloud社區維護的resilience4jstarter功能還有適用性不如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);
        }
    }
}

詳細源碼,請參考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章