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:
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线程切换的坑)
来源:https://juejin.cn/post/6844903961653149709
猜你喜欢
- 目录一、Collections.sort的简单使用二、问题提出三、Comparable实现排序四、Comparator实现排序五、Compa
- Purpose开发人员在合作的时候经常遇到以下场景:1.开发人员A在自己的本地数据库做了一些表结构的改动,并根据这些改动调整了DAO层的代码
- 本文实例讲述了Android编程使用WebView实现文件下载功能的两种方法。分享给大家供大家参考,具体如下:在应用中,通常会使用到文件下载
- 前言一个简单的单机小游戏:flypybird ,用来巩固java基础。涉及主要知识点:JFrame 、 JPanel 、 继承、 键盘/鼠标
- 说在前面大一软件工程在读,java萌新一只,第一次写博客,技术很菜勿喷。如有错误欢迎指出!这个小程序是给朋友的生日礼物,耗时半天,实际写起来
- Interval操作符:用于创建Observable,跟TimerTask类似,用于周期性发送信息,是一个可以指定线程的TimerTask首
- 简单工厂模式:由一个工厂对象决定创建出哪一种类的实例。1.抽象类public abstract class People { p
- 引言这是一篇基于Socket进行网络编程的入门文章,我对于网络编程的学习并不够深入,这篇文章是对于自己知识的一个巩固,同时希望能为初学的朋友
- 一、查询中排除标识字段1.1 测试查询@Testpublic void findAllTest() { List&
- 系列文章已完成,目录如下:jdk-logging log4j logback日志系统实现机制原理介绍commons-lo
- 目录1、前提知识2、实现思路:1、前提知识需要知道简单的IO流操作,以及简单的UDP发送数据包的原理。需要用到的类:DatagramSock
- 前言由于多核系统普遍存在,并发性编程的应用无疑比以往任何时候都要广泛。但并发性很难正确实现,用户需要借助新工具来使用它。很多基于 JVM 的
- 在开发中,我们通常需要将从数据库中查询的集合数据转换成类似文件系统一样的树形集合,比如:省市单位,部门机构,书籍分类等TreeNode对象@
- 一直以来不是怎么清楚自旋锁,最近有点时间,好好的学习了一下;所谓的自旋锁在我的理解就是多个线程在尝试获取锁的时候,其中一个线程获取锁之后,其
- 1、原来是将EditView放到了popupwindow,发现EditView原有的复制、粘贴、全选、选择功能失效了,所以便用DialogF
- Intro做项目的时候,页面上有一些敏感信息,需要用“*”隐藏一些比较重要的信息,于是打算写一个通用的方法。Let's do it
- java String的深入理解一、Java内存模型 按照官方的说法:Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和
- 本文实例为大家分享了C#线程中弹窗的制作代码,供大家参考,具体内容如下首先建立一个ShowFrom窗体,窗体中放入两个按钮分别为确定和取消分
- 首先需要有网络权限,然后我们这里匹配的网络请求是之前封装好的Okhttp。非常的简单方便,直接复制进去,依赖一下包,然后调用方法即可。 这里
- 注:如果没有 root 权限也是可以试试,一般情况下,都需要 root 权限,才能连接成功。1.需要确保你的开发 PC 和 Android