• Feign应用及源码剖析


    Feign是Netflix开发的声明式、模板化的HTTP客户端,Feign可帮助我们更加便捷、优雅地调用HTTP API。Feign可以做到使用 HTTP 请求远程服务时就像调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个 HTTP 请求。

    以下示例全部摘自官网源码 

    简单应用

    服务端代码,简单定义一个restful风格的接口,启动spring boot应用:

    1. @EnableDiscoveryClient
    2. @SpringBootApplication
    3. public class ProviderApplication {
    4. public static void main(String[] args) {
    5. SpringApplication.run(ProviderApplication.class, args);
    6. }
    7. @RestController
    8. class EchoController {
    9. @GetMapping("/echo/{string}")
    10. public String echo(@PathVariable String string) {
    11. System.out.println("hello Nacos Discovery " + string);
    12. return "hello Nacos Discovery " + string;
    13. }
    14. }
    15. }

    客户端源代码:

    1. @SpringBootApplication
    2. @EnableDiscoveryClient(autoRegister = true)
    3. @EnableFeignClients
    4. public class ConsumerApplication {
    5. public static void main(String[] args) {
    6. SpringApplication.run(ConsumerApplication.class, args);
    7. }
    8. @FeignClient(name = "service-provider", fallback = EchoServiceFallback.class,
    9. configuration = FeignConfiguration.class)
    10. public interface EchoService {
    11. @GetMapping("/echo/{str}")
    12. String echo(@PathVariable("str") String str);
    13. }
    14. }
    15. class FeignConfiguration {
    16. @Bean
    17. public EchoServiceFallback echoServiceFallback() {
    18. return new EchoServiceFallback();
    19. }
    20. }
    21. class EchoServiceFallback implements EchoService {
    22. @Override
    23. public String echo(@PathVariable("str") String str) {
    24. return "echo fallback";
    25. }
    26. }

    客户端定义了Feign相关的核心接口: EchoService接口类,这个接口没有具体的实现类,可以把这个接口理解为Mybatis的Mapper接口,最终调用接口方法实际上是调用这个接口的代理类,向服务端发起调用的,另外spring在解析这个接口也是跟Mybatis用的相同的技术,通过FactoryBean.getObject()调用FeignInvocationHandler生成代理对象注册到spring容器。EchoService的注解@FeignClient定义了服务的应用名称,以及服务降级的措施。

    1. @RestController
    2. public class TestController {
    3. @Autowired
    4. private EchoService echoService;
    5. @GetMapping("/echo-feign/{str}")
    6. public String feign(@PathVariable String str) {
    7. return echoService.echo(str);
    8. }
    9. }

    最后定义一个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

    1. /**
    2. * 日志级别
    3. * NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。
    4. * BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。
    5. * HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
    6. * FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据。
    7. * @return
    8. */
    9. @Bean
    10. public Logger.Level feignLoggerLevel() {
    11. return Logger.Level.FULL;
    12. }

     配置为FULL结果:

    配置为BASIC:

    作用:帮助程序员开发时查看调用信息。

    额外的,我们也可以通过定义一个配置类将全局生效,上面的只是局部定义了EchoService接口。

    1. @Configuration
    2. public class FeignConfig {
    3. @Bean
    4. public Logger.Level feignLoggerLevel() {
    5. return Logger.Level.FULL;
    6. }
    7. }

    Feign拦截器实现参数传递

    定义FeignAuthRequestInterceptor类,并实现RequestInterceptor接口的apply()方法,向RequestTemplate中设置token来实现下游服务的身份认证。

    1. public class FeignAuthRequestInterceptor implements RequestInterceptor {
    2. @Override
    3. public void apply(RequestTemplate template) {
    4. // 业务逻辑
    5. String access_token = UUID.randomUUID().toString();
    6. template.header("Authorization",access_token);
    7. }
    8. }

     并在FeignConfiguration中定义Bean

    1. /**
    2. * 自定义拦截器
    3. * @return
    4. */
    5. @Bean
    6. public FeignAuthRequestInterceptor feignAuthRequestInterceptor(){
    7. return new FeignAuthRequestInterceptor();
    8. }

    另外,Feign给我们提供了一个基础认证类BasicAuthRequestInterceptor,可直接在配置类中定义这个Bean,

    1. @Bean
    2. public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
    3. return new BasicAuthRequestInterceptor("rick", "123456");
    4. }

    测试:可以看到请求中添加一个请求头kay-value对。

    超时时间配置

    超时时间包括请求连接超时时间和请求读取(处理)数据超时时间,直接在application.properties中配置

    1. #请求连接超时时间,默认2s
    2. feign.client.config.service-provider.connect-timeout=3000
    3. #请求读取数据超时时间,默认5s
    4. feign.client.config.service-provider.read-timeout=6000

    将service-provider中服务睡眠7s,发起调用后

    1. @GetMapping("/echo/{string}")
    2. public String echo(@PathVariable String string) {
    3. System.out.println("hello Nacos Discovery " + string);
    4. try {
    5. Thread.sleep(7000);
    6. } catch (InterruptedException e) {
    7. e.printStackTrace();
    8. }
    9. return "hello Nacos Discovery " + string;
    10. }

    look look...Read timed out 了。 

    客户端组件拓展

    Feign发起调用后默认是使用JDK的HttpURLConnection发起http请求: 

     通过引入Apache httpclient 组件即可替换默认的jdk的HttpURLConnection

    1. <dependency>
    2. <groupId>org.apache.httpcomponentsgroupId>
    3. <artifactId>httpclientartifactId>
    4. <version>4.5.7version>
    5. dependency>
    6. <dependency>
    7. <groupId>io.github.openfeigngroupId>
    8. <artifactId>feign-httpclientartifactId>
    9. <version>10.1.0version>
    10. dependency>

     当然还可以引入其他的http client组件:如

    1. <dependency>
    2.     <groupId>io.github.openfeigngroupId>
    3.     <artifactId>feign-okhttpartifactId>
    4. dependency>

    GZIP压缩

    使用GZIP压缩功能就是将发送的数据进行压缩,提高网络传输效率,通过在客户端配置

    1. feign.compression.request.enabled=true
    2. feign.compression.request.mime-types=text/xml,application/xml,application/json
    3. feign.compression.request.min-request-size=2048
    4. feign.compression.response.enabled=true

    发起调用结果: 

    注意:GZIP不支持okhttp,在解析时只有不存在okhttp3,配置才会生效

    引入okhttp3,并配置使用okhttp3 发起http请求:

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

    当使用okhttp3时测试结果:没有使用GZIP压缩

    编解码器

    配置编解码器,不使用默认的编解码,使用配置的:

    1. @Bean
    2. public Decoder decoder() {
    3. return new JacksonDecoder();
    4. }
    5. @Bean
    6. public Encoder encoder() {
    7. return new JacksonEncoder();
    8. }

    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容器。

  • 相关阅读:
    LinkedIn技巧-领英怎么只给选择的好友群发消息?
    聊聊“死锁“
    今日睡眠质量记录82分
    编程(代码、软件)规范(适用嵌入式、单片机、上位机等)
    680. 验证回文串 II-先删后验
    CentOS7.6上实现Spring Boot(JAR包)开机自启
    Java网络编程
    【深度学习】Pytorch基础
    Tomcat 源码分析 (整体架构) (一)
    推荐一款es轻量级的压测工具
  • 原文地址:https://blog.csdn.net/weixin_36279234/article/details/126160133