SpringCloud中分析讲解Feign组件添加请求头有哪些坑梳理

网友投稿 648 2022-07-21


目录分析解决

按官方修改的示例:

#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 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> toMap(String[] input) {

Map> result =

new LinkedHashMap>(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(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 parameterProcessors = new ArrayList<>();

@Bean

@ConditionalOnProperty(name = "feign.feign-custom-contract", havingValue = "true", matchIfMissing = true)

public Contract feignContract(ConversionService feignConversionService) {

return new FeignCustomContract(this.parameterProceshttp://sors, 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-traceidhttp://":["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线程切换的坑)


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:一文搞懂Java中的注解和反射
下一篇:Mybatis
相关文章

 发表评论

暂时没有评论,来抢沙发吧~