前几天在运用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
来发送恳求,其他咱们熟知的还有:okhttp
和httpClient
,这些后边都会看到。趁着这次报错的时机,正好来看下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
注解的接口
1.2 注册Bean
在注册bean的时分有一个很重要的FeignClientFactoryBean
目标, 它是一个FactoryBean
, spring在整合其他框架的时分,经常会看到FactoryBean
目标,比方mybatis。
既然是FactoryBean
,那么就看它的getObject()
办法就行。
趁便说一下在履行Feign.Builder builder = feign(context)
办法时,会创立几个比较重要的目标,这儿阐明一下,后边会用到。
- 创立
SpringMvcContract
目标(在FeignClientsConfiguration
装备类) - 创立
Feign.Builder
目标(在FeignClientsConfiguration
装备类) - 创立
DefaultTargeter
目标(在FeignAutoConfiguration
装备类)
ok, 接下来咱们就持续往下看DefaultTargeter
目标的target办法
做了什么?
调用的是Feign.Builder
目标的target办法,那就持续跟进去看下:
回来了一个ReflectiveFeign
目标。内部创立了一个InvocationHandlerFactory
目标,看姓名就知道他是做什么的了吧。
咱们再持续看下ReflectiveFeign
目标的newInstance(target)
办法
这个办法咱们只关注标红的这2块,第1处和咱们遇到的”Method Not Allowed 405“ 有关,第二处不用看也知道了,便是回来一个署理目标,运用的是JDK的动态署理。
咱们就来看下,这2个办法都做了什么:
上面是类(接口)层面的约束,接下来看下办法层面的约束:
接下来便是参数层面的约束,这个也和咱们遇到的问题有关:
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 ?
> 不难发现,这儿面都是用来处理含有注解参数的,比方@RequestParam
, @PathVariable
等等,除了这些特定注解以外的恳求参数,比方@RequestBody
, 没有任何注解的参数
,统统都会放到boyIndex
中进行标记。
其实到这儿就差不多了,其他的内容因为和题目中遇到的问题无关,这儿也就不做过多赘述了,接下来咱们调用feign的接口,看看他的运转逻辑:
1.3 恳求feign
前面咱们有提到,他会经过 FeignInvocationHandler
来创立署理目标,当建议恳求的时分直接看它的invoke(...)
办法即可。
持续往下看:
咱们先看buildTemplateFromArgs.create()
办法的要点内容:
咱们再持续看executeAndDecode(template, options)
办法的内容:
拦截器的履行时机在这儿。
调用execute()
办法
再持续看 convertAndSend()
办法:
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的客户端是怎样处理的即可。
依然是从FeignInvocationHandler
的invoke()
办法作为剖析的入口:
要点看body的处理:
不难发现,他没有将GET改为POST的行为,可是它是将参数以JSON的方式放到了恳求体中,然后发送恳求。但从这儿看是没有问题的,可是接纳方怎样接纳参数呢? 正常来说应该是这样的:
这样的话,尽管恳求能够成功,可是无法获获取参数,所有值都是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()
作为入口:
咱们仍是看对body的处理:
在看下requestBuilder.method(input.httpMethod().name(), body)
办法的逻辑:
HttpMethod.permitsRequestBody(method)
办法的逻辑很简略:
public static boolean permitsRequestBody(String method) {
return !(method.equals("GET") || method.equals("HEAD"));
}
到这儿就很明显了,okHttp将会报错反常:
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();
}
}
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的运用进程中呈现的过错就剖析到这儿吧。祝我们新年快乐,万事顺意。