springCloud组件专题(二) --- feign

前言:

        feign是springCloud的一个重要组件,目前的微服务项目基本都是以feign作为服务间调用组件,它是一个声明式的rest客户端。

        本文中的示例均基于一个简单的微服务demo,创建了两个springboot项目,并在nacos上进行了注册,结构如下:

一、feign使用

        1.使用

        1. 引入依赖

        openfeign是需要引入的主依赖,我们使用feign时候一般和注册中心一起使用做自动负载均衡,所以我们也引入loadbalancer

<!-- openfeign  如果要做自动负载均衡,则还需要需要 loadbalancer -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- springCloud 提供的一个负载均衡器 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId> spring-cloud-starter-loadbalancer</artifactId>
</dependency>

        2.在springboot项目的启动类上加上注解 @EnableFeignClients

@SpringBootApplication
@EnableFeignClients
public class AdminServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(AdminServerApplication.class, args);
    }
}

        3.编写feign接口,指向要调用的远端服务

                step1.编写一个Intetface,并加上@FeignClient注解

                step2.接口中定义方法,方法使用MVC的注解,get/put/post/delete,路径注意和被调用服务的接口的访问路径一直

                **注意** 如果要做负载均衡,一般会在@FeignClient的value中方被调用服务的注册名,比如我的示例中,buness-server服务在nacos中的注册名称叫 rui-buness,所以@FeignClien的value值就要写成@FeignClient(value = "rui-buness")

        4.通过feign调用其他服务

        直接像调用普通的service中的方法一样使用feign接口调用远端服务即可。如下图

     

        2.结合nacos实现负载均衡

        工作中最常用的负载均衡方式就是权重负载均衡,feign结合loadbalance及nacos来实现。

        a.首先,打开loadbalance的nacos权重负载均衡开关,配置如下:

spring:
  cloud:
    loadbalancer:
      nacos:
        enabled: true

        b.然后,在nacos的控制端界面,打开服务列表,修改节点权重。操作方法如下:

        c.最后,在项目中通过feign正常调用即可。效果如下图

二.feign的日志配置

        1.日志使用:

        在配置文件中加入如下配置

# 修改remote目录下的日志级别
logging:
  level:
    com.rui.remote: debug
# feign日志
feign:
  client:
    config:
      default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
        loggerLevel: FULL #  日志级别

        2.配置讲解:

        feign的日志输入级别为debug,所以要看到fengn的日志,首先需要将feign调用的日志level调整到debug

        feign自身的日志有 四个级别,分别为 

        1)None:不显示日志,默认是这个级别

        2)Basic:只显示请求的mothed,url以及响应的状态码还有执行事件

        3)Headers:打印请求和相应的请求头的基本信息

        4)Full:打印请求和响应的header和body还有元数据

三.feign的拦截器的使用

     1.为何要使用feign的拦截器

        在工作中,我们的服务都会使用secutity做鉴权,security会校验请求的token,如果没有token的话,请求会被拦截无法执行。

        所以,为了解决这个问题,一般使用两种方案。

        方案1.发起feign调用的服务增加一个feign的拦截器,在feign调用请求发出之前,在请求的header中塞入当前的token,这样请求到达另一个服务时候,就会正常校验token了。

        方案2.发起feign调用的服务增加一个feign的拦截器,在feign调用请求发出之前,在请求的header中塞入一个识别标识,在被调用服务中,也增加一个拦截,且该拦截的优先级需要高于security的拦截,然后在该拦截中验证,请求是否带有我们自定义的标识,如果有则放行。该方案一般还会配合自定义注解,比如定义一个注解@inner,将这个注解放在controller上,然后我们的放行过滤器只对访问有inner注解的资源放行,没有该注解的,即使请求头有我们自定义的标识也不放行。

        2.如何使用feign的拦截器

           我们刚聊了使用feign拦截器的场景,这里就以上文中提到的方案1作为例子,展示一下如何通过feign的拦截器在请求头中塞入token。

            step1。自定义拦截器,实现RequestInterceptor接口

public class RuiFeignRequestInterceptor implements RequestInterceptor
{

    /**
     * apply方法是RequestInterceptor接口需要实现的方法
     * 其参数requestTemplate代表feign调用的请求
     * @param requestTemplate
     */
    @Override
    public void apply(RequestTemplate requestTemplate) {

        // 从当前请求中拿到请求头
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
        if (!StringUtils.isEmpty(httpServletRequest))
        {
            Map<String, String> headers = getHeaders(httpServletRequest);

            // 在请求头中拿到 authorization
            String authentication = headers.get(SecurityConstants.AUTHORIZATION_HEADER);
            if (!StringUtils.isEmpty(authentication))
            {
                // 将authorization塞入feign调用请求头
                requestTemplate.header(SecurityConstants.AUTHORIZATION_HEADER, authentication);
            }
        }
    }

    /**
     * 工具方法,用于获取请求头
     */
    public static Map<String, String> getHeaders(HttpServletRequest request)
    {
        Map<String, String> map = new LinkedCaseInsensitiveMap<>();
        Enumeration<String> enumeration = request.getHeaderNames();
        if (enumeration != null)
        {
            while (enumeration.hasMoreElements())
            {
                String key = enumeration.nextElement();
                String value = request.getHeader(key);
                map.put(key, value);
            }
        }
        return map;
    }
}

            step2.将刚才定义的FeignRequestInterceptor 交给spring容器

/**
 * Feign 配置注册
 **/
@Configuration
public class MyFeignConfiguration
{
    @Bean
    public RequestInterceptor requestInterceptor()
    {
        return new RuiFeignRequestInterceptor();
    }
}

            step3.在启动类的@EnableFeignClients注解上指定默认配置文件

@SpringBootApplication
// 这里指定feign的配置文件
@EnableFeignClients(defaultConfiguration = MyFeignConfiguration.class)
public class AdminServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(AdminServerApplication.class, args);
    }
}

            step4.测试

        postMan中,我们发送请求,带上一个token

     

        调用方查看日志,可以看到,请求头中已经带上了token

四.feign的优化

        1.okhttp介绍

        okhttp是一套高性能的处理Http网络的框架,对于安卓开发来说,RetroFit + OkHttp几乎是一套标配。很多公司在feign的使用中,也会选择okhttp作为作为连接池。

        2.使用okhttp

        1>引入依赖

<!-- okhttp 是一个处理网络请求的框架,用于替换feign默认的框架 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

        2>配置文件中增加配置

# okhtp
feign:
  okhttp:
    enabled: true

        通过以上两步,就可以将feign的默认http框架替换成okhttp

五.feign的原理

        个人觉得,关于每一个组件的具体原理,并不需要大家花太大的代价去了解,尤其feign这种本来就不是很复杂的组件,使用时候基本就能猜测出原理,面试中其实面试官更多想要的也是面试者对技术的理解,而不是必须详细的知道技术的细节,毕竟技术的实现迭代很快,但是技术的思维是一直受用的。

        1.feign原理概述

        对于feign的原理,其实在不清楚的情况下可以做一个大致的猜测。

        使用feign时候,我们其实做了两件事,一件事是在config中对feignclient进行配置,一件是自己定义了interface接口声明远程调用。所以可以推测,feign的实现肯定也会做两件事,分别是,根据我们的config来创建对应的feignClient;找到我们的interface,根据interface创建对应的动态代理对象;在调用feignService时候,实际是调用对应代理对象的invoke,然后代理对象中一定会去解析url,做负载均衡。

        不看源码仅通过猜测我们就可以得到以上推断,而实际上,feign的实现也是这么做的,是不是每一个步骤更复杂。不想浪费时间看源码的小伙伴现在就可以关浏览器了,想了解一下源码具体实现的小伙伴继续往下看。

        2.feign源码

        1.首先,@EnableFeignClients注解中写了@Import(FeignClientsRegistrar.class),所以我们知道自动装配会装配FeignClientsRegistrar。

        2.FeignClientsRegistrar类实现了ImportBeanDefinitionRegistrar,所以服务启动时候会进入registerBeanDefinitions方法

        3.该方法做了两件事,registerDefaultConfiguration,registerFeignClients。具体点进这俩方法。

        registerDefaultConfiguration做的事情是拿到FeginClientConfig上EnableClients的所有注解属性,从中获取defalultConfiguration属性,并注册到BeanDefinitionMap中。

        registerFeignClients会有拿取client属性,如果clients中有我们定义的FeginClient,我们会将其添加到我们的candidateComponents(BeanDeifition集合中),如果没有,则会创建一个扫描器,去扫描我们basePackage指定的包路径下添加了@FeginClient注解的类,并添加到我们的BeanDefinition中。

        4.细看一下registerFeignClients

        看到这个factoryBean,它是由入参的的BeanDefinitionregistry类型的registry转换成我们的ConfigurableBeanFactory类,然后获取contextId和name,type和isEnable,BeanDefinitionBuilder.genericBeanDefinition会去注册我们的BeanDenition,在这里会获取我们的url属性和path属性,这个就是我们实际调用的http地址了。同时去设置我们的fallback和fallbackFactory属性。完成属性填充后,通过Spring提供的registerBeanDefinition方法向BeanDefinitionRegistry注册了刚实例化的这个BeanDefinitionHolder。这里完成的是将FeignClient注解的类的信息交给工厂bean代理类,并将代理类的定义注册到Spring的容器中。至此,已经把要创建的接口代理对象的信息放入registry里面,之后spring在启动调用refresh方法的时候会负责bean的实例化。在实例化过程中,调用FeignClientFactoryBean的getObject方法

        5.spring调用上图中的回调函数,进入getObject,最终走到ReflectiveFeign.newInstance()方法,这一段的代码和调用栈如图

       

6.至此,代理对象实例被创建。

7.当代码调用feignService时候,其实是调用代理对象

第一个红色框体中会做路径替换等操作

第二个红框executeAndDecode方法,这里把请求交给了之前创建的LoadBalancerFeignClient,执行了它的execute方法

以上就是feign源码的大体流程。其中一些handler的细节,encoder,decoder编码的细节没有详细说,大家可以根据上面的截图自己debug看一下。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值