一,问题产生背景
Feign发送Get请求时,采用POJO传递参数 Method Not Allowed 405
@FeignClient("microservice-provider-user")public interface UserFeignClient {@RequestMapping(value = "/user", method = RequestMethod.GET)public PageBean<User> get(User user);}
二,问题产生原因
.www.protocol.http;public class HttpURLConnection extends .HttpURLConnection {// 这里很简单:只要你doOutput = true了,你是get请求的话也会强制给你转为POST请求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";}}...}}
这段代码是在 HttpURLConnection 中发现的,jdk原生的http连接请求工具类,原来是因为Feign默认使用的连接工具实现类,所以里面发现只要你有body体对象,就会强制的把get请求转换成POST请求。
三,解决方案
1,方案一使用Map 接收参数
量大的话改的东西多
使用@Validated验证的时候不支持了,需要手动调用校验方法
@FeignClient("microservice-provider-user")public interface UserFeignClient {//@RequestParam不要指定参数@RequestMapping(value = "/user", method = RequestMethod.GET)public PageBean<User> get(@RequestParam Map<String,Object> param);}
2,方案二不用实体接收,改为单独的参数
量大的话改的东西多
@FeignClient("microservice-provider-user")public interface UserFeignClient {@RequestMapping(value = "/user", method = RequestMethod.GET)public PageBean<User> get(@RequestParam("name") String name,@RequestParam("age") int age);}
3,方案三修改feign的请求工具
默认的是jdk的,可以修改为okhttp 或者 httpclent
yml:
feign:httpclient:enabled: true
maven:
<!--httpClient--><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.3</version></dependency><dependency><groupId>flix.feign</groupId><artifactId>feign-httpclient</artifactId><version>8.17.0</version></dependency>
四,方案四添加RequestInterceptor 拦截器
package com.mugua.demo.config;import com.fasterxml.jackson.databind.JsonNode;import com.fasterxml.jackson.databind.ObjectMapper;import feign.Request;import feign.RequestInterceptor;import feign.RequestTemplate;import org.springframework.beans.factory.annotation.Autowired;import org.ponent;import org.springframework.util.StringUtils;import java.io.IOException;import java.util.*;/*** GET 405** @author liwenchao*/@Componentpublic class FeignRequestParamInterceptor implements RequestInterceptor {private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();@Overridepublic void apply(RequestTemplate template) {// feign 不支持 GET 方法传 POJO, json body转queryif ("GET".equals(template.method()) && template.requestBody() != null) {try {JsonNode jsonNode = OBJECT_MAPPER.readTree(template.requestBody().asBytes());template.body(Request.Body.empty());Map<String, Collection<String>> queries = new HashMap<>();buildQuery(jsonNode, "", queries);template.queries(queries);} catch (IOException e) {e.printStackTrace();}}}private void buildQuery(JsonNode jsonNode, String path, Map<String, Collection<String>> queries) {// 叶子节点if (!jsonNode.isContainerNode()) {if (jsonNode.isNull()) {return;}Collection<String> values = puteIfAbsent(path, k -> new ArrayList<>());values.add(jsonNode.asText());return;}// 数组节点if (jsonNode.isArray()) {Iterator<JsonNode> it = jsonNode.elements();while (it.hasNext()) {buildQuery(it.next(), path, queries);}} else {Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();while (it.hasNext()) {Map.Entry<String, JsonNode> entry = it.next();if (StringUtils.hasText(path)) {buildQuery(entry.getValue(), path + "." + entry.getKey(), queries);} else { // 根节点buildQuery(entry.getValue(), entry.getKey(), queries);}}}}}
5,方案五重写SpringMvcContract
package com.mugua.demo.config;import feign.MethodMetadata;import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;import org.springframework.cloud.openfeign.support.SpringMvcContract;import org.springframework.core.convert.ConversionService;import org.ponent;import org.springframework.web.bind.annotation.RequestBody;import java.lang.annotation.Annotation;import java.util.List;/*** GET 405** @author lwc*/@Componentpublic class SpringMvcPojoObjectQueryContract extends SpringMvcContract {public SpringMvcPojoObjectQueryContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors, ConversionService conversionService) {super(annotatedParameterProcessors, conversionService);}@Overrideprotected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {boolean httpAnnotation = super.processAnnotationsOnParameter(data, annotations, paramIndex);//在springMvc中如果是Get请求且参数中是对象 没有声明为@RequestBody 则默认为Paramif (!httpAnnotation && "GET".equalsIgnoreCase(data.template().method())) {for (Annotation parameterAnnotation : annotations) {if (!(parameterAnnotation instanceof RequestBody)) {return false;}}data.queryMapIndex(paramIndex);return true;}return httpAnnotation;}}