当我们在SpringCloud项目中引入spring-cloud-starter-openfeign时,如果我们用Feign发送Get请求时,采用POJO对象传递参数,那么会可能会出现异常。那么如果你又不想用@RequestParam一个个参数写在调用方法内,有什么好的解决方案吗?
下面是我在调用某个接口,GET请求:
@FeignClient(name = "BiaoClient", url = "${boss.biao.url}")
public interface BiaoClient {
@GetMapping("/api/getDeviceStatus")
BiaoBaseResponse<DeviceStatusInfo> queryBiaoInfo(DeviceStatusInfoRequest request);
}
当发起调用的时候,会出现异常,大体意思是Request method 'POST' not supported
,为什么是POST请求呢?
究其原因是因为feign默认的远程调用使用的是jdk底层的HttpURLConnection,这在feign-core包下的Client接口中的convertAndSend方法可看到:
if (request.body() != null) {
if (contentLength != null) {
connection.setFixedLengthStreamingMode(contentLength);
} else {
connection.setChunkedStreamingMode(8196);
}
connection.setDoOutput(true);
OutputStream out = connection.getOutputStream();
if (gzipEncodedRequest) {
out = new GZIPOutputStream(out);
} else if (deflateEncodedRequest) {
out = new DeflaterOutputStream(out);
}
try {
out.write(request.body());
} finally {
try {
out.close();
} catch (IOException suppressed) { // NOPMD
}
}
}
该段代码片段会判断requestBody是否为空,我们知道GET请求默认是不会有requestBody的,因此该段代码会执行到HttpURLConnection中:
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";
}
最关键的代码片段已显示当请求方式为GET请求,会将该GET请求修改为POST请求,这也就是出现该异常的原因。
那么怎么解决呢?
当然如果你不用POJO的方式去传递出参数当然是可行的,如下:
@FeignClient(name = "BiaoClient", url = "${boss.biao.url}")
public interface BiaoClient {
@GetMapping("/api/getDeviceStatus")
BiaoBaseResponse<DeviceStatusInfo> queryBiaoInfo(@RequestParam("sn") String sn,
@RequestParam("ack") String ack);
}
如果想保持POJO作为参数?依然是有方案的。
方案一
目前网上搜索到的都是这个方案。
我们只需将feign底层的远程调用由HttpURLConnection修改为其他远程调用方式即可,而且基本不需要修改太多的代码,这里利用apache的HttpClient。
application.properties
加入feign.httpclient.enabled=true
- 加入依赖
<!-- 使用Apache HttpClient替换Feign原生httpclient -->
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>8.17.0</version>
</dependency>
- 需要@RequestBody,如下:
@FeignClient(name = "BiaoClient", url = "${boss.biao.url}")
public interface BiaoClient {
@GetMapping("/api/getDeviceStatus")
BiaoBaseResponse<DeviceStatusInfo> queryBiaoInfo(@RequestBody DeviceStatusInfoRequest request);
}
额外加一句,在GET方法里,加@RequestBody总感觉别扭。。。
方案二
这个方案是Spring Cloud OpenFeign官方提供的,我是在看官方文档看到的,于是在github上找查看了一下。
这个方案更推荐使用!在github上有这样一个Issue——Add support for feign's QueryMap annotation for Object mapping #79
,这个Issue已经是closed,看日期是解决是在2018-12-07号。方法也很简单。保持原来的不用改,不需要添加额外的依赖,加一个注解@SpringQueryMap
就搞定。
@FeignClient(name = "BiaoClient", url = "${boss.biao.url}")
public interface BiaoClient {
@GetMapping("/api/getDeviceStatus")
BiaoBaseResponse<DeviceStatusInfo> queryBiaoInfo(@SpringQueryMap DeviceStatusInfoRequest request);
}
下图是解决这个issue改变的代码:
注意,要用该注解,需要升级你的Spring Cloud OpenFeign到新的版本(2.1.0.RC1
以及之后的版本)。