你不知道的事---SringCloud的feign的繼承特性

經典雞翅發表於2020-05-20

前言

說起SpringChoud的feign大家用過的都說好。Feign是Netflix開發的宣告式、模板化的HTTP客戶端。對於我們微服務來說,微服務之間的api呼叫,使用feign來說是再方便不過的。本文先介紹一下,傳統的feign的呼叫寫法方式,再介紹我們的重點feign的繼承特性。
feign的繼承特性有很多的好處,可以進行引數和方法的統一管理,一次修改,feign和具體的controller都變了。
總之好處還是不少的。

傳統的feign的實現方式

傳統的feign是怎樣的實現的呢,我們先通過springmvc搞了一個controller,在controller裡面實現我們程式碼。此時另一個微服務想直接呼叫這個請求,那麼被呼叫的微服務就可以宣告一個feign的客戶端,將自身要提供給外部呼叫的方法,feign提供的方法的requestMapper路徑對映和controller中的保持一致即可訪問的到。

傳統feign的程式碼實現

我們先寫一個傳統的controller。我們的目的很簡單,這個controller來返回123。

@RestController
public class DemoController {

    @PostMapping("/demo/list")
    public String demo(
            @RequestBody DemoFeignQueryVO demoFeignQueryVO){
      return "123";
    }
}

有了controller,我們來寫feign。feign的程式碼是十分簡單的,只要保證feignClint的值是我們要呼叫的微服務的值,然後其中的postMapping的值和上方controller的一樣即可呼叫到。

@FeignClient(value = "user-system")
@Component
public interface IDemoFeign {

    @PostMapping("/demo/list")
    public String demo(
            @RequestBody DemoFeignQueryVO demoFeignQueryVO);

}

傳統feign的缺點

由上方的程式碼來看,有什麼缺點呢,比如我要更改controller的引數,那麼我需要改兩處地方,一處是自己的controller實現,一處是feign的介面。如果忘改了的話,就會產生未知的錯誤。如果更改了controller的路徑對映呢。同樣的,也需要改兩處位置。這是傳統形式的一種缺點。

feign的繼承特性

如果我們使用feign的繼承特性,就不會有上方的情況產生,繼承特性就是說,我們弄一個共用的介面,在介面上佈置我們的requestMapping註解,規定我們的方法引數。然後通過controller實現這個介面。controller就可以針對介面中的方法進行一一的實現。然後針對於我們的feign介面,我們直接繼承共用的介面。這樣feign介面和controller方法是完全保持一致的,當需要修改的時候,完全不需要兩個地方一起修改,直接更改共用的介面即可。

feign的繼承特性的程式碼實現

我們將上述的傳統方案的程式碼進行一個改寫,通過對比,就可以看出好處。
先宣告一個共用的介面。這裡面寫我們所有要提供給外面的方法。

public interface DemoInterface {
 
    @PostMapping("/demo/list")
    public String demo(
            @RequestBody DemoFeignQueryVO demoFeignQueryVO);
    
}

有了介面後,我們的controller直接實現這個介面。

@RestController
public class DemoController implements DemoInterface {

    @Override
    public String demo(
            @RequestBody DemoFeignQueryVO demoFeignQueryVO){
      return "123";
    }
}

我們此時就可以看到,controller不用關心路徑對映了。而且針對於引數來說,如果介面一修改,controller必然會提示報錯,編譯不會通過,不會使你漏下改動。
接下來,實現一下我們的feign,feign是極其簡單的,只要繼承共用的介面即可。

@FeignClient(value = "user-system")
public interface DemoFeign extends DemoInterface  {
}

看上面是不是覺得非常方便。feign和controller都不用關心路徑,通過共用埠保證了路徑一定一致,介面的引數檢查也保障了引數不會產生問題。

feign繼承特性帶來的requestMapping載入問題

在我們上述的提供方案中,如果requestMapping只是在註解上,是不會出現這種問題的。但是如果當requestMapping在controller的類上的時候,那麼問題就來了。我們先看一下SpringMvc載入requestMapping到容器的邏輯是什麼

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

這個是處理請求對映的判斷依據。從實現中我們看到,只要被掃描的類包含了@Controller或@RequestMapping註解,就會被springMVC載入,那麼controller實現了介面具備了requestmapping。feignClient也繼承了介面,那麼也就具備了requestMapping。兩個一摸一樣的requstMapping,那麼是必然會產生衝突的。那麼我們的目的是什麼呢。controller的正常載入,feignClient不應該進行載入。
我們重新修改下feign的配置類。

@Configuration
@ConditionalOnClass({Feign.class})
public class FeignConfiguration {
    @Bean
    public WebMvcRegistrations feignWebRegistrations() {
        return new WebMvcRegistrationsAdapter() {
            @Override
            public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
                return new FeignRequestMappingHandlerMapping();
            }
        };
    }
    private static class FeignRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
        @Override
        protected boolean isHandler(Class<?> beanType) {
            return super.isHandler(beanType) &&
                    !AnnotatedElementUtils.hasAnnotation(beanType, FeignClient.class);
        }
    }
}

上述程式碼我們進行了一個排除,帶feignClint我們不進行載入。
至此feign繼承特性的一個隱藏的坑在這裡已經沒了,大家可以放心使用了。

總結

feign的繼承特性是十分方便的一種方式,實實在在的減少了程式碼編寫量,也保證了路徑對映和引數的一致性。十分好用。
文中難免有不足,歡迎大家批評指正。

相關文章