前面我们分别通过 ​​SpringCloud-Ribbon​​​ 和 ​​SpringCloud-Hystrix​​​ 实现了客户端负载均衡和服务容错,而 ​​SpringCloud-Feign​​​ 不但整合了这两者的功能,而且还提供了一种比 ​​Ribbon​​​ 更简单的服务调用方式那就是 ​​声明式服务调用​​​。在 ​​SpringCloud-Feign​​ 中编写服务调用代码非常简单,几乎可以直接将服务提供者的代码复制过来,改为接口即可,下面通过例子来演示这个特性

搭建Feign Consumer

创建一个新的 ​​SpringBoot​​​ 应用,版本号为 ​​1.5.13.RELEASE​​​,​​artifactId​​​ 改为 ​​Feign-Consumer​​​,并修改 ​​pom.xml​​ 引入下面这些依赖:

<artifactId>Feign-Consumer</artifactId>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.13.RELEASE</version>
<relativePath/>
</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>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

除了 ​​spring-cloud-starter-feign​​​ 的依赖以外,我们还引入了 ​​spring-cloud-starter-eureka​​​ 目的是为了从 ​​Eureka​​ 服务注册中心中获取服务。

在 ​​SpringBoot​​​ 的入口类中加入 ​​@EnableFeignClients​​​ 和 ​​@EnableDiscoveryClient​​​ 注解,用于开启 ​​SpringCloud-Feign​​ 和服务注册与发现:

/**
* @author BNTang
**/
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

在前面几节中,我曾在服务提供者 ​​Eureka-Client​​​ 中定义了一个 ​​UserController​​,代码如下所示:

如果没有搭建的,前面几节中的文章如下

/**
* @author BNTang
**/
@RestController
@RequestMapping("user")
public class UserController {

private Logger log = LoggerFactory.getLogger(this.getClass());

@GetMapping("/{id:\\d+}")
public User get(@PathVariable Long id) {
log.info("获取用户id为 " + id + "的信息");
return new User(id, "bntang", "123456");
}

@GetMapping
public List<User> get() {
List<User> list = new ArrayList<>();
list.add(new User(1L, "bntang", "123456"));
list.add(new User(2L, "scott", "123456"));
log.info("获取用户信息 " + list);
return list;
}

@PostMapping
public void add(@RequestBody User user) {
log.info("新增用户成功 " + user);
}

@PutMapping
public void update(@RequestBody User user) {
log.info("更新用户成功 " + user);
}

@DeleteMapping("/{id:\\d+}")
public void delete(@PathVariable Long id) {
log.info("删除用户成功 " + id);
}

}

在 ​​SpringCloud-Ribbon​​​ 中访问这些服务需要通过 ​​RestTemplate​​​ 对象来实现,并且参数绑定的过程也比较繁琐。​​SpringCloud-Feign​​​ 对这个步骤进行了进一步的封装,在 ​​Feign Consumer​​​ 中调用这些服务只需要定义一个 ​​UserService​​​ 接口,然后将 ​​UserController​​​ 中的代码复制到 ​​UserService​​ 接口,然后将方法体去掉即可,如下所示:

/**
* @author BNTang
**/
@FeignClient("Server-Provider")
public interface UserService {

@GetMapping("user/{id}")
public User get(@PathVariable("id") Long id);

@GetMapping("user")
public List<User> get();

@PostMapping("user")
public void add(@RequestBody User user);

@PutMapping("user")
public void update(@RequestBody User user);

@DeleteMapping("user/{id}")
public void delete(@PathVariable("id") Long id);
}

对比 ​​Feign Consumer​​​ 中的 ​​UserService​​​ 和 ​​Eureka-Client​​​ 中的 ​​UserController​​ 代码,两者是不是很相似?

在 ​​UserService​​​ 中,我们通过 ​​@FeignClient("Server-Provider")​​​ 注解来获取我们需要的服务,其中 ​​Server-Provider​​​ 不区分大小写。需要注意的是,在定义各参数绑定时,​​@RequestParam​​​、​​@RequestHeader​​​ 等可以指定参数名称的注解,它们的 ​​value​​​ 千万不能少。在 SpringMVC 程序中,这些注解会根据参数名来作为默认值,但是在 Feign 中绑定参数必须通过 ​​value​​​ 属性来指明具体的参数名,不然会抛出 ​​illegalStateException​​​ 异常,​​value​​ 属性不能为空。

接下来我们在 ​​Feign Consumer​​​ 中定义一个 ​​TestController​​​,来调用 ​​UserService​​ 中定义的服务,代码如下所示:

SpringCloud-Feign声明式服务的调用_服务调用

/**
* @author BNTang
**/
@RestController
public class TestController {

@Autowired
private UserService userService;

@GetMapping("user/{id}")
public User getUser(@PathVariable Long id) {
return userService.get(id);
}

@GetMapping("user")
public List<User> getUsers() {
return userService.get();
}

@PostMapping("user")
public void addUser() {
User user = new User(1L, "bntang", "123456");
userService.add(user);
}

@PutMapping("user")
public void updateUser() {
User user = new User(1L, "bntang", "123456");
userService.update(user);
}

@DeleteMapping("user/{id}")
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
}

最后在配置一下 ​​application.yml​​,配置内容如下所示:

server:
port: 9000

spring:
application:
name: Server-Consumer

eureka:
client:
serviceUrl:
defaultZone: http://bntang:123456@peer1:8080/eureka/,http://bntang:123456@peer2:8081/eureka/