SpringCloud-Feign

什么是 Feign?

Feign 是一个声明式的 Web Service 客户端.而 OpenFeign 是对 Feign 进行增强,支持 Spring MVC 注解.OpenFeign 是一个声明式的 RESTful 网络请求客户端. Feign 目的就是让 Web Service 调用更简单. Feign 整合了 Ribbon 和 Hystrix,从而不需要开发者不需要针对 Feign 对其进行整合.而且还提供了 HTTP 请求模板.

Feign有以下特性

  • 可插拔的注解支持,包括 Feign 注解和JAX-RS注解.
  • 支持可插拔的HTTP编码器和解码器
  • 支持 Hystrix 和 Fallback
  • 支持 Ribbon 的负载均衡
  • 支持 HTTP 请求和响应的压缩

示例

创建工程添加必要依赖

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

启动类 必须要添加 @EnableFeignClients

@SpringBootApplication
@EnableFeignClients //当程序启动时,会进行扫描,扫描所有带有 @FeignClient 注解的类并进行处理.
public class Chapter4FeignApplication {
    public static void main(String[] args) {
        SpringApplication.run(Chapter4FeignApplication.class, args);
    }
}

Feign 类型的 Service 接口  使用注解 @FeignClient 注释该接口.并且设置必要的注解属性,其中 @RequestMapping 注解中的 value 属性必须要指定,一般是需要调用的接口的地址. method 指定调用方式.

@FeignClient(name = "github-client", url = "https://api.github.com")
//@FeignClient("hello-service)  //一般在真实应用中指定需要调用的服务实例的地址
public interface HelloFeignService {

    @RequestMapping(value = "/search/repositories", method = RequestMethod.GET)
    String searchRepo(@RequestParam("q") String queryStr);

    /**
     * 通过 @FeignClient 注解手动指定 url = "http://api.github.com" ,调用地址最终会根据指定的 url 和 @RequestMapping 对应的方法,
     * 拼接成 https://api.github.com/search/repositories?q=spring-cloud-dubbo
     */
}

controller类

@RestController
public class HelloFeignController {

    @Resource
    private HelloFeignService helloFeignService;

    @RequestMapping(value = "/search", method = RequestMethod.GET)
    public String searchGitubRepoByStr(@RequestParam("str") String str) {
        System.out.println("into the method......");
        return helloFeignService.searchRepo(str);
    }
}

启动项目,进行访问: http://localhost:8080/search?str=spring-cloud-dubbo 可以看到如下输出

使用Fegin步骤,

  • 在主程序入口使用 @EnableFeignClients 注解,开启对 Feign Client 扫描加载处理.
  • 在接口处使用 @FeignClients 注解.指定必要的注解属性.
  • 在具体的方法上指定需要调用的接口的地址, 调用方式,并且在方法体的参数当中使用注解标记参数.

Feign 工作原理

程序在启动的时, 扫描被 @FeignClients 注解注释的接口类,然后注入 Spring IOC 容器当中,当定义的 Feign 接口中的方法被调用时, 通过 JDK 动态代理的方式来生成实现类

,生成具体的 RequestTemplate,

当生成代理时,  Feign 会为接口的每个方法创建一个 RequestTemplate 对象, 该对象封装了 HTTP 请求需要的全部信息, 如请求参数名,请求方法等下信息都是在这个过程中确定的.

然后在由 RequestTemplate 生成 Request 对象,

在将 Request 对象交给 Client 去处理.

这里的 Client 可以是JDK远程的 URLConnection, 也可以是 apache 的 HttpClient 或者是其他的.最后 Client 被封装到 LoadBalanceClient 类, 这个类结合 Ribbon 负载均衡发起服务之间的调用.

Feign注解属性剖析

name:指定 FeignClient 的名称.如果项目使用了 Ribbon ,那么 name 属性会作为微服务的名称,用于服务发现

url: 一般用于调试,用于手动指定 @FeignClient 的调用地址

decode404: 当发生 404 错误时,如果该字段为 true ,会调用decoder 进行解码,否则抛出 FeignException.

configuration: Feign 配置类,可以自定义 Feign 的 Encoder, Decoder, LogLevel 等.

fallback: 定义容错的配置类,当调用远程接口失败或者超时时,会调用对应接口的容错逻辑,fallback 指定的类必须实现 @FeignClient 标记的接口

fallbackFactory: 工厂类,用于生成 fallback 类实例,通过这个属性可以实现每个接口通用的容错逻辑,减少重复代码

path:定义当前 FeignClient 的统一前缀,也就是所有方法级映射的统一前缀.相当于在 controller 上使用 @RequestMapping 一样,所有这个 controller 的方法都有这个统一的前缀

Feign开启gzip压缩

Spring Cloud Feign 支持对请求和响应进行 gzip 压缩,以便提高通信效率.

feign:
  compression:
    request:
      enabled: true
      mime-types: text/xml,application/xml,application/json # 配置压缩支持的MIME TYPE
      min-request-size: 2048  # 配置压缩数据大小的下限 单位是字节.
    response:
      enabled: true # 配置响应GZIP压缩

因为开启了压缩,所以调用 Feign 接口的时候返回值是二进制的,所以需要用 ResponseEntity<byte[]> 进行接收,不然会出现乱码

ResponseEntity<byte[]> searchRepo(@RequestParam("q") String queryStr);

Feign 还支持属性文件配置,可以对单个 Feign 或者所有的 Feign 进行配置.如果通过 Java 代码的方式配置 Feign ,然后又通过配置文件的方式配置 Feign, 那么属性文件当中的配置会覆盖Java代码中的配置.

Feign Client 开启日志

Feign 为每一个 FeignClient 都提供了一个 feign.Logger 实例,可以在测试环境当中针对每一个 feign 开启不同的日志级别,以便进行调试等操作.

  1. 可以在配置文件中开启
    1. 在配置文件当中配置日志输出
      logging:
        level:
          com.cn.springcloud.chapter4.service.HelloFeignService: debug

       

    2. 在主程序入口处配置日志 bean
      @Bean
          Logger.Level feignLoggerLeven() {
              return Logger.Level.FULL;
          }

      其中 Logger 的类型为 feign.Logger

  2. 通过配置类配置日志bean

    1. 在配置文件当中配置日志输出

      logging:
        level:
          com.cn.springcloud.chapter4.service.HelloFeignService: debug
    2. 编写日志配置类

      @Configuration
      public class HelloFeignServiceConfig {
      
          /**
           * 具体级别如下:
           * NONE: 不记录任何信息
           * BASIC: 仅记录请求方法,URL以及相应状态码和执行时间
           * HEADERS: 除了记录 BASIC 级别的信息之外,还会记录请求和响应的头信息
           * FULL: 记录所有的请求和响应的明细, 包括头信息,请求体, 元数据
           * @return
           */
          @Bean
          Logger.Level feignLoggerLevel() {
              return Logger.Level.BASIC;
          }
      }

       

 

Feign 超时设置

feign 的调用分为两层, Ribbon 调用和 Hystrix 调用.高版本的 Hystrix 默认是关闭的.如果出现 Ribbon 调用超时,可以在配置文件当中配置 Ribbon调用的超时时间,以及请求连接的时间

ribbon:
  ReadTimeout: 12000  #请求处理超时时间
  ConnectTimeout: 30000 #请求连接超时时间

Hystrix 超时

如果出现 Hystrix 超时报错,也可以进行一些必要的设置.Hystrix 默认的超时时间是 1 秒,如果超过这个时间未做处理,将会进入 fallback 代码,由于 bean 的懒加载机制, Feign 首次请求都会比较慢,如果这个响应时间大于 1 秒, 就会出现请求失败等问题.处理 Feign 首次访问失败的问题,可以 将 Hystrix 的超时时间加大, 或者 禁用 Hystrix 超时时间. 或者直接关闭 Hystrix (不推荐)

 

Feign 默认的 Client 替换

将原生 DJK 的替换成使用 Apache HTTP Client 

Feign 在默认的情况下是使用 JDK 远程的 URLConnection 发送 HTTP 请求的.没有连接池,但是对每个地址会保持一个长连接.

HttpsURLConnectionImpl 是 URLConnection 的子类.

我们可以使用 Apache 的 HTTP Client 去替换默认的 Feign 原始的 HTTP Client.

1, 加入依赖

<dependencies>
        <!-- openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- http client-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <!-- feign client-->
        <dependency>
            <groupId>com.netflix.feign</groupId>
            <artifactId>feign-httpclient</artifactId>
            <version>8.18.0</version>
        </dependency>
    </dependencies>

2, 在配置文件当中开启

这样就替换完成了,然后调用接口地址,debug 可以发现,从原来的使用JDK 原生的 HTTP Client 已经替换成了 Apache HTPP Client 了.

 

Feign 的 POST 和 GET 调用传递多个参数

在很多情况下,方法的调用会传递很多个参数, 而且还要写是对象.在Web开发中, Spring MVC 是支持 GET 方法直接绑定 POJO 的,但是 Feign 并未覆盖所有的 Spring MVC 功能.目前解决的方式有很多, 可以把 POJO 拆成一个一个的单独的属性放在方法参数里, 可以把方法参数以 Map 进行传递,使用 GET 传递@RequestBody. 其实还可以通过 Feign 的拦截器处理.通过实现 feign 的 RequestInterceptor 接口, 重写其中的方法便可以进行统一的拦截转换处理.

示例

构建 eureka server 注册中心, 端口 8761 (其他部分无特殊变化,按流程编写便可), 

构建 eureka client (服务提供者),提供业务服务,并将其注册到注册中心 eureka 提供服务的代码如下(配置文件等没有特殊变化,按流程编写就可以.):

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public String addUser(User user , HttpServletRequest request){
        String token = request.getHeader("oauthToken");
        return "hello,"+user.getName();
    }

    @RequestMapping(value = "/update", method = RequestMethod.POST)
    public String updateUser( @RequestBody User user){
        return "hello,"+user.getName();
    }
}

创建eureka client (服务消费者). 利用 feign 进行调用服务提供者提供的服务.并利用拦截器实现统一处理

拦截器实现:

/**
 * Feign 的拦截器,通过实现该拦截器,可以对参数进行一些处理
 */
@Component
public class FeignRequestInterceptor implements RequestInterceptor {

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 每一次调用 Feign 接口中的方法,发起远程调用的时候,该方法都会被调用
     * 并且使用给定的 RequestTemplate 添加数据
     * @param template  s
     */
    @Override
    public void apply(RequestTemplate template) {
        System.out.println("原始请求模板: " + template);
        if(template.method().equals("GET") && template.body() != null) {
            try {
                JsonNode jsonNode = objectMapper.readTree(template.body()); //转换成json
                template.body(null);
                Map<String, Collection<String>> queries = new HashMap<>();
                buildQuery(jsonNode, "", queries);
                template.queries(queries);  //替换新的请求模板.
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void buildQuery(JsonNode jsonNode, String path, Map<String, Collection<String>> queries) {
        if (!jsonNode.isContainerNode()) {   // 叶子节点
            if (jsonNode.isNull()) {
                return;
            }
            Collection<String> values = queries.get(path);
            if (null == values) {
                values = new ArrayList<>();
                queries.put(path, values);
            }
            values.add(jsonNode.asText());
            return;
        }
        if (jsonNode.isArray()) {   // 数组节点
            Iterator<JsonNode> it = jsonNode.elements();
            while (it.hasNext()) {
                buildQuery(it.next(), path, queries);   //这里使用了递归调用
            }
        } else {
            Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();
            while (it.hasNext()) {
                Map.Entry<String, JsonNode> entry = it.next();
                if (StringUtils.hasText(path)) {
                    buildQuery(entry.getValue(), path + "." + entry.getKey(), queries);
                } else {  // 根节点
                    buildQuery(entry.getValue(), entry.getKey(), queries);
                }
            }
        }
    }

}

controller 实现,提供对外服务

@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserFeignService userFeignService;
    
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public String addUser( @RequestBody @ApiParam(name="用户",value="传入json格式",required=true) User user){
        return userFeignService.addUser(user);
    }

    @RequestMapping(value = "/update", method = RequestMethod.POST)
    public String updateUser( @RequestBody @ApiParam(name="用户",value="传入json格式",required=true) User user){
        return userFeignService.updateUser(user);
    }
}

然后访问 http://localhost:8011/swagger-ui.html 在页面中进行正常访问便可以看到输出.

 

个人理解是, 对外提供的访问方式,在拦截器中被改变了, 将 post 请求的方式改变成了 get 方式,并对参数进行了拼接,然后由 feign 发起远程调用服务.在拦截器当中将 post 请求参数 body 中的参数拿出来,拼接到了所请求的地址后面,转成了 get 请求方式. 没体会到好处, 感觉有点繁琐. 还在努力学习当中. 

 

参考 <重新定义 Spring Cloud> 一书

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
springcloud-netflix是一个基于Spring Cloud的微服务框架。它提供了一系列工具和组件来简化开发和管理分布式系统的任务。其中包括Eureka、Feign和Zuul等组件。 在搭建springcloud-netflix项目时,需要创建父工程和子工程。父工程是springcloud-netflix-parent,子工程可以是springcloud-netflix-eureka、springcloud-netflix-service-pay等。每个子工程都需要在pom.xml文件中导入相应的依赖。 对于springcloud-netflix-eureka,需要导入spring-cloud-starter-netflix-eureka-server和spring-cloud-starter-netflix-eureka-client等依赖。此外,还需要配置相关的类。 对于springcloud-netflix-service-pay,需要导入spring-cloud-starter-netflix-eureka-client、spring-boot-starter-web和spring-cloud-starter-openfeign等依赖。同样,也需要配置相关的类。 对于Zuul,它是一个API Gateway服务器,提供了动态路由、监控、弹性和安全等边缘服务的框架。在搭建Zuul时,需要导入spring-cloud-starter-netflix-eureka-client、spring-boot-starter-web和spring-cloud-starter-netflix-zuul等依赖。同时,需要配置开启Zuul。 总之,springcloud-netflix是一个基于Spring Cloud的微服务框架,包括了Eureka、Feign和Zuul等组件,可以帮助简化开发和管理分布式系统的任务。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [SpringCloudNetflix](https://blog.csdn.net/Exist_W/article/details/131867868)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值