通过Feign调用报错

前几天在运用feign进行远程调用时,呈现了”Method Not Allowed 405″,”Request method ‘POST’ not supported” 的过错,肉眼查看了一下代码,感觉没有任何问题,我先把代码简略贴出来看一下.
feign的客户端:

@FeignClient(name = "serverFeignClient", url = "http://localhost:8080")
public interface ServerClientFeign {  
    @GetMapping("/server/hello")  
    String test(QueryRequest queryRequest);
}
@Data  
class QueryRequest {  
    private String name;  
    private String date;  
    private boolean valid;  
}

服务提供者:http://localhost:8080

@RestController
public class ServerController {  
    @GetMapping("/server/hello")  
    public String server(QueryParam queryParam) {  
       return "hello success: " + queryParam;  
    }
}
//`QueryParam` 属性值基本上和QueryRequest相同。

当建议恳求时,就会报错:Request method ‘POST’ not supported,不支持POST恳求?我这也没有POST呀,有点摸不到头脑,其时也没有细心探求,暂时根据报错提示,我就把恳求都改成POST了,就没有问题了,今天咱们来看下为什么会呈现这个问题。

feign是什么就不多做介绍了,它便是声明式的webservice客户端,集成了其他HTTP 客户端框架来发送恳求,默许运用的是JDK提供的HttpURLConnection来发送恳求,其他咱们熟知的还有:okhttphttpClient,这些后边都会看到。趁着这次报错的时机,正好来看下feign的履行进程。

1.探求feign的履行原理

在springboot的生态中,运用一个功用一般能够从 xxxAutoConfiguration 主动装备类或许 @Enablexxx 注解下手;运用feign的时分,在导入依赖后,要经过 @EnableFeignClients 注解来敞开feign的功用。咱们就从这个注解下手。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)  
@Documented  
@Import(FeignClientsRegistrar.class)  
public @interface EnableFeignClients {
   //.....
}

导入了 FeignClientsRegistrar类:

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    //要点看重写ImportBeanDefinitionRegistrar接口的办法
    @Override  
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {  
        //.....
    }
}

它实现了 ImportBeanDefinitionRegistrar接口,熟悉springboot履行流程的应该都对这个接口有形象, 简略说便是给容器注册目标的。

咱们对这个进程简略梳理一下:

1.1 经过@EnableFeignClients注解扫描指定包下的标注了@FeignClient注解的接口

通过Feign调用报错

1.2 注册Bean

在注册bean的时分有一个很重要的FeignClientFactoryBean目标, 它是一个FactoryBean, spring在整合其他框架的时分,经常会看到FactoryBean目标,比方mybatis。

通过Feign调用报错

既然是FactoryBean,那么就看它的getObject()办法就行。

通过Feign调用报错

趁便说一下在履行Feign.Builder builder = feign(context)办法时,会创立几个比较重要的目标,这儿阐明一下,后边会用到。

  1. 创立 SpringMvcContract 目标(在FeignClientsConfiguration 装备类)
  2. 创立 Feign.Builder 目标(在FeignClientsConfiguration 装备类)
  3. 创立 DefaultTargeter 目标(在FeignAutoConfiguration装备类)

ok, 接下来咱们就持续往下看DefaultTargeter目标的target办法做了什么?

通过Feign调用报错
调用的是Feign.Builder 目标的target办法,那就持续跟进去看下:

通过Feign调用报错
回来了一个ReflectiveFeign目标。内部创立了一个InvocationHandlerFactory 目标,看姓名就知道他是做什么的了吧。

咱们再持续看下ReflectiveFeign目标的newInstance(target)办法

通过Feign调用报错

这个办法咱们只关注标红的这2块,第1处和咱们遇到的”Method Not Allowed 405“ 有关,第二处不用看也知道了,便是回来一个署理目标,运用的是JDK的动态署理。

通过Feign调用报错

咱们就来看下,这2个办法都做了什么:

通过Feign调用报错

通过Feign调用报错

上面是类(接口)层面的约束,接下来看下办法层面的约束:

通过Feign调用报错

接下来便是参数层面的约束,这个也和咱们遇到的问题有关:

通过Feign调用报错

checkState(data.bodyIndex() == null) 阐明没有任何注解的参数只能有一个!

像这种就不行了,因为只能有一个。

@FeignClient(name = "oneRClient", url = "http://localhost:8080")
public interface OneRFeignClient {  
   @GetMapping(value = {"/server/hello"})  
   String test(String name, String date, boolean valid);  
}

ok, 咱们再来看下为什么processAnnotationOnParameter办法回来的是false ?

通过Feign调用报错
> 不难发现,这儿面都是用来处理含有注解参数的,比方@RequestParam, @PathVariable 等等,除了这些特定注解以外的恳求参数,比方@RequestBody, 没有任何注解的参数,统统都会放到boyIndex中进行标记。

