1. 程式人生 > 程式設計 >如何基於springcloud模擬RPC呼叫(Feign)

如何基於springcloud模擬RPC呼叫(Feign)

Feign簡介

Feign是一個宣告式的Web Service客戶端,它能夠讓Web Service客戶端的編寫變得更加容易(你只需建立一個介面,並在介面上新增相應註解即可)。除了Feign自帶的註解外它還支援JAX-RS註解,SpringCloud又為Feign增加了對SpringMVC註解的支援,同時為了能夠使用和Spring Web中預設使用的相同的httpMessageConverter,SpringCloud集成了Ribbon和Eureka,用來在使用Feign時能夠為其提供一個負載均衡的HTTP客戶端。

總起來說,Feign具有如下特性:

1.可插拔的註解支援,包括Feign註解和JAX-RS註解;

2.支援可插拔的HTTP編碼器和解碼器;

3.支援Hystrix和它的Fallback;

4.支援Ribbon的負載均衡;

5.支援HTTP請求和響應的壓縮。

接下來我們將通過對上一章《客戶端負載均衡(Ribbon)》中的 message-center 專案進行改造,演示如何使用Feign。

message-center改造

引入Feign依賴

由於Feign依賴中預設包含了Ribbon,所以只需要在 pom.xml 檔案中引入Feign依賴即可,Ribbon依賴無需重複引入:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.6.RELEASE</version>
  </parent>
 
  <properties>
    <spring-cloud.version>Finchley.SR2</spring-cloud.version>
  </properties>
 
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Eureka-Client 依賴 -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- Feign 依賴 -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
  </dependencies>
 
  <dependencyManagement>
    <dependencies>
      <!-- SpringCloud 版本控制依賴 -->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

修改啟動類

在MessageCenterApplication啟動類上增加@EnableFeignClients註解:

import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class MessageCenterApplication {
  public static void main(String[] args) {
    new SpringApplicationBuilder(MessageCenterApplication.class).web(WebApplicationType.SERVLET).run(args);
  }
}

這裡我們在啟動類中增加了@EnableFeignClients註解,用來開啟Feign客戶端發現功能。

如果你的Feign客戶端類檔案不在Spring的包掃描路徑之中,可以在@EnableFeignClients註解中對Feign客戶端的包路徑進行指定。

@SpringBootApplication
@EnableFeignClients(basePackages = "com.pengjunlee.client.**")
public class MessageCenterApplication {
  public static void main(String[] args) {
    new SpringApplicationBuilder(MessageCenterApplication.class).web(WebApplicationType.SERVLET).run(args);
  }
}

建立Feign客戶端

對外提供服務的HTTP介面定義在MessageController

@RestController
@RequestMapping("/api/v1/msg")
public class MessageController {
 
  @Value("${server.port}")
  private String port;
 
  /**
   * 返回一條訊息
   */
  @GetMapping("/get")
  public String getMsg() {
    return "This message is sent from port: " + port;
  }
}

接下來,我們在消費端message-center中為它建立一個Feign客戶端。新建一個介面取名MessageServiceClient,並在上面新增@FeignClient註解,完整程式碼如下:

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
 
@FeignClient(name = "message-service")
public interface MessageServiceClient {
 
  @GetMapping("/api/v1/msg/get")
  public String getMsg();
}

說明:此處@FeignClient註解的name屬性應與message-service應用的spring.application.name屬性相同,表示為message-service服務建立一個Feign客戶端。介面的對映地址路徑以及介面入參都必須與MessageController中的方法完全相同。

呼叫Feign客戶端

接下來,我們來看一看如何在消費端使用建立好的Feign客戶端對message-service服務進行呼叫,示例程式碼如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.pengjunlee.service.MessageServiceClient;
@RestController
@RequestMapping("/api/v1/center")
public class MessageCenterController {
  @Autowired
  private MessageServiceClient messageService;
  @GetMapping("/msg/get")
  public Object getMsg() {
    String msg = messageService.getMsg();
    return msg;
  }
}

啟動應用,再次請求 http://localhost:8781/api/v1/center/msg/get ,返回如下結果表明服務呼叫成功:

如何基於springcloud模擬RPC呼叫(Feign)

關於傳參

Feign除了支援自帶的註解和JAX-RS註解外,還支援 SpringMVC註解,常用的有:@RequestParam 、@PathVariable、@RequestBody 等。

例如,服務端提供如下兩個服務介面:

/**
 * 獲取訊息詳情
 */
@GetMapping("/api/v1/msg/detail/{id}")
public MessageEntity getDetail(@PathVariable(name = "id") Long id) {
  return messageService.getById(id);
}
 
/**
 * 新建一條訊息
 */
@PostMapping("/api/v1/msg/save")
public MessageEntity save(@RequestBody MessageEntity message) {
  return messageService.save(message);
}

相應的,在Feign客戶端中可以進行如下定義:

/**
 * 獲取訊息詳情
 */
@GetMapping("/api/v1/msg/detail/{id}")
public MessageEntity getDetail(@PathVariable(name = "id") Long id) ;
 
/**
 * 新建一條訊息
 */
@PostMapping("/api/v1/msg/save")
public MessageEntity save(@RequestBody MessageEntity message) ;

重寫Feign的預設配置

