SpringCloud详解声明式服务调用Feign

2022-07-21 08:19:32

SpringCloud Feign是一个声明式的Web服务客户端,我们只需创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了在使用SpringCloud Ribbon时自行封装服务调用客户端的开发量

1、快速入门

构建一个SpringBoot工程,命名为feign-consumer

添加如下依赖:

<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>

在主类上添加@EnableFeignClients注解开启SpringCloud Feign的支持功能

定义HelloService接口,通过@FeignClient注解指定服务名来绑定服务,然后再使用SpringMVC的注解来绑定具体服务提供的REST接口

@FeignClient("hello-service")publicinterfaceHelloService{@RequestMapping("/hello")
    Stringhello();}

在Controller中实现对Feign客户端的调用

@RestControllerpublicclassConsumerController{@Autowired
    HelloService helloService;@GetMapping("/feign-consumer")public StringhelloConsumer(){return helloService.hello();}}

application.properties

spring.application.name=feign-consumer
server.port=9000
eureka.client.register-with-eureka=false
eureka.client.service-url.defaultZone=http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

先启动服务注册中心以及两个HELLO-SERVICE服务,然后启动FEIGN-CONSUMER,访问http://localhost:9000/feign-consumer看到正确的响应结果

Feign实现的消费者利用Ribbon维护了针对HELLO-SERVICE的服务列表信息,并且通过轮询实现了客户端负载均衡

2、参数绑定

在服务提供方hello-service中增加新的接口定义

@RestController@RequestMapping("/api")publicclassHelloController{@GetMapping("/hello1")public Stringhello(@RequestParam String name){return"Hello"+ name;}@GetMapping("/hello2")public Userhello(@RequestHeader String name,@RequestHeader Integer age){returnnewUser(name, age);}@PostMapping("/hello3")public Stringhello(@RequestBody User user){return"Hello"+ user.getName()+","+ user.getAge();}}

在feign-consumer应用中实现这些新增的请求的绑定

@FeignClient(value="hello-service", path="/api")publicinterfaceHelloService{@GetMapping("/hello1")
    Stringhello(@RequestParam("name") String name);@GetMapping("/hello2")
    Userhello(@RequestHeader("name") String name,@RequestHeader("age") Integer age);@PostMapping("/hello3")
    Stringhello(@RequestBody User user);}

@FeignClient参数:

  • name和value:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
  • url:可以手动指定@FeignClient调用的地址
  • path:定义当前FeignClient的统一前缀

在FeignClient中定义各参数绑定时,@RequestParam、@RequestHeader、@PathVariable等可以指定参数名称的注解,它们的value不能少,不然会抛出IllegalStateException异常

3、继承特性

通过SpringCloud Feign的继承特性来实现REST接口定义的复用

创建一个Maven工程,命名为hello-service-api,在工程中需要定义可同时复用于服务端与客户端的接口,要使用到SpringMVC的注解,所以在pom.xml中引入spring-boot-starter-web依赖,pom.xml的具体内容如下:

<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.6.RELEASE</version><relativePath/><!-- lookup parent from repository --></parent><groupId>com.hand</groupId><artifactId>hello-service-api</artifactId><version>0.0.1-SNAPSHOT</version><name>hello-service-api</name><description>hello-service-api</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies></project>

创建HelloService接口

@RequestMapping("/api/hello-service")publicinterfaceHelloService{@GetMapping("/hello1")
        Stringhello1(@RequestParam String name);@PostMapping("/hello2")
        Stringhello2(@RequestBody User user);}

在service-provider项目中新增hello-service-api的依赖:

<dependency><groupId>com.hand</groupId><artifactId>hello-service-api</artifactId><version>0.0.1-SNAPSHOT</version></dependency>

创建ImplementController类实现hello-service-api中定义的HelloService接口

@RestControllerpublicclassImplementControllerimplementsHelloService{@Overridepublic Stringhello1(@RequestParam String name){return"Hello"+ name;}@Overridepublic Stringhello2(@RequestBody User user){return"Hello"+ user.getName()+","+ user.getAge();}}

在feign-consumer应用中新增hello-service-api的依赖

创建HelloServiceClient接口并继承hello-service-api包中的HelloService接口,然后添加@FeignClient注解来绑定服务

@FeignClient(value="hello-service")publicinterfaceHelloServiceClientextendsHelloService{}

在ConsumerController中注入HelloServiceClient的实例,触发对HelloServiceClient的实例的调用

@RestController@RequestMapping("/api")publicclassConsumerController{@Autowired
        HelloServiceClient helloServiceClient;@GetMapping("/feign-consumer1")public StringhelloConsumer1(){return helloServiceClient.hello1("tom");}@GetMapping("/feign-consumer2")public StringhelloConsumer2(){return helloServiceClient.hello2(newUser("tom",11));}}

