SpringCloud项目中Feign组件添加请求头所遇到的坑如何解决

其他教程   发布日期:2025年04月18日   浏览次数:204

本文小编为大家详细介绍“SpringCloud项目中Feign组件添加请求头所遇到的坑如何解决”,内容详细,步骤清晰,细节处理妥当,希望这篇“SpringCloud项目中Feign组件添加请求头所遇到的坑如何解决”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

    前言

    在spring cloud的项目中用到了feign组件,简单配置过后即可完成请求的调用。

    又因为有向请求添加Header头的需求,查阅了官方示例后,就觉得很简单,然后一顿操作之后调试报错...

    按官方修改的示例:

    1. #MidServerClient.java
    2. import feign.Param;
    3. import org.springframework.cloud.openfeign.FeignClient;
    4. import org.springframework.web.bind.annotation.RequestMapping;
    5. import org.springframework.web.bind.annotation.RequestMethod;
    6. @FeignClient(value = "edu-mid-server")
    7. public interface MidServerClient {
    8. @RequestMapping(value = "/test/header", method = RequestMethod.GET)
    9. @Headers({"userInfo:{userInfo}"})
    10. Object headerTest(@Param("userInfo") String userInfo);
    11. }

    提示错误:

    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相关的注解),而这个注解并不会处理解析上面列的注解

    1. @Configuration
    2. public class FeignClientsConfiguration {
    3. ・・・
    4. @Bean
    5. @ConditionalOnMissingBean
    6. public Contract feignContract(ConversionService feignConversionService) {
    7. return new SpringMvcContract(this.parameterProcessors, feignConversionService);
    8. }
    9. ・・・

    解决

    原因找到了

    spring cloud使用了自己的SpringMvcContract来解析注解,导致默认的注解解析方式失效。

    解决方案自然就是重新解析处理feign的注解,这里通过自定义Contract继承SpringMvcContract再把Feign.Contract.Default解析逻辑般过来即可(重载的方法是在SpringMvcContract基础上做进一步解析,否则Feign对RequestMapping相关对注解解析会失效)

    代码如下(此处只对@Headers、@Param重新做了解析):

    1. #FeignCustomContract.java
    2. import feign.Headers;
    3. import feign.MethodMetadata;
    4. import feign.Param;
    5. import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
    6. import org.springframework.cloud.openfeign.support.SpringMvcContract;
    7. import org.springframework.core.convert.ConversionService;
    8. import java.lang.annotation.Annotation;
    9. import java.lang.reflect.Method;
    10. import java.util.*;
    11. import static feign.Util.checkState;
    12. import static feign.Util.emptyToNull;
    13. public class FeignCustomContract extends SpringMvcContract {
    14. public FeignCustomContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors, ConversionService conversionService) {
    15. super(annotatedParameterProcessors, conversionService);
    16. }
    17. @Override
    18. protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
    19. //解析mvc的注解
    20. super.processAnnotationOnMethod(data, methodAnnotation, method);
    21. //解析feign的headers注解
    22. Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
    23. if (annotationType == Headers.class) {
    24. String[] headersOnMethod = Headers.class.cast(methodAnnotation).value();
    25. checkState(headersOnMethod.length > 0, "Headers annotation was empty on method %s.", method.getName());
    26. data.template().headers(toMap(headersOnMethod));
    27. }
    28. }
    29. @Override
    30. protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
    31. boolean isMvcHttpAnnotation = super.processAnnotationsOnParameter(data, annotations, paramIndex);
    32. boolean isFeignHttpAnnotation = false;
    33. for (Annotation annotation : annotations) {
    34. Class<? extends Annotation> annotationType = annotation.annotationType();
    35. if (annotationType == Param.class) {
    36. Param paramAnnotation = (Param) annotation;
    37. String name = paramAnnotation.value();
    38. checkState(emptyToNull(name) != null, "Param annotation was empty on param %s.", paramIndex);
    39. nameParam(data, name, paramIndex);
    40. isFeignHttpAnnotation = true;
    41. if (!data.template().hasRequestVariable(name)) {
    42. data.formParams().add(name);
    43. }
    44. }
    45. }
    46. return isMvcHttpAnnotation || isFeignHttpAnnotation;
    47. }
    48. private static Map<String, Collection<String>> toMap(String[] input) {
    49. Map<String, Collection<String>> result =
    50. new LinkedHashMap<String, Collection<String>>(input.length);
    51. for (String header : input) {
    52. int colon = header.indexOf(':');
    53. String name = header.substring(0, colon);
    54. if (!result.containsKey(name)) {
    55. result.put(name, new ArrayList<String>(1));
    56. }
    57. result.get(name).add(header.substring(colon + 1).trim());
    58. }
    59. return result;
    60. }
    61. }
    1. #FeignCustomConfiguration.java
    2. import feign.Contract;
    3. import org.springframework.beans.factory.annotation.Autowired;
    4. import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    5. import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
    6. import org.springframework.context.annotation.Bean;
    7. import org.springframework.context.annotation.Configuration;
    8. import org.springframework.core.convert.ConversionService;
    9. import java.util.ArrayList;
    10. import java.util.List;
    11. @Configuration
    12. public class FeignCustomConfiguration {
    13. ・・・
    14. @Autowired(required = false)
    15. private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();
    16. @Bean
    17. @ConditionalOnProperty(name = "feign.feign-custom-contract", havingValue = "true", matchIfMissing = true)
    18. public Contract feignContract(ConversionService feignConversionService) {
    19. return new FeignCustomContract(this.parameterProcessors, feignConversionService);
    20. }
    21. ・・・

    改完马上进行新一顿的操作, 看请求日志已经设置成功,响应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"]}"}

    以上就是SpringCloud项目中Feign组件添加请求头所遇到的坑如何解决的详细内容,更多关于SpringCloud项目中Feign组件添加请求头所遇到的坑如何解决的资料请关注九品源码其它相关文章!