Spring Boot | 使用Feign作爲HTTP客戶端調用遠程HTTP服務

Feign 簡介

Spring Cloud的Feign支持的一箇中心概念就是命名客戶端.Feign客戶端使用@FeignClient註冊組合成組件,按需調用遠程服務器.
Spring Cloud使用FeignClientsConfiguration創建一個新的集合作爲每個命名客戶端的ApplicationContext(應用上下文), 包含feign.Decoder,feign.Encoder和feign.Contract.

你可以使用 Jersey 和 CXF 這些來寫一個 Rest 或 SOAP 服務的java客服端。你也可以直接使用 Apache HttpClient 來實現。但是 Feign 的目的是儘量的減少資源和代碼來實現和 HTTP API 的連接。通過自定義的編碼解碼器以及錯誤處理,你可以編寫任何基於文本的 HTTP API。

Feign 通過註解注入一個模板化請求進行工作。只需在發送之前關閉它,參數就可以被直接的運用到模板中。然而這也限制了 Feign,只支持文本形式的API,它在響應請求等方面極大的簡化了系統。同時,它也是十分容易進行單元測試的。

Spring Cloud應用在啓動時,Feign會掃描標有@FeignClient註解的接口,生成代理,並註冊到Spring容器中。生成代理時Feign會爲每個接口方法創建一個RequetTemplate對象,該對象封裝了HTTP請求需要的全部信息,請求參數名、請求方法等信息都是在這個過程中確定的,Feign的模板化就體現在這裏。

maven依賴

<dependencies>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
</dependencies>

Feign客戶端接口(消費者)

@FeignClient(value = "user", url = "${addr.url}")
public interface UserClient {
        //feign獨有的註解方式 
        @RequestLine("GET /user/index")
        String index();

        @RequestMapping(value = "/get0/{id}", method = RequestMethod.GET)
        User findById(@PathVariable("id") Long id);

        @RequestMapping(value = "/get1", method = RequestMethod.GET)
        User get1(@RequestParam("id") Long id, @RequestParam("name") String name);

        @RequestMapping(value = "/get2", method = RequestMethod.GET)
        User get2(@RequestParam Map<String, Object> map);

        @RequestMapping(value = "/hello2", method=RequestMethod.GET)
        User hello2(@RequestHeader("name") String name, @RequestHeader("age") Integer age);
        
        @RequestMapping(value = "/hello3", method=RequestMethod.POST)
        String hello3(@RequestBody User user);
}

當前工程中有和Feign Client中一樣的Endpoint時,Feign Client的類上不能用@RequestMapping註解否則,當前工程該endpoint http請求且使用accpet時會報404.但是,如果不包含Accept header時,請求是可以的
或者像下面不在Feign Client上使用@RequestMapping註解,請求也是可以的,無論是否包含Accept:

@FeignClient(name = "card", url = "http://localhost:7913", 
	fallback = CardFeignClientFallback.class,
	configuration = FeignClientConfiguration.class)
public interface CardFeignClient {
    @RequestMapping(value = "/v1/card/balance", method = RequestMethod.POST, 
                produces = MediaType.APPLICATION_JSON_VALUE)
    Info info();
}

Feign將方法簽名中方法參數對象序列化爲請求參數放到HTTP請求中的過程,是由編碼器(Encoder)完成的。同理,將HTTP響應數據反序列化爲java對象是由解碼器(Decoder)完成的。
默認情況下,Feign會將標有@RequestParam註解的參數轉換成字符串添加到URL中,將沒有註解的參數通過Jackson轉換成json放到請求體中。注意,如果在@RequetMapping中的method將請求方式指定爲POST,那麼所有未標註解的參數將會被忽略,例如:

@RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET)
void update(@PathVariable("groupId") Integer groupId, 
            @RequestParam("groupName") String groupName, 
                             DataObject obj);

此時因爲聲明的是GET請求沒有請求體,所以obj參數就會被忽略。
在Spring Cloud環境下,Feign的Encoder只會用來編碼沒有添加註解的參數。如果你自定義了Encoder, 那麼只有在編碼obj參數時纔會調用你的Encoder。對於Decoder, 默認會委託給SpringMVC中的MappingJackson2HttpMessageConverter類進行解碼。只有當狀態碼不在200 ~ 300之間時ErrorDecoder纔會被調用。ErrorDecoder的作用是可以根據HTTP響應信息返回一個異常,該異常可以在調用Feign接口的地方被捕獲到。我們目前就通過ErrorDecoder來使Feign接口拋出業務異常以供調用者處理

