Spring Cloud Netflix 声明性REST客户端:Feign
部分内容摘自 Spring Cloud 官方文档中文版
本文源码地址:https://github.com/Wyxwx/SpringCloudDemo2
目录
名词解释:Feign
Feign 是一个声明式的 Web 服务客户端,这使得 Web 服务客户端的写入更加方便。Feign 具有包括Feign注释和JAX-RS注释的可插拔式的注释支持。Feign还支持可插拔编码器和解码器。Spring Cloud集成Ribbon和Eureka以在使用Feign时提供负载均衡的http客户端。
Feign 整合了ribbon,具有负载均衡的能力
Feign 的基本使用
根据 服务发现:Eureka (一) 注册和运行 创建一个服务注册中心(eureka_server)和两个功能相同的 Eureka 客户端(eureka_client_1、eureka_client_2)
两个客户端的配置文件分别改为:
server.port=8762
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
spring.application.name=ribbonClient
server.port=8763
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
spring.application.name=ribbonClient
两个客户端的 spring.application.name 相同,代表提供了同一种服务,分配不同的端口模拟在不同服务器的场景
在两个客户端模块中分别创建一个相同的 HelloController
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Value("${server.port}")
private String port;
@RequestMapping(value = "/hello")
public String hello(){
return "my port is " + port;
}
@RequestMapping(value = "/hi/{name}", method = RequestMethod.GET)
public String hi(@PathVariable("name") String name){
return "Hi, " + name + ", my port is " + port;
}
}
接下来,按照创建 Eureka 客户端的步骤创建一个新的 Module: feign
在创建之后的 pom.xml 里加入以下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
编写配置文件 application.properties
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
server.port=8765
spring.application.name=feignClient
修改启动类,为其加入 @EnableEurekaClient、 @EnableFeignClients(开启 Feign 功能)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableEurekaClient
@EnableFeignClients
@SpringBootApplication
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}
创建一个 Java 类 HelloWorld
import com.example.demo.config.FeignConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(name = "ribbonClient")
public interface HelloWorld {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
String hello();
@RequestMapping(method = RequestMethod.GET, value = "/hi/{name}")
String hi(@PathVariable("name") String name);
}
创建一个 HelloWorldController
import com.example.demo.interfacePackage.HelloWorld;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@RestController
public class HelloWorldController {
@Resource
private HelloWorld helloWorld;
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello(){
return helloWorld.hello();
}
@RequestMapping(value = "/hi/{name}", method = RequestMethod.GET)
public String hi(@PathVariable("name") String name){
return helloWorld.hi(name);
}
}
依次启动 eureka_server、eureka_client_1、eureka_client_2、feign
多次访问 http://localhost:8765/hi/wyx 可以看到 port 8762 和 port 8763 交替出现
覆盖 Feign 默认值
Spring Cloud可以通过使用@FeignClient
声明额外的配置(FeignClientsConfiguration
)来完全控制客户端
修改 HelloWorld,在 @FeignClient 中添加 configuration 属性
import com.example.demo.config.FeignConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(name = "ribbonClient", configuration = FeignConfiguration.class)
public interface HelloWorld {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
String hello();
@RequestMapping(method = RequestMethod.GET, value = "/hi/{name}")
String hi(@PathVariable("name") String name);
}
表明用 FeignConfiguration中的配置覆盖 FeignClientsConfiguration(默认配置)中的配置,对于在 FeignConfiguration中未出现的配置,仍然按照默认配置来
注意:
FeignConfiguration 类可以不必加入 @Configuration 注解。
加入了该注解后,若是该类在 Application Context 的 @ComponentScan 中,它将成为feign.Decoder, feign.Encoder, feign Contract 等的默认来源。所以在使用时,需要采取措施来避免这一现象。可以通过将其放置在任何 @ComponentScan 或 @SpringBootApplication 的单独的不重叠的包中,或者可以在 @ComponentScan 中明确排除。
servicedId
属性现在已被弃用,有利于 name
属性。
以前,使用 url
属性,不需要 name
属性。现在需要使用 name
name
和url
属性支持占位符
@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
//..
}
Feign Hystrix 支持
Feign 已经集成了 Hystrix,但是默认是关闭的,若想开启使用,则在配置文件中加入
feign.hystrix.enabled=true
Feign 中,回退操作与纯 hystrix 有些不同
创建类 FeignHystrix 继承 HelloWorld
实现的方法为出错回退时调用的方法
import org.springframework.stereotype.Component;
@Component
public class FeignHystrix implements HelloWorld {
@Override
public String hello() {
return "hello: sorry, there is an error";
}
@Override
public String hi(String name) {
return "hi: sorry, there is an error";
}
}
修改 HelloWorld 类,设置回退类为 FeignHystrix
import com.example.demo.config.FeignConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(name = "ribbonClient", fallback = FeignHystrix.class)
public interface HelloWorld {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
String hello();
@RequestMapping(method = RequestMethod.GET, value = "/hi/{name}")
String hi(@PathVariable("name") String name);
}
断开 eureka_client_1 和 eureka_client_2,重启 feign
访问 http://localhost:8765/hi/wyx,证明断路器成功工作
如果需要访问导致回退触发的原因,可以使用 @FeignClient 内的 fallbackFactory 属性。
创建 Java 类 FeignHystrixFactory
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
@Component
public class FeignHystrixFactory implements FallbackFactory<HelloWorld> {
@Override
public HelloWorld create(Throwable cause) {
return new HelloWorld() {
@Override
public String hello() {
return "hello: " + cause;
}
@Override
public String hi(String name) {
return "hi: " + cause;
}
};
}
}
修改 HelloWorld 注解,重启 feign
import com.example.demo.config.FeignConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(name = "ribbonClient", fallbackFactory = FeignHystrixFactory.class)
public interface HelloWorld {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
String hello();
@RequestMapping(method = RequestMethod.GET, value = "/hi/{name}")
String hi(@PathVariable("name") String name);
}
访问 http://localhost:8765/hi/wyx,证明断路器成功工作
若想在某一客户端上禁用 Hystrix,可以利用 configuration 实现
创建 FeignConfiguration,该方法即禁用 Hystrix
import feign.Feign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
public class FeignConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder(){
return Feign.builder();
}
}
修改 HelloWorld
import com.example.demo.config.FeignConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(name = "ribbonClient", configuration = FeignConfiguration.class)
public interface HelloWorld {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
String hello();
@RequestMapping(method = RequestMethod.GET, value = "/hi/{name}")
String hi(@PathVariable("name") String name);
}
重启 feign,访问 http://localhost:8765/hi/wyx ,出现报错画面,说明该客户端的断路器已禁用
Feign 请求/响应压缩
在一些场景下,可能需要对请求或响应进行压缩,此时可使用启用Feign的压缩功能。
可以通过启用其中一个属性来启用请求或响应 GZIP 压缩
feign.compression.request.enabled=true
feign.compression.response.enabled=true
设置支持的媒体类型列表,默认是 text/xml,application/xml,application/json
设置最小压缩大小,默认是2048
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
Feign 日志记录
每一个被创建的Feign客户端都会有一个logger。该logger默认的名称为Feign客户端对应的接口的全限定名。Feign日志记录只能响应DEBUG日志级别。
在配置文件中开启
logging.level.project.user.UserClient: DEBUG
可以为每个客户端配置的 Logger.Level 对象告诉Feign记录多少
-
NONE
,无记录(DEFAULT)。 -
BASIC
,只记录请求方法和URL以及响应状态代码和执行时间。 -
HEADERS
,记录基本信息以及请求和响应标头。 -
FULL
,记录请求和响应的头文件,正文和元数据。
例如
@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}