SpringCloud微服务服务间调用之OpenFeign介绍
开发微服务,免不了需要服务间调用。Spring Cloud框架提供了RestTemplate和FeignClient两个方式完成服务间调用,本文简要介绍如何使用OpenFeign完成服务间调用。
OpenFeign思维导图
在此奉上我整理的OpenFeign相关的知识点思维导图。
基础配置使用例子
(1)服务端:
@RestController
@RequestMapping("hello")
public class HelloController implements HelloApi {
@Override
public String hello(String name) {
return "Hello, "+name+"!";
}
}
API声明:
public interface HelloApi {
@GetMapping("/hello/{name}")
String hello(@PathVariable("name") String name);
@GetMapping("/bye/{name}")
ResponseValue<String> bye(@PathVariable("name") String name);
@GetMapping(value = "/download")
byte[] download(HttpServletResponse response);
}
(2)客户端:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
开启配置 @EnableFeignClients
,调用服务的代码:
@FeignClient(name="hello1", url = "127.0.0.1:8080", path = "hello")
public interface HelloApiExp extends HelloApi {
@GetMapping("/download")
Response download();
}
调用时的代码:
@RestController
@RequestMapping("client")
public class HelloClient {
@Autowired
private HelloApiExp helloApi;
@GetMapping("/hello/{name}")
public String hello(@PathVariable("name") String name){
return helloApi.hello(name);
}
}
浏览器访问URL:http://127.0.0.1:8080/client/hello/Mark,页面返回: Hello, Mark!
@FeignClient的简单用法
属性名称 | 属性说明 | 默认值 |
---|---|---|
name/value | 作为serviceId,bean name | |
contextId | 作为bean name,代替name/value的值 | |
qualifier | 限定词 | |
url | http的URL前缀(不包括协议名):主机名和端口号 | |
decode404 | 请求遇到404则抛出FeignExceptions | false |
path | 服务前缀,等同于ContextPath | |
primary | whether to mark the feign proxy as a primary bean | true |
高级配置——使用configuration配置类
通过自定义配置类统一配置Feign的各种功能属性,FeignClientsConfiguration为默认配置:
@FeignClient(name="hello1", url = "127.0.0.1:8080", configuration = FeignClientsConfiguration.class)
public interface HelloApi {
@GetMapping("/{name}")
String hello(@PathVariable("name") String name);
}
Decoder feignDecoder
Decoder类,将http返回的Entity字符解码(反序列化)为我们需要的实例,如自定义的POJO对象。一般使用FeignClientsConfiguration默认的feignDecoder就能满足返回String、POJO等绝大多数场景。
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(
new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
Encoder feignEncoder
Encode类对请求参数做编码(序列化)后,发送给http服务端。使用spring cloud默认的feignEncoder可以满足我们绝大多数情况。
使用Feign实现文件上传下载时需要特殊处理,使用feign-form
能够方便的实现。这里我们对feign-form
在spring cloud中的使用举一个简单的例子。
HelloApi接口声明:
public interface HelloApi {
@GetMapping(value = "/download")
byte[] download(HttpServletResponse response);
@PostMapping(value = "upload",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseValue<String> upload(@RequestBody MultipartFile file);
}
服务端代码:
@RestController
@RequestMapping("hello")
public class HelloController implements HelloApi {
@Override
public byte[] download(HttpServletResponse response) {
FileInputStream fis = null;
try{
File file = new File("E:\\图片\\6f7cc39284868762caaed525.jpg");
fis = new FileInputStream(file);
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment;filename=class.jpg");
return IOUtils.toByteArray(fis, file.length());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
@Override
public ResponseValue<String> upload(@RequestBody MultipartFile file) {
File destFile = new File("d:\\1.jpg");
ResponseValue<String> response = new ResponseValue<>();
try {
file.transferTo(destFile);
return response.ok("上传成功!", null);
} catch (IOException e) {
e.printStackTrace();
return response.fail("上传失败,错误原因:"+e.getMessage());
}
}
}
客户端代码:
pom.xml引入依赖:
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>3.8.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<version>3.8.0</version>
</dependency>
增加FeignClient配置类:
@Configuration
public class FeignMultipartSupportConfig extends FeignClientsConfiguration {
@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder();
}
}
FeignClient接口声明:
import feign.Response;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(name="hello1", url = "127.0.0.1:8080", path = "hello",
configuration = FeignMultipartSupportConfig.class)
public interface HelloApiExp extends HelloApi {
@GetMapping("/download")
Response download();
}
调用端代码:
@RestController
@RequestMapping("client")
public class HelloClient {
@GetMapping(value = "/download")
public byte[] download(HttpServletResponse response){
response.setHeader("Content-Disposition",
"attachment;filename=class.jpg");
//response.setHeader("Content-Type","application/octet-stream");
Response resp = helloApi.download();
Response.Body body = resp.body();
try(InputStream is = body.asInputStream()) {
return IOUtils.toByteArray(is, resp.body().length());
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
@PostMapping(value = "upload",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseValue<String> upload(@RequestBody MultipartFile file){
return helloApi.upload(file);
}
}
Retryer feignRetryer
请求重试策略类,默认不重试,可配置成feign.Retryer.Default,启用重试,默认间隔100毫秒重试一次,最大间隔时间限制为1秒,最大重试次数5次。
@Configuration
public class FeignRetryConfig extends FeignClientsConfiguration {
@Bean
@Override
public Retryer feignRetryer() {
return new Retryer.Default();
}
}
Feign.Builder feignBuilder
FeignClient的Builder,我们可以通过他使用代码的方式设置相关属性,代替@FeignClient的注解过的接口,如下面的代码:
@GetMapping("/hello/{name}")
public String hello(@PathVariable("name") String name){
String response = feignBuilder
.client(new OkHttpClient())
.encoder(new SpringFormEncoder())
.requestInterceptor(new ForwardedForInterceptor())
.logger(new Slf4jLogger())
.logLevel(Logger.Level.FULL)
.target(String.class, "http://127.0.0.1:8080");
return response;
//return helloApi.hello(name);
}
其实@FeignClient生成的代理类也是通过它构建的。代码中的feignBuilder.client()可以使用RibbonClient,就集成了Ribben。
FeignLoggerFactory feignLoggerFactory
设置LoggerFactory类,默认为Slf4j。
Feign.Builder feignHystrixBuilder
配置Hystrix,从下面的配置类可以看出,@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
,如果引用了Hystrix的相关依赖,并且属性feign.hystrix.enabled
为true,则构建@FeignClient代理类时使用的FeignBuilder会使用feignHystrixBuilder。Feign通过这种方式集成了Hystrix。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
OpenFeign+consul使用示例
背景介绍
本示例使用consul作为服务注册中心,基于SpringCloud框架开发两个微服务,一个user-service(服务提供方),一个feignusercommodity-service(服务调用方),具体版本信息如下
软件/框架 | 版本 |
---|---|
consul | v1.2.0 |
Spring Boot | 2.0.1.RELEASE |
Spring Cloud | Finchley.RELEASE |
openFeign使用默认版本的,也就是spring-cloud-starter-openfeign 2.0.0版本。
主要代码
核心代码主要包括两点,
1, 对应接口添加@FeignClient,并完成对应服务提供者的requestMapping映射。
2,在启动类加上@EnableFeignClients(basePackages = {"com.yq.client"}), 我的serviceClieng位于com.yq.client包。
提供方的主要接口如下:
ServiceClient类的主要实现如下.
注意:User 类在两个服务中是一样,实际项目中我们可以把它放到公共依赖包中。
@FeignClient(value = "user-service", fallbackFactory = UserServiceFallbackFactory.class)
public interface UserServiceClient {
@RequestMapping(value="/v1/users/{userId}", method= RequestMethod.GET, produces = "application/json;charset=UTF-8")
public User getUser(@PathVariable(value = "userId") String userId);
@RequestMapping(value="/v1/users/queryById", method= RequestMethod.GET, produces = "application/json;charset=UTF-8")
public User getUserByQueryParam(@RequestParam("userId") String userId);
@RequestMapping(value="/v1/users", method= RequestMethod.POST, produces = "application/json;charset=UTF-8")
public String createUser();
}
完整代码看 user-servcie, feignusercommodity-service,里面的pom文件,serviceClient都是完整的可以运行的。 欢迎加星,fork。
效果截图
第一张截图,两个服务都正常在consul上注册,完成服务间调用
第二张截图,两个服务都正常在consul上注册,完成服务间调用, 这是consul down了,服务间调用可以继续,因为feignusercommodity-service服务缓存了user-service服务的服务提供地址信息
第三张截图,feignusercommodity-service服务正常在consul上注册,但是user-service没有注册,系统给出了“com.netflix.client.ClientException: Load balancer does not have available server for client: user-service”
第四张截图,user-service提供方的对应方法报异常,服务调用能正常获取到该异常并显示。
故障转移
使用Feign可以完成服务间调用,但是总存在一种情况:服务提供方没有注册到注册中心、服务提供方还没开发完成(因为也就无法调用)等等。此时如果我们需要完成服务间调用该如何做呢?
Feign
提供了fallback
机制,也就是当对方服务还没ready(一般情况是服务提供方在注册中心上没有可用的实例),可以返回信息供服务进行下,也就是服务降级。
故障转移机制,如果@FeignClient
指定了fallback
或fallbackFactory
属性,http请求调用失败时会路由到fallback处理类的相同方法中。
fallback
@FeignClient声明:
@FeignClient(name="hello1", url = "127.0.0.1:8080", path = "hello",
configuration = FeignMultipartSupportConfig.class,
fallback = HelloApiFallback.class)
public interface HelloApiExp extends HelloApi {
@GetMapping("/download")
Response download();
}
HelloApiFallback代码需要实现HelloApiExp接口(包括父接口)的所有方法:
@Slf4j
public class HelloApiFallback implements HelloApiExp {
@Override
public Response download() {
log.error("下载文件出错。");
return null;
}
@Override
public String hello(String name) {
log.error("调用hello接口出错。");
return "调用hello接口出错,请联系管理员。";
}
@Override
public ResponseValue<String> bye(String name) {
log.error("调用bye接口出错。");
ResponseValue<String> response = new ResponseValue<>();
return response.fail("调用hello接口出错,请联系管理员。");
}
@Override
public byte[] download(HttpServletResponse response) {
log.error("调用bye接口出错。");
return new byte[0];
}
@Override
public ResponseValue<String> upload(MultipartFile file) {
log.error("调用上传文件接口出错。");
ResponseValue<String> response = new ResponseValue<>();
return response.fail("上传文件出错,请联系管理员。");
}
}
fallbackFactory
为@FeignClient接口所有方法指定统一的故障处理方法。
@FeignClient(name="hello1", url = "127.0.0.1:8080", path = "hello",
configuration = FeignMultipartSupportConfig.class,
fallbackFactory = FallbackFactory.Default.class)
public interface HelloApiExp extends HelloApi {
@GetMapping("/download")
Response download();
}
FallbackFactory.Default实现如下,请求失败后,统一路由到create(Throwable cause)方法。
/** Returns a constant fallback after logging the cause to FINE level. */
final class Default<T> implements FallbackFactory<T> {
// jul to not add a dependency
final Logger logger;
final T constant;
public Default(T constant) {
this(constant, Logger.getLogger(Default.class.getName()));
}
Default(T constant, Logger logger) {
this.constant = checkNotNull(constant, "fallback");
this.logger = checkNotNull(logger, "logger");
}
@Override
public T create(Throwable cause) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "fallback due to: " + cause.getMessage(), cause);
}
return constant;
}
@Override
public String toString() {
return constant.toString();
}
}
Feign结合Hystrix可以实现服务降级
主要使用consul 1.2.0, Spring Boot 1.5.12, Spring Cloud Edgware.RELEASE。
需要引入Hystrix依赖并在启动类和配置文件中启用Hystrix
pom文件增加如下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
启动类加上@EnableHystrix
,@EnableHystrixDashboard
,@EnableFeignClients
@SpringBootApplication
@EnableHystrix
@EnableHystrixDashboard
@EnableDiscoveryClient
@EnableCircuitBreaker
//@EnableTurbine
@EnableFeignClients(basePackages = {"com.yq.client"})
public class HystrixDemoApplication {
private static final Logger logger = LoggerFactory.getLogger(HystrixDemoApplication.class);
public static void main(String[] args) {
SpringApplication.run(HystrixDemoApplication.class, args);
logger.info("HystrixDemoApplication Start done.");
}
}
配置文件中feign启用hystrix
feign.hystrix.enabled=true
实现自己的fallback服务
feignClient类
@FeignClient(value = "user-service", fallback = UserServiceClientFallbackFactory.class)
@Component
public interface UserServiceClient {
@RequestMapping(value = "/v1/users/{userId}", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
String getUserDetail(@PathVariable("userId") String userId);
}
自定义的fallback类
@Component
@Slf4j
public class UserServiceClientFallbackFactory implements UserServiceClient{
@Override
public String getUserDetail(String userId) {
log.error("Fallback2, userId={}", userId);
return "user-service not available2 when query '" + userId + "'";
}
}
效果截图
第一张截图
虽然我们创建了fallback类,也引入了Hystrix,但是没有启用feign.hystrix.enabled=true,所以无法实现服务降级,服务间调用还是直接报异常。
第二张截图
我们创建了fallback类,也引入了Hystrix,同时启用feign.hystrix.enabled=true,所以当user-service不可用时,顺利实现服务降级。
第三张, user-service服务正常, fallback不影响原有服务间调用正常进行。
参考文档
官方文档在这里: http://cloud.spring.io/spring-cloud-openfeign/single/spring-cloud-openfeign.html
fallback官方文档:http://cloud.spring.io/spring-cloud-openfeign/single/spring-cloud-openfeign.html#spring-cloud-feign-hystrix-fallback
SpringCloud微服务服务间调用之OpenFeign介绍的更多相关文章
- SpringCloud微服务实战——搭建企业级开发框架(十一):集成OpenFeign用于微服务间调用
作为Spring Cloud的子项目之一,Spring Cloud OpenFeign以将OpenFeign集成到Spring Boot应用中的方式,为微服务架构下服务之间的调用提供了解决方案.首先, ...
- SpringCloud实战 | 第五篇:SpringCloud整合OpenFeign实现微服务之间的调用
一. 前言 微服务实战系列是基于开源微服务项目 有来商城youlai-mall 版本升级为背景来开展的,本篇则是讲述SpringCloud整合OpenFeign实现微服务之间的相互调用,有兴趣的朋友可 ...
- 小D课堂 - 新版本微服务springcloud+Docker教程_4-04 高级篇幅之服务间调用之负载均衡策略调整实战
笔记 4.高级篇幅之服务间调用之负载均衡策略调整实战 简介:实战调整默认负载均衡策略实战 自定义负载均衡策略:http://cloud.spring.io/spring-cloud-stati ...
- 小D课堂 - 新版本微服务springcloud+Docker教程_4-01 常用的服务间调用方式讲解
笔记 第四章 服务消费者ribbon和feign实战和注册中心高可用 1.常用的服务间调用方式讲解 简介:讲解常用的服务间的调用方式 RPC: 远程过程调用,像调用本地 ...
- ②SpringCloud 实战:引入Feign组件,完善服务间调用
这是SpringCloud实战系列中第二篇文章,了解前面第一篇文章更有助于更好理解本文内容: ①SpringCloud 实战:引入Eureka组件,完善服务治理 简介 Feign 是一个声明式的 RE ...
- SpringCloud Alibaba实战(8:使用OpenFeign服务调用)
源码地址:https://gitee.com/fighter3/eshop-project.git 持续更新中-- 在上一个章节,我们已经成功地将服务注册到了Nacos注册中心,实现了服务注册和服务发 ...
- 【微服务】之五:轻松搞定SpringCloud微服务-调用远程组件Feign
上一篇文章讲到了负载均衡在Spring Cloud体系中的体现,其实Spring Cloud是提供了多种客户端调用的组件,各个微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使 ...
- SpringCloud微服务之跨服务调用后端接口
SpringCloud微服务系列博客: SpringCloud微服务之快速搭建EurekaServer:https://blog.csdn.net/egg1996911/article/details ...
- SpringCloud初体验:三、Feign 服务间调用(FeignClient)、负载均衡(Ribbon)、容错/降级处理(Hystrix)
FeignOpenFeign Feign是一种声明式.模板化的HTTP客户端. 看了解释过后,可以理解为他是一种 客户端 配置实现的策略,它实现 服务间调用(FeignClient).负载均衡(Rib ...
随机推荐
- SpringCloud 2020.0.4 系列之 Stream 消息广播 与 消息分组 的实现
1. 概述 老话说的好:事情太多,做不过来,就先把事情记在本子上,然后理清思路.排好优先级,一件一件的去完成. 言归正传,今天我们来聊一下 SpringCloud 的 Stream 组件,Spring ...
- MAC电脑如何将常规视频中音频提取出来(转换格式并调整采样频率),并利用讯飞语音识别文字
1.下载好相关视频 2.选中需要提取视频,鼠标右键找到「编码所选视频文件」 3.设置中,下拉选择「仅音频」,点击继续 4.找到已提取成功的音频,鼠标右键或快捷键「command + I」,显示简介.默 ...
- JDK的第三个LTS版本JDK17来了
目录 简介 JDK17中的新特性 语言上的新特性 核心库的优化 支持新的平台 预览特性 其他改动 总结 简介 2021年9月JDK17发布了,JDK17是最新的一个LTS版本.所谓LTS版本就是可以得 ...
- GitHub 12个实用技巧-从projiect项目管理、代码链接到博客wiki全过程
1 在GitHub.com上编辑代码 2 粘贴图片 3 美化代码 4 在PRs中巧妙关闭issues 5 链接到评论 6 链接到代码 7 灵活使用GitHub地址栏 8 创建复选框列表 9 在GitH ...
- Django笔记&教程 总目录
本篇博客只有目录,正文内容在目录章节链接的博客里 除目录本身外,没有链接的章节,说明内容还没开始编辑 本项目笔记仍在不断创作中,还有些内容会根据自身所学不断更新完善 本项目主要为markdwon文档, ...
- python中jsonpath模块,解析多层嵌套的json数据
1. jsonpath介绍用来解析多层嵌套的json数据;JsonPath 是一种信息抽取类库,是从JSON文档中抽取指定信息的工具,提供多种语言实现版本,包括:Javascript, Python, ...
- BootStrap中模态框踩坑
在模态框中使用html标签上的自定义属性来打开模态框后,在使用JS关闭模态框,就会出现多层蒙板问题 出现这个问题的原因就是没有仔细看bootstrap的官方文档,我人麻了,搞了好久 务必将模态框的 H ...
- [loj3330]猜数游戏
如果两个数$a_{x}$和$a_{y}$,$\exists 0<i,a_{x}^{i}\equiv a_{y}(mod\ p^{k})$,就建一条$x$到$y$的有向边,再对这张图强连通分量缩点 ...
- javascript-初级-day08
return <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" ...
- IDEA安装JavaFx
jdk11之后jdk就不内置javafx了,需要自己下载 在idea中新建JavaFx项目: 创建成功后发现代码标红 这个时候要把刚刚下载的JavaFx包解压后添加进去 选择到自己解压的路径的文件 ...