Feign get接口传输对象,调用方接口代码:java
@FeignClient(name = "manage") public interface AccessApiService { @RequestMapping(value = "/interface/listWithRules", method = RequestMethod.GET) Result<PageQueryResult<InterfaceInfo>> listWithRules(ApplicationSearchParams params); }
Feign中定义的一个get接口,参数是一个对象,调用后返回一个 「Request method ‘POST’ not supported」的报错。spring
断点打进去看,查看feign.Client.Default内部类的execute方法,最终使用了jdk自带的HttpURLConnection
最终能够看到这段代码:apache
private synchronized OutputStream getOutputStream0() throws IOException { try { if(!this.doOutput) { throw new ProtocolException("cannot write to a URLConnection if doOutput=false - call setDoOutput(true)"); } else { if(this.method.equals("GET")) { this.method = "POST"; }
传输对象这种状况,被粗暴的用修改请求method的方式来解决,在http协议里复杂的内容能够放入body。因此就有了上面莫名其妙的报错。咱们想,若是不修改请求方式,依然使用get,咱们就必需要吧对象中的参数一个个拿出来拼接到url里,试想下这个对象再复杂一些,好比包了其余的对象呢,怎么拼接?此时咱们想一个对象直接转json丢body传输的确是个好办法,怪不的上面想这么粗暴的办法。
可是修改请求方式破坏了服务提供放的接口,看起来不是很优雅。找解决方案的时候说使用httpclient代替jdk自带方式就能够解决问题了。
具体以下配置:json
feign: httpclient: enabled: true
<dependency> <groupId>com.netflix.feign</groupId> <artifactId>feign-httpclient</artifactId> <version>${feign-httpclient}</version> </dependency>
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.3</version> </dependency>
如此请求服务就通了,但是会发现对象的参数都是空的,还有一步:
服务提供的接口上须要加上@RequestBody来接参数。对,就是@RequestBody!也就是在body里拿的参数。mvc
Result<PageQueryResult<InterfaceInfo>> listWithRules(@RequestBody ApplicationSearchParams params);
那么问题来了,一个get请求怎么来的body呢?
好吧,http协议只是一个协议,理论上只要你想实现不管用什么请求方式均可以带body,只是咱们规范约定body是post请求专用而已。
那么httpclient并不会作这个事,仍是feign在调用httpclient时,产生了一个get请求而且带着body。
具体代码在feign.httpclient.ApacheHttpClient#toHttpUriRequest:app
if (request.body() != null) { entity = null; Object entity; if (request.charset() != null) { ContentType contentType = this.getContentType(request); content = new String(request.body(), request.charset()); entity = new StringEntity(content, contentType); } else { entity = new ByteArrayEntity(request.body()); } requestBuilder.setEntity((HttpEntity)entity); }
至此,彷佛咱们只能选择后者来解决了,这种方式彷佛也是有点畸形,毕竟都破坏掉http协议规范了。
因此不推荐使用,推荐全部get请求,调用方的接口代码所有写成相似以下:spring-boot
@RequestMapping(value = "/interface/listWithRules", method = RequestMethod.GET) Result<PageQueryResult<InterfaceInfo>> listWithRules( @RequestParam(value = "gatewayName") String gatewayName, @RequestParam(value = "modifiedTime") Integer modifiedTime, @RequestParam(value = "pageIndex") Integer pageIndex, @RequestParam(value = "pageSize") Integer pageSize);
而服务提供方可使用对象。这里注意每一个参数注解RequestParam必需要带有value字段,不然会有这个报错:
「RequestParam.value() was empty on parameter 0」
问题又来了,在spring mvc使用中都是默认参数名字叫什么这个value就叫什么的呀,怎么这里还必需要本身定义一下呢?
你遇到的问题世界上总有人已经遇到过,我以为这个解答是最好的,其余你都不用再看了:
https://stackoverflow.com/questions/44313482/fiegn-client-with-spring-boot-requestparam-value-was-empty-on-parameter-0/52099007post
再展开一下,feign在解析@RequestParam注解时的代码在org.springframework.cloud.netflix.feign.annotation.RequestParamParameterProcessor 以下:ui
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) { int parameterIndex = context.getParameterIndex(); Class<?> parameterType = method.getParameterTypes()[parameterIndex]; MethodMetadata data = context.getMethodMetadata(); if (Map.class.isAssignableFrom(parameterType)) { checkState(data.queryMapIndex() == null, "Query map can only be present once."); data.queryMapIndex(parameterIndex); return true; } RequestParam requestParam = ANNOTATION.cast(annotation); String name = requestParam.value(); checkState(emptyToNull(name) != null, "RequestParam.value() was empty on parameter %s", parameterIndex); context.setParameterName(name); Collection<String> query = context.setTemplateParameter(name, data.template().queries().get(name)); data.template().query(name, query); return true; }
由于经过反射又没法拿到方法字段的名字(jdk8以上,能够经过增长编译参数开启这个能力),因此feign就放弃了,而spring 为何能作到获取到方法字段名称呢?
由于它有一套使用asm为基础解系class文件的能力,就是直接打开class看,那还有什么查不到的哦。具体类:LocalVariableTableParameterNameDiscoverer 这个类已经关系的asm了。this
到这里一场追寻告一段落,会想一下也很简单,为了解决get请求传输复杂对象的状况,一个http请求必然会面临这个问题,可是话说回来,一个get接口规范上不该该会定义什么复杂对象,而是几个参数而已,若是需呀很复杂的对象才能完成这个get接口,可能需呀思考这个接口设计的合理性了。