溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

聲明式HTTP客戶端 - Spring Cloud OpenFeign

發(fā)布時(shí)間:2020-06-23 18:01:34 來源:網(wǎng)絡(luò) 閱讀:3089 作者:ZeroOne01 欄目:編程語言

Feign

什么是Feign:

  • Feign是Netflix開源的聲明式Http客戶端
  • 關(guān)于Feign的基本使用方式在微服務(wù)之間的通信的方式一文中介紹過,這里不再贅述

Feign的組成:
聲明式HTTP客戶端 - Spring Cloud OpenFeign

  • Feign.Builder:所有的FeignClient都是由Feign.Builder構(gòu)建
  • Client:feign.Client.Default內(nèi)部實(shí)際用的是HttpURLConnection,而LoadBalanceFeignClient默認(rèn)情況下傳入的就是feign.Client.Default,只是加了個(gè)負(fù)載均衡的功能。相比于feign.Client.Default,LoadBalanceFeignClient支持傳入指定的Client
  • Contract:原生的Feign是不支持@GetMapping、@PostMapping...等SpringMVC注解的,Spring Cloud對(duì)其擴(kuò)展后才支持

指定Feign的日志級(jí)別

Feign默認(rèn)是不打印任何日志的,但在實(shí)際項(xiàng)目中接口調(diào)用出現(xiàn)問題需要調(diào)試代碼或需要查看某個(gè)接口調(diào)用所執(zhí)行的耗時(shí),那么第一時(shí)間就會(huì)想到查看Feign的日志,此時(shí)要如何去開啟Feign的日志呢?主要有兩種方式,通過代碼配置或通過配置文件配置。另外,配置生效范圍還分為局部配置和全局配置,我們先來介紹細(xì)粒度的局部配置。

需要注意的是,F(xiàn)eign的日志級(jí)別與Spring Boot不一樣,所以不能直接配置Spring Boot的日志級(jí)別去開啟。Feign的日志級(jí)別如下表:
聲明式HTTP客戶端 - Spring Cloud OpenFeign

1、局部配置 - 代碼配置;通過代碼配置有兩個(gè)主要的步驟,先在代碼定義相應(yīng)的配置類,然后再到配置文件中配置FeignClient接口的日志級(jí)別。首先,定義Feign日志級(jí)別的配置類。代碼如下:

package com.zj.node.contentcenter.configuration;

import feign.Logger;
import org.springframework.context.annotation.Bean;

/**
 * @author 01
 * @date 2019-07-29
 **/
public class UserCenterFeignConfig {

    @Bean
    public Logger.Level level(){
        // 設(shè)置Feign的日志級(jí)別為FULL
        return Logger.Level.FULL;
    }
}

注:該類不要加上@Configuration注解,否則將會(huì)因?yàn)楦缸由舷挛膾呙柚丿B而成為全局配置

由于不是做的全局配置,所以除此之外還需要在FeignClient接口中指定該配置類:

package com.zj.node.contentcenter.feignclient;

import com.zj.node.contentcenter.configuration.UserCenterFeignConfig;
import com.zj.node.contentcenter.domain.dto.user.UserDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = "user-center", configuration = UserCenterFeignConfig.class)
public interface UserCenterFeignClient {

    @GetMapping("/users/{id}")
    UserDTO findById(@PathVariable Integer id);
}

然后在配置文件中添加如下配置:

# 設(shè)置日志級(jí)別
logging:
  level:
    # 這里需要配置為debug,否則feign的日志級(jí)別配置不會(huì)生效
    com.zj.node.contentcenter.feignclient.UserCenterFeignClient: debug

配置完成后,啟動(dòng)項(xiàng)目執(zhí)行相應(yīng)的調(diào)用代碼,控制臺(tái)輸出的日志如下:
聲明式HTTP客戶端 - Spring Cloud OpenFeign


2、局部配置 - 配置文件配置;這種配置方式就比較簡(jiǎn)單,也是比較常用的方式,只需在配置文件中添加如下配置即可:

# 定義feign相關(guān)配置
feign:
  client:
    config:
      # 微服務(wù)名稱
      user-center:
        # 設(shè)置feign日志級(jí)別
        loggerLevel: full

# 設(shè)置日志級(jí)別
logging:
  level:
    # 這里需要配置為debug,否則feign的日志級(jí)別配置不會(huì)生效
    com.zj.node.contentcenter.feignclient.UserCenterFeignClient: debug

1、全局配置 - 代碼配置;同樣定義一個(gè)配置類:

public class GlobalFeignLoggerConfig {

    @Bean
    public Logger.Level level(){
        // 設(shè)置Feign的日志級(jí)別為FULL
        return Logger.Level.FULL;
    }
}

