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

sprinboot升級啓動時FeignClient報錯

問題表現

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中,這樣方便管理和維護。

發佈了78 篇原創文章 · 獲贊 20 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章