github:https://github.com/Maple521/springcloud/tree/master/feign-consumer
通过前两章对和Spring Cloud Ribbon 和 Spring Cloud Hystrix 的介绍,我们已经掌握了开发微服务应用时的两个重磅武器,学会了如何在微服务架构中实现客户端负载均衡的服务调用,以及如何通过断路器来保护我们的微服务应用。这两者,将被作为基础工具类框架广泛地应用在各个微服务的实现中,不仅包括我们自身的业务类微服务,也包括一些基础设施类服务(比如网关)。此外,在实践过程中,我们会发现对这两个框架的使用几乎是同时出现的。既然如此,那么是否有更高层次的封装来整合这两个基础工具以简化开发呢?本章我们即将介绍的Spring Cloud Feign,就是这样一个工具。它基于Netflix Feign,整合Spring Cloud Ribbon 和 Spring Cloud Hystrix,除了提供这两个强大的功能之外,它还提供了一种声明式的Web服务客户端的定义方式。
我们在使用Spring Cloud Ribbon时,通常都会利用他对RestTemplate的请求拦截,来实现对依赖服务的接口调用,RestTemplate已经实现了对HTTP请求的封装处理,形成了一套模板化的调用方法,在之前的例子中,我们只是简单介绍了调用的实现,但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以我们通常都会针对各个微服务,自行封装一些客户端类,来包装这些依赖服务的调用,这个时候我们会发现,由于RestTemplate的封装,几乎每一个调用都是简单的模板化内容。综合上述这些情况,Spring Cloud Feign在此基础上做了一进一步封装,由它来帮助我们定义和实现依赖服务接口的定义。在Spring Cloud Feign的实现下,我们只需创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量。Spring Cloud Feign具备插拔的注解支持,包括Feign注解和JAX-RS注解。同时,为了适应Spring广大用户,它在Netflix Feign的基础上扩展了对SpringMVC的注解支持。这对于习惯于SpringMVC的开发者来说,无疑是一个好消息,因为这样可以大大减少学习使用它的成本。另外对于Feign自身的一些主要组件,比如编码器和解码器等,他也以可插拔的方式提供,在有需求的时候我们可以方便的扩展和替换他们。
下面的示例将继续使用之前我们实现的hello-service服务,这里我们会通过Spring Cloud Feign提供的声明式服务绑定功能来实现对该服务接口的调用。
(1)首先,创建一个Spring Boot基础工程,取名为 feign-consumer,pom文件为:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.feign</groupId>
<artifactId>feign-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>feign-consumer</name>
<description>Demo project for Feign Consumer</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Brixton.SR5</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(2)创建应用主类FeignConsumerApplication,并通过@EnableFeignClients注解开启Spring Cloud Feign的支持功能。
package com.feign.feignconsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
}
(3)定义HelloService接口,通过@FeignClient注解指定服务名来绑定服务,然后再使用Spring MVC的注解来绑定具体该服务提供的REST接口。
package com.feign.feignconsumer.service;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient("hello-service")
public interface HelloService {
@RequestMapping("/hello")
String hello();
}
注意:这里服务名不区分大小写,所以使用hello-service和HELLO-SERVICE都是可以的。另外,在Brixton.SR5版本中,原有的serviceId属性已经被废弃,若要写属性名,可以使用name或value。
(4)接着,创建一个ConsumerController来实现 对Feign客户端的调用。使用@Autowired直接注入上面定义的HelloService实例,并在helloConsumer函数中调用这个绑定了hello-service服务接口的客户端来向该服务发起 /hello 接口的调用。
package com.feign.feignconsumer.controller;
import com.feign.feignconsumer.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ConsumerController {
@Autowired
HelloService helloService;
@RequestMapping(value = "/feign-consumer", method = RequestMethod.GET)
public String helloConsumer() {
return helloService.hello();
}
}
(5)最后,同Ribbon实现的服务消费者一样,需要 在application.properties中指定服务注册中心,并定义自身的服务名为 feign-consumer,为了方便本地调试与之前的Ribbon消费者区分,端口使用9001.
spring.application.name=feign-consumer
server.port=9001
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
测试验证
如之前验证Ribbon客户端负载均衡一样,我们先启动服务注册中心以及两个HELLO-SERVICE,然后启动FEIGN-CONSUMER,此时我们在Eureka信息面板中可看到如下内容:
发送几次GET请求道http://localhost:9001/feign-consumer,可以得到如之前Ribbon实现时一样的效果:超过1000ms时,报错。
时间在1000ms之内呢,返回Hello World
并且根据控制台的输出,我们可以看到Feign实现的消费者,依然是利用了Ribbon维护了针对HELLO-SERVICE的服务列表信息,并且通过轮询实现了客户端负载均衡。而与Ribbon不同的是,通过Feign我们只需定义服务绑定接口,以声明式的方法,优雅而简单地实现了服务调用。我们看下Feign控制台打印情况:
DynamicServerListLoadBalancer for client hello-service initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=hello-service,current list of Servers=[192.168.0.103:8081, 192.168.0.103:8082],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:2; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:192.168.0.103:8081; 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]
, [Server:192.168.0.103:8082; 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.DomainExtractingServerList@7fff989d
是不是很面熟呢.....和 ribbon-consumer这里是一样的。