「Feign」OpenFeign入门以及远程调用
csdh11 2025-05-09 17:49 2 浏览
一、OpenFeign介绍
OpenFeign是一种声明式,模版化的HTTP客户端。使用OpenFeign进行远程调用时,开发者完全感知不到这是在进行远程调用,而是像在调用本地方法一样。使用方式是注解+接口形式,把需要调用的远程接口封装到接口当中,映射地址为远程接口的地址。在启动SpringCloud应用时,Feign会扫描标有@FeignClient注解的接口,生成代理并且注册到Spring容器当中。生成代理时Feign会为每个接口方法创建一个RequestTemplate对象,该对象封装HTTP请求需要的全部信息,请求参数名、请求方法等信息都是在这个过程中确定的,模版化就体现在这里。
二、OpenFeign的使用
- 搭建前置环境,在pom.xml文件中引入依赖,可以选择使用注册中心或者配置中心
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2020.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 配置中心依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<!-- 注册中心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- 健康检查,将服务注册到consul需要 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- openfeign,在需要远程调用的服务中引入 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
1.使用注册中心
- 使用注册中心,将服务注册到consul(nacos),调用者拿到被调用服务的地址端口进行调用
spring.cloud.consul.host=192.168.0.124 #consul地址
spring.cloud.consul.port=8080 #端口号
spring.cloud.consul.discovery.service-name=service-test-01 #服务名称
spring.cloud.consul.discovery.health-check-interval=1m #健康检查间隔时间
server.port=10000 #服务端口号
- 在配置类上开启服务发现以及允许远程调用
@EnableDiscoveryClient //开启服务发现
@EnableFeignClients //开启服务调用,只需要在调用方开启即可
- 服务运行之后可以在consul的UI界面看到运行的服务,consul会定时检查服务的健康状态
- 创建远程调用接口
@FeignClient("serviceName")
public interface Service2Remote {
/** 这里有自定义解码器对远程调用的结果进行解析,拿到真正的返回类型,所以接口返回值类型和远程接口返回类型保持一致 **/
@PostMapping("/page")
List<QuestionResp> pageQuestion(PageQuestionReq req);
}
- 简单使用
@RestController
@RequestMapping("/service/remote")
public class RemoteController {
@Autowired
private Service2Remote service2Remote;
@PostMapping("/getQuestionList")
public List<QuestionResp> getQuestionList(@RequestBody PageQuestionReq req){
List<QuestionResp> result = service2Remote.pageQuestion(req);
//对拿到的数据进行处理...
return result;
}
}
2.使用配置中心
- 将请求的URL写在配置中心进行读取修改配置文件
spring.cloud.consul.config.format=KEY_VALUE #consul支持yaml格式和Key-value形式
spring.cloud.consul.config.enabled=true #开启配置
spring.cloud.consul.config.prefixes=glab/plat/wt/application/test #consul配置存放的外层文件夹目录
spring.cloud.consul.config.default-context=config #父级文件夹
spring.cloud.consul.config.watch.delay=1000 #轮询时间
spring.cloud.consul.discovery.enabled=false #关闭注册
remote.url=www.baidu.com #请求地址
- 创建远程调用接口
@FeignClient(name = "service2RemoteByUrl",url = "${remote.url}") //name需要配置,URL从配置中心读取
public interface Service2RemoteByUrl {
@PostMapping("/page")
List<QuestionResp> pageQuestion(PageQuestionReq req);
}
3.自定义解码器(编码器)
//自定义解码器实现Decoder接口,重写decode方法即可,根据具体需求进行编写
//如果是自定义编码器,需要实现Encoder接口,重写encode方法
public class FeignDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException,DecodeException, FeignException {
if (response.body() == null){
throw new DecodeException(ErrorEnum.EXECUTE_ERR.getErrno(),"没有获取到有效结果值",response.request());
}
// 拿到值
String result = Util.toString(response.body().asReader(Util.UTF_8));
Map<String,Object> resMap = null;
try {
resMap = JSON.parseObject(result, Map.class);
} catch (Exception e) {
//返回结果是字符串
return result;
}
}
4.远程调用携带Cookie
- 由于feign调用是新创建一个Request,因此在请求时不会携带一些原本就有的信息,例如Cookie,因此需要自定义RequestInterceptor对Request进行额外设置,一般情况下,写入Cookie是比较常的做法,如下设置
@Configuration
public class BeanConfig {
@Bean
public RequestInterceptor requestInterceptor(){
return template -> {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//此处可以根据业务而具体定制携带规则
String data = request.getParameter("data");
String code = null;
try {
//这里需要转码,否则会报错
code = URLEncoder.encode(data, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
template.query("data",code);
//请求头中携带Cookie
String cookie = request.getHeader("Cookie");
template.header("Cookie",cookie);
};
}
@Bean
public Decoder decoder(){
return new FeignDecoder();
}
}
三、调用流程解析
//在使用EnableFeignClients开启feign功能时,点击进入会看到该注解是通过ImportFeignClientsRegistrar类生效的,其中有个方法
//registerBeanDefinitions执行两条语句
registerDefaultConfiguration(metadata, registry); //加载默认配置信息
registerFeignClients(metadata, registry); //注册扫描标有FeignClient的接口
//关注registerFeignClients方法
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); //在basePackage路径下扫描并添加标有FeignClient的接口
}
for (BeanDefinition candidateComponent : candidateComponents) { //遍历
if (candidateComponent instanceof AnnotatedBeanDefinition) {
registerClientConfiguration(registry, name, attributes.get("configuration")); //
registerFeignClient(registry, annotationMetadata, attributes); //注册到Spring容器当中,方法详细在FeignClientsRegistrar类当中
}
}
//在对feign调用时进行断点调试
//在生成Feign远程接口的代理类时,调用处理器是Feign提供的FeignInvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
//equals,hashCode,toString三个方法直接本地执行
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
//执行方法对应的方法处理器MethodHandler,这个接口是Feign提供的,与InvocationHandler无任何关系,只有一个invoke方法
return dispatch.get(method).invoke(args);
}
//点进上面的invoke方法
public Object invoke(Object[] argv) throws Throwable {
//创建一个request模版
RequestTemplate template = buildTemplateFromArgs.create(argv);
while (true) {
try {
return executeAndDecode(template, options); //创建request执行并且解码
}
}
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template); //创建Request并增强
Response response = client.execute(request, options); //执行调用请求,不再继续分析了
response = response.toBuilder().request(request).requestTemplate(template).build();
//如果有重写解码器,使用自定义的解码器,feign默认使用SpringEncoder
if (decoder != null)
return decoder.decode(response, metadata.returnType());
}
Request targetRequest(RequestTemplate template) {
//如果自定义了RequestInterceptor,在这里可以对Request进行增强
for (RequestInterceptor interceptor : requestInterceptors) {
//执行自定义的apply方法
interceptor.apply(template);
}
//创建Request
return target.apply(template);
}
四、补充
- 关于Client接口的实现类,使用注册中心和使用配置中心其流程稍有区别
//使用配置中心拿url方式进行调用,使用的是Client的默认内部实现类 Default ,其中Default使用的是HttpURLConnection进行Http请求的
HttpURLConnection connection = convertAndSend(request, options);
//如果使用的是服务发现,使用的使用Client的实现类FeignBlockingLoadBalancerClient,它会去根据配置的服务名去注册中心查找服务的IP地址和端口号,执行使用的仍然是默认实现类Default,通过HttpURLConnection请求
//FeignBlockingLoadBalancerClient,根据服务名称查找服务IP地址、端口 88行
ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
//具体实现方法,BlockingLoadBalancerClient类中 145行
Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
//还有其他实现Client接口的客户端,例如ApacheHttpClient,ApacheHttpClient带有连接池功能,具有优秀的HTTP连接复用能力,需要通过引入依赖来使用
相关推荐
- OKHttp原理解析(okhttp的作用)
-
Okhttp应该是Android目前非常流行的第三方网络库,尝试讲解他的使用以及原理分析,分成几个部分:...
- 快速教会你优雅的解决TCP客户端端口耗尽的问题
-
Hello,我是Henry,相信各位开发老爷在使用大并发网络连接的时候,可能都遇到过TCP客户端端口耗尽的问题,这是一个常见问题,以下是系统性的解决方案及技术细节:1.理解端口限制的本质...
- 这3个接口基础知识,产品经理需要知道
-
产品经理在工作中,避免不了要阅读接口文档,希望本文能够帮助大家更好的了解接口。接口,即客户端(浏览器)向服务器提交请求,服务器向客户端返回响应。本质就是数据的传输与接收。本文主要介绍接口相关的基础知识...
- Java 11新特性对开发者的影响:让编程更高效、更自由
-
Java11新特性对开发者的影响:让编程更高效、更自由在这个瞬息万变的编程世界里,每一代Java的更新都承载着无数开发者对性能优化、生产力提升以及代码美感追求的期望。作为继Java8之后的首个长期...
- 干货-okHttp的优点-收藏了(okhttp的好处)
-
OkHttp相较于其它的实现有以下的优点.支持SPDY,允许连接同一主机的所有请求分享一个socket。如果SPDY不可用,会使用连接池减少请求延迟。使用GZIP压缩下载内容,且压缩操作对用...
- 如何在 Java 项目中集成 DeepSeek
-
一、使用官方SDK基础集成1.添加依赖(Maven)<dependency><groupId>com.deepseek</groupId>...
- spring cloud gateway 性能优化思路
-
SpringCloudGateway是一个高性能的API网关,但在实际的生产环境中,可能会遇到一些性能瓶颈。以下是一些SpringCloudGateway的性能优化方面:调整线程池...
- 你对Android中的okHttp的使用真的了解吗
-
框架下载地址:https://github.com/square/okhttp今天给大家讲解下网络框架okhttp的使用,这个框架非常强大,很多框架都用它来加载网络资源,目前很多开发者还在用As...
- 京东大佬问我,Nginx并发连接如何设置?详细说明
-
京东大佬问我,Nginx并发连接如何设置?详细说明首先,我需要回忆一下Nginx的并发模型。Nginx是基于事件驱动的异步架构,所以它的并发处理能力和配置参数有很大关系。主要的参数应该包括worker...
- 如何实现一个连接池?一文带你深入浅出,彻底搞懂
-
-前言-【2w1h】是技术领域中一种非常有效的思考和学习方式,即What、Why和How;坚持【2w1h】,可以快速提升我们的深度思考能力。...
- Golang 网络编程(golang 系统编程)
-
TCP网络编程存在的问题:拆包:对发送端来说应用程序写入的数据远大于socket缓冲区大小,不能一次性将这些数据发送到server端就会出现拆包的情况。通过网络传输的数据包最大是1500字节,当TCP...
- Spring6|Spring Boot3有哪些HTTP客户端可以选择
-
个人博客:无奈何杨(wnhyang)个人语雀:wnhyang...
- 10. 常用标准库(标准库有哪些)
-
本章深入解析Go语言核心标准库的关键功能与生产级应用技巧,结合性能优化与安全实践,提供高效开发指南。10.1fmt/io/os10.1.1fmt高级格式化...
- Nginx之连接池(nginx 长连接 连接复用)
-
我们知道Nginx利用连接池来增加它对资源的利用率。下面我们一起来看看Nginx是如何使用连接池的。从上一节模块开始已经慢慢会接触一些Nginx的源码部分来。每个worker进程都有一个独立的ngx...
- 开发者必备的Android开发资源之OkHttp
-
小编在这里给各位Android开发者介绍的资源包括工具、库和网站等。有效地利用它们,将有助于减轻我们的工作量,提高我们的工作效率。为什么需要一个HTTP库Android系统提供了两种HTTP通信类,H...
- 一周热门
- 最近发表
- 标签列表
-
- mydisktest_v298 (34)
- document.appendchild (35)
- 头像打包下载 (61)
- acmecadconverter_8.52绿色版 (39)
- word文档批量处理大师破解版 (36)
- server2016安装密钥 (33)
- mysql 昨天的日期 (37)
- parsevideo (33)
- 个人网站源码 (37)
- centos7.4下载 (33)
- mysql 查询今天的数据 (34)
- intouch2014r2sp1永久授权 (36)
- 先锋影音源资2019 (35)
- jdk1.8.0_191下载 (33)
- axure9注册码 (33)
- pts/1 (33)
- spire.pdf 破解版 (35)
- shiro jwt (35)
- sklearn中文手册pdf (35)
- itextsharp使用手册 (33)
- 凯立德2012夏季版懒人包 (34)
- 冒险岛代码查询器 (34)
- 128*128png图片 (34)
- jdk1.8.0_131下载 (34)
- dos 删除目录下所有子目录及文件 (36)