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级别的信息外,还会