然后配置啟動(dòng)類上的@EnableFeignClients注解的defaultConfiguration屬性,如下:

@EnableFeignClients(
        basePackages = "com.zj.node.contentcenter.feignclient",
        defaultConfiguration = GlobalFeignLoggerConfig.class
)

接著將配置文件中的日志配置從特定的類修改為包名,如下:

# 設(shè)置日志級(jí)別
logging:
  level:
    # 這里需要配置為debug,否則feign的日志級(jí)別配置不會(huì)生效
    com.zj.node.contentcenter.feignclient: debug

2、全局配置 - 配置文件配置;

# 定義feign相關(guān)配置
feign:
  client:
    config:
      # default表示為全局配置
      default:
        # 設(shè)置feign日志級(jí)別
        loggerLevel: full

# 設(shè)置日志級(jí)別
logging:
  level:
    # 這里需要配置為debug,否則feign的日志級(jí)別配置不會(huì)生效
    com.zj.node.contentcenter.feignclient: debug

Feign支持的配置項(xiàng)

由于使用代碼方式配置和使用配置文件配置所支持的配置項(xiàng)不同,所以分為兩類。

1、代碼方式所支持的配置項(xiàng):

配置項(xiàng) 作用
Feign.Builder Feign的入口
Client Feign底層用什么http客戶端去請(qǐng)求
Contract 契約,注解支持
Encoder 編碼器,用于將對(duì)象轉(zhuǎn)換成Http請(qǐng)求消息體
Decoder ×××,將響應(yīng)消息體轉(zhuǎn)換成對(duì)象
Logger 日志管理器
Logger.Level 指定日志級(jí)別
Retryer 指定重試策略
ErrorDecoder 指定異?!痢痢?/td>
Request.Options 超時(shí)時(shí)間
Collection<RequestInterceptor> 請(qǐng)求攔截器
SetterFactory 用于設(shè)置Hystrix的配置屬性,F(xiàn)eign整合Hystrix才會(huì)用

2、配置文件所支持的配置項(xiàng):
聲明式HTTP客戶端 - Spring Cloud OpenFeign

代碼配置 vs 配置文件配置:
聲明式HTTP客戶端 - Spring Cloud OpenFeign

  • 關(guān)于優(yōu)先級(jí):細(xì)粒度配置文件配置 > 細(xì)粒度代碼配置 > 全局配置文件配置 > 全局代碼配置

配置最佳實(shí)踐總結(jié):

  • 盡量使用配置文件配置,配置文件滿足不了需求的情況下再考慮使用代碼配置
  • 在同一個(gè)微服務(wù)內(nèi)盡量保持單一性,例如統(tǒng)一使用配置文件配置,盡量不要兩種方式混用,以免增加定位問題的復(fù)雜度

Feign的繼承

所謂Feign的繼承實(shí)際是為了服務(wù)之間能夠復(fù)用代碼,例如現(xiàn)在用戶中心服務(wù)有一個(gè)按id查詢用戶信息的接口如下:

@Slf4j
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @GetMapping("/{id}")
    public User findById(@PathVariable Integer id) {
        log.info("get request. id is {}", id);
        return userService.findById(id);
    }
}

若我想在內(nèi)容中心服務(wù)通過Feign調(diào)用該接口,就需要新建一個(gè)interface,并編寫如下代碼:

@FeignClient(name = "user-center")
public interface UserCenterFeignClient {

    @GetMapping("/users/{id}")
    UserDTO findById(@PathVariable Integer id);
}

可以看到,方法的定義實(shí)際上是一樣的,所以這時(shí)候就可以利用Feign的繼承特性復(fù)用這種代碼。首先需要?jiǎng)?chuàng)建一個(gè)單獨(dú)的項(xiàng)目或maven模塊,因?yàn)檫@樣才能通過添加maven依賴的方式引入到不同的項(xiàng)目中。這里暫且稱為api模塊吧,在api模塊中定義一個(gè)這樣的接口,代碼如下:

@RequestMapping("/users")
public interface UserApi {

    @GetMapping("/{id}")
    User findById(@PathVariable Integer id);
}

然后在用戶中心服務(wù)中添加api模塊的依賴,接著實(shí)現(xiàn)UserApi接口,改寫之前的UserController如下:

@Slf4j
@RestController
@RequiredArgsConstructor
public class UserController implements UserApi {

    private final UserService userService;

    @Override
    public User findById(@PathVariable Integer id) {
        log.info("get request. id is {}", id);
        return userService.findById(id);
    }
}

在內(nèi)容中心服務(wù)中也添加api模塊的依賴,改寫之前的UserCenterFeignClient代碼,讓其繼承UserApi,代碼如下:

