天天看點

Feign 自定義編碼器、解碼器和用戶端,Feign 轉發請求頭(header參數)、Feign輸出Info級别日志

Feign 的編碼器、解碼器和用戶端都是支援自定義擴充,可以對請求以及結果和發起請求的過程進行自定義實作,Feign 預設支援 JSON 格式的編碼器和解碼器,如果希望支援其他的或者自定義格式就需要編寫自己的編碼器和解碼器,如果希望編寫自己的編碼器,需要實作 feign.codec.Encoder 接口,解碼器需要實作 feign.codec.Decoder 接口,示例如下:

自定義編碼器和解碼器

自定義編碼器

實作的自定義編碼器隻是輸出了需要編碼的參數資訊,而具體的編碼還是使用 JSON 格式,使用了 GsonEncoder 編碼器來完成具體的編碼工作,參數 object 表示需要進行編碼的對象,參數 bodyType 為 object 對象的類型,參數 template 表示的就是請求模闆,該方法就是需要實作将參數 object 進行編碼并指派到 template 請求模闆中。

package org.lixue.feignclient;

import feign.RequestTemplate;

import feign.codec.EncodeException;

import feign.codec.Encoder;

import feign.gson.GsonEncoder;

import java.lang.reflect.Type;

public class MyEncoder implements Encoder{

private GsonEncoder gsonEncoder;

publicMyEncoder(){

gsonEncoder = new GsonEncoder();

}

public void encode(Object object,Type bodyType,RequestTemplate template) throws EncodeException{

System.out.println("encode object is class"+object.getClass().getName());

System.out.println("encode object is value"+object);

System.out.println("encode bodyType is class"+bodyType.getClass().getName());

System.out.println("encode bodyType is value"+bodyType);

gsonEncoder.encode(object,bodyType,template);

自定義解碼器

實作的自定義解碼器使用了 GsonDecoder 解碼器來完成具體的編碼工作,解碼器相對簡單,隻需要從響應中擷取響應封包,然後按照指定的編碼格式相應的解碼并建立指定的類型執行個體即可。

import feign.FeignException;

import feign.Response;

import feign.codec.DecodeException;

import feign.codec.Decoder;

import feign.gson.GsonDecoder;

import java.io.IOException;

import java.lang.reflect.Method;

public class MyDecoder implements Decoder{

private GsonDecoder gsonDecoder;

publicMyDecoder(){

gsonDecoder=newGsonDecoder();

public Object decode(Response response,Type type)throws IOException,DecodeException,FeignException{

return gsonDecoder.decode(response,type);

測試驗證

在完成自定義編碼器和解碼器的開發後,隻需要在 Feign 的 builder 方法中,增加解碼器和編碼器即可,需要注意的是,如果方法請求參數或傳回的不是對象,不需要進行編碼或解碼,就不能增加編碼器或解碼器,示例代碼如下:

import feign.Feign;

import feign.Logger;

public class Startup{

public static void main(String[]args){

HelloWorldClient speakClient=

Feign.builder().target(HelloWorldClient.class,"http://localhost:8080/");

// 參數和傳回都不是對象,不需要附加編碼器和解碼器

System.out.println(speakClient.speak("isbody"));

HelloWorldClient findByIdClient=

Feign.builder().decoder(new GsonDecoder())

.target(HelloWorldClient.class,"http://localhost:8080/");

// 傳回的是對象,需要附加解碼器

Person person=findByIdClient.findById(34);

System.out.println("personid="+person.getId()+"age="+person.getAge()+"name="+person.getName()+"message="+person.getMessage());

HelloWorldClient createClient=

Feign.builder().client(newMyClient())

.decoder(newMyDecoder())

.encoder(newMyEncoder())

Person newPerson=new Person();

newPerson.setId(3434);

newPerson.setAge(34);

newPerson.setName("343434");

newPerson.setMessage("33333333333333333");

// 參數和傳回都是對象,需要附加解碼器和編碼器

ReturnValuereturnValue=createClient.create(newPerson);

System.out.println(returnValue.parseString());

自定義 Feign 用戶端

Feign 使用一個 feign.Client 接口來發送請求,預設實作是使用 HttpURLConnection 連接配接 HTTP 服務,我們可以實作 feign.Client 接口來完成自定義 Feign 用戶端的開發,該接口隻有一個方法 execute ,用于執行請求,下面實作了一個自定義的 Feign 用戶端,主要完成了請求的日志記錄,示例代碼如下:

import feign.Client;

import feign.Request;

import java.util.Collection;

import java.util.Map;

public class MyClient implements Client{

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

System.out.println("execute request method="+request.method());

System.out.println("execute request headers");

Map<String,Collection<String>> headers=request.headers();

for(Map.Entry<String,Collection<String>> entry:headers.entrySet()){

StringBuilderstringBuilder=newStringBuilder();

for(intj=0;j<entry.getValue().size();j++){

if(stringBuilder.length()>0){

stringBuilder.append(",");

stringBuilder.append(entry.getValue());

System.out.println(entry.getKey()+":"+stringBuilder.toString());

byte[] body=request.body();

if(body!=null){

System.out.println("execute request body="+newString(body));

// 使用 Feign 預設的用戶端請求

return new Client.Default(null,null).execute(request,options);

和附加編碼器、解碼器類似,隻需要在 Feign 的 builder 方法中附加自定義的用戶端即可,代碼如下:

Feign.builder().client(new MyClient())

.decoder(new GsonDecoder())

Feign 轉發請求頭(header參數)

<code>在做接口請求時,我們經常會在header頭中增加一些鑒權資訊,如token 或 jwt,那麼在通過fegin從A server去調用B server的接口時,如果B server的接口需要header資訊,我們需要将A sever擷取的header轉發到B上。</code>

我們需要實作Feign提供的一個接口<code>RequestInterceptor</code>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

<code>@Configuration</code>

<code>public</code> <code>class</code> <code>FeignConfiguration </code><code>implements</code> <code>RequestInterceptor{</code>

<code>    </code><code>private</code> <code>final</code> <code>Logger logger = LoggerFactory.getLogger(getClass());</code>

<code>            </code><code>@Override</code>

<code>            </code><code>public</code> <code>void</code> <code>apply(RequestTemplate template) {</code>

<code>                </code><code>ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder</code>

<code>                        </code><code>.getRequestAttributes();</code>

<code>                </code><code>HttpServletRequest request = attributes.getRequest();</code>

<code>                </code><code>Enumeration&lt;String&gt; headerNames = request.getHeaderNames();</code>

<code>                </code><code>if</code> <code>(headerNames != </code><code>null</code><code>) {</code>

<code>                    </code><code>while</code> <code>(headerNames.hasMoreElements()) {</code>

<code>                        </code><code>String name = headerNames.nextElement();</code>

<code>                        </code><code>String values = request.getHeader(name);</code>

<code>                        </code><code>template.header(name, values);</code>

<code>                    </code><code>}</code>

<code>                    </code><code>logger.info(</code><code>"feign interceptor header:{}"</code><code>,template);</code>

<code>                </code><code>}</code>

<code>               </code><code>/* Enumeration&lt;String&gt; bodyNames = request.getParameterNames();</code>

<code>                </code><code>StringBuffer body =new StringBuffer();</code>

<code>                </code><code>if (bodyNames != null) {</code>

<code>                    </code><code>while (bodyNames.hasMoreElements()) {</code>

<code>                        </code><code>String name = bodyNames.nextElement();</code>

<code>                        </code><code>String values = request.getParameter(name);</code>

<code>                        </code><code>body.append(name).append("=").append(values).append("&amp;");</code>

<code>                </code><code>if(body.length()!=0) {</code>

<code>                    </code><code>body.deleteCharAt(body.length()-1);</code>

<code>                    </code><code>template.body(body.toString());</code>

<code>                    </code><code>//logger.info("feign interceptor body:{}",body.toString());</code>

<code>                </code><code>}*/</code>

<code>            </code><code>}</code>

<code>        </code><code>}</code>

在<code>@FeignClient</code>注解裡面的屬性加上<code>configuration = FeignConfiguration.class</code>就可以了。如

<code>@FeignClient</code><code>(name = </code><code>"a-server"</code><code>,  configuration = FeignConfiguration.</code><code>class</code><code>)</code>

<code>public</code> <code>interface</code> <code>AServer{</code>

<code>}</code>

bootstrap.yml增加

Feign 自定義編碼器、解碼器和用戶端,Feign 轉發請求頭(header參數)、Feign輸出Info級别日志

Feign調用開啟Hystrix時無法擷取ThreadLocal

在項目中使用根據請求頭處理異常資訊國際化的問題,但是在feign調用的時候無法傳遞請求頭,這個問題看了好久最後才知道feign開啟hystrix預設會建立一個線程,而我的請求頭資料是通過攔截器放到ThreadLocal裡的在新線程就無法擷取了

先看一下原來是怎麼實作的

首先是header封裝類

把請求頭封裝到ThreadLocal中

每次請求的時候通過filter封裝把請求頭資料放入context中

但是在feign中開啟hystrix的話新線程的ThreadLocal是無法擷取主線程的資料的,這個時候就要用到InheritableThreadLocal,隻需要改一行代碼

InheritableThreadLocal是ThreadLocal的子類,可以解決父線程和子線程的資料傳輸問題

當在主線程開啟一個新線程時,會執行Thread的init方法

init方法中有這麼一段代碼

當父線程中的inheritableThreadLocal被指派時,會将目前線程的inheritableThreadLocal變量進行createInheritedMap(),看一下這個方法的具體實作,它會繼續調用ThreadLocalMap(parentMap),主要的目的是父線程的變量值指派給子線程。

當讀取ThreadLocal的地方是個線程池的時候,inheritableThreadLocal也會有問題

https://www.jianshu.com/p/24a3614dde41?utm_campaign=maleskine&amp;utm_content=note&amp;utm_medium=seo_notes&amp;utm_source=recommendation

Feign輸出Info級别日志

  spring cloud netfix元件中,feign相關的日志預設是不會輸出的,需要自定義配置才能輸出,并且Feign隻對Debug基本的日志做出響應, 實際業務需要輸出Info級别的日志,是以需要做自定義配置,覆寫相關配置Bean。

  Feign用戶端可以配置各種的Logger.Level對象,告訴Feign記錄哪些日志。Logger.Level的值有以下選擇。

    NONE,無記錄(DEFAULT)。

    BASIC,隻記錄請求方法和URL以及響應狀态代碼和執行時間。

    HEADERS,記錄基本資訊以及請求和響應标頭。

    FULL,記錄請求和響應的頭檔案,正文和中繼資料。

    根據Feign配置的描述,需要将Logger.Level 配置到用戶端中:

Feign 自定義編碼器、解碼器和用戶端,Feign 轉發請求頭(header參數)、Feign輸出Info級别日志
Feign 自定義編碼器、解碼器和用戶端,Feign 轉發請求頭(header參數)、Feign輸出Info級别日志
Feign 自定義編碼器、解碼器和用戶端,Feign 轉發請求頭(header參數)、Feign輸出Info級别日志
Feign 自定義編碼器、解碼器和用戶端,Feign 轉發請求頭(header參數)、Feign輸出Info級别日志

    因為feign隻對日志級别為debug級别做出響應,是以如果需要列印出日志,還需要修改用戶端的日志級别在application.properties中要設定一行這樣的配置:

        logging.level.&lt;你的feign client全路徑類名&gt;: DEBUG

    操作完成這三步驟,當調用Send 方法的時候就會列印出Debug級别的日志。

  在實際生産環境中,我們常常需要使用Info級别日志,使用上述針對對每個用戶端的配置進行修改,那樣将會有大量的配置。是以,需要将修改Feign的日志,對Info級别進行相應。

Feign 自定義編碼器、解碼器和用戶端,Feign 轉發請求頭(header參數)、Feign輸出Info級别日志
Feign 自定義編碼器、解碼器和用戶端,Feign 轉發請求頭(header參數)、Feign輸出Info級别日志

  自定義一個Logger類,繼承Feign.Logger,将代碼中的Debug修改成為Info

Feign 自定義編碼器、解碼器和用戶端,Feign 轉發請求頭(header參數)、Feign輸出Info級别日志
Feign 自定義編碼器、解碼器和用戶端,Feign 轉發請求頭(header參數)、Feign輸出Info級别日志

  将自定義Logger類加入到Feign用戶端配置的Config中,這樣Feign系統間調用就能列印出Info級别的日志。

  列印出Feign系統間調用Info級别的日志,核心的思想是Spring Boot項目中,能夠自定義配置,自定義的配置優先級大于預設的配置。詳情參見Spring Boot自定義配置。

繼續閱讀