SpringCloud項(xiàng)目中Feign組件添加請(qǐng)求頭所遇到的坑及解決
前言
在spring cloud的項(xiàng)目中用到了feign組件,簡(jiǎn)單配置過(guò)后即可完成請(qǐng)求的調(diào)用。
又因?yàn)橛邢蛘?qǐng)求添加Header頭的需求,查閱了官方示例后,就覺(jué)得很簡(jiǎn)單,然后一頓操作之后調(diào)試報(bào)錯(cuò)...
按官方修改的示例:
#MidServerClient.java
import feign.Param;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(value = "edu-mid-server")
public interface MidServerClient {
@RequestMapping(value = "/test/header", method = RequestMethod.GET)
@Headers({"userInfo:{userInfo}"})
Object headerTest(@Param("userInfo") String userInfo);
}提示錯(cuò)誤:
java.lang.IllegalArgumentException: method GET must not have a request body.
分析
通過(guò)斷點(diǎn)debug發(fā)現(xiàn)feign發(fā)請(qǐng)求時(shí)把userInfo參數(shù)當(dāng)成了requestBody來(lái)處理,而okhttp3會(huì)檢測(cè)get請(qǐng)求不允許有body(其他類型的請(qǐng)求哪怕不報(bào)錯(cuò),但因?yàn)椴皇窃O(shè)置到請(qǐng)求頭,依然不滿足需求)。
查閱官方文檔里是通過(guò)Contract(Feign.Contract.Default)來(lái)解析注解的:
Feign annotations define the Contract between the interface and how the underlying client should work. Feign's default contract defines the following annotations:
| Annotation | Interface Target | Usage |
|---|---|---|
| @RequestLine | Method | Defines the HttpMethod and UriTemplate for request. Expressions, values wrapped in curly-braces {expression} are resolved using their corresponding @Param annotated parameters. |
| @Param | Parameter | Defines a template variable, whose value will be used to resolve the corresponding template Expression, by name. |
| @Headers | Method, Type | Defines a HeaderTemplate; a variation on a UriTemplate. that uses @Param annotated values to resolve the corresponding Expressions. When used on a Type, the template will be applied to every request. When used on a Method, the template will apply only to the annotated method. |
| @QueryMap | Parameter | Defines a Map of name-value pairs, or POJO, to expand into a query string. |
| @HeaderMap | Parameter | Defines a Map of name-value pairs, to expand into Http Headers |
| @Body | Method | Defines a Template, similar to a UriTemplate and HeaderTemplate, that uses @Param annotated values to resolve the corresponding Expressions. |
從自動(dòng)配置類找到使用的是spring cloud的SpringMvcContract(用來(lái)解析@RequestMapping相關(guān)的注解),而這個(gè)注解并不會(huì)處理解析上面列的注解
@Configuration
public class FeignClientsConfiguration {
···
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
···解決
原因找到了
spring cloud使用了自己的SpringMvcContract來(lái)解析注解,導(dǎo)致默認(rèn)的注解解析方式失效。
解決方案自然就是重新解析處理feign的注解,這里通過(guò)自定義Contract繼承SpringMvcContract再把Feign.Contract.Default解析邏輯般過(guò)來(lái)即可(重載的方法是在SpringMvcContract基礎(chǔ)上做進(jìn)一步解析,否則Feign對(duì)RequestMapping相關(guān)對(duì)注解解析會(huì)失效)
代碼如下(此處只對(duì)@Headers、@Param重新做了解析):
#FeignCustomContract.java
import feign.Headers;
import feign.MethodMetadata;
import feign.Param;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.core.convert.ConversionService;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import static feign.Util.checkState;
import static feign.Util.emptyToNull;
public class FeignCustomContract extends SpringMvcContract {
public FeignCustomContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors, ConversionService conversionService) {
super(annotatedParameterProcessors, conversionService);
}
@Override
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
//解析mvc的注解
super.processAnnotationOnMethod(data, methodAnnotation, method);
//解析feign的headers注解
Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
if (annotationType == Headers.class) {
String[] headersOnMethod = Headers.class.cast(methodAnnotation).value();
checkState(headersOnMethod.length > 0, "Headers annotation was empty on method %s.", method.getName());
data.template().headers(toMap(headersOnMethod));
}
}
@Override
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
boolean isMvcHttpAnnotation = super.processAnnotationsOnParameter(data, annotations, paramIndex);
boolean isFeignHttpAnnotation = false;
for (Annotation annotation : annotations) {
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotationType == Param.class) {
Param paramAnnotation = (Param) annotation;
String name = paramAnnotation.value();
checkState(emptyToNull(name) != null, "Param annotation was empty on param %s.", paramIndex);
nameParam(data, name, paramIndex);
isFeignHttpAnnotation = true;
if (!data.template().hasRequestVariable(name)) {
data.formParams().add(name);
}
}
}
return isMvcHttpAnnotation || isFeignHttpAnnotation;
}
private static Map<String, Collection<String>> toMap(String[] input) {
Map<String, Collection<String>> result =
new LinkedHashMap<String, Collection<String>>(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<String>(1));
}
result.get(name).add(header.substring(colon + 1).trim());
}
return result;
}
}#FeignCustomConfiguration.java
import feign.Contract;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class FeignCustomConfiguration {
···
@Autowired(required = false)
private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();
@Bean
@ConditionalOnProperty(name = "feign.feign-custom-contract", havingValue = "true", matchIfMissing = true)
public Contract feignContract(ConversionService feignConversionService) {
return new FeignCustomContract(this.parameterProcessors, feignConversionService);
}
···改完馬上進(jìn)行新一頓的操作, 看請(qǐng)求日志已經(jīng)設(shè)置成功,響應(yīng)OK!:
請(qǐng)求:{"type":"OKHTTP_REQ","uri":"/test/header","httpMethod":"GET","header":"{"accept":["/"],"userinfo":["{"userId":"sssss","phone":"13544445678],"x-b3-parentspanid":["e49c55484f6c19af"],"x-b3-sampled":["0"],"x-b3-spanid":["1d131b4ccd08d964"],"x-b3-traceid":["9405ce71a13d8289"]}","param":""}
響應(yīng){"type":"OKHTTP_RESP","uri":"/test/header","respStatus":0,"status":200,"time":5,"header":"{"cache-control":["no-cache,no-store,max-age=0,must-revalidate"],"connection":["keep-alive"],"content-length":["191"],"content-type":["application/json;charset=UTF-8"],"date":["Fri,11Oct201913:02:41GMT"],"expires":["0"],"pragma":["no-cache"],"x-content-type-options":["nosniff"],"x-frame-options":["DENY"],"x-xss-protection":["1;mode=block"]}"}
(另一種實(shí)現(xiàn)請(qǐng)求頭的方式:實(shí)現(xiàn)RequestInterceptor,但是存在hystrix線程切換的坑)
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java反射之類的實(shí)例對(duì)象的三種表示方式總結(jié)
下面小編就為大家?guī)?lái)一篇Java反射之類的實(shí)例對(duì)象的三種表示方式總結(jié)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10
java多線程編程之為什么要進(jìn)行數(shù)據(jù)同步
數(shù)據(jù)同步就是指在同一時(shí)間,只能由一個(gè)線程來(lái)訪問(wèn)被同步的類變量,當(dāng)前線程訪問(wèn)完這些變量后,其他線程才能繼續(xù)訪問(wèn),下面看一下為什么要進(jìn)行數(shù)據(jù)同步2014-01-01
使用java實(shí)現(xiàn)BBS論壇發(fā)送郵件過(guò)程詳解
這篇文章主要介紹了使用java發(fā)送郵件過(guò)程詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04
淺談java String.split丟失結(jié)尾空字符串的問(wèn)題
下面小編就為大家?guī)?lái)一篇淺談java String.split丟失結(jié)尾空字符串的問(wèn)題。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02
java實(shí)現(xiàn)大文件導(dǎo)出的實(shí)現(xiàn)與優(yōu)化
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)大文件導(dǎo)出的實(shí)現(xiàn)與優(yōu)化的相關(guān)資料,文中的示例代碼講解詳細(xì),對(duì)我們深入了解java有一定的幫助,感興趣的小伙伴可以了解下2023-11-11
Mybatis一對(duì)多和多對(duì)一處理的深入講解
Mybatis可以通過(guò)關(guān)聯(lián)查詢實(shí)現(xiàn),關(guān)聯(lián)查詢是幾個(gè)表聯(lián)合查詢,只查詢一次,通過(guò)在resultMap里面的association,collection節(jié)點(diǎn)配置一對(duì)一,一對(duì)多的類就可以完成,這篇文章主要給大家介紹了關(guān)于Mybatis一對(duì)多和多對(duì)一處理的相關(guān)資料,需要的朋友可以參考下2021-09-09

