Feign 是声明式、模板化的 HTTP 客户端, 可以帮助我们更快捷、优雅地调用 HTTP API;Spring Cloud 为 Feign 添加了 Spring MVC 的注解支持,并整合了 Ribbon 和 Eureka 来为使用 Feign 时提供负载均衡;在 Spring Cloud 中使用 Feign 是非常容易的。
本篇主要介绍 SpringBoot 中要玩转 Feign 需要掌握的如添加 pom 依赖、客户端注解启用、切换底层 HttpClient、配置数据压缩、调整日志级别、定制配置、配置的优先级机制、增加拦截器以及拦截器的追加机制等知识。
- <dependencies>
- <!--openfein的依赖-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- <version>2.1.3.RELEASE</version>
- </dependency>
- </dependencies>
- 复制代码
在 SpringBoot 的启用类上添加注解@EnableFeignClients,@EnableFeignClients用于开启 Feign,会自动扫描@FeignClient标注的 FeignClient 接口。
- @SpringBootApplication
- @EnableFeignClients
- @EnableWeb
- public class FeignApplication {
- public static void main(String[] args) {
- SpringApplication.run(FeignApplication.class,args);
- }
- }
- 复制代码
- @FeignClient(
- name = "demo-service",
- url = "http://localhost:8080/feign/server/",
- configuration = FeignInterceptor.class,
- fallback = TestService.DefaultFallback.class
- )
- public interface TestService {
-
- @RequestMapping(value = "/getError/{id}", method = RequestMethod.GET)
- public String getError(@RequestParam("id") Integer id);
-
-
- @RequestMapping(value = "/get1", method = RequestMethod.GET)
- public String get1();
-
- @RequestMapping(value = "/get2/{param}", method = RequestMethod.GET)
- public String get2(@RequestParam("param") String param);
-
- @RequestMapping(value = "/post1", method = RequestMethod.POST)
- public FeignDemo post1(@RequestBody FeignDemo demo);
- 复制代码
- @RestController
- @RequestMapping("/feign/server")
- public class FeignServerController {
-
- @GetMapping("/get1")
- public String get1() {
- return "get1";
- }
- @GetMapping("/get2/{para}")
- public String get2(@PathVariable("para") String para){
- return para;
- }
- @PostMapping("/post1")
- public FeignDemo post1(@RequestBody FeignDemo demo) {
- return demo;
- }
- }
- 复制代码
- public class FeignDemo {
- private String name;
- private Integer 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 "FeignDemo{" +
- "name='" + name + ''' +
- ", age=" + age +
- '}';
- }
- }
- 复制代码
- @RunWith(SpringJUnit4ClassRunner.class)
- @SpringBootTest(classes = {FeignApplication.class},webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
- @ActiveProfiles("dev,feign")
- public class FeignClientTest {
-
- @Autowired
- private TestService testService;
-
- @Test
- public void testFallback(){
- testService.getError(1);
- }
- @Test
- public void testGet1(){
- System.out.println(testService.get1());
- System.out.println(testService.get2("abc"));
- System.out.printf("..");
- FeignDemo feignDemo = new FeignDemo();
- feignDemo.setName("name");
- feignDemo.setAge(1);
- System.out.println(testService.post1(feignDemo));
- }
-
- @Component
- public class DefaultFallback implements TestService {
-
- @Override
- public String getError(@RequestParam("id") Integer id){
- return "";
- }
-
- @Override
- public String get1() {
- return null;
- }
-
- @Override
- public String get2(String param) {
- return null;
- }
-
- @Override
- public FeignDemo post1(FeignDemo demo) {
- return null;
- }
-
- }
- }
- 复制代码
Feign 中自带的是 HttpURLConnection,这个 client 健壮性差,可替换为成熟的 Apache HttpClient 或 OkHttp 来进行网络请求。
使用 Apache 的 httpclient 替换 Feign 中默认的 client。
2.1.1 添加依赖
- <!--httpclient的依赖,因为选择了使用httpclient-->
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpclient</artifactId>
- </dependency>
- <dependency>
- <groupId>io.github.openfeign</groupId>
- <artifactId>feign-httpclient</artifactId>
- <version>10.4.0</version>
- </dependency>
- 复制代码
2.1.2 配置启用
配置中添加如下信息,表示启用httpclient。
- feign:
- httpclient:
- enabled: true
- 复制代码
2.2.1 添加依赖
在 Feign 中使用OkHttp作为网络请求框架,则只需要在 pom 文件中加上feign-okhttp的依赖,代码如下:
- <dependency>
- <groupId>io.github.openfeign</groupId>
- <artifactId>feign-okhttp</artifactId>
- <version>10.2.0</version>
- </dependency>
- 复制代码
2.2.2 配置启用
- feign:
- okhttp:
- enabled: true
- 复制代码
在发送和接收请求的时候,其内部将日志的打印输出定义成了四个等级,对应的详情如下:
| 级别 | 说明 |
|---|---|
| NONE | 不做任何记录 |
| BASIC | 仅记录请求方法和 URL 以及响应状态代码和执行时间 |
| HEADERS | 记录基本信息以及请求和响应标头 |
| FULL | 记录请求和响应的标题,正文和元数据 |
注意需要指定接口的全限定名
- logging:
- level:
- com.zto.titans.test.feign.service.TestService : DEBUG
- 复制代码
- @Configuration
- public class FooConfiguration {
- @Bean
- Logger.Level feignLoggerLevel() {
- return Logger.Level.FULL;
- }
- }
- 复制代码
这个一看即懂,不再废话。
可以分别对 HTTP 通信的request和response设置是否启用 GZIP 压缩,配置方法如下:
- feign:
- compression:
- request:
- enabled: true
- mime-types: text/xml,application/xml,application/json # 配置压缩支持的MIME TYPE
- min-request-size: 2048 # 配置压缩数据大小的下限
- response:
- enabled: true # 配置响应GZIP压缩
- 复制代码
有 2 种途径设置 FeignClient 的配置,通过自定义配置类来设置配置和在配置文件中设置,其中配置文件方式有点特殊,它里边可以指定全局配置对所有 FeignClient 有效,也可以为特定名称的 FeignClient 设置专属的配置。
实现一个配置类
- public class TestConfiguration {
- @Bean
- Logger.Level feignLoggerLevel() {
- return Logger.Level.FULL;
- }
- }
- 复制代码
将配置类 TestConfiguration 指定给configuration。
- @FeignClient(
- name = "test-service",
- configuration = {FeignInterceptor2.class,TestConfiguration.class}
- )
- 复制代码
feign.client.config.default.xxx ,这个default意为全局的配置属性。
- feign:
- client:
- config:
- default:
- connectTimeout: 5000
- readTimeout: 5000
- loggerLevel: basic
- 复制代码
feign.client.config.feignName.xxx , 给名字为feignName的FeignClient指定专属的配置。
- feign:
- client:
- config:
- feignName:
- connectTimeout: 5000
- readTimeout: 5000
- loggerLevel: full
- errorDecoder: com.example.SimpleErrorDecoder
- retryer: com.example.SimpleRetryer
- requestInterceptors:
- - com.example.FooRequestInterceptor
- - com.example.BarRequestInterceptor
- decode404: false
- encoder: com.example.SimpleEncoder
- decoder: com.example.SimpleDecoder
- 复制代码
从org.springframework.cloud.openfeign.FeignClientFactoryBean#configureFeign中可以确认以上 3 种配置的优先级:
- configureUsingConfiguration(context, builder); // 1
- configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),builder); //2
- configureUsingProperties(properties.getConfig().get(this.contextId),builder);//3
- 复制代码
feign.client.config.default.xxx设置全局配置feign.client.config.feignName.xxx设置专属配置5.4.1 优先级的效果
配置文件里的专属配置 -覆盖-> 配置文件里的全局配置 -覆盖-> 配置类的配置
5.4.2 追加的原则
RequestInterceptor 是拦截器,可以在发送前做一些处理,比如统一添加header信息。每一类中的requestInterceptors可以存储多个拦截器,拦截器并非覆盖的效果,而是链式追加的效果;从执行顺序来看优先级是:1 > 2 > 3,即先执行 配置类中指定的拦截器,然后是 配置文件中指定的全局拦截器,最后是配置文件中指定的专属拦截器。
需特别注意:RequestInterceptor 的实现类(例如 RI-A,RI-B)上如果添加了@Component注解,就都会被扫描识别到,并被追加到第一类的requestInterceptors列表中;倘若不小心 RI-A 还在第 2 类中又被指定了,则还会将拦截器 RI-A 追加在第二类的requestInterceptors列表中,结果是会 RI-A 总计会执行 2 次;若也在第三类中指定 RI-A,则 RI-A 也在其列表中追加,结果是 RI-A 总计会执行 3 次。
5.4.3 拦截器的效果验证
以一个实例来验证说明效果
- class FeignInterceptor implements RequestInterceptor {
- @Override
- public void apply(RequestTemplate requestTemplate) {
- requestTemplate.header("user", "myuser1");
- requestTemplate.header("password", "mypassword");
- }
- }
- 复制代码
- class FeignInterceptor1 implements RequestInterceptor {
- @Override
- public void apply(RequestTemplate requestTemplate) {
- requestTemplate.header("user1", "myuser1");
- requestTemplate.header("password1", "mypassword1");
- }
- }
- 复制代码
- class FeignInterceptor2 implements RequestInterceptor {
- @Override
- public void apply(RequestTemplate requestTemplate) {
- requestTemplate.header("user2", "myuser2");
- requestTemplate.header("password2", "mypassword2");
- }
- }
- 复制代码
- @FeignClient(
- name = "test-service",
- url = "http://localhost:8080/feign/server/",
- configuration = {FeignInterceptor.class,TestConfiguration.class},
- fallback = TestService.DefaultFallback.class
- )
- 复制代码
default 指定了一个,test-service里指定一个
- feign:
- httpclient:
- enabled: true
- okhttp:
- enabled: true
- client:
- config:
- default:
- connectTimeout: 5000
- readTimeout: 5000
- #loggerLevel: none
- requestInterceptors:
- - com.zto.titans.test.feign.service.FeignInterceptor1
- test-service:
- #loggerLevel: basic
- requestInterceptors:
- - com.zto.titans.test.feign.service.FeignInterceptor2
- logging:
- level:
- com.zto.titans.test.feign.service.TestService : DEBUG
- 复制代码
根据追加逻辑,最终执行的顺序是:
本篇主要介绍 SpringBoot 中要玩转 Feign 需要掌握的如添加 pom 依赖、客户端注解启用、切换底层 HttpClient、配置数据压缩、调整日志级别、定制配置、配置的优先级机制、增加拦截器以及拦截器的追加机制等知识,以实例 + 效果的方式帮读者高效全面并深入的理解它们。