1. 程式人生 > >Spring Cloud(三):Web服務客戶端之Feign

Spring Cloud(三):Web服務客戶端之Feign

前文介紹了實現客戶端負載均衡的Ribbon,但直接使用Ribbon的API來實現服務間的呼叫相對較為繁瑣,服務間的呼叫能否像本地介面呼叫一樣便捷、透明,更符合程式設計習慣呢?Feign就是用來幹這事的。

Feign

Feign是一個宣告式的Web服務客戶端,讓服務之間的呼叫變得非常簡單——定義帶@FeignClient註解的介面,本地直接@Autowired 介面,通過呼叫介面的方法來實現遠端服務的呼叫。

 支援的註解包括Feign註解與JAX-RS(Java API for RESTful Web Services)註解。

 每一個Feign的客戶端都包含一系列對應的元件,Spring Cloud通過FeignClientsConfiguration 為每一個命名的Feign客戶端建立一個元件集合,包括feign.Decoder,feign.Encoder,feign.Contract等。

 

Feign提供的預設bean實現及說明

Bean型別預設實現類說明
Decoder ResponseEntityDecoder ResponseEntityDecoder封裝了SpringDecoder,解碼器,將服務的響應訊息進行解碼
Encoder SpringEncoder 編碼器
Logger Slf4jLogger 日誌框架
Contract SpringMvcContract 支援註解契約,使用SpringMvcContract可以對Spring MVC註解提供支援
Feign.Builder HystrixFeign.Builder 使用斷路器來裝飾Feign介面
Client LoadBalancerFeignClient 如果是ribbon則 LoadBalancerFeignClient, 如果是spring cloud LoadBalancer 則 FeignBlockingLoadBalancerClient,預設ribbon

 

跟Ribbon類似,可以通過配置類來自定義Feign客戶端,如

@FeignClient(name = "hello-service", configuration = CustomConfiguration.class)
public interface StoreClient {
    //..
}

public class CustomConfiguration {
    @Bean
    public Contract feignContract() {
        return new feign.Contract.Default();
    }
    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("user", "password");
    }
}

 

這樣Feign客戶端就包含了FeignClientsConfiguration 與CustomConfiguration 中定義的元件,並且後者會覆蓋前者(即自定義配置的優先順序高於預設配置)。 

 

自定義配置類不需要加註解@Configuration,如果加了且被@ComponentScan掃描到,則將成為所有Feign客戶端的預設配置

 

同樣Feign客戶端也支援通過配置檔案來配置

feign:
    client:
        config:
            feignName:
                connectTimeout: 5000
                readTimeout: 5000
                loggerLevel: full
                errorDecoder: com.example.SimpleErrorDecoder
                retryer: com.example.SimpleRetryer
                requestInterceptors:
                    - com.example.FooRequestInterceptor
                    - com.example.BarRequestInterceptor
                decode404: false
                encoder: com.example.SimpleEncoder
                decoder: com.example.SimpleDecoder
                contract: com.example.SimpleContract

 


對於應用於所有Feign客戶端的全域性預設配置,也可以通過兩種方式 

  1. 通過@EnableFeignClients 的defaultConfiguration 屬性指定預設配置類

  2. 在配置檔案中通過名稱為default的配置實現

    feign:
        client:
            config:
                default:
                    connectTimeout: 5000
                    readTimeout: 5000
                    loggerLevel: basic

     

優先順序同Ribbon, 配置檔案>自定義配置類>預設的FeignClientsConfiguration


案例演示

本文案例演示基於前面搭建的springcloud-eureka 與 springcloud-eureka-client 兩個示例專案。

  1. 新建springcloud-feign專案,pom.xml中加入依賴

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

     spring-cloud-starter-openfeign 包含了spring-cloud-starter-netflix-ribbon 與 spring-cloud-starter-loadbalancer。

 

  1. 啟動類加上@EnableFeignClients 註解

    @SpringBootApplication
    @EnableFeignClients
    public class FeignApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(FeignApplication.class, args);
        }
    }

     

  2. 定義Feign client(feign client支援繼承)

    @FeignClient("hello-service")
    public interface HelloClient extends BaseHelloClient{
    
        @RequestMapping("hello/param")
        String hello(@SpringQueryMap QueryParam param);
    }

     

  3. 呼叫Feign client

    @RestController
    public class FeignController {
    
        @Autowired
        private HelloClient helloClient;
    
        @RequestMapping("feign")
        public String feignTest(){
            return "呼叫Hello-service返回:" + helloClient.hello();
        }
    
        @RequestMapping("feign/param")
        public String feignTestParam(QueryParam param) {
            return "呼叫Hello-service返回:" + helloClient.hello(param);
        }
    }

     

依次啟動三個專案,呼叫http://localhost:8083/feign 能正常返回呼叫hello-service的結果。

 本示例專案還通過@SrpingQueryMap 註解實現了Feign對 pojo用於GET請求引數的支援。如果不加@SrpingQueryMap, 則pojo引數是無法通過Feign client傳遞的,可去掉註解自行驗證下。


一些知識點

  1. 如果需要定製化產生的查詢引數map,可以實現並注入一個自定義的 QueryMapEncoder bean

  2. Feign client的日誌可通過feign client介面的全路徑名進行配置,如logging.level.project.user.UserClient: DEBUG,預設為NONE(即不列印日誌)。全域性設定

    @Configuration
    public class FooConfiguration {
        @Bean
        Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }
    }

    可設定的level值

    • NONE:不記錄日誌 ,預設

    • BASIC:只記錄請求方法,url,以及響應狀態碼與執行時間

    • HEADERS:包括BASIC與請求、響應頭

    • FULL:包括請求與響應的headers,body,metadata

  1. Feign預設使用Ribbon來做負載均衡,可通過配置spring.cloud.loadbalancer.ribbon.enabled=false 來使用spring cloud loadbalancer(目前Ribbon處於維護狀態,近期內不做更新)

  2. 可通過配置feign.okhttp.enabled=true 或 feign.httpclient.enabled=true 來使用OkHttpClient 或ApacheHttpClient, 預設使用的是JDK 原生的URLConnection 傳送HTTP請求,沒有連線池

  3. 如果需要在RequestInterceptor 中使用ThreadLocal中的變數, 那麼要麼禁用Hystrix,要麼設定hystrix的執行緒隔離策略為SEMAPHORE

    feign:
        hystrix:
            enabled: false
    # 或者
    hystrix:
        command:
            default:
                execution:
                    isolation:
                        strategy: SEMAPHORE
  4. 使用有Hystrix fallback的Feign時,會在ApplicationContext中存在多個同類型bean, 導致@Autowired 失效。為了解決這個問題,Spring cloud netflix 將所有feign例項標為@Primary,如果要關閉該特性, 可將@FeignClient的 primary屬性置為false。

    @FeignClient(name = "hello", primary = false)
    public interface HelloClient {
        // ...
    }

     

本文示例程式碼:https://github.com/ronwxy/springcloud-demos

 


 

認真生活,快樂分享
歡迎關注微信公眾號:空山新雨的技術空間