Spring-Cloud之Feign


原文:https://blog.vchar.top/java/1621167133.html

Feign的引入可以讓我們通過接口注解的形式實現服務間的調用,讓調用者無需再關心接口地址這些配置,同時對於接口需要的參數能夠更清晰地了解,簡化了調用的流程。下面我們通過一些Feign的示例帶你快速了解如何使用它。示例使用的Spring-Cloud的版本是Hoxton.SR8,Spring-Boot的版本是2.3.4.RELEASE。示例項目的源代碼

添加相關的依賴

feign中已經對ribbon進行集成支持,在spring最新版本已經開始棄用ribbon了,而推薦使用Cloud LoadBalancer(feign也支持)。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

使用示例

feign其實是一個偽RPC的組件;我們之前在服務間調用需要的使用者去手動設置請求地址,在加入ribbon后,雖然省略了具體的服務器地址和端口,但是接口地址還是需要手動編寫;而引入feign后就可以解決這個問題,由接口的開發者提供給調用者一個接口類的方法的jar包,調用者直接依賴該包,然后注入該接口類即可像調用方法一樣調用其他的服務。同時feign提供了在方法調用失敗時的處理策略。這樣如果接口地址發生改變了,那么只需要更新jar包即可;同時調用者在使用的時候也會更加清晰具體的參數。

要使feign生效,我們還需要在啟動類上添加如下注解來啟用feign:@EnableFeignClients ;下面對該注解主要的屬性進行說明:

  • value和name:服務名稱;這個2個隨意選擇一個配置即可;
  • path:請求路徑的前綴;當多個接口地址有相同的前綴時,可以將前綴配置到path中;
  • fallback和fallbackFactory:這2個都是對接口熔斷降級處理,也就是異常處理,只能選擇其中一個;

簡單示例

定義一個接口,在里面編寫需要調用的某個服務的restful接口,然后在類上添加 @FeignClient ,在上面配置要調用的服務名稱,也就是說feign會幫助我們拼接這個請求地址(也就是我們在ribbon那里做的事情),因此必須正確才可以。

@FeignClient(name = "feign-server-b") // 服務名稱,必須匹配,否則待會將無法替換為真實的地址
public interface GoodsFeignClientDemo1 {

    /**
     * 這里的路徑必須和對應要調用的接口地址對應
     */
    @GetMapping("/goods")
    Goods findById(@RequestParam(value = "id")Long id);
}

在業務代碼中使用

@Service
public class OrderServiceImpl implements OrderService {
    
    // 注入該接口的feign接口
    @Autowired
    private GoodsFeignClientDemo1 goodsFeignClientDemo1;

    @Override
    public void demo1() {
        Goods goods = goodsFeignClientDemo1.findById(1L);
        System.out.println("示例1:"+goods);
    }
}

編寫feign客戶端接口的參數注解示例

由於feign最終是要根據我們編寫的接口方法來組裝請求的參數和方式的,因此為了讓feign能夠正常識別我們的參數,需要加上相關注解才可行;比如GET請求的參數加上@RequestParam注解,如果是個對象就需要加上@SpringQueryMap注解。下面是一些常用的示例。

  • GET請求參數中有多個參數
@GetMapping("/goods/demo2")
Goods findById(@RequestParam(value = "id") Long id, @RequestParam(value = "goodsName")String goodsName);
  • GET請求參數是一個自定義對象
@GetMapping("/goods/demo3")
Goods findById(@SpringQueryMap GoodsParams params);
  • GET請求參數是一個自定義對象和普通類型的組合
@GetMapping("/goods/demo4")
Goods findById(@SpringQueryMap GoodsParams params, @RequestParam(value = "id") Long id);
  • GET請求參數在URL路徑上
@GetMapping("/goods/demo5/{id}")
Goods findGoods(@PathVariable(value = "id") Long id);
  • 請求參數在body里面
@PostMapping("/goods/demo6")
String checkGoods(@RequestBody GoodsParams params);

為feign添加接口異常處理

可以通過配置@EnableFeignClients注解的fallback或者是fallbackFactory的屬性來實現在請求接口異常時返回默認的數據。注意需要在配置中打開feign的hystrix配置:

feign:
  hystrix:
    enabled: true

使用fallback

注意:使用fallback無法打印具體的異常信息

@FeignClient(name = "feign-server-b", fallback = GoodsFeignClientFallback.class)
public interface GoodsFeignClientDemo3 {

    @GetMapping("/goods")
    Goods findById(@RequestParam(value = "id")Long id);
}

