The bean 'xx.FeignClientSpecification', defined in null, could not be registered


原文鏈接:https://blog.csdn.net/CL_YD/article/details/103408028

問題表現

springboot從1.x升級到2.x后,解決了好多好多問題,什么maven依賴、import package變化、包沖突、編譯不通過、application.properties配置變更等一系列問題后,終於來到了啟動環節,啟動后控制台提示ApplicationContext啟動失敗,里面有一句The bean 'xx.FeignClientSpecification', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.

問題分析

很明顯是兩個Bean注冊到Spring容器中的名稱相同,但是有沒有開啟spring.main.allow-bean-definition-overriding=true

為什么1.x中可以正常啟動,2.x就不行呢?因為1.x中spring.main.allow-bean-definition-overriding默認是 true,而2.x中默認是false

到這里已經有一個很簡單的解決方案:在application.properties里面添加一行spring.main.allow-bean-definition-overriding=true,但是這並不是最完美的方案,為什么2.x要設置為false,為什么FeignClient的bean名稱會相同?如何去避免FeignClient在IOC容器中的名稱相同能?

首先簡單理以下FeignClient的注冊原理:

    • 在啟動類上添加@EnableFeignClients 注解,然后在Feign接口上添加@FeignClient注解,該接口就會被注冊到IOC容器;
    • @EnableFeignClients注解上有一個@Import(FeignClientsRegistrar.class),這個FeignClientsRegistrar類負責加載和注冊FeignClient;
    • FeignClientsRegistrarregisterBeanDefinitions方法內容如下:
      @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerDefaultConfiguration(metadata, registry); registerFeignClients(metadata, registry); } 
    • 暫時不看第一行的

registerDefaultConfiguration

      方法,直接進

registerFeignClients

      方法查看;
    • 這個方法的核心是找出所有@FeignClient注解的接口,並依此注冊,但注冊時並不是僅僅注冊FeignClient本身:
      registerClientConfiguration(registry, name,attributes.get("configuration")); registerFeignClient(registry, annotationMetadata, attributes); 

registerBeanDefinitions

      類似,依然是先注冊一個

configuration

    ,再注冊FeignClient;
  • 依然暫時不看registerClientConfiguration方法,直接進入registerFeignClient方法,發現注冊FeignClient使用的是FeignClient對應接口的className作為beanName的,因此不可能重復,這時候問題就回到了我們暫時不看的兩個方法;
  • 先進入registerClientConfiguration方法,發現將一個名為nameconfiguration注冊到了IOC容器中,其中configuration是一個FeignClientSpecification類型的對象,來自於@FeignClientconfiguration屬性,而name的獲取方法如下:
    private String getClientName(Map<String, Object> client) { if (client == null) { return null; } String value = (String) client.get("contextId"); if (!StringUtils.hasText(value)) { value = (String) client.get("value"); } if (!StringUtils.hasText(value)) { value = (String) client.get("name"); } if (!StringUtils.hasText(value)) { value = (String) client.get("serviceId"); } if (StringUtils.hasText(value)) { return value; } throw new IllegalStateException("Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName()); }
  • 可以看出:name來自於@FeignClient的一個屬性,到底取哪一個值,又一個優先級:contextId、value、name、serviceId,如果@FeignClient注解只指定了value值,而幾個@FeignClientvalue值一樣,那么在注冊FeignClientSpecification的時候必定會出現beanName重復;
  • 我想springboot 2.x將允許beanName重復的配置值從true改為false,應該是為了注冊到IOC容器和使用IOC容器的bean更加安全和規范,避免同名bean被覆蓋,也避免使用beanName注入時類型錯誤;
  • 那這個FeignClientSpecification有什么用呢?其實這個類是FeignClient的一些配置,比如重試、超時、日志策略,而FeignClient設計的思路是,同一個service,使用同一個configuration,方便管理,但有時候我們並不是把同一個service的所有接口都放在一個FeignClient里,而是分散開來;
  • 再回到registerDefaultConfiguration方法,這個方法注冊了一個全局通用的配置,當某一個FeignClient的配置為null的時候,就是用這個default的配置。

解決方案

解決方案有二:

    1. 簡單粗暴:spring.main.allow-bean-definition-overriding=true,但隱患有二:一是假設真有beanName相同但真實對象不同,而注入的時候使用了beanName注入,可能導致異常;二是假設需要配置configuration,只在某一個FeignClient配置了configuration,可能導致失效或不應該使用configuration的FeignClient也使用配置策略,因為允許重寫就導致同一個名稱的bean到底對應哪一個對象,嚴重依賴於注冊順序。
    2. 更多考慮:把同一個service的所有接口整合到同一個FeignClient接口中,如果整合有困難,可以考慮指定contextId,因為contextId的優先級最高,注冊到IOC容器的名稱也會因為contextId的不同而不同。但也有一個隱患:指定contextId可能會導致每個FeignClient都需要指定同一個configuration才可以讓同一個service的配置策略生
      /** * 1.5.21 */ @FeignClient(EurekaService.SID) @RequestMapping(EurekaService.CONTEXT) public interface SidFeignClient { } /** * 2.1.6 */ @FeignClient(value = EurekaService.SID, contextId = "sidFeignClient") @RequestMapping(EurekaService.CONTEXT) public interface SidFeignClient { } 

 

綜上所訴,最好的辦法是將同一個service的接口整合到同一個FeignClient中,這樣方便管理和維護。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM