中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Spring Cloud Feign怎么添加自定義Header

發布時間:2021-07-08 18:01:18 來源:億速云 閱讀:636 作者:chen 欄目:大數據

本篇內容介紹了“Spring Cloud Feign怎么添加自定義Header”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

背景

最近在調用一個接口,接口要求將token放在header中傳遞。由于我的項目使用了feign, 那么給請求中添加 header 就必須要去feign中找方法了。

方案一:自定義 RequestInterceptor

在給 @FeignClient 注解的接口生成代理對象的時候,有這么一段:

class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
    @Override
	public Object getObject() throws Exception {
		return getTarget();
	}
    // getTarget() 最終會調用到 configureUsingConfiguration()
    protected void configureUsingConfiguration(FeignContext context,Feign.Builder builder) {
		Map<String, RequestInterceptor> requestInterceptors = context.getInstances(this.contextId, RequestInterceptor.class);
		if (requestInterceptors != null) {
			builder.requestInterceptors(requestInterceptors.values());
		}
        ...
	}
}

生成代理類時,會使用到 spring 上下文的 RequestInterceptor, 而 @FeignClient 的代理類在執行的時候,會去使用該攔截器:

final class SynchronousMethodHandler implements MethodHandler {
	Request targetRequest(RequestTemplate template) {
      for (RequestInterceptor interceptor : requestInterceptors) {
        interceptor.apply(template);
      }
      return target.apply(template);
  }
}

所以自定義自己的攔截器,然后注入到 spring 上下文中,這樣就可以在請求的上下文中添加自定義的請求頭:

@Service
public class MyRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        template.header("my-header","header");
    }
}

優點

實現簡單,使用現有接口注入即可

缺點

操作的是全局的 RequestTemplate,比較難以根據不同的服務方提供不同的 header。 雖然可以在 template 中根據 uri 來判斷不同的服務提供方,然后添加對應的header,但是憑空多了很多配置信息,維護也比較困難。

方案二:在 @RequestMapping 注解中增加 header 信息

既然我們用到了 openfeign 框架,那我們找找 openfeign 官方是怎么解決的(https://github.com/OpenFeign/feign):

// openfeign 官方文檔
public interface ContentService {
  @RequestLine("GET /api/documents/{contentType}")
  @Headers("Accept: {contentType}")
  String getDocumentByType(@Param("contentType") String type);
}

通過上述官方代碼示例,我們可以發現,其實使用原生的 API 就可以滿足我們的需求:

@FeignClient(name = "feign",url = "127.0.0.1:8080")
public interface FeignTest {
    @RequestMapping(value = "/test")
    @Headers({"app: test-app","token: ${test-app.token}"})
    String test();
}

然而比較遺憾的是,@Headers 并沒有生效,生成的RequestTemplate中,沒有上述兩個 Header 信息。 跟蹤代碼,我們發現,ReflectFeign在生成遠程服務的代理類的時候,會通過 Contract 接口準備數據。 而*@Headers* 注解沒有生效的原因是:官方的 Contract 沒有生效:

class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
    protected Feign.Builder feign(FeignContext context) {
		Feign.Builder builder = get(context, Feign.Builder.class)
				// required values
				.logger(logger)
				.encoder(get(context, Encoder.class))
				.decoder(get(context, Decoder.class))
				.contract(get(context, Contract.class));
                ...
	}

}

對于 springcloud-openfeign 來說,在創建 Feign 相關類的時候,使用的是容器中注入的 Contract:

@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
    return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}

public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
    @Override
	public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
		....
        // 注意這里,它只取了 RequestMapping 注解
		RequestMapping classAnnotation = findMergedAnnotation(targetType, RequestMapping.class);
		....
		parseHeaders(md, method, classAnnotation);
		}
		return md;
	}
}

到這里我們就梳理出來整個事情的來龍去脈了:

  1. openfeign 是支持給方法加上自定義 header 的,它用的是自己的注解 @Headers

  2. springcloud-openfeign 使用了 openfeign的核心功能,但是關于 @Headers 的注解沒有使用

  3. springcloud 使用了自己的 SpringMvcContract 來處理請求的相關資源信息,里面只使用 @RequestMapping 注解

我們比較容易想到的是,既然 @RequestMapping 注解中有 headers 的屬性,我們可以試一下

@FeignClient(name = "server",url = "127.0.0.1:8080")
public interface FeignTest {
    @RequestMapping(value = "/test",headers = {"app=test-app","token=${test-app.token}"})
    String test();
}

親測可用,這樣我們就可以給特定的服務單獨定制頭信息啦。

優點

