Feign是Netflix开发的声明式、模板化的HTTP客户端,Feign可帮助我们更加便捷、优雅地调用HTTP API。Feign可以做到使用 HTTP 请求远程服务时就像调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个 HTTP 请求。
以下示例全部摘自官网源码
简单应用
服务端代码,简单定义一个restful风格的接口,启动spring boot应用:
@EnableDiscoveryClient
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
@RestController
class EchoController {
@GetMapping("/echo/{string}")
public String echo(@PathVariable String string) {
System.out.println("hello Nacos Discovery " + string);
return "hello Nacos Discovery " + string;
}
}
}
客户端源代码:
@SpringBootApplication
@EnableDiscoveryClient(autoRegister = true)
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class,
configuration = FeignConfiguration.class)
public interface EchoService {
@GetMapping("/echo/{str}")
String echo(@PathVariable("str") String str);
}
}
class FeignConfiguration {
@Bean
public EchoServiceFallback echoServiceFallback() {
return new EchoServiceFallback();
}
}
class EchoServiceFallback implements EchoService {
@Override
public String echo(@PathVariable("str") String str) {
return "echo fallback";
}
}
客户端定义了Feign相关的核心接口: EchoService接口类,这个接口没有具体的实现类,可以把这个接口理解为Mybatis的Mapper接口,最终调用接口方法实际上是调用这个接口的代理类,向服务端发起调用的,另外spring在解析这个接口也是跟Mybatis用的相同的技术,通过FactoryBean.getObject()调用FeignInvocationHandler生成代理对象注册到spring容器。EchoService的注解@FeignClient定义了服务的应用名称,以及服务降级的措施。
@RestController
public class TestController {
@Autowired
private EchoService echoService;
@GetMapping("/echo-feign/{str}")
public String feign(@PathVariable String str) {
return echoService.echo(str);
}
}
最后定义一个restful接口向外提供调用路口,通过spring的@Autowired注入EchoService代理对象,在方法体内调用echoService.echo()方法就会向服务端发起restful调用。
这样一个完整的Feign调用就完成了:
Feign相关的拓展机制
Feign日志配置
在application.properties中设置Feign所在包路径的日志级别为Debug,只有这样接下来的Feign配置才能生效:
logging.level.com.alibaba.cloud.examples=debug
在Feign使用中的FeignConfiguration中注册Bean
/**
* 日志级别
* NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。
* BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。
* HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
* FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据。
* @return
*/
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
配置为FULL结果:
配置为BASIC:
作用:帮助程序员开发时查看调用信息。
额外的,我们也可以通过定义一个配置类将全局生效,上面的只是局部定义了EchoService接口。
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
Feign拦截器实现参数传递
定义FeignAuthRequestInterceptor类,并实现RequestInterceptor接口的apply()方法,向RequestTemplate中设置token来实现下游服务的身份认证。
public class FeignAuthRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 业务逻辑
String access_token = UUID.randomUUID().toString();
template.header("Authorization",access_token);
}
}
并在FeignConfiguration中定义Bean
/**
* 自定义拦截器
* @return
*/
@Bean
public FeignAuthRequestInterceptor feignAuthRequestInterceptor(){
return new FeignAuthRequestInterceptor();
}
另外,Feign给我们提供了一个基础认证类BasicAuthRequestInterceptor,可直接在配置类中定义这个Bean,
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("rick", "123456");
}
测试:可以看到请求中添加一个请求头kay-value对。
超时时间配置
超时时间包括请求连接超时时间和请求读取(处理)数据超时时间,直接在application.properties中配置
#请求连接超时时间,默认2s
feign.client.config.service-provider.connect-timeout=3000
#请求读取数据超时时间,默认5s
feign.client.config.service-provider.read-timeout=6000
将service-provider中服务睡眠7s,发起调用后
@GetMapping("/echo/{string}")
public String echo(@PathVariable String string) {
System.out.println("hello Nacos Discovery " + string);
try {
Thread.sleep(7000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello Nacos Discovery " + string;
}
look look...Read timed out 了。
客户端组件拓展
Feign发起调用后默认是使用JDK的HttpURLConnection发起http请求:
通过引入Apache httpclient 组件即可替换默认的jdk的HttpURLConnection
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>10.1.0</version>
</dependency>
当然还可以引入其他的http client组件:如
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
GZIP压缩
使用GZIP压缩功能就是将发送的数据进行压缩,提高网络传输效率,通过在客户端配置
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
feign.compression.response.enabled=true
发起调用结果:
注意:GZIP不支持okhttp,在解析时只有不存在okhttp3,配置才会生效
引入okhttp3,并配置使用okhttp3 发起http请求:
feign.httpclient.enabled=false
feign.okhttp.enabled=true
当使用okhttp3时测试结果:没有使用GZIP压缩
编解码器
配置编解码器,不使用默认的编解码,使用配置的:
@Bean
public Decoder decoder() {
return new JacksonDecoder();
}
@Bean
public Encoder encoder() {
return new JacksonEncoder();
}
Feign源码解析
@EnableFeignClients
在启动类添加@EnableFeignClients表示开启Feign,该注解导入FeignClientsRegistrar类,其实现了ImportBeanDefinitionRegistrar接口
spring在解析@Import注解时,会将FeignClientsRegistrar解析为Bean注入到spring容器,并执行其实现的registerBeanDefinitions()方法。
第一个registerDefaultConfiguration() 方法是解析@EnableFeignClients的配置,一般情况都没有。我们重点关注registerFeignClients()方法:
首先,解析@EnableFeignClients注解类的clients属性,这里为null,所以执行else的逻辑;创建扫描器,获取扫描的包路径,紧接着开始遍历包路径下符合要求的类(即被@FeignClients注解的接口类),将他们添加candidateComponents这个集合中;
再遍历candidateComponents集合,调用registerFeignClient():
该方法会创建FeignClientFactoryBean对象,并设置了factoryBean的属性,它实现了spring框架的FactoryBean接口,最后将factoryBean封装为BeanDefinition,注册spring容器中
FeignClientFactoryBean实现了该接口的两个核心方法getObject()和getObjectType(),其中变量definition持有的lambda表达式会调用getObject(),最终会在spring中执行这个方法
target():1、拼接URL。2.调用loadBalance通过targeter.target()方法创建代理对象并返回
最终会创建ReflectiveFeign实例,并调用其newInstance()创建FeignInvocationHandler(实现了InvocationHandler)对象,被代理的对象是HystrixTargeter(从spring容器获取的),还维护了dispatch集合,它封装了所有被代理对象的方法,最后通过JDK动态代理创建代理对象并返回。至此,整个过程就结束了。
总结
spring cloud解析Feign接口,然后通过spring容器获取Targeter,最后生成Targeter的代理对象通过FactoryBean注册到spring容器。