Feign在默認情況下使用的是JDK原生的URLConnection發送HTTP請求,沒有連接池,但是對每個地址會保持一個長連接,即利用HTTP的persistence connection 。我們可以用Apache的HTTP Client替換Feign原始的http client, 從而獲取連接池、超時時間等與性能息息相關的控制能力。Spring Cloud從Brixtion.SR5版本開始支持這種替換,首先在項目中聲明Apache HTTP Client和feign-httpclient依賴:

<!-- 使用Apache HttpClient替換Feign原生httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <dependency>
            <groupId>com.netflix.feign</groupId>
            <artifactId>feign-httpclient</artifactId>
            <version>${feign-httpclient}</version>
        </dependency>

然後在application.properties中添加
feign.httpclient.enabled=true

配置文件application.yml

#feign
feign:
  hystrix:
    enabled: true
  httpclient:
    enabled: true
addr:
  url: http://10.164.13.166:8080/msg-center/v1/sms/send

配置類Configuration

使用了配置@Configuration參數,自己定義Configuration類來自定義FeignClientsConfiguration,並且Configuration類的類路徑不能在啓動類Application的掃描路徑下,否則會覆蓋該項目所有的Feign接口的默認配置

package com.spring.feigin.config;

import feign.auth.BasicAuthRequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class EurekaConfiguration {
    //配置只允許使用Feign自己的註解url方式:@RequestLine
    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("zhihao.miao", "123456");
    }
    //配置eureka的登錄名和密碼
    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("zhihao.miao", "123456");
    }
}

定義主體啓動類

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

feign使用Hystrix

添加依賴
<!-- 整合hystrix,其實feign中自帶了hystrix,引入該依賴主要是爲了使用其中的hystrix-metrics-event-stream,用於dashboard -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
feign接口註解中,增加fallback指定類
@FeignClient (value = "${name}",  url = "${addr.url}", fallback = UserFallBack.class)
指定類中處理熔斷的後續邏輯
@Slf4j
@Component
public class PosMemberClientFallBack implements PosMemberClient {
    @Override
    public String addMember(MemberDTO memberDTO) {
        log.warn("調用會員服務失敗");
        return ("調用服務失敗,熔斷”);
    }
}
配置文件
#hystrix
hystrix:
  command:
    default:
      execution:
         isolation:
           strategy: THREAD
           thread:
             timeoutInMilliseconds: 30000
   threadpool:
     default:
       coreSize: 500 #缺省爲10
修改啓動類

在啓動類上添加@EnableHystrix 註解

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

配置的fallback class也必須在FeignClient Configuration中實例化,否則會報
java.lang.IllegalStateException: No fallback instance of type class異常。

 @Configuration
    public class FooConfiguration {
        @Bean
        @Scope("prototype")
        public Feign.Builder feignBuilder() {
            return Feign.builder();
        }
        //實例化fallback
        @Bean
        public HystrixClientFallback fb(){
            return new HystrixClientFallback();
        }
    }

在Spring Cloud中,Feign和Ribbon在整合了Hystrix後,可能會出現首次調用失敗的問題,要如何解決該問題呢?
Hystrix默認的超時時間是1秒,如果超過這個時間尚未響應,將會進入fallback代碼。
而首次請求往往會比較慢(因爲Spring的懶加載機制,要實例化一些類),這個響應時間可能就大於1秒了。
解決方案 :
方法一
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000
該配置是讓Hystrix的超時時間改爲5秒
方法二
hystrix.command.default.execution.timeout.enabled: false
該配置,用於禁用Hystrix的超時時間
方法三
feign.hystrix.enabled: fals
該配置,用於索性禁用feign的hystrix。該做法除非一些特殊場景,不推薦使用。

Feign的擴展配置

#Hystrix支持,如果爲true,hystrix庫必須在classpath中
feign.hystrix.enabled=false

#請求和響應GZIP壓縮支持
feign.compression.request.enabled=true
feign.compression.response.enabled=true
#支持壓縮的mime types
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048

# 日誌支持
logging.level.project.user.UserClient: DEBUG
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章