上两篇对Spring Cloud Ribbon和Spring Cloud Hystrix的介绍,已经掌握了发微服务的应用时的两个重磅武器,学习了如何在微服务架构中实现客户端负载均衡的服务调用以及如何通过断路器保护我们的微服务应用。在实践过程中我们会发现对这两个框架的使用几乎是同时的,既然如此,那么可否有更高层次的封装来整合这两个基础工具以简单开发呢。下面学习的Spring Cloud Feign这样一个工具,就是基于Netflix Feign实现的,整合了Spring Cloud Ribbon 与Spring Cloud Hysrix,除了提供这两者的强大功能外,它还提供了一种声明试的Web服务客户端定义方式。
使用Spring Cloud Ribbon时,通常会利用它对RestTemplate的请求拦截来实现对依赖服务的接口调用,而RestTempplate已经实现了对HTTP请求的封装处理,形成一套模板化的调用方法。Spring Cloud Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。
下面简单实例展现Spring Cloud Feign在服务客户端定义上所带的方便。下面把我们之前实现的hello-service服务,通过Spring Cloud Feign提供的声明式服务绑定功能来实现对服务接口的调用。
1、创建一个Spring boot工程 命名为fegin-consumer,并在pom.xml中引入spring-cloud-starter-eureka和spring-cloud-starter-fegin
具体pom.xml
<?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>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--spring boot 单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--eureka客户端依赖 -->
<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>
<!--表明是一个web应用 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--该模块能够自动为 Spring Boot 构建的应用提供
一 系列用千监控的端点-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<!--服务注册中心-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR6</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、在应用主类,并通过@EnableFeignClients注解开启Spring Cloud Feign的支持功能。
package com.example.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.example.feignconsumer.service;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @Author:peishunwu
* @Description:
* @Date:Created in ${Time} 2018/1/22
*/
@FeignClient("/hello-service")
public interface HelloService
{
@RequestMapping("/hello")
String hello();
}
注意:这里的服务名称不区分大小写,所以使用hello-service和HELLO-SERVICE都是可以的,在Brixton.SR5版本中,原有的serviceId属性已经被废弃,若要写属性名,可以使用name或者value
4、接着创建一个Controller来实现对Feign客户端的调用,使用@Autowired直接注入上面定义的HelloService实例,并在controller中调用这个绑定了hello-service服务接口的客户端来向该服务发起/hello接口的调用。
package com.example.feignconsumer.web;
import com.example.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;
/**
* @Author:peishunwu
* @Description:
* @Date:Created in ${Time} 2018/1/22
*/
@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/
6、验证
如之前验证 Ribbon 客户端负载均衡一 样, 我们先启动服务注册中心以及两个
HELLO-SERVICE, 然后启动 FEIGN-CONSUMER, 此时我们在 Eureka 信息面板中可看到
如下内容:
发送几次 GET 请求到 http://localhost:9001/feign-consumer, 可以得到如
之前Ri bbon 实现时一 样的效果, 正确返回了”Hello World” 。 并且根据控制台的输出, 我们可以看到 Feign 实现的消费者,依然是利用 Ribbon 维护了针对 HELLO-SERVICE 的服务列表信息, 并且通过轮询实现了客户端负载均衡。 而与 Ribbon 不同的是, 通过 Feign 我们只需定义服务绑定接口, 以声明式的方法, 优雅而简单地实现了服务调用
遇到的问题:
java.lang.IllegalStateException: Service id not legal hostname (/hello-service)
at org.springframework.util.Assert.state(Assert.java:70) ~[spring-core-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.cloud.netflix.feign.FeignClientsRegistrar.getName(FeignClientsRegistrar.java:231) ~[spring-cloud-netflix-core-1.2.4.RELEASE.jar:1.2.4.RELEASE]
at org.springframework.cloud.netflix.feign.FeignClientsRegistrar.registerFeignClient(FeignClientsRegistrar.java:177) ~[spring-cloud-netflix-core-1.2.4.RELEASE.jar:1.2.4.RELEASE]
at org.springframework.cloud.netflix.feign.FeignClientsRegistrar.registerFeignClients(FeignClientsRegistrar.java:163) ~[spring-cloud-netflix-core-1.2.4.RELEASE.jar:1.2.4.RELEASE]
at org.springframework.cloud.netflix.feign.FeignClientsRegistrar.registerBeanDefinitions(FeignClientsRegistrar.java:88) ~[spring-cloud-netflix-core-1.2.4.RELEASE.jar:1.2.4.RELEASE]
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars(ConfigurationClassBeanDefinitionReader.java:354) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:143) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:116) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:320) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:228) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:270) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:93) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:686) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
原因:hello-service前面多了个/
解决:
去除hello-service前面的/
package com.example.feignconsumer.service;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @Author:peishunwu
* @Description:
* @Date:Created in ${Time} 2018/1/22
*/
@FeignClient("hello-service")
public interface HelloService
{
@RequestMapping("/hello")
String hello();
}