简介

在项目中我们有时候需要调用第三方的API,微服务架构中这种情况则更是无法避免——各个微服务之间通信。比如一般的项目中,有时候我们会使用 HTTP Client 发送 HTTP 请求来进行调用,而在微服务架构,Spring Cloud 全家桶中,Spring Cloud Feign 则是更常见的选择。那么,我如何只使用 Spring Cloud Feign 而不引入整个 Spring Cloud 呢?

什么是Feign?

Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。

而Feign则会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。Feign整合了Ribbon和Hystrix,可以让我们不再需要显式地使用这两个组件。

总起来说,Feign具有如下特性:

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

这看起来有点像我们springmvc模式的Controller层的RequestMapping映射。这种模式是我们非常喜欢的。Feign是用@FeignClient来映射服务的。

首先找一个AIP

免费的API特别多,github上也有免费API地址汇总的repo,但这些都太正式了。有趣的事物总是会相互吸引的,无意间我发现了这么一个网站,“渣男:说话的艺术”(lovelive.tools) ,每次请求都可以获取一句甜言蜜语(渣男语录),特别良心的是,作者提供了API列表,给作者点赞!

如何调用第三方服务?

首先,我们先快速构建一个 Spring Boot 的 web 项目,这里我就省略了。然后在pom中添加feign的相关依赖

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

然后,在启动类添加响应的注解 @EnableFeignClients:

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

接着,我们便可以配置我们的 Client 了,我们先创建一个接口类,比如叫BadGuyFeignClient ,并声明为 FeignClient:

@FeignClient
public interface BadGuyFeignClient { }

@FeignClient有以下几个较常用属性:

属性名 默认值 作用 备注
value 空字符串 调用服务名称,和name属性相同,如果项目使用了 Ribbon,name属性会作为微服务的名称,用于服务发现;
serviceId 空字符串 服务id,作用和name属性相同 已过期
name 空字符串 调用服务名称,和value属性相同,如果项目使用了 Ribbon,name属性会作为微服务的名称,用于服务发现;
url 空字符串 url一般用于调试,可以手动指定@FeignClient调用的地址
decode404 false 配置响应状态码为404时是否应该抛出FeignExceptions
configuration {} Feign配置类,可以自定义 Feign的 Encoder、Decoder、LogLevel、Contract; 参考FeignClientsConfiguration
fallback void.class 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口 底层依赖hystrix,启动类要加上@EnableHystrix
fallbackFactory void.class 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
path 空字符串 自动给所有方法的requestMapping前加上前缀,类似与controller类上的requestMapping

然后,我们便可以配置对应的属性,这里我们只是用来实现类似于 HTTP Client 的功能,所以只是简单配置了url和path这些属性:

@FeignClient(name = "badGuy", url = "${bab.guy.url}", path = "api")
public interface BadGuyFeignClient { /**
* 随机获取一句甜言蜜语
*
* @return
*/
@GetMapping("SweetNothings")
String getSweetNothings(); /**
* 获取 count 条甜言蜜语
*
* @param count 获取甜言蜜语条数
* @return Json 格式的结果
*/
@GetMapping("SweetNothings/{count}/Serialization/Json")
ReturnResult<List<String>> getSweetNothingsJsonByCount(@PathVariable("count") Integer count); }

声明为FeignClient之后,我们便可以在代码中使用@Resource或者@Autowire进行注入使用了:

@Component
public class BadServiceImpl implements BadGuyService { @Autowired
private BadGuyFeignClient badGuyFeignClient; @Override
public List<String> getQuotations(Integer count) {
if (count == null || count <= 0) {
String singleQuotation = badGuyFeignClient.getSweetNothings();
return new ArrayList<String>() {{
add(singleQuotation);
}};
}
return badGuyFeignClient.getSweetNothingsJsonByCount(count).getReturnObj();
}
}

然后Controller中是这么写的:

@RestController
@Log4j2
@RequestMapping("/api/badGuy")
public class BadGuyController { @Resource
private BadGuyService badGuyService; @GetMapping({"quotations", "quotations/{count}"})
public PlainResult<List<String>> getBadGuyQuotations(
@PathVariable(value = "count", required = false) Integer count
) {
PlainResult<List<String>> result = new PlainResult<>();
try {
List<String> resultStrings = badGuyService.getQuotations(count);
result.setData(resultStrings);
} catch (Exception e) {
log.error("Failed to get bad guy quotations.", e);
result.setErrorMessage("error");
}
return result;
} }

启动项目之后,我们可以访问

http://localhost:8080/api/badGuy/quotations

或者

http://localhost:8080/api/badGuy/quotations/10

后面跟数字,即可得到对应条目数的结果

{
"success":true,
"code":0,
"message":"successful",
"data":[
"我从未拥有过你一秒钟,心里却失去过你千万次。",
"现在几点了?是我们幸福的起点。",
"我不很快乐,因为你不很爱我。但所谓不很快乐者,并不等于不快乐,正如不很爱我不等于不爱我一样。",
"我看你挺笨的吹口哨都不会,要不要我嘴对嘴教你。",
"我玩了六年英雄联盟,后来才发现你才是我的英雄。",
"这里一切都丑的,风、雨、太阳,都丑,人也丑,我也丑得很。只有你是青天一样可羡。",
"他对她说,他依然爱她,和过去一样,至死不渝",
"你猜我喜欢什么制服” “被你制服”",
"人说红颜薄命,你做我的红颜,我愿为你薄命。",
"有时候我想比你晚出生一百年,你的一生被拍成一部电影,而我一生只做一件事:独自坐在房间,面对墙上的荧光屏,用我的一生把你的一生看完。"
]
}

Feign client 如何设置请求头信息?

在调用http接口时,一般都需要在请求头里面添加相应鉴权参数,类似于ak、sk之类的密钥或者某个动态参数等,那么在使用Feign client调用时,该如何添加请求头信息呢?

准备接口

首先,准备一个第三方服务接口,我这里直接在rap上定义了一个查询用户信息接口,调用该接口必须在请求头传一个token参数和请求体中传userId,postman请求示例如下图:

如何添加请求有呢,这里提供两种方式。

在请求方法上添加请求头参数

示例代码如下:

@FeignClient(name = "apiClient", url = "${test.url}", path = "api")
public interface BasicApiClient {
/**
* 查询用户信息接口-第一种方式
*
* @param userId
* @param token
* @return
*/
@GetMapping(value = "/queryUser")
PlainResult<User> queryUser(@RequestParam("userId") String userId,
@RequestHeader(name = "token") String token); }

注意queryUser方法中多了一个@RequestHeader参数token,这个就相当于往请求头添加了一个token参数,这种情况适用于该token是一个在业务中经常动态变化的参数,需要在接口调用方动态获取。

利用@FeignClient的configuration属性

新建一个配置类如下

public class ClientConfiguration {

    @Value("${test.token}")
private String token; @Bean
public RequestInterceptor headerInterceptor() {
return new RequestInterceptor() { @Override
public void apply(RequestTemplate template) {
List<String> authorizationList = Lists.newArrayList(token);
List<String> contentTypeList = Lists.newArrayList("application/x-www-form-urlencoded;charset=utf-8");
Map<String, Collection<String>> headers = ImmutableMap.of("token", authorizationList, "Content-Type", contentTypeList);
template.headers(headers);
}
};
}
}

修改请求类如下:

@FeignClient(name = "apiClientTwo", url = "${test.url}", path = "api", configuration = ClientConfiguration.class)
public interface CommonApiClient { /**
* 查询用户信息接口-第二种方式
*
* @param userId
* @return
*/
@GetMapping(value = "/queryUser")
PlainResult<User> queryUser(@RequestParam("userId") String userId);
}

注意:这里使用了@FeignClient的configuration属性,并在该配置类中往请求头添加了token入参,这种方式适用于往请求头中存放的参数是固定的,类似于ak、sk或者用于授权的应用ID密钥之类的。

完整项目源码请参考:springboot-middleware-feign

FeignClient与HttpClient的区别是什么?

HttpClient与之同样实现的还有Okhttp、Httpurlconnection、RestTemplate等等,其 URL 参数是以编程方式构造的,数据被发送到其他服务。在更复杂的情况下,我们将不得不RestTemplate深入到更低级别的 API提供的甚至是 API的细节。

FeignClient则更像是在基于 REST 的服务调用上提供更高级别的抽象,在客户端编写声明式REST 服务接口,并使用这些接口来编写客户端程序。开发人员不用担心这个接口的实现。这将在运行时由 Spring 动态配置。通过这种声明性的方法,开发人员不需要深入了解由 HTTP 提供的 HTTP 级别API的细节的RestTemplate

总的来讲,FeignClient更具抽象性,也更简单、灵活。

总结

本文简单介绍了如何使用Spring Cloud Feign组件来替代HttpClient来实现简单调用第三方服务的方法,除了集成Feign组件,我们也可以在项目中加入Ribbon用于服务发现,加入Hystrix用于服务熔断等等,这样就会完整地构建出一个基本服务了。

参考

  1. https://juejin.im/post/5daf10836fb9a04e054da1b5
  2. https://blog.csdn.net/weixin_38809962/article/details/80354878
  3. https://www.jianshu.com/p/8bca50cb11d8

结语

欢迎关注微信公众号『码仔zonE』,专注于分享Java、云计算相关内容,包括SpringBoot、SpringCloud、微服务、Docker、Kubernetes、Python等领域相关技术干货,期待与您相遇!

微服务实战SpringCloud之Spring Cloud Feign替代HTTP Client的更多相关文章

  1. 【SpringCloud构建微服务系列】使用Spring Cloud Config统一管理服务配置

    一.为什么要统一管理微服务配置 对于传统的单体应用而言,常使用配置文件来管理所有配置,比如SpringBoot的application.yml文件,但是在微服务架构中全部手动修改的话很麻烦而且不易维护 ...

  2. 微服务生态组件之Spring Cloud OpenFeign详解和源码分析

    Spring Cloud OpenFeign 概述 Spring Cloud OpenFeign 官网地址 https://spring.io/projects/spring-cloud-openfe ...

  3. 微服务生态组件之Spring Cloud LoadBalancer详解和源码分析

    Spring Cloud LoadBalancer 概述 Spring Cloud LoadBalancer目前Spring官方是放在spring-cloud-commons里,Spring Clou ...

  4. 微服务架构 | *2.3 Spring Cloud 启动及加载配置文件源码分析(以 Nacos 为例)

    目录 前言 1. Spring Cloud 什么时候加载配置文件 2. 准备 Environment 配置环境 2.1 配置 Environment 环境 SpringApplication.prep ...

  5. Spring Cloud 微服务四:熔断器Spring cloud hystrix

    前言:在微服务架构中,一般都是进程间通信,有可能调用链都比较长,当有底层某服务出现问题时,比如宕机,会导致调用方的服务失败,这样就会发生一连串的反映,造成系统资源被阻塞,最终可能造成雪崩.在sprin ...

  6. 微服务组件--注册中心Spring Cloud Eureka分析

    Eureka核心功能点 [1]服务注册(register):Eureka Client会通过发送REST请求的方式向Eureka Server注册自己的服务,提供自身的元数据,比如ip地址.端口.运行 ...

  7. 微服务架构下使用Spring Cloud Zuul作为网关将多个微服务整合到一个Swagger服务上

    注意: 如果你正在研究微服务,那必然少不了服务之间的相互调用,哪么服务之间的接口以及api就必须生成系统的管理文档了.如果你希望更好的管理你的API,你希望有一个工具能一站式地解决API相关的所有事情 ...

  8. SpringCloud微服务实战二:Spring Cloud Ribbon 负载均衡 + Spring Cloud Feign 声明式调用

    1.Spring Cloud Ribbon的作用 Ribbon是Netflix开发的一个负载均衡组件,它在服务体系中起着重要作用,Pivotal将其整合成为Spring Cloud Ribbon,与其 ...

  9. 笔记:Spring Cloud Feign 声明式服务调用

    在实际开发中,对于服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以我们通常会针对各个微服务自行封装一些客户端类来包装这些依赖服务的调用,Spring Cloud Feign 在此基础上做了进 ...

随机推荐

  1. Python高手是怎样炼成的!

    很多想从事python行业的朋友都会问到,零基础如何自学成为Python高手?根据小北多年教育的经验,我总结了几个小建议,想看干货的请看下文! 如何克服入门难问题? 其实小北觉得,最好的方法就是和一群 ...

  2. Android Studio--家庭记账本(一)

    今天通过观看视频,根据老师所讲内容,编译代码.实现了Android Studio记账本里面的增加功能 源代码如下: CostBean.java: package com.example.family; ...

  3. 牛!Python 也能实现图像姿态识别溺水行为了!

    作者 | 李秋键 责编 | Carol 封图 | CSDN 下载自视觉中国 众所周知随着人工智能智能的发展,人工智能的落地项目也在变得越来越多,尤其是计算机视觉方面. 很多人学习python,不知道从 ...

  4. 90行代码让微信地球转起来,太酷了!(python实现)

    1.微信地球 手机重启后打开微信的一瞬间,会看到一幅有名的图片,上面站着一个 张小龙 . 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手. ...

  5. eclipse中启动tomcat出现错误的解决方法

    前段时间跟着老师做课设,各方面调试都没有问题.近段时间想起来,看看之前写过的代码,翻着翻着就发现启动tomcat出现了错误 错误如下: 错误原因:tomcat路径配置有问题,之前可能配置好了然后由于种 ...

  6. C#LeetCode刷题-拓扑排序

    拓扑排序篇 # 题名 刷题 通过率 难度 207 课程表   40.0% 中等 210 课程表 II   39.8% 中等 329 矩阵中的最长递增路径   31.0% 困难 ​​​​​​​

  7. C#LeetCode刷题之#104-二叉树的最大深度​​​​​​​(Maximum Depth of Binary Tree)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4072 访问. 给定一个二叉树,找出其最大深度. 二叉树的深度为根 ...

  8. Vercel托管博客

    背景 本站已托管至Vercel,但作为一名前端小白,昨天帮朋友托管博客到Vercel的过程中到处碰壁,就想写一篇博客记录一下. 注册登录 点击Vercel官网{% btn 'https://verce ...

  9. 单元测试新方法:用setUp方法 @Before注释

    public class CentralizedPUDMatchServicePacTest { PacMatchService pacMatchService; @Before public voi ...

  10. golang 递归自己,输出自己的源代码

    问题: [2min 大家自己想想] 一个程序P运行后能否输出自己的源代码?并且格式保持一致(换行.空格等) 思考: 这个问题的本质是一个递归问题,设有P运行后生成G 既P->G &&am ...