OpenFeign 底层实现原理
失败重试机制的底层实现原理
- (1) 添加注解
- 在主启动类或者配置类上添加
@EnableFeignClients
注解。
- (2) 动态代理
@EnableFeignClients
注解会触发 SpringBoot 框架的自动配置机制,扫描所有标记有 @FeignClient
的接口,并为它们创建代理实例。
- (3) RequestTemplate 发送 HTTP 请求
- 此处的 RequeustTemplate 可以理解为 RestTemplate,因为两者的目的相同。
- OpenFeign 不能直接发送 HTTP 请求,它在动态代理里面做了一些事情,也就是将注解里面请求的路由地址取出来,然后拼接出来一个 URL 请求的地址,然后再使用 RequestTemplate(RestTemplate)去发送 HTTP 请求。
- (4) RestTemplate 依靠 HTTP 框架实现 Web 请求的发送
- RestTemplate 只是一个模板方法类,它只是规定了一些调用的 API,底层并没有真正的实现,它依靠 HTTP 框架实现 Web 请求的发送(比如 Apache Http Client、Okhttp)。
常见使用错误
Request method ‘POST’ not supported
- 错误信息:在调用 OpenFeign 下面这段代码时,抛出了
Request method 'POST' not supported
的异常
1 2 3 4 5 6 7 8 9 10
|
@FeignClient(name = MicroServiceName.WECHAT_SERVICE) public interface WechatSubscribeUserService {
@RequestMapping(value = "/wechat/subscribe/get", method = RequestMethod.GET) WechatSubscribeUser getSubscribeUser(@RequestBody WechatSubscribeUserVo vo);
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@RestController @RequestMapping("/wechat/subscribe") public class WechatSubscribeUserController {
@Autowired private WechatSubscribeUserService subscribeUserService;
@GetMapping("/get") public WechatSubscribeUser get(WechatSubscribeUserVo vo) { String toOpenId = vo.getToOpenId(); String fromOpenId = vo.getFromOpenId(); return subscribeUserService.getUser(fromOpenId, toOpenId); }
}
|
- 错误原因:OpenFeign 原生的连接工具默认使用了 JDK 中的
HttpURLConnection
类进行实现,下面这段代码是在 HttpURLConnection
中发现的,所以只要 HTTP 请求里有 Body 体对象,就会强制的把 GET 请求转换成 POST 请求。
1 2 3 4 5 6 7 8 9 10 11
| 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"; } } } }
|
- 第一种解决方案:不使用 POJO 对象作为参数,而是传入多个独立的参数,并添加
@RequestParam
注解
1 2 3 4 5 6 7 8 9 10
|
@FeignClient(name = MicroServiceName.WECHAT_SERVICE) public interface WechatSubscribeUserService {
@RequestMapping(value = "/wechat/subscribe/get", method = RequestMethod.GET) WechatSubscribeUser getSubscribeUser(@RequestParam("fromOpenId") String fromOpenId, @RequestParam("toOpenId") String toOpenId);
}
|
- 第二种解决方案:使用 POJO 对象作为参数,同时添加
@SpringQueryMap
注解
1 2 3 4 5 6 7 8 9 10
|
@FeignClient(name = MicroServiceName.WECHAT_SERVICE) public interface WechatSubscribeUserService {
@RequestMapping(value = "/wechat/subscribe/get", method = RequestMethod.GET) WechatSubscribeUser get(@SpringQueryMap WechatSubscribeUserVo vo);
}
|
提示
Feign 的 @QueryMap
注解支持将 POJO 用作 GET 请求的参数映射,但默认的 @QueryMap
注解与 Spring 不兼容,因为它缺少 value
属性。Spring Cloud OpenFeign 提供了等效的 @SpringQueryMap
注解,用于将 POJO 或 Map 参数映射为查询参数。简而言之,Feign 的 GET 请求无法解析对象参数,如果传参是一个类对象,框架就需要把这个类对象解析成查询参数,但是直接在方法中传参框架不会自动把类对象解析成查询参数。@SpringQueryMap
注解的作用就是把 POJO 解析成 k1=v1&k2=v2
的查询参数格式。
- 第三种解决方案:使用 Apache HttpClient 或者 OkHttp 替换掉 OpenFeign 原生使用的
HttpURLConnection
连接工具,然后添加 @RequestBody
注解
1 2 3 4 5 6 7 8 9 10
|
@FeignClient(name = MicroServiceName.WECHAT_SERVICE) public interface WechatSubscribeUserService {
@RequestMapping(value = "/wechat/subscribe/get", method = RequestMethod.GET) WechatSubscribeUser getSubscribeUser(@RequestBody WechatSubscribeUserVo vo);
}
|
warning
笔者尝试使用 Apache HttpClient 或者 OkHttp 替换 OpenFeign 默认使用的 HttpURLConnection
,但并没有生效,有兴趣的可以参考这里的 替换教程。