解決@FeignClient的地址被映射成Mapping地址

 

前端時間打算將FeignClient進行服務調用的接口類抽取成獨立的模塊

發生報錯後看了一遍SpringMVC的初始化源碼後解決問題

過程比較清晰覺得有必要記錄一下

項目情況:

項目API模塊

A項目 Controller實現API模塊的接口

B項目 FeignClient繼承API模塊的接口

這樣子A項目的Controller與B項目的FeignClient方法就通過 API模塊的接口達成了一致

如圖

需求

我這時候有一個C服務也需要調用A服務的Controller

那就需要把B服務的FeignClient接口複製一份到C服務中使用

問題

當我需要用A服務的接口時, 我就要去其他服務找找有沒有繼承好的接口複製過來用.

感覺複用性不高,重複性動作無意義.還不如把FeignClient也放到API模塊中大家一起用,省的到處複製粘貼

報錯

當我把FeignClient丟到API模塊後出現報錯

Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'com.xxx.remote.market.MarketEntryClient' method 
public abstract com.xxx.XHResult<x> com.xxx.MarketEntryService.findAll()
to {[/market/entry/findAll],methods=[GET]}: There is already 'marketEntryController' bean method

大意就是Maping路徑對應的Bean方法已經存在, 意思就是URL路徑重複註冊了.

驗證

從API模塊中刪除FeignClient接口就不會報錯, 問題點就在於FeignClient接口

尋找報錯位置:

通過到springMVC源碼中搜索報錯的關鍵字 There is already

位置org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#assertUniqueMethodMapping

可以看到是register方法中調用的這個驗證,

alt+f7找到向上找register的調用源頭

 

調用來源org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods

中調用的registerHandlerMethod再調用的register方法

而register方法中的Mapping參數在上層叫做handler,由更上層提供

那就繼續向上找

源頭就在這,這裏名字叫做beanName

if (beanType != null && isHandler(beanType)) {
   detectHandlerMethods(beanName);
}

我在這裏打下斷點重新啓動項目

這裏FeignClient接口竟然被當做Handler類調用註冊了

Controler的方法在這之前已經註冊過,這裏FeignClient的方法再次註冊肯定出問題

看到在調用detectHandlerMethods方法前有一個isHandler(beanType)的判斷,跟進

@Override
protected boolean isHandler(Class<?> beanType) {
   return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
         AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

從源碼上可以看到只要帶有@Controller或@RequestMapping註解的方法都會返回true

正好我們的FeignClient接口是繼承帶有@RequestMapping註解的接口所以也會返回true

這裏從網上查了資料可以通過覆寫isHandler方法來排除@FeignClient註解的方法

在springBoot 2.x中有兩種方式(都是通過繼承RequestMappingHandlerMapping覆寫isHandler方法), 

完整代碼

第一種方式

@Configuration
@ConditionalOnClass({Feign.class})
public class FeignConfig extends WebMvcConfigurationSupport {

    @Override
    @Nullable
    public RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
        return new FeignRequestMappingHandlerMapping();
    }

    private static class FeignRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
        @Override
        protected boolean isHandler(Class<?> beanType) {
            return super.isHandler(beanType) &&
                    !AnnotatedElementUtils.hasAnnotation(beanType, FeignClient.class);
        }
    }
}

這種寫法會讓WebMvcAutoConfiguration失效(SpringBoot自動裝配MVC配置的類)

當項目中有WebMvcConfigurationSupport的類就不會初始化

第二種方式

@Configuration
@ConditionalOnClass({Feign.class})
public class FeignConfig implements WebMvcRegistrations {

    private RequestMappingHandlerMapping requestMappingHandlerMapping = new FeignRequestMappingHandlerMapping();

    @Override
    @Nullable
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return requestMappingHandlerMapping;
    }

    private static class FeignRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
        @Override
        protected boolean isHandler(Class<?> beanType) {
            return super.isHandler(beanType) &&
                    !AnnotatedElementUtils.hasAnnotation(beanType, FeignClient.class);
        }
    }
}

這種方法WebMvcAutoConfiguration會生效

會裝載WebMvcAutoConfiguration裏的

@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {

在構造方法中就會將實現WebMvcRegistrations接口的類傳入

在調用createRequestMappingHandlerMapping的時候就可以把我們自定義的RequestMappingHandlerMapping載入

這兩種方法在我們項目中實測中都沒什麼問題, 可以完美的排除帶有FeignClient的接口方法

 

一些細節

第一種方式是通過繼承WebMvcConfigurationSupport覆寫createRequestMappingHandlerMapping方法

實現的自定義RequestMappingHandlerMapping, 在源碼上也有說明,允許用戶自定義

第二種方法是依賴於SpringBoot默認自動自動配置的方式插入的

其實EnableWebMvcConfiguration繼承的DelegatingWebMvcConfiguration上游也是繼承的WebMvcConfigurationSupport.

如果你項目用了@EnableWebMvc註解

配置類也是DelegatingWebMvcConfiguration

WebMvcConfigurationSupport類上註釋原話

This is the main class providing the configuration behind the MVC Java config.

表示這個類是SpringMVC配置的核心

 

補充:

使用第一種方式之後項目引入靜態資源放在rescoure\static目錄,會出現無法映射的情況(錯誤404)

原因是實現了WebMvcConfigurationSupport會讓SpringBoot默認的靜態資源配置不生效

解決

實現addResourceHandlers方法即可.

如何實現:

照抄SpringBoot默認實現代碼

位置WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#addResourceHandlers

registry.addResourceHandler(staticPathPattern)
        .addResourceLocations(getResourceLocations(
                this.resourceProperties.getStaticLocations()))
        .setCachePeriod(getSeconds(cachePeriod))
        .setCacheControl(cacheControl);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章