什么是Spring Cloud Feign
Spring Cloud Feign 是基于 Netflix Feign 实现的,整合了 Spring Cloud Ribbon 和 Spring Cloud Hystrix,除了提供这两者的强大功能之外,还提供了一种声明式的 Web 服务客户端定义方式。
一、快速入门
1、创建一个 Spring Boot 基础工程,取名为 feign-consumer,并在 pom.xml 中引入 spring-cloud-starter-eureka 和 spring-cloud-starter-feign 依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
2、在主类上通过 @EnableFeignClients 注解开启 Spring Cloud Feign 的支持功能。
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
}
3、定义 HelloService 接口,通过 @FeignClient 注解指定服务名来绑定服务,然后再使用 Spring MVC 的注解来绑定具体该服务提供的 REST 接口。
@FeignClient(value = "hello-service")
public interface HelloService {
@RequestMapping(value = "/hello")
String hello();
}
4、创建一个 ConsumerController 来实现对 Feign 客户端的调用。使用 @Autowired 直接注入上面定义的 HelloService 实例,并在 helloConsumer 函数中调用这个绑定了 hello-service 服务接口的客户端来向该服务发起 /hello接口的调用。
@RestController
public class ConsumerController {
@Autowired
HelloService helloService;
@RequestMapping(value = "feign-consumer", method = RequestMethod.GET)
public String helloConsumer(){
return helloService.hello();
}
}
5、在 application.properties 中指定注册中心,并定义自身的服务名为 feign-consumer
spring.application.name=feign-consumer
server.port=9001
eureka.client.service-url.defaultZone=http://localhost:1111/eureka/
6、验证
启动 eureka-server 和 两个hello-service,然后启动 feign-consumer,发送请求到 http://localhost:9001/feign-consumer,正确返回。
与 Ribbon 不同的是,通过 Feign 我们只需定义服务绑定接口,以声明式的方法,优雅而简单地实现了服务调用。
二、参数绑定
传入参数和反回复杂对象的用法:
1、先扩展一下服务提供方 hello-service 。增加下面这些接口定义。
@RequestMapping(value = "/hello1", method = RequestMethod.GET)
public String hello1(@RequestParam String name){
return "HELLO " + name;
}
@RequestMapping(value = "/hello2", method = RequestMethod.GET)
public User hello2(@RequestHeader String name, @RequestHeader Integer age){
return new User(name, age);
}
@RequestMapping(value = "/hello3", method = RequestMethod.POST)
public String hello3(@RequestBody User user){
return "HELLO," + user.getName()+","+user.getAge();
}
2、定义User 对象,需要注意,这里必须要有User 的默认构造函数。不然,Spring Cloud Feign 根据 JSON 字符串转换 User 对象会抛出异常。
public class User {
private String name;
private Integer age;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
3、在 feign-consumer 中创建与上面一样的 User 类。
4、在 HelloService 接口中增加对上述三个新增接口的绑定声明。
@FeignClient(value = "hello-service")
public interface HelloService {
@RequestMapping(value = "/index")
String hello();
@RequestMapping(value = "/hello1", method = RequestMethod.GET)
String hello1(@RequestParam(value = "name") String name);
@RequestMapping(value = "/hello2", method = RequestMethod.GET)
User hello2(@RequestParam(value = "name") String name, @RequestHeader(value = "age") Integer age);
@RequestMapping(value = "/hello3", method = RequestMethod.POST)
String hello3(@RequestBody User user);
}
5、在 ConsumerController 中新增一个 /feign-consumer2 接口
@RestController
public class ConsumerController {
@Autowired
HelloService helloService;
@RequestMapping(value = "feign-consumer", method = RequestMethod.GET)
public String helloConsumer(){
return helloService.hello();
}
@RequestMapping(value = "/feign-consumer2", method = RequestMethod.GET)
public String helloConsumer2(){
StringBuilder sb = new StringBuilder();
sb.append(helloService.hello1("didi")).append("\n");
sb.append(helloService.hello2("didi", 18)).append("\n");
sb.append(helloService.hello3(new User("didi", 20))).append("\n");
return sb.toString();
}
}
6、验证
http://localhost:9001/feign-consumer2 ,触发 HelloService 对新增接口的调用
三、继承特性
通过 Spring Cloud Feign 的继承特性来实现 REST 接口定义的复用
1、为了能够复用 DTO 与接口定义,我们先创建一个基础的 Maven 工程,命名为 hello-service-api。
2、在 hello-service-api 中需要定义可同时复用于服务端与客户端的接口,我们要使用到 Spring MVC 的注解,所以在 pom.xml 中引入 spring-boot-starter-web 依赖
3、将 User 对象复制到 hello-service-api 工程中。
4、在 hello-service-api 工程中创建 HelloService 接口
@RequestMapping(value = "/refactor")
public interface HelloService {
@RequestMapping(value = "/hello4", method = RequestMethod.GET)
String hello4(@RequestParam(value = "name") String name);
@RequestMapping(value = "/hello5", method = RequestMethod.GET)
User hello5(@RequestHeader(value = "name") String name, @RequestHeader(value = "age") Integer age);
@RequestMapping(value = "/hello6", method = RequestMethod.POST)
String hello6(@RequestBody User user);
}
5、执行命令 mvn install 将该模块构建到本地仓库。
6、对 hello-service 进行重构,在 pom.xml 的 dependency 节点中,新增对 hello-service-api 的依赖。
<dependency>
<groupId>com.didispace</groupId>
<artifactId>hello-service-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
7、创建 RefactorHelloController 类实现 hello-service-api 中定义的 HelloService 接口,并参考之前的 HelloController 来实现这三个接口
@RestController
public class RefactorHelloController implements HelloService {
@Override
public String hello4(@RequestParam(value = "name") String name) {
return "HELLO " + name;
}
@Override
public User hello5(@RequestHeader(value = "name") String name, @RequestHeader(value = "age") Integer age) {
return new User(name, age);
}
@Override
public String hello6(@RequestBody User user) {
return "HELLO," + user.getName()+","+user.getAge();
}
}
8、完成了服务提供者的重构,接下来在服务消费者 feign-consumer 的 pom.xml 文件中,如在服务提供者一样,新增对 hello-service-api 的依赖。
9、创建 RefactorHelloService 接口,并继承 hello-service-api 包中的 HelloService 接口,然后添加 @FeignClient 注解来绑定服务。
@FeignClient (value="Hello-Service")
public interface RefactorHelloService extends HelloService{
}
10、在ConsumerController中注入RefactorHelloService 并新增一个请求来触发
@RequestMapping(value = "/feign-consumer3",method=RequestMethod.GET)
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.didispace.dto.User("MIMI","123456"))).append("\n");
return sb.toString();
}
四、Ribbon配置
1、全局配置
ribbon.ConnectTimeout=500
ribbon.ReadTimeout=5000
2、指定服务配置
HELLO-SERVICE.ribbon.ConnectTimeout=500
HELLO-SERVICE.ribbon.ReadTimeout=2000
HELLO-SERVICE.ribbon.okToRetryOnAllOperations=true
HELLO-SERVICE.ribbon.MaxAutoRetriesNextServer=2
HELLO-SERVICE.ribbon.MaxAutoRetries=1
3、重试机制
默认实现
五、Hystrix配置
1、全局配置
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
#关闭 Hystrix 功能
#feign.hystrix.enabled=false
#关闭熔断功能
#hystrix.command.default.execution.timeout.enabled=false
2、禁用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 {
···
}
4、指定命令配置
对 /hello 接口的熔断时间的配置
hystrix.command.hello.execution.isolation.thread.timeoutInMilliseconds=5000
5、服务降级配置
对 feign-consumer 工程进行改造,为HelloService接口实现一个服务降级类
@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);
}
六、其他配置
1、请求压缩
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
2、日志配置
logging.level.com.didispace.web.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;
}
}
@FeignClient(name="HELLO_SERVICE",configuration = FullLogConfiguration.class
public interface HelloService{
...
}
Feign的Logger级别:
NONE
BASIC
HEADERS
FULL