異常處理實現類

@Component
public class GoodsFeignClientFallback implements GoodsFeignClientDemo3 {

    @Override
    public Goods findById(Long id) {
        System.out.println(id+"請求接口異常");
        return null;
    }
}

使用fallbackFactory

@FeignClient(name = "feign-server-b", fallbackFactory = GoodsFeignClientFallbackFactory.class)
public interface GoodsFeignClientDemo4 {
    
    @GetMapping("/goods")
    Goods findById(@RequestParam(value = "id")Long id);
}

異常處理實現類

@Component
public class GoodsFeignClientFallbackFactory implements FallbackFactory<GoodsFeignClientDemo4> {
    @Override
    public GoodsFeignClientDemo4 create(Throwable throwable) {
        // 這里通過匿名實現
        return new GoodsFeignClientDemo4() {
            @Override
            public Goods findById(Long id) {
                // 這里可以進行異常的處理
                System.out.println(id+"請求接口異常: "+throwable.getMessage());
                return null;
            }
        };
    }
}

通過feign自定義攔截器實現請求統一參數添加

當我們需要在請求中添加一些默認的統一參數,那么可以通過添加自定義攔截器來實現。首先需要實現RequestInterceptor接口

public class CustomRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        System.out.println("攔截請求...做統一處理");
        requestTemplate.header("uid", "9527");
    }
}

定義bean配置類

public class FeignConfiguration {

    @Bean
    public RequestInterceptor requestInterceptor(){
        return new CustomRequestInterceptor();
    }
}

在feignClient上配置

@FeignClient(name = "feign-server-b", configuration = FeignConfiguration.class)
public interface GoodsFeignClientDemo5 {

    @GetMapping("/goods")
    Goods findById(@RequestParam(value = "id") Long id);
}

一些可能遇見的問題

當一個服務需要暴露的接口較多時,通常我們會選擇分模塊分包分類來處理,此時對一個服務來說就要定義多個feign client接口;由於最終bean的名稱是根據服務名來生成,因此會出現同名的bean導致啟動失敗。也就是會出現如下提示:

2021-05-17 17:02:49.325 ERROR 39740 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

The bean 'feign-server-b.FeignClientSpecification' could not be registered. A bean with that name has already been defined and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

這樣只需要在配置文件里面加上如下配置即可:spring.main.allow-bean-definition-overriding=true

同時項目中接口地址和feign中地址必須保持唯一(也就是RequestMapping中配置的值),否則將可能會出現問題。

feign配置

對請求參數和響應的數據進行壓縮,默認是沒有開啟的。

feign:
  compression:
    # 請求和響應壓縮配置
    request:
      enabled: true
      mime-types: text/xml,application/xml,application/json
      min-request-size: 2048
    response:
      enabled: true

對服務進行個性化配置

feign:
  client:
    config:
      # 全局配置
      default:
        connectTimeout: 1000
        readTimeout: 1000
        loggerLevel: BASIC
      # 單獨某個服務配置
      feign-server-b:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: full

在開發測試環境打開feign的請求日志打印

feign:
  client:
    config:
      # 單獨某個服務配置
      feign-server-b:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: full
# 將feign客戶端請求類的日志級別調整為 DEBUG  
logging.level.top.vchar.feign.GoodsFeignClientDemo5: DEBUG

加上之后控制台會打印如下日志:

...
2021-05-18 10:10:15.551 DEBUG 41344 --- [eign-server-b-1] top.vchar.feign.GoodsFeignClientDemo5    : [GoodsFeignClientDemo5#findById] ---> GET http://feign-server-b/goods?id=1 HTTP/1.1
2021-05-18 10:10:15.551 DEBUG 41344 --- [eign-server-b-1] top.vchar.feign.GoodsFeignClientDemo5    : [GoodsFeignClientDemo5#findById] Accept-Encoding: gzip
2021-05-18 10:10:15.551 DEBUG 41344 --- [eign-server-b-1] top.vchar.feign.GoodsFeignClientDemo5    : [GoodsFeignClientDemo5#findById] Accept-Encoding: deflate
2021-05-18 10:10:15.551 DEBUG 41344 --- [eign-server-b-1] top.vchar.feign.GoodsFeignClientDemo5    : [GoodsFeignClientDemo5#findById] uid: 9527
2021-05-18 10:10:15.551 DEBUG 41344 --- [eign-server-b-1] top.vchar.feign.GoodsFeignClientDemo5    : [GoodsFeignClientDemo5#findById] ---> END HTTP (0-byte body)
...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM