Feign远程调用

友若
• 阅读 4043

有关微服务中,服务与服务如何通信,我已经给大家介绍了Ribbon远程调用的相关知识,不知道大家有没有发现Ribbon的问题呢?

Ribbon的问题

在Ribbon中,如果我们想要发起一个调用,是这样的:

@Resource
private RestTemplate restTemplate

String result = restTemplate.getForObject("http://my-goods/goods/get", String.class);
Goods goods = JSONObject.parseObject(result, Goods.class);

这就像一个普通的http请求一样,需要对入参和出参进行手动处理。

打一眼看上去好像没什么问题,但仔细一想就不对劲了:这个被调用的接口都是我们自己写的,入参和出参都是确定的,甚至写被调用的接口的人都是同一个...有没有一种更好的方式,比如像调用本地方法一样直接调用它呢?

比如这样:

Goods goods = goodsServer.get();

这个术语叫:远程方法调用

今天的主角Feign就为我们实现了这样的功能,下面有请~

什么是Feign

官方是这样介绍的:Feign 是受RetrofitJAXRS-2.0WebSocket启发的 Java 到 HTTP 客户端binder(我也不知道这里怎么翻译)。

为什么说是binder?在看Feign所实现的功能及出发点来说,Feign本身并未实现HTPP客户端,而且在其他组件的客户端的上层进行了增强,我们可以先来感受一下Feign所带给我们的功能

客户端

  • java.net.URL
  • Apache HTTP
  • OK Http
  • Ribbon
  • Java 11 http2
  • ....

规范

  • Feign
  • JAX-RS
  • JAX-RS 2
  • SOAP
  • Spring 4
  • ....

编解码

  • GSON
  • JAXB
  • Jackson
  • ....

其他

  • Hystrix
  • SLF4J
  • Mock
更多内容查看github: https://github.com/OpenFeign/...

基本使用

1. 在商品服务中编写crud

小伙伴可以自己随便找个项目写,主要就是搞几个接口来调用

@RestController
@RequestMapping("/goods")
public class GoodsController {

    @GetMapping("/get-goods")
    public Goods getGoods(){
        return new Goods().setName("苹果")
                .setPrice(1.1)
                .setNumber(2);
    }

    @GetMapping("/list")
    public List<Goods> list(){
        ArrayList<Goods> goodsList = new ArrayList<>();
        Goods apple = new Goods().setName("苹果")
                .setPrice(1.1)
                .setNumber(2);
        goodsList.add(apple);
        Goods lemon = new Goods().setName("柠檬")
                .setPrice(5.1)
                .setNumber(3);
        goodsList.add(lemon);
        return goodsList;
    }

    @PostMapping("save")
    public void save(@RequestBody Goods goods){
        System.out.println(goods);
    }

    @DeleteMapping
    public void delete(String id){
        System.out.println(id);
    }
}

2. 建立一个demo,引入依赖

<dependencies>
  <dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-core</artifactId>
  </dependency>
  <!-- 用于编解码 -->
  <dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-jackson</artifactId>
  </dependency>
</dependencies>

3.编写feign规范的接口

public interface GoodsApi {

    @RequestLine("GET /goods/get-goods")
    Goods getGoods();

    @RequestLine("GET /goods/list")
    List<Goods> list();

    @RequestLine("POST /goods/save")
    @Headers("Content-Type: application/json")
    void save(Goods goods);

    @RequestLine("DELETE /goods?id={id}")
    @Headers("Content-Type: application/json")
    void delete(@Param("id") String id);
}
Feign规范是什么相信小伙伴一看例子就懂了

4.测试

public class FeignDemo {

    public static void main(String[] args) {
        // 构建feign接口
        GoodsApi goodsApi = Feign.builder()
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                .target(GoodsApi.class, "http://localhost:8082");
        // 调用测试
        System.out.println(goodsApi.getGoods());
        System.out.println(goodsApi.list());
        goodsApi.save(new Goods().setName("banana"));
        goodsApi.delete("1");
    }
}

灵光一闪

看到这里,不知道大家有没有灵光一闪呢?

我要是把构建的代码写这样

@Bean
public GoodsApi goodsApi(){
  return Feign.builder()
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                .target(GoodsApi.class, "http://localhost:8082");
}

然后使用时直接注入,woc,这不是爽翻天?

比较常用的使用方式

回顾什么是Feign章节,除了上面基本使用之外,feign还支持Spring 4的规范,以及各种http客户端(如okHttp),重试超时,日志等,我给大家介绍一个比较常用的方式

增加依赖

<!-- spring4规范 -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-spring4</artifactId>
  <version>10.10.1</version>
</dependency>
<!-- ribbon客户端 -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-ribbon</artifactId>
</dependency>
<!-- okhttp客户端 -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>
<!-- 日志 -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-slf4j</artifactId>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
</dependency>

编写接口

public interface Spring4GoodsApi {

    @GetMapping("/goods/get-goods")
    Goods getGoods();

    @GetMapping("/goods/list")
    List<Goods> list();

    @PostMapping(value = "/goods/save", consumes = MediaType.APPLICATION_JSON_VALUE)
    void save(Goods goods);

    @DeleteMapping("/goods")
    void delete(@RequestParam(value = "id") String id);
}

测试

public class Spring4FeignDemo {

    public static void main(String[] args) {
        Spring4GoodsApi goodsApi = Feign.builder()
                // 使用spring4规范
                .contract(new SpringContract())
                // 使用jackson编解码
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                // okhttp客户端
                .client(new OkHttpClient())
                // 请求失败重试,默认最大5次
                .retryer(new Retryer.Default())
                // 请求超时配置
                .options(new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true))
                // 日志配置,将在请求前后打印日志
                .logger(new Slf4jLogger())
                // 日志等级配置,BASIC:只打印请求路径和响应状态码基本信息
                .logLevel(Logger.Level.BASIC)
                .target(Spring4GoodsApi.class, "http://localhost:8082");
        System.out.println(goodsApi.getGoods());
        System.out.println(goodsApi.list());
        goodsApi.save(new Goods().setName("banana"));
        goodsApi.delete("1");
    }
}

拦截器

是个http客户端就会有拦截器机制,用于在请求前统一做一些操作:比如添加请求头

feign的拦截器使用方式如下:

public class AuthFeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        System.out.println("进入拦截器");
           HttpServletRequest request =
                ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        template.header("token", request.getHeader("token"));
    }
}
实现RequestInterceptor即可

在build时加入

Feign.builder()
  .requestInterceptor(new AuthFeignInterceptor())
  .target(Spring4GoodsApi.class, "http://localhost:8082");

以上就是Feign的常用方式了,学会之后,整合到Spring Cloud也是手到擒来。

整合Spring Cloud

官网文档:https://docs.spring.io/spring...

该整合样例为订单服务调用商品服务

直接三板斧走起

1. 加入依赖

在order-server中引入依赖

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

2. 增加注解

在Application类中加上注解EnableFeignClients

@EnableFeignClients
@SpringBootApplication
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(GoodsApplication.class, args);
    }
}

任何一个配置类都可以,但是推荐在启动类上加,因为注解默认扫描该注解类路径下的所有包,然后启动类又是在最顶端的,所以这样就可以扫描到所有的包了。

当然,你也可以直接扫描某个包,毕竟一般feign接口都放在一起

3. 编写配置

见配置章节

三板斧结束,开始编写样例

4. 编写样例

@FeignClient(name = "my-goods", path = "/goods", contextId = "goods")
public interface GoodsApi {

    @GetMapping("/get-goods")
    Goods getGoods();

    /**
     * get 方式传参加上需@SpringQueryMap注解
     */
    @GetMapping("/goods")
    Goods getGoods(@SpringQueryMap Goods goods);

    @GetMapping("/list")
    List<Goods> list();

    @PostMapping(value = "/save")
    void save(Goods goods);

    @DeleteMapping
    void delete(String id);
}

FeignClient:

​ name: 调用的服务名称

​ path: 路径前缀,该类下的所有接口都会继承该路径

​ contextId: 用于区分不同的feign接口,因为一般来说一个服务不止一个feign接口,比如还有个GoodsDetailApi(商品详情), 但是他们的name属性是相同的,都是商品服务,所有需要一个contextId来区分不同的业务场景

其他的与常用方式相同

5. 测试

@RestController
@RequestMapping("/order")
public class OrderController {
    @Resource
    private GoodsApi goodsApi;

    @GetMapping("/get-goods")
    public Goods getGoods(){
        return goodsApi.getGoods();
    }
}

配置

在常用方式中,我们构建一个feign接口的各种属性,是通过硬编码完成的,整合到spring之后,可以通过配置的方式完成了,更加的灵活。

日志

feign:
  client:
    config:
      # 全局配置, 配置类里面的属性名叫defaultConfig, 值却是default, 注意不要搞错
      default:
        loggerLevel: FULL
      # 单独服务配置 对应的是contextId 优先级更高
      goods:
        loggerLevel: BASIC
全局配置这里特别坑,不看源码根本不知道怎么配,小伙伴一定要注意

客户端

feign默认使用的HttpURLConnection作为客户端,小伙伴也可以替换成其他的客户端

前言:所有的客户端都为Client接口的实现类,想要知道是否替换成功只需在对应的实现类中打个断点

使用httpclient

引入依赖

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
</dependency>
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-httpclient</artifactId>
  <version>10.10.1</version>
</dependency>

这样就可以了,不用修改任何配置,这是因为当服务中包含ApacheHttpClient的class时,httpClient的feign自动配置类就会生效,并且比默认的HttpURLConnection自动配置类优先级更高。此时服务就将注入HttpClient作为客户端。

源码如下:

Feign远程调用

@Import 导入的顺序是HttpClient, OkHttp, Default(HttpURLConnection)

Feign远程调用

该配置类的生效条件就是存在ApacheHttpClient类,而feign.httpclient.enabled配置默认不配也是生效。

使用OkHttp

引入依赖

<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>

由于我们在上一步引入了httpclient依赖,而httpclient的优先级比okhttp高,并且是默认生效,所以想要okhttp生效有两种方式:

  • 删除掉httpclient的依赖
  • 显示关闭httpclient

这里我使用第二种方式

feign:
  # 将httpclient关闭
  httpclient:
    enabled: false
  # 将okhttp开启
  okhttp:
    enabled: true

GZIP压缩

有时当请求数据过大时,进行压缩数据可以有效的提高请求性能,feign也提供这样的配置方式

注意:压缩只在非okhttp客户端时生效

feign:
  httpclient:
    enabled: true
  # 配置压缩
  compression:
    request:
      enabled: true
      # 压缩的类型,默认就是这些
      mime-types: text/xml, application/xml, application/json
      # 压缩的字节最小阈值, 超过该大小才进行压缩,默认1024
      min-request-size: 10
这里我使用httpclient,不写也可以,我只是为了让大家知道我没用okhttp

容错重试

互联网应用无法解决的问题之一:网络分区。当被服务提供者出现这种情况,一直无法响应情况,我们也不可能让服务消费者一直傻傻的等着,所以我们可以给服务配置一个超时时间,超过一定的时间被调用的服务未响应,就把请求掐断。

feign的配置:

feign:
  client:
    config:
      # 全局配置
      default:
        # 连接超时时间 单位毫秒  默认10秒
        connectTimeout: 1000
        # 请求超时时间 单位毫秒  默认60秒
        readTimeout: 5000

互联网应用无法解决的问题之二:网络抖动。当经历这种情况时,我们只能让服务进行重试。

feign配置:

feign:
  client:
    config:
      # 全局配置
      default:
        # 重试 默认重试5次
        retryer: feign.Retryer.Default

拦截器

拦截器与常用方式相同,实现RequestInterceptor接口即可

@Component
public class AuthFeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        System.out.println("进入拦截器");
           HttpServletRequest request =
                ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        template.header("token", request.getHeader("token"));
    }
}
加上@Component注解放到spring容器中,服务启动时会自动加入到feign的拦截器链

也可以使用配置的方式

feign:
  client:
    config:
      # 全局配置
      default:
        requestInterceptors:
          - com.my.micro.service.order.interceptor.AuthFeignInterceptor

小结

本篇详细介绍了一种远程方法调用方式:Feign,现在,带大家来简单的回顾一下。

什么是Feign?

一种远程方法调用客户端,整合了ribbon,httpclient, okhttp, 支持各种各样的规范,如Spring4

Feign的基本使用方式?

编辑接口,加上Feign支持的规范注解,使用Feign.Builder构建出代理类,发起调用。

如何整合SpringCloud?

引入依赖,加上@EnableFeignClients注解,根据需求增加配置

本篇内容基本涵盖了Feign的常用方式,希望大家有所收获,我们下期再见~

gittee: https://gitee.com/lzj960515/m...

个人博客空间:https://zijiancode.cn/archive...

看完之后想必有所收获吧~ 想要了解更多精彩内容,欢迎关注公众号:程序员阿鉴,阿鉴在公众号欢迎你的到来~

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
JavaWeb 调用接口
JavaWeb 如何调用接口CreateTime2018年4月2日19:04:29Author:Marydon1.所需jar包!(https://oscimg.oschina.net/oscnet/0f139
Stella981 Stella981
3年前
Linux应急响应(一):SSH暴力破解
0x00前言SSH是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议,主要用于给远程登录会话数据进行加密,保证数据传输的安全。SSH口令长度太短或者复杂度不够,如仅包含数字,或仅包含字母等,容易被攻击者破解,一旦被攻击者获取,可用来直接登录系统,控制服务器所有权限。0x01应急场景某天,网站
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这