Feign的使用实例
在这个部分,先做一个快速入门的演示来认识 Feign,然后结合 Spring MVC,说明 Feign 能扩展 Spring MVC,本节重点是理解参数传递的不同与相同点。
Feign演示实例
按照惯例,仍然先演示一个实例,让我们对 Feign 有一个直观的印象。实例中需要启动三个项目,服务注册中心、服务提供者、服务消费者。
在服务调用之前,做准备工作。首先启动服务注册中心,此处还是选择在第 10 章使用的服务注册中心,下面是服务注册中心的配置。
server.port=8764
eureka.client.service-url.defaultZone=http://localhost:8764/eureka/
eureka.client.register-with-eureka=false
然后,启动服务提供者。在服务提供者中,有一段服务程序将被暴露出来,在后面的服务消费者中,将会调用这段代码。为了不翻看前面的代码,这里展示一下将会被调用的服务的程序。代码如下所示。
package com.cloudtest.eurekaprovider.controller;
@RestController
public class HelloController {
@Autowired
private DiscoveryClient client;
@Autowired
private Registration registration; // 服务注册
private final Logger logger = LoggerFactory.getLogger(HelloController.class);
@GetMapping("/hello")
public String hello(){
ServiceInstance instance = serviceInstance();
String result = "host:port=" + instance.getUri() + ", "
+ "service_id:" + instance.getServiceId();
logger.info(result);
return "hello eureka!";
}
public ServiceInstance serviceInstance() {
List<ServiceInstance> list = client.getInstances(registration.getServiceId());
if (list != null && list.size() > 0) {
for(ServiceInstance itm : list){
if(itm.getPort()==8091){
return itm;
}
}
}
return null;
}
}
在上面的代码中,可以看到提供的 Rest 接口是 “/hello”。在服务提供者中,我们的配置文件也会用到,代码如下所示。
server.port=8091
spring.application.name=helloService
eureka.client.service-url.defaultZone=http://localhost:8764/eureka/
现在,开始写服务消费者。为了方便重现程序,我们先看实例的程序结构,如图12.1所示。

在上面的程序结构中,演示实例主要位于 feign 包,当然启动类也需要修改。首先,给客户端添加 Feign 依赖,代码如下所示。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
然后,修改启动类,给启动类添加 @EnableFeignClients 注解,其功能是开启 Spring Cloud Feign。代码如下所示。
package com.cloudtest.eurekaconsumer;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class EurekaconsumerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaconsumerApplication.class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
}
最后,定义一个接口,该接口是内部要调用的接口,代码如下所示。
package com.cloudtest.eurekaconsumer.feign;
/**
* 定义需要调用的接口
*/
@FeignClient(name="helloService")
@Component
public interface HelloInterface {
@GetMapping("/hello")
public String getHelloMsg();
}
在上面的代码中,我们需要添加 @FeignClient 注解绑定应用服务,并使用属性 name 制定应用服务名,这个服务名需要和服务注册中心的应用名相同,不过不区分大小写。在上面代码中,制定的应用服务名为 helloService。可以发现 helloService 与前面服务提供者中的配置文件中的应用名是相同的。
当知道应用服务名后,还需要指定调用的 Rest 接口。在上面的代码中,getHelloMsg 方法调用的接口是 “/hello”,这个 hello 接口就是指定的服务提供者的 “/hello” 接口。
然后就是如何使用。新建一个控制类,在浏览器上进行调用,代码如下所示。
package com.cloudtest.eurekaconsumer.feign.controller;
@RestController
public class HelloMsgController {
@Autowired
HelloInterface helloInterface;
@GetMapping("/helloTest")
public String helloMsg(){
String msg=helloInterface.getHelloMsg();
System.out.println("+++"+msg+"+++");
return "success";
}
}
在上面的代码中,使用注解 @Autowired 引进接口。然后在代码中,直接使用接口中的方法,就像在调用本地类中的方法。
在这里,将返回的内容放入变量 msg 中,然后输出,最后运行、检查效果。进入链接 http://localhost:8070/helloTest 。浏览器效果如图12.2所示。

这个效果最明显,说明程序成功运行。控制台如图12.3所示。

这个效果是说明服务调用之后,返回的值是 “hello eureka”。我们看服务提供者的 “/hello”,可以发现代码的最后是 “return "hello eureka!"”,说明这里返回结果是正确的,同时说明可以使用这种方式进行服务间的调用。
查看日志。为什么还要查看日志?这里主要看服务提供者的控制台上是否有日志输出。进入服务提供者的控制台,我们发现有如下日志。
2019-01-21 22:31:35.315 INFO 17088 --- [nio-8091-exec-9]
c.c.e.controller.HelloController :
host:port=http://DESKTOP-TQ4OD79:8091, service_id:HELLOSERVICE
Feign与Spring MVC
在12.1.1节中演示的实例程序,可以帮助我们快速理解在 Spring Cloud 项目中 Feign 是如何使用的。虽然在客户端写 Restful 风格的接口和以前有些不同,但是在 Eureka 中,客户端也是服务端的概念下,理解起来也不困难。
上一个章节中,我们直接简单地调用服务,不涉及参数的传递。如果读者认真看过11.2节的 RestTemplate 的介绍后,这里就很好理解,因为两者使用方式的注解相同。
(1)服务提供者
我们对服务提供者程序进行修改,为了方便重现程序,先看程序结构,如图12.4所示。

新建一个参数请求控制类 ParamHelloController,代码如下所示。
package com.cloudtest.eurekaprovider.controller;
@RestController
public class ParamHelloController {
@GetMapping("/getUserName")
public String hello(@RequestParam String name){
return "get:" + "name=" +name;
}
@GetMapping("/getUser")
public String hello(@RequestHeader String name,@RequestHeader String age){
return "get:" + "name=" +name+" age=" +age;
}
@PostMapping("/postUser")
public String hello(@RequestBody User user){
return "post:" + user.getName()+", "+user.getAge();
}
}
在上面的代码中列举了三种场景,即两种 GET 方式和一种 POST 方式的场景。在代码中,引用了 User 类,代码如下所示。
package com.cloudtest.eurekaprovider.bean;
public class User {
private String name;
private String age;
//其他省略
}
(2)服务消费者
在开始程序前,依旧先展示程序结构,如图12.5所示。

首先,复制一份服务提供者的 User 对象,在这里需要保证两个程序相同,因为若在服务提供者的 User 对象中添加参数,需要在服务消费者处使用 User 中的参数。
然后定义一个接口, ParamHelloInterface 接口的代码如下所示。
package com.cloudtest.eurekaconsumer.feign;
@FeignClient(name="helloService")
public interface ParamHelloInterface {
@GetMapping("/getUserName")
public String hello(@RequestParam("name") String name);
@GetMapping("/getUser")
public String hello(@RequestHeader("name") String name,@RequestHeader("age") String age);
@PostMapping("/postUser")
public String hello(@RequestBody User user);
}
在上面的代码中需要注意,在 Spring MVC 中,@RequestParam 注解中不需要指定参数名,因为默认的值就是参数名,但是在这里,如果不指定参数名则不能通过编译。当然,@RequestHeader 注解也需要指定参数名。然后进行服务调用,代码如下所示。
package com.cloudtest.eurekaconsumer.feign.controller;
@RestController
public class ParamHelloMsgController {
@Autowired
ParamHelloInterface paramHelloInterface;
@GetMapping("/testParamHello")
public String test(){
String msg1=paramHelloInterface.hello("tom1");
System.out.println("msg1:"+msg1);
String msg2=paramHelloInterface.hello("tom2","18");
System.out.println("msg2:"+msg2);
String msg3=paramHelloInterface.hello(new User( "tom3" ,"19"));
System.out.println("msg3:"+msg3);
return "param test success";
}
}
其实,我们还需要修改一些东西。因为这个代码是直接在实例的基础上添加的,但是定义接口时,另外定义了一个接口。因此,如果不修改,程序将会报错,下面是日志。
Description:
The bean 'helloService.FeignClientSpecification', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.
这里的意思是,我们不能有 @FeignClient(相同服务名)这种情况,因为不允许,所以要注释与本实例无关的接口与控制类。
最后,开始测试。访问链接 http://localhost:8070/testParamHello,控制台日志如下。
2019-01-21 23:58:59.706 INFO 18272 --- [nio-8070-exec-2] c.n.l. DynamicServerListLoadBalancer: DynamicServerListLoadBalancer for client helloService initialized:DynamicServerListLoadBalancer:{NFLoadBalancer:name=helloService,current list of Servers=[DESKTOP-TQ4OD79:8091],Load balancer stats=Zone stats:{defaultzone=[Zone:defaultzone; Instance count:1; A c t i v e c o n n e c t i o n s count: 0;
Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:DESKTOP-TQ4OD79:8091; Zone:defaultZone;
Total Requests:0;Successive connection failure:0; Total blackout seconds:0;Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0;total failure count in last (1000) msecs:0; average resp time:0.0;90 percentile resp time:0.0; 95 percentile resp time:0.0;min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.Domain ExtractingServerList@7c17214e
msg1:get:name=tom1
msg2:get:name=tom2age=18
msg3:post:tom3,19
对于上面的日志,主要指加粗的部分。第一处,说明使用的是 Ribbon 负载均衡;第二处,说明使用的是服务提供者,具体信息可以在服务注册中心的信息面板上看;第三处,就是我们调用服务返回的信息。