1.Feign简介
目录
3. Feign的Encoder、Decoder和ErrorDecoder
Feign是一个声明式的Web服务客户端。这使得Web服务客户端的写入更加方便 要使用Feign创建一个界面并对其进行注释。它具有可插入注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud增加了对Spring MVC注释的支持,并使用Spring Web中默认使用的HttpMessageConverters
。Spring Cloud集成Ribbon和Eureka以在使用Feign时提供负载均衡的http客户端。
在Spring Cloud Netflix栈中,各个微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端。我们可以使用JDK原生的URLConnection
、Apache的Http Client
、Netty的异步HTTP Client, Spring的RestTemplate
。但是,用起来最方便、最优雅的还是要属Feign了。
Feign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用Feign, 我们可以做到使用HTTP请求远程服务时能与调用本地方法一样的编码体验,开发者完全感知不到这是远程方法,更感知不到这是个HTTP请求。
现在为止所进行的 有Rest 服务调用实际上都会出现一个非常尴尬的局面,所有的数据调用和转换都必须由户自己来完成,而我们本身不擅 所有的数据调用和转换都必须由户自己来完成,我们习惯的编程模式是:通过接口来实现业务 长这些,我们习惯的编程模式是:通过接口来实现业务 ,这可以通过feign来实现,Feign = RestTempate + HttpHeader Ribbon Eureka综合体 综合体 = 业务接口的自动实例化。
2.使用Feign
2.1 pom.xml中添加
老版本用spring-cloud-starter-feign,Finchley.SR1版本的用spring-cloud-starter-openfeign
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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">
<parent>
<artifactId>spring-cloud</artifactId>
<groupId>com.alen</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>feign-service</artifactId>
<dependencies>
<!--eureka client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!--feign支持-->
<!--feign支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2 定义接口
为了让Feign知道在调用方法时应该向哪个地址发请求以及请求需要带哪些参数,我们需要定义一个接口:
package com.alen.service;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 定义一个feign接口,通过@ FeignClient(“服务名”),来指定调用哪个服务
**/
@FeignClient(value ="eureka-client")
public interface HelloService {
@RequestMapping("/hello")
//必须显示的指定age,不显示还不行
String hello(@RequestParam("age") Integer age);
}
A: @FeignClient
用于通知Feign组件对该接口进行代理(不需要编写接口实现),使用者可直接通过@Autowired
注入。
B: @RequestMapping
表示在调用该方法时需要向/group/{groupId}
发送GET
请求。
C: @PathVariable
与SpringMVC
中对应注解含义相同。
Spring Cloud应用在启动时,Feign会扫描标有@FeignClient
注解的接口,生成代理,并注册到Spring容器中。生成代理时Feign会为每个接口方法创建一个RequetTemplate
对象,该对象封装了HTTP请求需要的全部信息,请求参数名、请求方法等信息都是在这个过程中确定的,Feign的模板化就体现在这里。
在本例中,我们将Feign与Eureka和Ribbon组合使用,@FeignClient(name = "ea")
意为通知Feign在调用该接口方法时要向Eureka中查询名为ea
的服务,从而得到服务URL。
controller中调用这个接口
@RestController
public class HelloController {
@Autowired
private HelloService helloService;
/**
* 通过Controller调用远程服务
* @param age
* @return
*/
@RequestMapping("/hello")
public String home(@RequestParam Integer age) {
return helloService.hello(age);
}
}
2.3 启动类
@SpringBootApplication
//通过注解@EnableEurekaClient 表明自己是一个eurekaclient.
@EnableEurekaClient
//开启Feign的功能
@EnableFeignClients
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
2.4 application.yml文件
server:
port: 8014
eureka:
instance:
hostname: localhost
# 发呆时间,即服务失效时间(缺省为90s),就是超过15秒没有续约就会从注册表中剔除
lease-expiration-duration-in-seconds: 15
# 心跳时间,即服务续约间隔时间(缺省为30s)
lease-renewal-interval-in-seconds: 5
client:
#设置eureka服务器所在的地址,查询服务和注册服务都需要依赖这个地址。高可用的注册中心时,
#可以配置多个注册中心,通过逗号隔开
service-url:
defaultZone: http://localhost:8010/eureka/
Feign的源码实现的过程如下:
- 首先通过@EnableFeignCleints注解开启FeignCleint
- 根据Feign的规则实现接口,并加@FeignCleint注解
- 程序启动后,会进行包扫描,扫描所有的@ FeignCleint的注解的类,并将这些信息注入到ioc容器中。
- 当接口的方法被调用,通过jdk的代理,来生成具体的RequesTemplate
- RequesTemplate在生成Request
- Request交给Client去处理,其中Client可以是HttpUrlConnection、HttpClient也可以是Okhttp
- 最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。
2.5 覆盖Feign默认值,自定义FeignClient
默认的配置类为FeignClientsConfiguration,这个类在spring-cloud-netflix-core的jar包下,打开这个类,可以发现它是一个配置类,注入了很多的相关配置的bean,包括feignRetryer、FeignLoggerFactory、FormattingConversionService等,其中还包括了Decoder、Encoder、Contract,如果这三个bean在没有注入的情况下,会自动注入默认的配置。
- Decoder feignDecoder: ResponseEntityDecoder(这是对SpringDecoder的封装)
- Encoder feignEncoder: SpringEncoder
- Logger feignLogger: Slf4jLogger
- Contract feignContract: SpringMvcContract
- Feign.Builder feignBuilder: HystrixFeign.Builder
@Configuration
public class FeignClientsConfiguration {
...//省略代码
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
}
@Bean
@ConditionalOnMissingBean
public Encoder feignEncoder() {
return new SpringEncoder(this.messageConverters);
}
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
...//省略代码
}
重写配置:
你可以重写FeignClientsConfiguration中的bean,从而达到自定义配置的目的,比如FeignClientsConfiguration的默认重试次数为Retryer.NEVER_RETRY,即不重试,那么希望做到重写,写个配置文件,注入feignRetryer的bean,代码如下:
@Configuration
public class FeignClientConfig{
@Bean
public Retryer feignRetryer() {
return new Retryer.Default(100, SECONDS.toMillis(1), 5);
}
}
在上述代码更改了该FeignClient的重试次数,重试间隔为100ms,最大重试时间为1s,重试次数为5次。
在这种情况下,客户端由FeignClientsConfiguration中的组件与FeignClientConfig中的任何组件组成(后者将覆盖前者)。
需要注意的是: FeignClientConfig类不能包含在主应用程序上下文的@ComponentScan中,否则该配置会被所有的@FeignClient共享。Ribbon的自定义配置中也需要注意这个问题。可以通过将其放置在任何@ComponentScan或@SpringBootApplication的单独的不重叠的包中,或者可以在@ComponentScan中明确排除。
2.6 .Feign 相关配置
Feign之中最为核心的作用就是将 Rest 服务的信息转换为接口,但是在实际使用之中也需要 考虑到一些配置情况, 例如:数据压缩,Rest 的核心本质在于: JSON 数据传输( XML、文本),于是就必须思考一种情况,玩意 、文本),用户发送的数据很大呢? 所以这个时候可考虑修改 application.yml 配置文件对传输数据进行压缩; 配置文件对传输数据进行压缩;
feign:
compression:
request:
mime-types: # 可以被压缩的类型
# - text/xml
# - application/xml
# - application/json
min-request-size: 2048 # 超过2048的字节进行压缩
2、 如果有需要则可以在项目之中开启 feign的相关日志信息(默认不开启) :
为每个创建的Feign客户端创建一个记录器。默认情况下,记录器的名称是用于创建Feign客户端的接口的完整类名。Feign日志记录仅响应DEBUG
级别
修改 application.yml配置文件,追加日志追踪:
logging:
level:
# feign接口包所在位置
cn.mldn.service: DEBUG
修改 FeignClientConfig ,开启日志的输出:
您可以为每个客户端配置的Logger.Level
对象告诉Feign记录多少。选择是:
-
NONE
,无记录(DEFAULT)。 -
BASIC
,只记录请求方法和URL以及响应状态代码和执行时间。 -
HEADERS
,记录基本信息以及请求和响应标头。 -
FULL
,记录请求和响应的头文件,正文和元数据。
例如,以下将Logger.Level
设置为FULL
:
@Configuration
public class FeignClientConfig {
@Bean
public Logger.Level getFeignLoggerLevel() {
return feign.Logger.Level.FULL ;
}
}
3. Feign的Encoder、Decoder和ErrorDecoder
Feign将方法签名中方法参数对象序列化为请求参数放到HTTP请求中的过程,是由编码器(Encoder)完成的。同理,将HTTP响应数据反序列化为java对象是由解码器(Decoder)完成的。
默认情况下,Feign会将标有@RequestParam
注解的参数转换成字符串添加到URL中,将没有注解的参数通过Jackson转换成json放到请求体中。注意,如果在@RequetMapping
中的method
将请求方式指定为POST
,那么所有未标注解的参数将会被忽略,例如:
@RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET)
void update(@PathVariable("groupId") Integer groupId, @RequestParam("groupName") String groupName, DataObject obj);
此时因为声明的是GET请求没有请求体,所以obj
参数就会被忽略。
在Spring Cloud环境下,Feign的Encoder*只会用来编码没有添加注解的参数*。如果你自定义了Encoder, 那么只有在编码obj
参数时才会调用你的Encoder。对于Decoder, 默认会委托给SpringMVC中的MappingJackson2HttpMessageConverter
类进行解码。只有当状态码不在200 ~ 300之间时ErrorDecoder才会被调用。ErrorDecoder的作用是可以根据HTTP响应信息返回一个异常,该异常可以在调用Feign接口的地方被捕获到。我们目前就通过ErrorDecoder来使Feign接口抛出业务异常以供调用者处理。
Feign的HTTP Client
Feign在默认情况下使用的是JDK原生的URLConnection
发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection
。我们可以用Apache的HTTP Client替换Feign原始的http client, 从而获取连接池、超时时间等与性能息息相关的控制能力。Spring Cloud从Brixtion.SR5
版本开始支持这种替换,首先在项目中声明Apache HTTP Client和feign-httpclient
依赖:
<!-- 使用Apache HttpClient替换Feign原生httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>${feign-httpclient}</version>
</dependency>
然后在application.properties
中添加:
feign.httpclient.enabled=true
总结
通过Feign, 我们能把HTTP远程调用对开发者完全透明,得到与调用本地方法一致的编码体验。这一点与阿里Dubbo中暴露远程服务的方式类似,区别在于Dubbo是基于私有二进制协议,而Feign本质上还是个HTTP客户端。如果是在用Spring Cloud Netflix搭建微服务,那么Feign无疑是最佳选择。
参考:
http://blog.csdn.net/neosmith/article/details/52449921
https://springcloud.cc/spring-cloud-dalston.html#spring-cloud-feign