@FeignClient(name = "user-center")
public interface UserCenterFeignClient extends UserApi {
}

可以看到,繼承了UserApi后,此時(shí)不需要再定義與目標(biāo)接口相同的方法了,復(fù)用了上級(jí)接口的代碼,這就是所謂Feign的繼承。

其實(shí)關(guān)于這種使用方式存在許多爭(zhēng)議,我們來看看官方怎么說:

It is generally not advisable to share an interface between a server and a client. It introduces tight coupling, and also actually doesn’t work with Spring MVC in its current form (method parameter mapping is not inherited).

大致翻譯如下:

通常不建議在服務(wù)提供者(server)和服務(wù)消費(fèi)者(client)之間共享接口,因?yàn)檫@種方式引入了緊耦合,并且實(shí)際上在當(dāng)前形式下也不適用于Spring MVC(方法參數(shù)映射不會(huì)被繼承)

  • 關(guān)于方法參數(shù)映射不會(huì)被繼承:在上面的代碼示例中可以看到,實(shí)現(xiàn)UserApi的UserController方法參數(shù)上,依舊需要寫MVC相關(guān)的注解,因?yàn)檫@些注解是不會(huì)被繼承的。簡(jiǎn)單來說就是這類注解得寫在實(shí)現(xiàn)類的方法參數(shù)上才會(huì)生效,而對(duì)于團(tuán)隊(duì)中對(duì)此不甚熟悉的開發(fā)人員來說也會(huì)造成一定的”迷惑“

官網(wǎng)文檔地址如下:

https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#spring-cloud-feign-inheritance

關(guān)于繼承特性的爭(zhēng)議:

  • 官方觀點(diǎn):不建議使用
    • 理由上面已說明
  • 業(yè)界觀點(diǎn):很多公司使用
    • 理由1:代碼可復(fù)用;面向契約
    • 理由2:在業(yè)務(wù)需求變更比較頻繁的情況,無需修改太多的代碼

如何抉擇:

根據(jù)項(xiàng)目情況權(quán)衡利弊即可,若需要這種特性帶來的好處又可以承受緊耦合帶來的負(fù)面影響,那么就選擇使用該特性,否則就不要使用


Feign發(fā)送多參數(shù)GET請(qǐng)求的坑

使用過Spring MVC的都知道,當(dāng)一個(gè)GET接口有多個(gè)請(qǐng)求參數(shù)時(shí)可以使用對(duì)象來接收。例如用戶服務(wù)中,有這樣一個(gè)接口如下:

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/query")
    public User query(User user) {
        return user;
    }
}

使用postman發(fā)送如下請(qǐng)求是可以正常接收并響應(yīng)的:
聲明式HTTP客戶端 - Spring Cloud OpenFeign

所以在另一個(gè)服務(wù)中使用Feign調(diào)用這種類型的接口時(shí),我們很自然而然的就會(huì)寫成如下形式:

@FeignClient(name = "user-center")
public interface UserCenterFeignClient {

    @GetMapping("/users/query")
    UserDTO query(UserDTO userDTO);
}

實(shí)際上這種使用Feign發(fā)送多參數(shù)GET請(qǐng)求的方式是會(huì)有坑的,因?yàn)閷⒍鄥?shù)包裝成對(duì)象時(shí),F(xiàn)eign在底層會(huì)將其轉(zhuǎn)換為POST請(qǐng)求,并把對(duì)象序列化塞到http body中,所以就會(huì)由于不支持該請(qǐng)求方法而報(bào)405錯(cuò)誤。

關(guān)于這個(gè)坑我們做個(gè)實(shí)驗(yàn)來驗(yàn)證一下,在內(nèi)容中心服務(wù)中,定義一個(gè)接口如下:

@RestController
@RequestMapping("/shares")
@RequiredArgsConstructor
public class ShareController {

    private final UserCenterFeignClient userCenterFeignClient;

    @GetMapping("/queryUser")
    public UserDTO queryUser(UserDTO userDTO){
        return userCenterFeignClient.query(userDTO);
    }
}

然后通過postman進(jìn)行請(qǐng)求,可以看到直接報(bào)405錯(cuò)誤了:
聲明式HTTP客戶端 - Spring Cloud OpenFeign

此時(shí)用戶服務(wù)的控制臺(tái)中,輸出了如下日志信息:

Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]

那么我們要如何去解決這個(gè)坑呢?最顯而易見的方式就是不將參數(shù)包裝成對(duì)象,而是拆解開來使用@RequestParam一個(gè)個(gè)寫上去。然而這種方式有個(gè)很明顯的弊端,如果有很多參數(shù)的時(shí)候,一個(gè)個(gè)寫就比較累,而且代碼也不好看。在這種“走投無路”的情況下,就會(huì)想著要不就不用GET了,換成POST吧。雖然這種方法也可行,但是卻違背了RESTful的規(guī)范。

