【Spring Cloud总结】12.覆写Feign的默认配置

接上篇《11.Feign的简介及基础使用》  Spring Cloud版本为Finchley.SR2版

上一篇我们讲了声明式REST服务调用的组件Feign的介绍以及基本的使用,像之前的Ribbon一样,Feign也可以修改其默认配置。本篇我们就来探讨复写Feign的默认配置的几种方式。
本部分官方文档:https://cloud.spring.io/spring-cloud-static/Finchley.SR2/single/spring-cloud.html#spring-cloud-feign

一、Feign默认配置修改方式

在Spring Cloud的官方文档中,针对Feign的默认配置覆盖,有下面的描述:

Spring Cloud的Feign支持的一个中心概念是命名客户端。

每一个feign client是整体的一部分,它们一起工作以按需联系远程服务器,并且该整体具有一个名称,开发人员可以使用@FeignClient将其命名。

Spring Cloud根据需要使用FeignClientsConfiguretion为每个命名的客户端创建一个新的整体作为ApplicationContext。这包含(其他)feign.Decoder,feign.Encoder和feign.Contract。

Spring Cloud允许您通过声明其他配置(在其上FeignClientsConfiguration)使用来完全控制假装客户端@FeignClient。例:

@FeignClient(name =“stores”,configuration = FooConfiguration.class)
 public  interface StoreClient {
     // .. 
}

在这种情况下,客户端由已经FeignClientsConfiguration与任何in 组成的组件组成FooConfiguration(后者将覆盖前者)。

看到这里大家是不是感觉似曾相识?没错,之前我们学习的Ribbon也是这种模式!Ribbon的默认配置是RibbonClientConfiguration,Feign的默认配置是FeignClientsConfiguretion;Ribbon的声明式覆盖方式是在@RibbonClient注解后添加自定义的configuration,而Feign是在@FeignClient注解后添加自定义的configuration。这样看来,Feign的配置覆盖方式和Ribbon配置是一样的,我们只需要了解Feign有哪些我们可以修改的基本配置即可。

在官方文档中,说明了Feign的默认配置项:
Decoder feignDecoder: ResponseEntityDecoder (为SpringDecoder的包装类)
Encoder feignEncoder: SpringEncoder
Logger feignLogger: Slf4jLogger
Contract feignContract: SpringMvcContract
Feign.Builder feignBuilder: HystrixFeign.Builder
Client feignClient: 如果启用的Ribbon,则为LoadBalancerFeignClient, 否则使用默认的FeignClient。
这里Decoder为请求信息的编码器,Encoder为请求信息的解码器,Logger为日志类, Contract为Feign的契约类(功能是将我们传入的接口进行解析验证,看注解的使用是否符合规范,然后返回给我们接口上各种相应的元数据),Builder为Feign的构造类,然后就是Feign的Client。

我们就可以在自定义的configuration配置类,去重新覆盖上面的Bean,来实现Feign的默认配置的修改,以符合项目的实际要求。

二、Feign默认配置修改实例

我们回顾一下上一章我们为movie服务去声明式调用user工程服务的UserFeignClient接口:

package com.microserver.cloud;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.microserver.cloud.entity.User;

@FeignClient("microserver-provider-user")
public interface UserFeignClient {
    @RequestMapping(value="/findById/{id}", method=RequestMethod.GET)
    public User findById(@PathVariable("id") Long id);
    
    @RequestMapping(value="/postUser", method=RequestMethod.POST)
    public String postUser(@RequestBody User user);
}

我们这里修改@FeignClient,来添加configuration参数进行自定义配置:

import com.microserver.config.TestFeignConfiguration;
@FeignClient(name="microserver-provider-user",configuration = TestFeignConfiguration.class )
public interface UserFeignClient {
    //代码省略...
}

然后我们在之前的com.microserver.config包下新建TestFeignConfiguration类:

package com.microserver.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TestFeignConfiguration {
    
}

这里将TestFeignConfiguration类放在com.microserver.config包下而不是com.microserver.cloud下的子包,原因和之前Ribbon的配置一样,是为了避免在主Application Context的组件扫描下,该配置被所有的Feign客户端共享。

那么下面我们来覆盖Feign的一些配置,这里我们将Feign的默认配置项中的Contract契约类为Default类:

package com.microserver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import feign.Contract;

@Configuration
public class TestFeignConfiguration {
    @Bean
    public Contract feignContract() {
        return new feign.Contract.Default();
    }
}

这里的意思是,Feign原来的默认Contract契约类为SpringMvcContract,意味着我们可以使用Spring MVC的服务请求注解(如@RequestMapping注解)来进行声明式服务的定义,如果我们更换Contract契约类为Default类,这里我们只能使用Feign自己的注解,而不能使用Spring MVC的注解了。

我们这里先不修改UserFeignClient中的@RequestMapping注解,先启动movie工程看看(Eureka顺便也启起来),是不是FeignClient不再支持Spring MVC的注解了。当启动起来之后,控制台出现以下异常:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'movieController': Unsatisfied dependency expressed through field 'userFeignClient'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.microserver.cloud.UserFeignClient': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: Method findById not annotated with HTTP method type (ex. GET, POST)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:584)
    //后续异常省略......
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.microserver.cloud.UserFeignClient': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: Method findById not annotated with HTTP method type (ex. GET, POST)
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:178) ~[spring-beans-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:101) ~[spring-beans-5.0.10.RELEASE.jar:5.0.10.RELEASE]
    //后续异常省略......