先启动服务注册中心以及HELLO-SERVICE服务,然后启动FEIGN-CONSUMER,访问ConsumerController中定义的两个Restful API接口看到正确的响应结果

4、Feign原理

Feign的关键机制是使用了动态代理

1)首先,对某个接口定义了@FeignClient注解,Feign就会针对这个接口创建一个动态代理

2)接着调用接口的时候,本质就是调用Feign创建的动态代理对象

3)Feign的动态代理会根据在接口上的@RequestMapping等注解,来动态构造要请求的服务的地址

4)针对这个地址,发起请求、解析响应

5、配置详解

1)、Ribbon配置

使用@FeignClient(value = “hello-service”)来创建Feign客户端的时候,同时也创建了一个名为hello-service的Ribbon客户端,可以使用@FeignClient注解中的name或value属性值设置对应的Ribbon参数来指定服务配置

Ribbon的详细配置查看:https://blog.csdn.net/qq_40378034/article/details/95099115

2)、Hystrix配置

通过如下配置关闭SpringCloud Feign的Hystrix支持

feign.hystrix.enabled=false

针对某个服务客户端关闭Hystrix支持:

  • 构建一个关闭Hystrix的配置类
publicclassDisableHystrixConfiguration{@Bean@Scope("prototype")public Feign.BuilderfeignBuilder(){return Feign.builder();}}
  • 在@FeignClient注解中通过configuration参数引入上面实现的配置
@FeignClient(value="hello-service", path="/api", configuration= DisableHystrixConfiguration.class)publicinterfaceHelloService{@GetMapping("/hello1")
    Stringhello(@RequestParam("name") String name);@GetMapping("/hello2")
    Userhello(@RequestHeader("name") String name,@RequestHeader("age") Integer age);@PostMapping("/hello3")
    Stringhello(@RequestBody User user);}

指定命令配置:

对于Hystrix命令的配置,采用hystrix.command.HystrixCommandKey作为前缀,HystrixCommandKey默认情况下会采用Feign客户端中的方法名作为标识

服务降级配置:

  • 服务降级逻辑的实现只需要为Feign客户端的定义接口编写一个具体的接口实现类,其中每个重写方法的实现逻辑都可以用来定义相应的服务降级逻辑
@ComponentpublicclassHelloServiceFallbackimplementsHelloService{@Overridepublic Stringhello(String name){return"error";}@Overridepublic Userhello(String name, Integer age){returnnewUser("未知",0);}@Overridepublic Stringhello(User user){return"error";}}
  • 在服务绑定接口HelloService中,通过@FeignClient注解的fallback属性来指定对应的服务降级实现类
@FeignClient(value="hello-service", path="/api", fallback= HelloServiceFallback.class)publicinterfaceHelloService{@GetMapping("/hello1")
    Stringhello(@RequestParam("name") String name);@GetMapping("/hello2")
    Userhello(@RequestHeader("name") String name,@RequestHeader("age") Integer age);@PostMapping("/hello3")
    Stringhello(@RequestBody User user);}

3)、FeignRibbonClientAutoConfiguration源码分析

Feign在默认情况下使用的是JDK原生的URLConnection发送HTTP请求,没有连接池,性能较差,但是对每个地址会保持一个长连接

查看配置类FeignRibbonClientAutoConfiguration:

@ConditionalOnClass({ ILoadBalancer.class, Feign.class})@Configuration@AutoConfigureBefore(FeignAutoConfiguration.class)@EnableConfigurationProperties({ FeignHttpClientProperties.class})@Import({ HttpClientFeignLoadBalancedConfiguration.class,
		OkHttpFeignLoadBalancedConfiguration.class,
		DefaultFeignLoadBalancedConfiguration.class})publicclassFeignRibbonClientAutoConfiguration{

此类import的3个类:HttpClientFeignLoadBalancedConfiguration、OkHttpFeignLoadBalancedConfiguration、DefaultFeignLoadBalancedConfiguration

HttpClientFeignLoadBalancedConfiguration:

@Configuration@ConditionalOnClass(ApacheHttpClient.class)@ConditionalOnProperty(value="feign.httpclient.enabled", matchIfMissing=true)classHttpClientFeignLoadBalancedConfiguration{@Bean@ConditionalOnMissingBean(Client.class)public ClientfeignClient(CachingSpringLoadBalancerFactory cachingFactory,
			SpringClientFactory clientFactory, HttpClient httpClient){
		ApacheHttpClient delegate=newApacheHttpClient(httpClient);returnnewLoadBalancerFeignClient(delegate, cachingFactory, clientFactory);}

当引入ApacheHttpClient类且feign.httpclient.enabled=true时,会初始化这个配置类

OkHttpFeignLoadBalancedConfiguration:

@Configuration@ConditionalOnClass(OkHttpClient.class)@ConditionalOnProperty("feign.okhttp.enabled")classOkHttpFeignLoadBalancedConfiguration{@Bean@ConditionalOnMissingBean(Client.class)public ClientfeignClient(CachingSpringLoadBalancerFactory cachingFactory,
			SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient){
		OkHttpClient delegate=newOkHttpClient(okHttpClient);returnnewLoadBalancerFeignClient(delegate, cachingFactory, clientFactory);}

当引入OkHttpClient类且feign.okhttp.enabled=true,会初始化这个配置类

DefaultFeignLoadBalancedConfiguration:

@ConfigurationclassDefaultFeignLoadBalancedConfiguration{@Bean@ConditionalOnMissingBeanpublic ClientfeignClient(CachingSpringLoadBalancerFactory cachingFactory,
			SpringClientFactory clientFactory){returnnewLoadBalancerFeignClient(newClient.Default(null, null), cachingFactory,
				clientFactory);}}

为Feign配置HttpURLConnection

feignClient()方法:只有没有以上两个Client对象时,才在这个方法中使用Client.Default生成LoadBalancerFeignClient

查看Client.Default的源码,使用HttpURLConnection建立连接且每次请求都建立一个新的连接

publicstaticclassDefaultimplementsClient{public Responseexecute(Request request, Options options)throws IOException{
            HttpURLConnection connection=this.convertAndSend(request, options);returnthis.convertResponse(connection, request);}

4)、Feign使用ApacheHttpClient

添加依赖:

<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId></dependency>

application.properties

feign.httpclient.enabled=true

4)、Feign使用okhttp3

添加依赖:

<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-okhttp</artifactId></dependency>

application.properties

feign.httpclient.enabled=false
feign.okhttp.enabled=true

配置类:

@Configuration@ConditionalOnClass(Feign.class)@AutoConfigureBefore(FeignAutoConfiguration.class)publicclassFeignOkHttpConfig{@Beanpublic okhttp3.OkHttpClientokHttpClient(){returnnewokhttp3.OkHttpClient.Builder().readTimeout(60, TimeUnit.SECONDS).connectTimeout(60, TimeUnit.SECONDS).writeTimeout(120, TimeUnit.SECONDS).connectionPool(newConnectionPool()).build();}}

5)、其他配置

请求压缩:

SpringCloud Feign支持对请求与响应进行GZIP压缩,以减少通信过程中的性能损耗

开启请求域响应的压缩功能:

feign.compression.request.enabled=true
feign.compression.response.enabled=true

指定压缩的请求数据类型,设置请求压缩的大小下限:

feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048

日志配置:

SpringCloud Feign在构建被@FeignClient注解修饰的服务客户端时,会为每一个客户端都创建一个feign.Logger实例,可以利用该日志对象的DEBUG模式来帮助分析Feign的请求细节。可以在application.properties文件中使用logging.level.<FeignClient>的参数配置格式来开启指定Feign客户端的DEBUG日志,其中<FeignClient>为Feign客户端定义接口的完整路径

logging.level.com.hand.feign.client.*=debug

但是只是添加了如上配置,还无法实现对DEBUG日志的输出。这时由于Feign客户端默认的Logger.Level对象定义为NONE级别,该级别不会记录任何Feign调用过程中的信息,所以需要调整它的级别,针对全局的日志级别,可以在应用主类中直接加入Logger.Level的Bean创建

@EnableFeignClients@SpringBootApplicationpublicclassFeignConsumerApplication{publicstaticvoidmain(String[] args){
        SpringApplication.run(FeignConsumerApplication.class, args);}@Bean
    Logger.LevelfeignLoggerLevel(){return Logger.Level.FULL;}}

也可以通过实现配置类,然后在具体的Feign客户端来指定配置类以实现是否要调整不同的日志级别

@ConfigurationpublicclassFullLogConfiguration{@Bean
    Logger.LevelfeignLoggerLevel(){return Logger.Level.FULL;}}
@FeignClient(value="hello-service", path="/api", configuration= FullLogConfiguration.class, fallback= HelloServiceFallback.class)publicinterfaceHelloService{@GetMapping("/hello1")
    Stringhello(@RequestParam("name") String name);@GetMapping("/hello2")
    Userhello(@RequestHeader("name") String name,@RequestHeader("age") Integer age);@PostMapping("/hello3")
    Stringhello(@RequestBody User user);}

Feign的Logger级别主要有下面4类:

  • NONE:不记录任何信息
  • BASIC:仅记录请求方法、URL以及响应状态码和执行时间
  • HEADERS:除了记录BASIC级别的信息外,还会
  • 作者:邋遢的流浪剑客
  • 原文链接:https://blog.csdn.net/qq_40378034/article/details/95794545
    更新时间:2022-07-21 08:19:32