SpringCloud中分析講解Feign組件添加請求頭有哪些坑梳理
按官方修改的示例:
#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); }
提示錯誤:
java.lang.IllegalArgumentException: method GET must not have a request body.
分析
通過斷點debug發現feign發請求時把userInfo參數當成瞭requestBody來處理,而okhttp3會檢測get請求不允許有body(其他類型的請求哪怕不報錯,但因為不是設置到請求頭,依然不滿足需求)。
查閱官方文檔裡是通過Contract(Feign.Contract.Default)來解析註解的:
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. |
從自動配置類找到使用的是spring cloud的SpringMvcContract(用來解析@RequestMapping相關的註解),而這個註解並不會處理解析上面列的註解
@Configuration public class FeignClientsConfiguration { @Bean @ConditionalOnMissingBean public Contract feignContract(ConversionService feignConversionService) { return new SpringMvcContract(this.parameterProcessors, feignConversionService); }
解決
原因找到瞭:spring cloud使用瞭自己的SpringMvcContract來解析註解,導致默認的註解解析方式失效。 解決方案自然就是重新解析處理feign的註解,這裡通過自定義Contract繼承SpringMvcContract再把Feign.Contract.Default解析邏輯般過來即可(重載的方法是在SpringMvcContract基礎上做進一步解析,否則Feign對RequestMapping相關對註解解析會失效)
代碼如下(此處隻對@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); }
改完馬上進行新一頓的操作, 看請求日志已經設置成功,響應OK!:
請求: {"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":""}
響應 {"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"]}"}
feign官方鏈接
spring cloud feign 鏈接
(另一種實現請求頭的方式:實現RequestInterceptor,但是存在hystrix線程切換的坑)
到此這篇關於SpringCloud中分析講解Feign組件添加請求頭有哪些坑梳理的文章就介紹到這瞭,更多相關SpringCloud Feign組件內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Feign如何實現第三方的HTTP請求
- Spring Cloud Feign請求添加headers的實現方式
- Feign調用中的兩種Header傳參方式小結
- 如何自定義feign調用實現hystrix超時、異常熔斷
- 輕量級聲明式的Http庫——Feign的獨立使用