Caused by: java.lang.IllegalStateException: Method findById not annotated with HTTP method type (ex. GET, POST)
    at feign.Util.checkState(Util.java:126) ~[feign-core-9.7.0.jar:na]
    //后续异常省略......

通过异常我们可以看到,系统在初始化userFeignClient失败,原因是findById方法的修饰注解(annotated with)没有遵循HTTP方法的规则,实际上也就意味着,使用了“feign.Contract.Default()”的feignClient,只识别Feign的注释,不再识别Spring MVC的HTTP注释。

那么Feign的HTTP请求注解是什么?我们可以在Feign的Github(https://github.com/OpenFeign/feign)的README.md中看到类型的注释:

这里@RequestLine注解就是Feign的HTTP请求修饰注解,该注解以Request-Line的方式去解析一个HTTP请求串,该请求串由三个参数组成,每个参数之间由空格分隔开来,分别代表method请求方式(GET/POST),Request-URI请求路径,HTTP-Version请求HTTP协议版本(不写的话,默认为HTTP/1.1)。
例如下面的格式:

@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

就是GET请求的、路径为“/repos/{owner}/{repo}/contributors”的HTTP请求,其中花括号代表动态参数,可以与下面方法中的参数绑定。

我们这里改造一下之前UserFeignClient中的findById请求以及postUser请求,将之前的方法暂时注释掉:

package com.microserver.cloud;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;

import com.microserver.cloud.entity.User;
import com.microserver.config.TestFeignConfiguration;

import feign.Param;
import feign.RequestLine;

@FeignClient(name="microserver-provider-user",configuration = TestFeignConfiguration.class )
public interface UserFeignClient {
    
    @RequestLine("GET /findById/{id}")
    public User findById(@Param("id") Long id);
    
    @RequestLine("POST /postUser")
    public String postUser(@RequestBody User user);
    
//    @RequestMapping(value="/findById/{id}", method=RequestMethod.GET)
//    public User findById(@PathVariable("id") Long id);

//    @RequestMapping(value="/postUser", method=RequestMethod.POST)
//    public String postUser(@RequestBody User user);
    
}

可以看到Param以及RequestLine注解所在的包也是Feign的包(import feign.*)。我们重启movie服务、,可以看到没有异常了:

然后在eureka启动的基础上,再启动user工程,分别访问两个服务,也都是正常的:


证明我们对Feign的自定义配置成功了~

另外使用url请求的例子如下(使用默认的FeignClientsConfiguretion配置):

@FeignClient(name = "user-service", url = "http://localhost:7900/")
public interface UserFeignClient {
    @RequestMapping(value="/findById/{id}", method=RequestMethod.GET)
    public User findById(@Param("id") Long id);
}

有url的时候,name可以自定义,但不可以和其它的重复。

使用Feign请求动态URL的方式详见:https://my.oschina.net/joryqiao/blog/1925633

三、Feign的其它配置

1、Feign请求/响应压缩
Feign提供了启用请求/响应的GZIP压缩功能,我们可以通过下面的属性(在properties或yml配置文件)来进行压缩操作的开启:
feign.compression.request.enabled =true
feign.compression.response.enabled =true
除了开启压缩操作以外,还可以为Web服务器设置一些压缩请求参数的设置:
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
如这里设置了压缩介质的类型和最小请求阈值长度。

2、Feign日志记录
我们可以为每个创建的Feign客户端创建一个Logger日志记录器,默认情况下,Logger的名称是用于创建Feign客户端的接口的完整类名。要注意的是,Feign日志记录仅响应DEBUG级别。

例子(application.yml中):

logging.level.project.user.UserClient:DEBUG

这里就是为接口路径为“project.user.UserClient”的Feign客户端设置日志级别。
另外,我们同时还可以在配置类中覆盖“logging.Level”参数,为每个客户端配置Feign日志的记录量,一共有以下选择:
NONE,没有记录(DEFAULT)。
BASIC,仅记录请求方法和URL以及响应状态代码和执行时间。
HEADERS,记录基本信息以及请求和响应标头。
FULL,记录请求和响应的标题,正文和元数据。
例如下面的配置就是将“logging.Level”参数配置为FULL:

@Configuration
public class FooConfiguration {
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

下面我们来测试一下,在movie工程中,我们首先在application.yml中为UserFeignClient开启日志开关:

logging:
  level:
    com.microserver.cloud.UserFeignClient: DEBUG

然后在TestFeignConfiguration配置文件中设置查看的记录量为“FULL”全量:

package com.microserver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import feign.Contract;
import feign.Logger;

@Configuration
public class TestFeignConfiguration {
    @Bean
    public Contract feignContract() {
        return new feign.Contract.Default();
    }
    
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

重启movie服务(Eureka服务和user服务也是启动中),访问之前的http://localhost:7901/movie/1服务,可以在控制台中看到如下信息:

可以看到日志中记录了请求URL参数,响应信息的HTTP的响应码(200),content-type内容类型、transfer-encoding转换编码、消息体等信息,所以我们配置的Feign日志开关是成功的。

至此,有关Feign组件的介绍就到这里啦~

本部分参考源码下载:https://download.csdn.net/download/u013517797/11485067

后面我们来学习Eureka的高级应用部分。

 

参考:《51CTO学院Spring Cloud高级视频》

转载请注明出处:https://blog.csdn.net/acmman/article/details/98632461

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章