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

作者:沙砾 时间:2023-04-28 03:46:58 

前言

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

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

按官方修改的示例:

#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:

AnnotationInterface TargetUsage
@RequestLineMethodDefines the HttpMethod and UriTemplate for request. Expressions, values wrapped in curly-braces {expression} are resolved using their corresponding @Param annotated parameters.
@ParamParameterDefines a template variable, whose value will be used to resolve the corresponding template Expression, by name.
@HeadersMethod, TypeDefines 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.
@QueryMapParameterDefines a Map of name-value pairs, or POJO, to expand into a query string.
@HeaderMapParameterDefines a Map of name-value pairs, to expand into Http Headers
@BodyMethodDefines 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线程切换的坑)

来源:https://juejin.cn/post/6844903961653149709

标签:SpringCloud,Feign,请求头
0
投稿

猜你喜欢

  • Java实战之王者荣耀的英雄是怎么产生的?

    2021-08-25 12:35:49
  • Spring Boot 实例代码之通过接口安全退出

    2022-02-14 04:41:36
  • android实现可自由移动、监听点击事件的悬浮窗

    2022-04-12 14:15:31
  • SSH框架网上商城项目第24战之Struts2中处理多个Model请求的方法

    2023-02-14 20:49:21
  • Android入门之画图详解

    2023-11-09 11:47:47
  • 详解kafka中的消息分区分配算法

    2021-06-02 08:16:15
  • 获取Spring的上下文环境ApplicationContext的最简单方式

    2023-04-17 18:14:40
  • 浅谈关于spring profile的误解

    2021-07-25 05:48:43
  • Android实现中轴旋转特效 Android制作别样的图片浏览器

    2023-07-03 00:37:16
  • 浅谈c++性能测试工具之计算时间复杂度

    2023-07-09 12:40:05
  • Android仿银行客户签名并且保存签名的截图文件并命名为本地时间

    2023-09-19 22:42:48
  • java实现excel和txt文件互转

    2023-10-07 23:04:05
  • JAVA中Collections工具类sort()排序方法

    2021-10-22 04:56:29
  • C#实现快捷键的几种常用方法汇总

    2021-08-23 15:01:54
  • 详解Mybatis中常用的约束文件

    2023-11-28 08:02:17
  • C#使用WebClient登录网站并抓取登录后的网页信息实现方法

    2022-05-19 10:43:24
  • java 中HttpClient传输xml字符串实例详解

    2023-11-24 13:40:54
  • C#实现泛型动态循环数组队列的方法

    2022-11-03 05:05:42
  • Android实现ViewPager无限循环效果(一)

    2022-08-02 18:58:34
  • Java代码实现简单酒店管理系统

    2023-11-21 03:38:57
  • asp之家 软件编程 m.aspxhome.com