Feign實現動態URL

nuccch發表於2022-02-14

需求描述

動態URL的需求場景:
有一個非同步服務S,它為其他業務(業務A,業務B...)提供非同步服務介面,在這些非同步介面中執行完指定邏輯之後需要回撥相應業務方的介面。
統一回撥方法

這在諸如風控稽核,支付回撥等場景中挺常見的。

那麼,這個回撥業務方介面該怎麼實現呢?
首先,需要約定好回撥這些業務方介面時的請求方法(通常為POST請求),請求引數格式(通常為JSON格式,方便擴充套件)和響應訊息格式(通常為JSON格式)。

具體呼叫業務方介面時有2種辦法來實現:
1.在服務S的每一個非同步介面中都獨立寫一套回撥的邏輯
2.因為回撥的方法型別和引數格式是約定好的,所以可以寫一個統一的公共回撥方法即可

方法1顯然不是最優選擇,這樣做會帶來大量重複的程式碼邏輯,而且非常不利用後期維護和升級。
方法2的實現更加靈活一些,便於擴充套件。

如下將闡述如何使用Feign框架定義一個公共的回撥方法。

具體實現

在Feign中能實現動態URL的基礎是框架本身就支援,只需要在介面方法中包含一個java.net.URI引數,Feign就會將該引數值作為目標主機地址,詳見Interface Annotations一節中的“Overriding the Request Line”部分。
如下將分別闡述獨立使用Feign和使用Spring Cloud OpenFeign實現定義統一的回撥方法。

使用Feign定義統一回撥方法

定義統一回撥方法:

public interface CallbackAPI {
    /**
     * 統一回撥介面方法,請求訊息體格式為JSON,響應訊息體格式也為JSON
     * @param host 介面主機地址,如:http://localhost:8080,該引數是實現動態URL的關鍵
     * @param path 介面路徑,如:/test/hello
     * @param queryMap 動態URL引數集合
     * @param body 請求訊息體物件
     * @return
     */
    @RequestLine("POST {path}")
    @Headers({
        "Content-Type: application/json",
        "Accept: application/json"
    })
    Object callback(URI host, @Param("path") String path, @QueryMap Map<String, Object> queryMap, Subject body);
}

呼叫回撥方法:

CallbackAPI callbackAPI = Feign.builder()
        .encoder(new GsonEncoder())
        .decoder(new GsonDecoder())
        .logger(new Slf4jLogger())
        .logLevel(Logger.Level.FULL)
        .target(CallbackAPI.class, "EMPTY"); // 注意:這裡的url引數不能為空字串,但是可以設定為任意字串值,在這裡設定為“EMPTY”
String uri = "http://localhost:8080";
Map<String, Object> queryMap = new HashMap<>(0);
queryMap.put("k", "v");
Subject body = Subject.builder().id(10).build();
Object result = callbackAPI.callback(URI.create(uri), "/test/simple/post/json", queryMap, body);
System.out.println(result);

詳細請求日誌如下:

2022-02-14 15:32:13,118 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] ---> POST http://localhost:8080/test/simple/post/json?k=v HTTP/1.1
2022-02-14 15:32:13,118 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] Accept: application/json
2022-02-14 15:32:13,118 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] Content-Length: 14
2022-02-14 15:32:13,118 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] Content-Type: application/json
2022-02-14 15:32:13,118 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] 
2022-02-14 15:32:13,118 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] {
  "id": 10
}
2022-02-14 15:32:13,118 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] ---> END HTTP (14-byte body)
2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] <--- HTTP/1.1 200 (32ms)
2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] connection: keep-alive
2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] content-length: 9
2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] content-type: application/json
2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] date: Mon, 14 Feb 2022 07:32:13 GMT
2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] keep-alive: timeout=60
2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] 
2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] {"id":10}
2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] <--- END HTTP (9-byte body)

顯然,動態設定目標主機和介面路徑已經成功了。

使用Spring Cloud Feign定義統一回撥方法

Spring Cloud Feign中實現定義統一回撥介面方法可以直接使用註解進行標註,非常簡潔。

定義統一回撥方法:

// 注意:這裡的url屬性值不能為空字串,但是可以設定為任意字串值,在這裡設定為“EMPTY”
@FeignClient(value = "CallbackAPI", url = "EMPTY", configuration = CallbackConfiguration.class)
public interface CallbackAPI {
    /**
     * 統一回撥介面方法,請求訊息體格式為JSON,響應訊息體格式也為JSON
     * @param host 介面主機地址,如:http://localhost:8080
     * @param path 介面路徑,如:/test/hello
     * @param queryMap 動態URL引數集合
     * @param body 請求訊息體物件
     * @return
     */
    @RequestMapping(value = "{path}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    Object callback(URI host,
                    @PathVariable("path") String path,
                    @SpringQueryMap Map<String, Object> queryMap,
                    @RequestBody Object body);
}

// 回撥介面配置
public class CallbackConfiguration {
    @Bean
    public Encoder feignEncoder() {
        return new GsonEncoder();
    }

    @Bean
    public Decoder feignDecoder() {
        return new GsonDecoder();
    }

    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default();
    }

    @Bean
    public Logger feignLogger() {
        return new Slf4jLogger();
    }

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

呼叫回撥方法:

@Autowired
CallbackAPI callbackAPI;

String uri = "http://localhost:8080";
Map<String, Object> queryMap = new HashMap<>(0);
queryMap.put("k", "v");
Subject subject = Subject.builder().id(10).build();
Object result = this.callbackAPI.callback(URI.create(uri), "/test/simple/post/json", queryMap, subject);

詳細請求日誌如下:

2022-02-14 15:38:38.908 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] ---> POST http://localhost:8080/test/simple/post/json?k=v HTTP/1.1
2022-02-14 15:38:38.908 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] Accept: application/json
2022-02-14 15:38:38.908 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] Content-Length: 14
2022-02-14 15:38:38.908 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] Content-Type: application/json
2022-02-14 15:38:38.908 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] 
2022-02-14 15:38:38.908 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] {
  "id": 10
}
2022-02-14 15:38:38.908 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] ---> END HTTP (14-byte body)
2022-02-14 15:38:38.924 DEBUG 20184 --- [           main] s.n.www.protocol.http.HttpURLConnection  : sun.net.www.MessageHeader@539c48307 pairs: {POST /test/simple/post/json?k=v HTTP/1.1: null}{Accept: application/json}{Content-Type: application/json}{User-Agent: Java/1.8.0_271}{Host: localhost:8080}{Connection: keep-alive}{Content-Length: 14}
2022-02-14 15:38:38.945 DEBUG 20184 --- [           main] s.n.www.protocol.http.HttpURLConnection  : sun.net.www.MessageHeader@681e913c6 pairs: {null: HTTP/1.1 200}{Content-Type: application/json}{Content-Length: 9}{Date: Mon, 14 Feb 2022 07:38:38 GMT}{Keep-Alive: timeout=60}{Connection: keep-alive}
2022-02-14 15:38:38.952 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] <--- HTTP/1.1 200 (37ms)
2022-02-14 15:38:38.952 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] connection: keep-alive
2022-02-14 15:38:38.952 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] content-length: 9
2022-02-14 15:38:38.952 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] content-type: application/json
2022-02-14 15:38:38.952 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] date: Mon, 14 Feb 2022 07:38:38 GMT
2022-02-14 15:38:38.953 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] keep-alive: timeout=60
2022-02-14 15:38:38.953 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] 
2022-02-14 15:38:38.955 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] {"id":10}
2022-02-14 15:38:38.955 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] <--- END HTTP (9-byte body)

從日誌詳情看,在Spring Cloud Feign中同樣實現動態URL的效果。

總結

在Feign中實現動態URL的關鍵在於2點:
1.使用URI型別的引數作為請求目標主機地址
2.將請求路徑部分使用表示式進行替換

【參考】
https://www.cnblogs.com/syui-terra/p/14386188.html Feign 動態URL 解決記錄
https://blog.csdn.net/kysmkj/article/details/89672952 Feign 訪問遠端api,動態指定url

相關文章