那有沒有一個(gè)完美的解決方案呢?答案是有的,那就是使用@SpringQueryMap注解,該注解相當(dāng)于feign.QueryMap,目的是將對(duì)象轉(zhuǎn)換為GET參數(shù)。那么我們就來試試看吧,修改UserCenterFeignClient代碼如下:

@FeignClient(name = "user-center")
public interface UserCenterFeignClient {

    @GetMapping("/users/query")
    UserDTO query(@SpringQueryMap UserDTO userDTO);
}

注:該注解在spring-cloud-starter-openfeign: 2.1.0及之后的版本才開始支持的,之前的版本只能使用其他方式解決該問題。之所以會(huì)有這個(gè)坑,也是因?yàn)樵鶩eign的體系讓Spring Cloud無法封裝得與Spring MVC完全一致的編程體驗(yàn)

修改完代碼后重啟項(xiàng)目,再次使用postman請(qǐng)求就沒有報(bào)錯(cuò)了:
聲明式HTTP客戶端 - Spring Cloud OpenFeign


Feign脫離Ribbon使用

我們都知道Feign內(nèi)部整合了Ribbon,所以才能有負(fù)載均衡功能及從服務(wù)發(fā)現(xiàn)組件獲取服務(wù)實(shí)例的調(diào)用地址功能。那么如果需要調(diào)用一個(gè)沒有注冊(cè)到服務(wù)發(fā)現(xiàn)組件上的服務(wù)或地址,即脫離Ribbon去使用Feign的話,要如何做呢?非常簡(jiǎn)單,只需要配置一下@FeignClient注解的url屬性即可。如下示例:

// name是必須配置的,否則項(xiàng)目都無法啟動(dòng),url屬性通常是配置basic地址
@FeignClient(name = "baidu", url = "https://www.baidu.com")
public interface TestFeignClient {

    @GetMapping
    String index();
}

然后定義一個(gè)接口測(cè)試一下:

@RestController
@RequiredArgsConstructor
public class TestController {

    private final TestFeignClient feignClient;

    @GetMapping("/baidu")
    public String baiduIndex() {
        return feignClient.index();
    }
}

啟動(dòng)項(xiàng)目,瀏覽器訪問如下:
聲明式HTTP客戶端 - Spring Cloud OpenFeign


Feign性能優(yōu)化

RestTemplate VS Feign:
聲明式HTTP客戶端 - Spring Cloud OpenFeign

從上圖中可以看到,F(xiàn)eign只在性能和靈活性上輸給了RestTemplate,至于靈活性官方也說了無論如何優(yōu)化也不可能像RestTemplate一樣,而性能則是可以進(jìn)一步提高的。

默認(rèn)情況下Feign的性能在RestTemplate的50%左右,雖然項(xiàng)目的瓶頸一般不會(huì)出現(xiàn)在Feign上,但如果能讓Feign的性能更好一些,也只是有利無害,所以本小節(jié)簡(jiǎn)單談?wù)凢eign的性能優(yōu)化。

默認(rèn)情況下Feign底層是使用HttpURLConnection發(fā)送請(qǐng)求的,眾所周知HttpURLConnection是沒有使用連接池的,所以可以針對(duì)這點(diǎn)進(jìn)行優(yōu)化。例如,將底層的http請(qǐng)求客戶端為更換為Apache的HttpClient或者OkHttp等使用了連接池的http客戶端,據(jù)測(cè)試使用了連接池后可以提升15%左右的性能。

另一個(gè)優(yōu)化的點(diǎn)就是設(shè)置合理的日志級(jí)別,之前已經(jīng)介紹過日志級(jí)別的配置方式了,所以這里僅演示如何為Feign更換其他的http請(qǐng)求客戶端及配置連接池。

這里先以HttpClient為例,第一步加依賴:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

第二步,添加配置:

feign:
  httpclient:
    # 讓feign啟用httpclient作為發(fā)送http請(qǐng)求的客戶端
    enabled: true
    # 最大連接數(shù)
    max-connections: 200
    # 單個(gè)路徑的最大連接數(shù)
    max-connections-per-route: 50    

使用okhttp也是一樣的,第一步加依賴:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

第二步,添加配置:

feign:
  okhttp:
    # 讓feign啟用okhttp作為發(fā)送http請(qǐng)求的客戶端
    enabled: true
    # 最大連接數(shù)
    max-connections: 200
    # 單個(gè)路徑的最大連接數(shù)
    max-connections-per-route: 50
向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI