接上文,上文主要讨论通过 RestTemplate 来简化 Http 调用,在这里我们试试基于 Spring Cloud 的 Feign 实现服务之间的 "RPC" 调用。
1. 简述
我们这里的 RPC 加了引号,是因为 Feign 是通过 Http 协议来实现的,而通常的 RPC 为了提高传输的效率,一般都是私有的 TCP 协议,数据的传输报文结构尽可能的简单或直接是二进制。除此之外,Feign 满足 RPC 的基本特点:
- 让远程的服务调用就像调用本地的函数一样方便
- 通过Spring Cloud 或类似 Dubbo 等框架,可以服务注册,新增服务,负载均衡等。
以下我们主要以例子尝试第一个特点。
RPC 的基本实现套路是先创建一个接口文件,这个接口文件用来描述服务提供方的接口描述,然后用一个工具读取这个接口文件生成服务使用方和服务提供方二套 Java 代码(大部分 RPC 技术支持多种语言)。服务使用方和提供方把对应的 Java 代码拷贝回自己的项目里,这些自动生成的代码不要做任何修改,最后就可以使用远程的服务像调用本地函数一样了。
回到 Feign ,Feign 简单很多。我们以上一篇里的服务提供方示例为这里的服务提供方。我们再实现一个基于 Feign 的服务消费方
2. 服务消费方
2.1 引入相应的库
ext {
set('springCloudVersion', "Greenwich.SR2")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter'
implementation 'org.springframework.cloud:spring-cloud-starter-feign:1.4.7.RELEASE'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
这里要注意的是因为 springcloud 使用了最新的版本,对应的springboot 及 feign 都要使用新的版本,否则会报运行错误:
Exception in thread “main” java.lang.AbstractMethodError
2.2 定义 RPC 的接口文件
这里最为简单,直接拷贝服务提供方的 Controller 里所有方法,连注解都拷贝过来,但是因为是一个 Java 的 interface,所以把方法体都删除。
@FeignClient(
name = "demo-service",
url = "http://localhost:8080/test/",
fallback = TestService2Fallback.class
)
public interface TestService2 {
@RequestMapping(value = "/get1", method = RequestMethod.GET)
public String get1(@RequestParam("para") String para);
@RequestMapping(value = "/get2/{id}", method = RequestMethod.GET)
public String get2(@RequestParam("id") Integer id);
@RequestMapping(value = "/get3/{id}", method = RequestMethod.GET)
public Map get3(@RequestParam("id") Integer id);
@RequestMapping(value = "/get4", method = RequestMethod.GET)
public String get4(@RequestHeader(name = "token") String token);
@RequestMapping(value = "/post1", method = RequestMethod.POST)
public String post1(@RequestBody TestModel model);
@RequestMapping(value = "/post2", method = RequestMethod.POST)
public String post2(@RequestBody TestModel model, @RequestHeader(name = "token") String token);
@RequestMapping(value = "/put1", method = RequestMethod.PUT)
public String put1(@RequestBody TestModel model, @RequestHeader(name = "token") String token);
@RequestMapping(value = "/del1/{id}", method = RequestMethod.DELETE)
public String del1(@RequestParam("id") Integer id);
}
这里要注意几个问题:
- @FeignClient 注解要标识服务的地址和一个对应这个接口的实现类,这个类是用来解决远程服务接口出错后的缺省值
- 函数里的参数 @PathVariable 得改成 @RequestParam
- 函数里的参数 HttpServletRequest request 改成 @RequestHeader(name = "token") String token 。这里需要修改服务提供方的代码。
2.3 确保扫描 FeignClient
在主程序位置加上注解 @EnableFeignClients ,确保能扫描到上面定义的 FeignClient 接口。
@SpringBootApplication
@EnableFeignClients
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}
2.4 运行
最后使用远程接口就很简单了,和调用本地函数没有任何区别,这里定义了2个 service,TestService1 是本地 Service ,TestService2是远程服务 http://localhost:8080/test/ 对应的接口。
但是使用方式完全一样:
String temp1 = testService2.get1("ttt");
String temp2 = testService1.test1();
String temp3 = testService2.get2(23);
2.5 设置Header内容
服务之间调用通常也是需要验证的,比如可以把验证的用户密码放到 http 请求的 header 里。通过实现 RequestInterceptor 接口,完成对所有的Feign请求,设置Header。
@FeignClient注解添加一个属性:
@Component
class FeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("user", "myuser1");
requestTemplate.header("password", "mypassword");
}
}
3. 总结
回到 RPC 的常规流程,Feign的开发流程也是先定义一个接口(Java Interface),然后把这个 java 文件分别发给接口的服务方和提供方,然后分别实现和调用这个接口,省去了通过 RPC 工具来生成一大堆客户端和服务端代码。
另外如果要涉及到多服务、注册服务等功能还需要引用 Spring Cloud 其它注册中心等库。
所有源码参考git,对应的服务端代码参考上一篇