實現更加簡單了,甚至都不用自己實現接口,只需要自己在相關注解中增加對應屬性配置即可

缺點

雖然不用給全局的請求增加header,但是對于相同的服務方,卻要在每個@RequestMapping注解中添加相同的header配置,會比較麻煩,能否添加全局的呢?

方案三:自定義 Contract

通過 SpringMvcContract 代碼我們也很容易發現,對于類的注解,它只會處理 RequestMapping,其它也都忽略了。 那么如果我們重新定義自己的 Contract,就可以隨心所欲實現自己的想要的功能啦。

  1. 方便起見,我們直接復用 openfeign 的 @Header

  2. 簡單起見,我們直接繼承 SpringMvcContract

  3. 自定義自己的 Contract,然后注入到 spring 上下文中

/**
  * 為了處理簡單,我們直接繼承 SpringMvcContract
  */
@Service
public class MyContract extends SpringMvcContract {
    /**
         * 該屬性是為了使用 springcloud config
         */
    private ResourceLoader resourceLoader;

    @Override
    protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
        //這里復用原有 SpringMvcContract 邏輯
        super.processAnnotationOnClass(data, clz);

        // 以下是新加的邏輯(其實是使用的 openfeign 自帶的 Contract.Default的邏輯)
        if (clz.isAnnotationPresent(Headers.class)) {
            String[] headersOnType = clz.getAnnotation(Headers.class).value();
            Map<String, Collection<String>> headers = toMap(headersOnType);
            headers.putAll(data.template().headers());
            data.template().headers(null); // to clear
            data.template().headers(headers);
        }
    }

    private Map<String, Collection<String>> toMap(String[] input) {
        Map<String, Collection<String>> result = new LinkedHashMap<>(input.length);
        for (String header : input) {
            int colon = header.indexOf(':');
            String name = header.substring(0, colon);
            if (!result.containsKey(name)) {
                result.put(name, new ArrayList<>(1));
            }
            result.get(name).add(resolve(header.substring(colon + 1).trim()));
        }
        return result;
    }

    private String resolve(String value) {
        if (StringUtils.hasText(value)
            && resourceLoader instanceof ConfigurableApplicationContext) {
            return ((ConfigurableApplicationContext) this.resourceLoader).getEnvironment()
                .resolvePlaceholders(value);
        }
        return value;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
        // 注意,因為SpringMvcContract 也使用了 resourceLoader,所以必須給它指定解析器,否則不會解析占位符
        super.setResourceLoader(resourceLoader);
    }
}

使用的時候直接對 接口做 header的配置即可:

@Headers({"app: test-app","token: ${test-app.token}"})
public interface FeignTest {
    @RequestMapping(value = "/test")
    String test();
}

優點

可以根據自己的需要自由定義

缺點

自定義帶來一定的學習成本,而且因為是直接繼承 spring 的實現,為以后升級留下隱患

方案四:在接口上使用 @RequestMapping,并加上 headers 屬性

聰明的讀者也許在方案二的結尾就能反應過來:springcloud 支持*@RequestMapping*注解的 header,而該注解完全可以用在類上面!

@FeignClient(name = "feign",url = "127.0.0.1:8080")
@RequestMapping(value = "/",headers = {"app=test-app","token=${test-app.token}"})
public interface FeignTest {
    @RequestMapping(value = "/test")
    String test();
}

優點

完全不用自定義,原生支持

缺點

基本沒有。 可能對于有些不習慣在類上使用 @RequestMapping 注解的同學來說,有點強迫癥,不過基本可以忽略

思考:為什么沒有一開始想到將注解放到接口定義那里

  1. 思維定勢,工作內容問題,很少會在feign接口上使用 @RequestMapping

  2. SpringMvcContract 中的 processAnnotationOnClass 方法中沒有關于對 header的處理,導致一開始忽略這個

  3. SpringMvcContract 是在 parseAndValidatateMetadata 中解決類上面的 header 的問題

“Spring Cloud Feign怎么添加自定義Header”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

彭州市| 桃源县| 锡林郭勒盟| 包头市| 方正县| 历史| 凌云县| 随州市| 兰溪市| 高邑县| 手机| 佛冈县| 乌拉特中旗| 南木林县| 克山县| 阳朔县| 玉环县| 禹州市| 勐海县| 家居| 霸州市| 兴安盟| 法库县| 浠水县| 浦县| 剑河县| 靖边县| 金阳县| 武山县| 泗阳县| 双鸭山市| 和平区| 和静县| 毕节市| 井冈山市| 肇州县| 霍林郭勒市| 卓资县| 米脂县| 安徽省| 怀来县|