经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
SpringCloud中分析讲解Feign组件添加请求头有哪些坑梳理
来源:jb51  时间:2022/6/21 10:20:48  对本文有异议

按官方修改的示例:

  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. }

提示错误:

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

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

解决

原因找到了: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. @Autowired(required = false)
  14. private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();
  15. @Bean
  16. @ConditionalOnProperty(name = "feign.feign-custom-contract", havingValue = "true", matchIfMissing = true)
  17. public Contract feignContract(ConversionService feignConversionService) {
  18. return new FeignCustomContract(this.parameterProcessors, feignConversionService);
  19. }

改完马上进行新一顿的操作, 看请求日志已经设置成功,响应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组件内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号