其实到这儿就差不多了,其他的内容因为和题目中遇到的问题无关,这儿也就不做过多赘述了,接下来咱们调用feign的接口,看看他的运转逻辑:

1.3 恳求feign

前面咱们有提到,他会经过 FeignInvocationHandler 来创立署理目标,当建议恳求的时分直接看它的invoke(...) 办法即可。

通过Feign调用报错

持续往下看:

通过Feign调用报错

咱们先看buildTemplateFromArgs.create()办法的要点内容:

通过Feign调用报错

通过Feign调用报错

咱们再持续看executeAndDecode(template, options)办法的内容:

通过Feign调用报错

拦截器的履行时机在这儿。

调用execute()办法

通过Feign调用报错

再持续看 convertAndSend()办法:

通过Feign调用报错

通过Feign调用报错

ok, 这儿就完好的演示了,为什么我的feign调用会呈现 "status":405,"error":"Method Not Allowed" 的问题了。

1.4 怎样处理?

最简略的处理办法便是在办法参数上加一个 @SpringQueryMap注解,其他的不用做任何改动。因为这个注解将会自定义目标转成Map,然后以名值对的方式拼接到url上。

2.集成HttpClient

<dependency>
   <groupId>io.github.openfeign</groupId>  
   <artifactId>feign-httpclient</artifactId>  
   <version>11.8</version>  
</dependency>

敞开:

feign:
  httpclient:  
    enabled: true  

在启动阶段没有任何的差异,只是装备的客户端不相同罢了,既然如此,那咱们就直接恳求,看Http Client的客户端是怎样处理的即可。

依然是从FeignInvocationHandlerinvoke()办法作为剖析的入口:

通过Feign调用报错

通过Feign调用报错

要点看body的处理:

通过Feign调用报错
不难发现,他没有将GET改为POST的行为,可是它是将参数以JSON的方式放到了恳求体中,然后发送恳求。但从这儿看是没有问题的,可是接纳方怎样接纳参数呢? 正常来说应该是这样的:

通过Feign调用报错

这样的话,尽管恳求能够成功,可是无法获获取参数,所有值都是null, 现在恳求方将参数以JSON的方式放到了恳求体里面了,接纳方要怎样获取呢? 用@RequestBody注解就能够从恳求体中获取参数。

  @GetMapping("/server/hello")
  public String server(@RequestBody QueryParam queryParam) {  
     return "恳求参数:" + queryParam + ", 当时时刻:" + LocalDateTime.now();  
  }

这样写尽管能够获取参数值,可是这种写法有点“另类”了。

所以, HttpClient比较默许的Client$Default, 能够发送恳求,可是无法获取参数值。这一点要注意。

3.集成OkHttp

<dependency>
   <groupId>io.github.openfeign</groupId>  
   <artifactId>feign-okhttp</artifactId>  
   <version>11.8</version>  
</dependency>

敞开:

feign:
  okhttp:  
    enabled: true

启动阶段没有任何差异,只是装备了OkHttp的客户端,所以咱们从建议恳求开端看,以FeignInvocationHandler#invoke()作为入口:

通过Feign调用报错

咱们仍是看对body的处理:

通过Feign调用报错

在看下requestBuilder.method(input.httpMethod().name(), body)办法的逻辑:

通过Feign调用报错

HttpMethod.permitsRequestBody(method)办法的逻辑很简略:

public static boolean permitsRequestBody(String method) {
   return !(method.equals("GET") || method.equals("HEAD"));  
}

到这儿就很明显了,okHttp将会报错反常:

通过Feign调用报错

4.总结

仍是以上面的代码为例: feign client:

@FeignClient(name = "oneRClient", url = "http://localhost:8080")
public interface OneRFeignClient {  
   @GetMapping(value = {"/server/hello"})  
   String test(QueryRequest queryRequest);  
}

服务端:

@RestController
public class ServerController {  
    @GetMapping("/server/hello")  
    public String server(QueryParam queryParam) {  
        return "恳求参数2:" + queryParam + ", 当时时刻:" + LocalDateTime.now();  
    }
}

通过Feign调用报错

GET恳求下:关于参数是Map,自定义类这种的,运用 @SpringQueryMap 能够将数据以键值对的方式拼接到url上。如果参数是单个String这种的,就不要用@SpringQueryMap 了,尽管它也会以键值对的方式拼接到url上,可是key固定是value, 值是内存地址,所以单个String这种的,乖乖用 @RequestParam就好了。

@FeignClient(name = "oneRClient", url = "http://localhost:8080")
public interface OneRFeignClient {  
   @GetMapping(value = {"/server/hello"})  
   String test(@SpringQueryMap QueryRequest queryRequest); 
}

好了,关于在feign的运用进程中呈现的过错就剖析到这儿吧。祝我们新年快乐,万事顺意。