在Spring Cloud對Feign的支援實現中,一個核心的概念就是客戶端命名,每一個Feign客戶端都是整個元件系統的一部分,它們相互協同一起工作來按照需求與遠端伺服器取得聯絡。並且它們每一個都有自己的名字,應用程式開發人員可以使用@feignclient來給它取名。Spring Cloud按照自己的需要又使用FeignClientsConfiguration為每一個已命名的客戶端建立了一個ApplicationContext,額外包含了一個feign.Decoder、一個 feign.Encoder 和一個 feign.Contract。你可以通過指定@FeignClient註解的contextId 屬性來設定ApplicationContext的名字。

SpringCloud還允許你在FeignClientsConfiguration的基礎之上使用@FeignClient宣告一些額外的配置,從而實現對Feign客戶端的完全控制,如下例所示:

@FeignClient(name = "message-service",configuration = MessageConfiguration.class)
public interface MessageServiceClient {
  //..
}

在這個例子中,這個Feign客戶端將由FeignClientsConfiguration 和MessageConfiguration中的元件一起組成(後者會覆蓋前者的配置)。

注意:本例中,MessageConfiguration不必用@Configuration註解進行標註,如果確實要加上@Configuration註解,你需要注意把MessageConfiguration排除在@ComponentScan和@SpringBootApplication掃描的包路徑之外,否則它將成為feign.Decoder、feign.Encoder 和 feign.Contract 等的預設值。

下表列出了 Spring Cloud Netflix 預設為Feign提供的所有 Bean(Bean型別 Bean名稱:Bean實現):

Decoder feignDecoder: ResponseEntityDecoder (包裝了一個 SpringDecoder)
Encoder feignEncoder: SpringEncoder
Logger feignLogger: Slf4jLogger
Contract feignContract: SpringMvcContract
Feign.Builder feignBuilder: HystrixFeign.Builder
Client feignClient: 啟用 Ribbon 時是 LoadBalancerFeignClient,否則使用 feign.Client.Default。

你可以使用 OkHttpClient 或者 ApacheHttpClient 的Feign客戶端,只需要將 feign.okhttp.enabled 或者 feign.httpclient.enabled 設定為 true ,並將相應類新增到專案的CLASSPATH即可。你也可以使用自定義的HTTP 客戶端,使用 Apache 時提供一個ClosableHttpClient 型別Bean或者使用OK HTTP時提供一個OkHttpClient 型別Bean。

預設情況下,Spring Cloud Netflix 並沒有為Feign提供下列的Bean,但依然會從Spring容器中查詢這些型別的Bean用來建立Feign客戶端。

Logger.Level
Retryer
ErrorDecoder
Request.Options
Collection<RequestInterceptor>
SetterFactory

建立這些型別的一個Bean 並將它寫到 @FeignClient 宣告的配置中,這樣你就能夠對這些Bean中的每一個進行重寫。例如下面的 MessageConfiguration 利用feign.Contract.Default替代了SpringMvcContract 並向RequestInterceptor 集合中添加了一個RequestInterceptor 。

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

當然,@FeignClient 也支援通過配置檔案進行配置。

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

預設配置可以通過@EnableFeignClients的defaultConfiguration屬性進行指定,然後會被應用到所有的Feign客戶端。如果你希望使用配置檔案對所有的@FeignClient進行配置,可以使用 default 作為Feign客戶端的名稱。

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

如果我們同時使用了@Configuration Bean和檔案配置,檔案配置會優先生效。如果你希望優先使用 @Configuration Bean中的配置,可以將 feign.client.default-to-properties 設定為 false 。

如果你需要在RequestInterceptor中使用ThreadLocal 變數,你需要將Hystrix 的執行緒隔離策略設定為 SEMAPHORE 或者直接禁用Hystrix 。

# To disable Hystrix in Feign
feign:
 hystrix:
  enabled: false
 
# To set thread isolation to SEMAPHORE
hystrix:
 command:
  default:
   execution:
    isolation:
     strategy: SEMAPHORE 

關於超時

在啟用Ribbon的情況下,Feign客戶端是一個LoadBalancerFeignClient Bean,其內部有一個 execute() 方法用來發送一個HTTP請求並獲取響應, 本質上其實還是使用的Ribbon做負載均衡,並使用RestTemplate傳送的請求。execute() 介面宣告如下:

public Response execute(Request request,Request.Options options) throws IOException;

其中,Request 用來封裝HTTP請求的詳細資訊。

/**
 *
 * An immutable request to an http server.
 *
 */
public final class Request {
  private final String method;
  private final String url;
  private final Map<String,Collection<String>> headers;
  private final byte[] body;
  private final Charset charset;
  // ...
}

Options 則封裝了一些請求控制引數:

public static class Options {
 
  private final int connectTimeoutMillis;
  private final int readTimeoutMillis;
  private final boolean followRedirects;
  public Options(int connectTimeoutMillis,int readTimeoutMillis) {
    this(connectTimeoutMillis,readTimeoutMillis,true);
  }
  public Options() {
    this(10 * 1000,60 * 1000);
  } 
  // ...
}

從Options 原始碼來看,Feign客戶端預設的讀取超時時間為60秒。若同時使用了Hystrix,由於Hystrix 預設的讀取超時時間為1秒,會導致Feign客戶端預設的讀取超時時間設定無效,即超過1秒即為讀取超時。可使用如下配置同時對Feign客戶端和Hystrix 的超時配置進行重寫。

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。