声明式服务调用SpringCloud Feign
文章目录
快速入门
创建项目 feign-consumer 9010
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>
<groupId>com.zk.springcloud.feign</groupId>
<artifactId>springcloud-feign</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springcloud-feign</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.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>
<spring-cloud.version>Finchley.SR1</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-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</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>
创建应用启动类,并通过 @EnableFeignClients 注解开启 Spring Cloud Feign 的支持功能
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class SpringcloudFeignApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudFeignApplication.class, args);
}
}
定义 HelloService 接口,通过 @FeignClient 注解指定服务名来绑定服务,然后再使用 SpringMVC 的注解来绑定具体该服务提供的 REST 接口
这里服务名不区分大小写,所以使用 SERIVCE-USER 和 service-user 都是可以的
@FeignClient("SERIVCE-USER")
@Service
public interface HelloService {
@RequestMapping("/hello")
String hello();
}
方法调用
@RestController
public class ConsumerController {
@Autowired
HelloService helloService;
@GetMapping(value = "/feign-consumer")
public String helloConsumer(){
return helloService.hello();
}
}
配置文件
spring.application.name=feign-consumer
server.port=9010
eureka.client.service-url.defaultZone=http://localhost:9001/eureka/,http://localhost:9004/eureka/
测试验证,如之前验证 Ribbon 客户端负载均衡一样,先启动注册中心以及两个 SERVICE-USER ,然后启动 FEIGN-CONSUMER ,多次调用 http://localhost:9010/feign-consumer , 结果与之前 Ribbon 实现时一样的效果 Hello World 9006 和 Hello World 9003 切换显示。依然是利用 Ribbon 维护对 SERVICE-USER 的服务列表信息,并且通过轮询实现了客户端负载均衡。
参数绑定
feign-consumer 9010 , HelloService 添加方法
@GetMapping(value = "/hello1")
String hello(@RequestParam("name") String name);
@GetMapping(value = "/hello2")
User hello(@RequestHeader("name") String name,@RequestHeader("password") String password);
@PostMapping(value = "/hello3")
String hello(@RequestBody User user);
User 对象如下, 这里必须要有 User 的默认构造函数,不然 Spring Cloud Feign 根据 JSON 字符串转换 User 对象会抛出异常
public class User {
String name;
String password;
public User() {
}
public User(String name, String password) {
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
对 UserService 9003 项目改造
@FeignClient("SERIVCE-USER")
@Service
public interface HelloService {
@RequestMapping("/hello")
String hello();
@GetMapping(value = "/hello1")
String hello(@RequestParam("name") String name);
@GetMapping(value = "/hello2")
User hello(@RequestHeader("name") String name,@RequestHeader("password") String password);
@PostMapping(value = "/hello3")
String hello(@RequestBody User user);
}
在 feign-consumer 9010 项目添加调用
@GetMapping(value = "/feign-consumer2")
public String helloConsumer2(){
StringBuilder sb = new StringBuilder();
sb.append(helloService.hello()).append("\n");
sb.append(helloService.hello("DIDI")).append("\n");
sb.append(helloService.hello("DIDI","123456")).append("\n");
sb.append(helloService.hello(new User("DIDI","admin"))).append("\n");
return sb.toString();
}
访问 http://localhost:9010/feign-consumer2 可获取 Hello World 9003 Hello DIDI User{name=‘DIDI’, password=‘123456’} Hello DIDI, admin
继承特性
创建名为 user-service-api 的 maven 项目
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/>
<groupId>com.zk.springcloud</groupId>
<artifactId>springcloud-user-service-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.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>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
复制上节中的 User 对象到 user-service-api 工程
创建 HelloService 接口
package com.zk.springcloud;
import org.springframework.web.bind.annotation.*;
@RequestMapping("/refactor")
public interface HelloService {
@GetMapping(value = "/hello4")
String hello(@RequestParam("name") String name);
@GetMapping(value = "/hello5")
User hello(@RequestHeader("name") String name,@RequestHeader("password") String password);
@PostMapping(value = "/hello6")
String hello(@RequestBody User user);
}
对 user-service 和 feign-consumer 重构
对 user-service 重构
添加 user-serivce-api的依赖
<dependency>
<groupId>com.zk.springcloud</groupId>
<artifactId>springcloud-user-service-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
创建 RefactorHelloController 类继承 user-service-api 中定义的 HelloService 接口
package com.zk.springcloud.service;
import com.zk.springcloud.HelloService;
import com.zk.springcloud.User;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RefactorHelloController implements HelloService {
@Override
public String hello(@RequestParam("name") String name) {
return "Hello " + name;
}
@Override
public User hello(@RequestHeader("name") String name,@RequestHeader("password") String password) {
return new User(name,password);
}
@Override
public String hello(@RequestBody User user) {
return "Hello " + user.getName() + ", " + user.getPassword();
}
}
对 feign-consumer 重构
创建 RefactorHelloService 接口
import org.springframework.stereotype.Service;
@FeignClient(value = "SERIVCE-USER")
@Service
public interface RefactorHelloService extends HelloService {
}
添加调用
@GetMapping(value = "/feign-consumer3")
public String helloConsumer3(){
StringBuilder sb = new StringBuilder();
sb.append(refactorHelloService.hello("MIMI")).append("\n");
sb.append(refactorHelloService.hello("MIMI","123456")).append("\n");
sb.append(refactorHelloService.hello(new com.zk.springcloud.User("MIMI","123456"))).append("\n");
return sb.toString();
}
访问 http://localhost:9010/feign-consumer3 获得如下结果
Hello MIMI User{name='MIMI', password='123456'} Hello MIMI, 123456
Ribbon 配置
全局配置
ribbon.ConnectTimeout=500
ribbon.ReadTimeout=5000
指定服务配置
serivce-user.ribbon.ConnectTimeout=500
serivce-user.ribbon.ReadTimeout=2000
serivce-user.ribbon.okToRetryOnAllOperations=true
serivce-user.ribbon.MaxAutoRetriesNextServer=2
serivce-user.ribbon.MaxAutoRetries=1
重试机制
feign-consumer 添加上述指定服务配置
user-service 修改如下超时
@GetMapping(value = "/hello")
public String hello() throws InterruptedException {
int sleepTime = new Random().nextInt(3000);
log.info("sleepTime: " + sleepTime);
Thread.sleep(sleepTime);
return "Hello World 9003";
}
访问 http://localhost:9010/feign-consumer
在 user-service 可以看到重试日志
Hystrix 配置
全局配置
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
#关闭 Hystrix 功能
#feign.hystrix.enabled=false
#关闭熔断功能
#hystrix.command.default.execution.timeout.enabled=false
禁用 Hystrix
全局关闭 Hystrix
#关闭 Hystrix 功能
feign.hystrix.enabled=false
针对某个客户端关闭 Hystrix ,通过使用 @Scope(“prototype”) 注解为指定的客户端配置 Feign.Builder 实例
构建一个关闭 Hystrix 的配置类
@Configuration
public class DisableHystrixConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder(){
return Feign.builder();
}
}
在 HelloService 的 @FeignClient 注解中,通过 configuration 参数引入上面实例的配置
@FeignClient(value = "SERIVCE-USER",configuration = DisableHystrixConfiguration.class)
@Service
public interface HelloService {
···
}
指定命令配置
针对尝试机制中对 /hello 接口的熔断时间的配置可通过如下配置
hystrix.command.hello.execution.isolation.thread.timeoutInMilliseconds=5000
服务降级配置
对 feign-consumer 工程进行改造
package com.zk.springcloud.feign;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
@Component
public class HelloServiceFallBack implements HelloService {
@Override
public String hello() {
return "error";
}
@Override
public String hello(@RequestParam("name") String name) {
return "error";
}
@Override
public User hello(@RequestHeader("name") String name,@RequestHeader("password") String password) {
return new User("未知","0");
}
@Override
public String hello(User user) {
return "error";
}
}
在服务绑定接口 HelloService 中,通过 @FeignClient 注解的 fallback 属性来指定对应的服务降级实现类
@FeignClient(value = "SERIVCE-USER",fallback = HelloServiceFallBack.class)
@Service
public interface HelloService {
@RequestMapping("/hello")
String hello();
@GetMapping(value = "/hello1")
String hello(@RequestParam("name") String name);
@GetMapping(value = "/hello2")
User hello(@RequestHeader("name") String name,@RequestHeader("password") String password);
@PostMapping(value = "/hello3")
String hello(@RequestBody User user);
}
配置文件开启 feign.hystrix
feign.hystrix.enabled=true
将类 DisableHystrixConfiguration 注释
将 user-service 服务关闭,访问 http://localhost:9010/feign-consumer2 获得如下结果:
error error User{name='未知', password='0'} error
其他配置
请求压缩
feign.compression.request.enabled=true
feign.compression.response.enabled=true
#设置压缩的大小下限,超过的才进行压缩,以下配置为默认值
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
日志配置
logging.level.com.zk.springcloud.feign.HelloService=DEBUG
feign-consumer 启动类配置
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class SpringcloudFeignApplication {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
public static void main(String[] args) {
SpringApplication.run(SpringcloudFeignApplication.class, args);
}
}
也可以通过实现配置类,然后在具体的Feign 客户端来指定配置类以实现是否要调整不同的日志界别
@Configuration
public class FullLogConfiguration {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
调用 http://localhost:9010/feign-consumer
请求详细日志
2018-10-08 14:49:34.051 DEBUG 21632 --- [-SERIVCE-USER-5] com.zk.springcloud.feign.HelloService : [HelloService#hello] ---> GET http://SERIVCE-USER/hello HTTP/1.1
2018-10-08 14:49:34.051 DEBUG 21632 --- [-SERIVCE-USER-5] com.zk.springcloud.feign.HelloService : [HelloService#hello] ---> END HTTP (0-byte body)
2018-10-08 14:49:34.872 DEBUG 21632 --- [-SERIVCE-USER-5] com.zk.springcloud.feign.HelloService : [HelloService#hello] <--- HTTP/1.1 200 (821ms)
2018-10-08 14:49:34.872 DEBUG 21632 --- [-SERIVCE-USER-5] com.zk.springcloud.feign.HelloService : [HelloService#hello] content-length: 16
2018-10-08 14:49:34.873 DEBUG 21632 --- [-SERIVCE-USER-5] com.zk.springcloud.feign.HelloService : [HelloService#hello] content-type: text/plain;charset=UTF-8
2018-10-08 14:49:34.873 DEBUG 21632 --- [-SERIVCE-USER-5] com.zk.springcloud.feign.HelloService : [HelloService#hello] date: Mon, 08 Oct 2018 06:49:34 GMT
2018-10-08 14:49:34.873 DEBUG 21632 --- [-SERIVCE-USER-5] com.zk.springcloud.feign.HelloService : [HelloService#hello]
2018-10-08 14:49:34.873 DEBUG 21632 --- [-SERIVCE-USER-5] com.zk.springcloud.feign.HelloService : [HelloService#hello] Hello World 9003
2018-10-08 14:49:34.873 DEBUG 21632 --- [-SERIVCE-USER-5] com.zk.springcloud.feign.HelloService : [HelloService#hello] <--- END HTTP (16-byte body)