openfeign解释及其应用

news2024/11/24 13:22:43

文章目录

  • 前言
  • 一、@FeignClient详解
    • 注解使用范围
    • 注解属性说明
      • `value()`
      • `name()`
      • `contextId()`
      • `qualifiers()`
      • `configuration`
      • `fallback`
      • `fallbackFactory`
      • `path`
  • 二、openfeign走网关gateway


前言

本文讨论的是springcloud分布式微服务架构下,如何让openfeign请求也走gateway网关

本文是建立在笔者的这篇文章的基础之上:SpringCloud微服务搭建实战

需要自己把项目从gitee上克隆下来,并结合SpringCloud微服务搭建实战博客阅读后,才能顺利阅读本文。


一、@FeignClient详解

注解使用范围

@FeignClient这个注解一般推荐用在接口上,这一点可以从它的源码上看出

在这里插入图片描述

@Target规定了这个注解的使用范围,这个Element.Type,点进去可以看到注释

在这里插入图片描述
意思就是可以应用于类、接口(包括注解接口)、枚举或记录(record)声明。这意味着 @FeignClient 不仅可以应用于接口,也可以应用于类、枚举或记录。

但是实际当中我们只能用在接口上来实现远程http请求调用,如下

在这里插入图片描述

那为什么只推荐用在接口上呢?原因有以下两点

  1. 动态代理机制:Feign 使用 Java 动态代理来实现接口的实例化。如果将 @FeignClient 应用于接口,Feign 可以轻松地生成代理对象。

  2. 方法定义:接口中的抽象方法可以方便地定义 HTTP 请求,而不需要具体的实现。

并且spring官方对@FeignClient注解的使用有代码上的限制,在类FeignClientRegistrar中有如下代码

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
		Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
		final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
			Set<String> basePackages = getBasePackages(metadata);
			for (String basePackage : basePackages) {
				candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
			}
		}
		else {
			for (Class<?> clazz : clients) {
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}

		for (BeanDefinition candidateComponent : candidateComponents) {
			if (candidateComponent instanceof AnnotatedBeanDefinition beanDefinition) {
				// verify annotated class is an interface
				AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
				Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");

				Map<String, Object> attributes = annotationMetadata
						.getAnnotationAttributes(FeignClient.class.getCanonicalName());

				String name = getClientName(attributes);
				String className = annotationMetadata.getClassName();
				registerClientConfiguration(registry, name, className, attributes.get("configuration"));

				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}

从代码上可以看出,如果注解的类不是接口,则会抛出异常信息

@FeignClient can only be specified on an interface

注解属性说明

下面对@FeignClient的各个属性做解释说明,源码如下(已去除多余注释)

package org.springframework.cloud.openfeign;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FeignClient {

	@AliasFor("name")
	String value() default "";

	String contextId() default "";

	@AliasFor("value")
	String name() default "";

	String[] qualifiers() default {};

	String url() default "";

	boolean dismiss404() default false;

	Class<?>[] configuration() default {};

	Class<?> fallback() default void.class;

	Class<?> fallbackFactory() default void.class;

	String path() default "";

	boolean primary() default true;

}

value()

  • 服务名称,带有可选的协议前缀。它是 name() 的同义词。
  • 必须为所有客户端指定名称,无论是否提供了 URL。
  • 可以通过属性键来指定,例如 ${propertyKey}。

value = “consumer-module”,这个就是接口所要调用的微服务模块的应用名称,如下图

在这里插入图片描述

当然这里是写死的(日常开发中都是固定值,毕竟这是某一微服务模块的统一对外的接口),如果应用名称是灵活可配置的,可以这么写 value = "${service.name}"

在这里插入图片描述
属性配置在bootstrap文件中,这个配置也可以挪到nacos中去

在这里插入图片描述

name()

  • 服务 ID,带有可选的协议前缀。它是 value() 的同义词。

name和value属性互为同义词,即这两个只使用其中一个时,随便用哪一个都可以 ,value = “consumer-module” 等价于 name= “consumer-module”,若两个都使用,则优先以value值为准。name的其他如动态属性用法和value一致。

contextId()

  • 确保每个FeignClient接口有一个唯一的标识,特别是在多个接口调用同一个服务提供者的情况下,避免混淆和错误‌。

这很好理解,标识FeignClient接口的唯一性。笔者这里是producer-module通过@FeignClient注解的接口调用consumer-module模块的controller某个接口,那如果有另一个模块也通过另一个@FeignClient注解的接口调用consumer-module的相同接口,就必须给每个@FeignClient注解标注的接口加上contextId属性了,一般就是接口的名字首字母小写。

qualifiers()

  • 在多环境或多客户端配置中进一步区分不同的 Feign 客户端实例。通过指定不同的 qualifiers,可以实现更灵活的配置管理和环境隔离。

这个用的很少基本淘汰不用了,如果生产环境只有一个,没有必要引入更多的代码。当然如果不同的生产环境想调用同的接口,可以尝试下。

configuration

  • 定义了Feign客户端的自定义配置类。这些类可以包含对构成客户端的部分组件的覆盖定义,

比如我定义一个CustomClientConfig

package com.microservice.api.consumer.config;

import feign.Client;
import feign.okhttp.OkHttpClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CustomClientConfig {

    @Bean
    public Client feignClient() {
        // 使用 OkHttpClient 作为 HTTP 客户端
        return new OkHttpClient();
    }
}

consumer-module-api的pom文件引入如下依赖

<!-- Feign 的 OkHttpClient 支持 -->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
        </dependency>

在接口上就可以这么写 configuration = {CustomClientConfig.class},这样openfeign在请求时就会使用okhttp了,在Nacos的共享配置share-config-dev.yml

在这里插入图片描述
加入如下内容

#service-base-url
service:
  gateway-base-url: http://localhost:8080
  producer-base-url: http://localhost:8081
  consumer-base-url: http://localhost:8082

要使用OkhttpClient,必须显示写上url = “ s e r v i c e . c o n s u m e r − b a s e − u r l " 。至于 p a t h ,如果你的请求直接调用服务提供者,则不需要加上。如果想让内部服务间的请求也从 g a t e w a y 走,那么 u r l = " {service.consumer-base-url}"。至于path,如果你的请求直接调用服务提供者,则不需要加上。如果想让内部服务间的请求也从gateway走,那么url = " service.consumerbaseurl"。至于path,如果你的请求直接调用服务提供者,则不需要加上。如果想让内部服务间的请求也从gateway走,那么url="{service.gateway-base-url}”,同时放开path=“consumer”。

在这里插入图片描述

path = “consumer”,这样produce-module通过openfeign调用consumer-module请求地址会变成http://localhost:8080/consumer/message/commit,根据gateway的路由匹配规则,会找到consumer服务进行调用
在这里插入图片描述

fallback

  • 指定当Feign客户端接口出现故障时的回退类。回退类必须实现由@FeignClient注解标注的接口,并且必须是一个有效的Spring Bean。
  • 当你需要一个简单的静态回退逻辑时,使用 fallback。
  • 适用于大多数简单场景。

这个没什么好说的就是异常回调类,且必须加上注解@Component保证注册到Spring容器

在这里插入图片描述

RemoteConsumeFallback类如下

package com.microservice.api.consumer.fallback;

import com.microservice.api.consumer.RemoteConsumerService;
import com.microservice.core.domain.AjaxResult;
import org.springframework.stereotype.Component;

/**
 * 当不使用FallbackFactory时,使用此Fallback
 */
@Component
public class RemoteConsumeFallback implements RemoteConsumerService {
    @Override
    public AjaxResult getConsumeMessage() {
        return AjaxResult.error("AjaxResult获取消费信息失败");
    }

    @Override
    public AjaxResult consumeMessagePost() {
        return AjaxResult.error("AjaxResult提交消费者信息失败");
    }
}

fallbackFactory

  • 定义了指定Feign客户端接口的回退工厂。回退工厂必须生成实现了该接口的回退类实例,并且也必须是一个有效的Spring Bean。

日常开发中我们可能直接就使用fallbackFactory不使用fallback,其实它的正确合理用法如下,首先定义一个feign的客户端ConsumerModuleClient

package com.microservice.api.consumer.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.http.ResponseEntity;

@FeignClient(
    name = "consumer-module",
    fallbackFactory = ConsumerModuleFallbackFactory.class
)
public interface ConsumerModuleClient {

    @PostMapping("/message/commit")
    ResponseEntity<String> commitMessage(@RequestBody String message);
}

然后定义一个回退类工厂ConsumerModuleFallbackFactory,其中可根据远程调用抛出的异常类型分别返回不同的fallback回退类

ConsumerModuleFallbackFactory代码如下

package com.microservice.api.consumer.fallback;

import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;

import javax.servlet.http.HttpServletResponse;

@Component
public class ConsumerModuleFallbackFactory implements FallbackFactory<ConsumerModuleClient> {

    @Override
    public ConsumerModuleClient create(Throwable cause) {
        if (cause instanceof HttpClientErrorException) {
            return new HttpClientErrorFallback((HttpClientErrorException) cause);
        } else if (cause instanceof HttpServerErrorException) {
            return new HttpServerErrorFallback((HttpServerErrorException) cause);
        } else {
            return new DefaultFallback(cause);
        }
    }
}

HttpClientErrorFallback的代码如下

package com.microservice.api.consumer.fallback;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;

@Component
public class HttpClientErrorFallback implements ConsumerModuleClient {

    private final HttpClientErrorException cause;

    public HttpClientErrorFallback(HttpClientErrorException cause) {
        this.cause = cause;
    }

    @Override
    public ResponseEntity<String> commitMessage(String message) {
        return ResponseEntity.status(cause.getStatusCode())
                .body("Client error: " + cause.getMessage());
    }
}

HttpServerErrorFallback代码如下

package com.microservice.api.consumer.fallback;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpServerErrorException;

@Component
public class HttpServerErrorFallback implements ConsumerModuleClient {

    private final HttpServerErrorException cause;

    public HttpServerErrorFallback(HttpServerErrorException cause) {
        this.cause = cause;
    }

    @Override
    public ResponseEntity<String> commitMessage(String message) {
        return ResponseEntity.status(cause.getStatusCode())
                .body("Server error: " + cause.getMessage());
    }
}

fallbackFactory回退工厂是用来根据异常的不同返回不同的回退类fallback的
我们一般是怎么用的呢?直接这么用的,并没有根据异常类型的不同区分使用不同的fallback回退类,而且内部回退类还是直接匿名内部类实现的new RemoteConsumerService(),搞笑吧?大家都这么写,我们也这么写,却不知道其是否合理。

在这里插入图片描述

path

  • 指定应用于所有方法级别映射的路径前缀。

这个很简单,就是请求路径的前缀,请求从gateway走,就加上前缀path = "consumer"让路由匹配,请求直接调用服务,则去除前缀。上下两个不同的@FeignClient注解说明了这一点

//@FeignClient(contextId = "remoteConsumerService",
//        value = "consumer-module",
//        url = "${service.gateway-base-url}",
//        configuration = {CustomClientConfig.class},
//        path = "consumer",
//        fallback = RemoteConsumeFallback.class)

@FeignClient(contextId = "remoteConsumerService",
 value = "consumer-module",
 url = "${service.consumer-base-url}",
 configuration = {CustomClientConfig.class},
 fallbackFactory = RemoteConsumeFallbackFactory.class)
public interface RemoteConsumerService {

    @GetMapping(value = "/message/handle")
    AjaxResult getConsumeMessage();

    @PostMapping(value = "/message/commit")
    AjaxResult consumeMessagePost();

}


二、openfeign走网关gateway

openfeign默认请求是不走网关gateway的,都是内部服务间的直接调用那如何使其通过网关呢?下面通过示例说明

首先在gateway-module下创建一个请求过滤器RequestLoggingFilter

package com.microservice.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class RequestLoggingFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求信息
        ServerHttpRequest request = exchange.getRequest();
        // 打印请求信息
        System.out.println("Request Path: " + request.getURI());
        // 将请求继续传递给下一个过滤器
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        // 设置较低的order值以确保此过滤器在路由匹配之前执行
        return Ordered.HIGHEST_PRECEDENCE + 1;
    }
}

注意:getOrder方法是提升次过滤器的优先级,展示路由的完整路径,避免展示被gateway网关去除前缀后的路径

直接consumer调用方式如下
在这里插入图片描述
使用这种方式请求

在这里插入图片描述
查看gateway的控制台日志,可以看到只有一次,请求是从gateway进入路由匹配到produce-module再直接通过feign调用consumer-module

在这里插入图片描述
使用如下配置时

@FeignClient(contextId = "remoteConsumerService",
        value = "consumer-module",
        url = "${service.gateway-base-url}",
        path = "consumer",
        fallback = RemoteConsumeFallback.class)

再次请求会发现gateway打印了两次日志,原因就是openfeign的请求又被转到了gateway网关

在这里插入图片描述

以上就是笔者对openfeign注解的理解和应用,希望能对大家有所帮助

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2205581.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浏览器服务端文件下载控制(安全阻止、文件浏览器打开还是下载行为控制)

文章目录 简介Chrome已阻止不安全内容下载PDF直接打开txt、xml、js文件被自动打开了而不是下载阿里OSS设置response header阿里OSS修改metadata 简介 随着浏览器的发展&#xff0c;有很多安全方面的限制&#xff0c;对我们的文件下载行为产生了很大的影响。 在JavaScript下载…

鸿蒙OS投票机制

(基于openharmony5.0) 投票机制 param get | grep ohos.boot.time 图 投票机制参数图 只有当所有的投票完成&#xff0c;开机动画才会退出&#xff0c;整理需要投票的系统应用&#xff08;三方应用不参与投票&#xff09;如下图所示&#xff1a; 以进程foundation为例&…

pytorh学习笔记——波士顿房价预测

机器学习的“hello world”&#xff1a;波士顿房价预测 波士顿房价预测的背景不用提了&#xff0c;简单了解一下数据集的结构。 波士顿房价的数据集&#xff0c;共有506组数据&#xff0c;每组数据共14项&#xff0c;前13项是影响房价的各种因素&#xff0c;比如&…

深入探究d3d9.dll文件:从d3d9.dll文件丢失的原因到解决方案

在使用电脑的过程中&#xff0c;你是否突然遇到过这样令人头疼的情况&#xff1a;当你试图打开某个游戏或者特定的图形软件时&#xff0c;屏幕上弹出一个恼人的错误提示框&#xff0c;上面赫然写着“d3d9.dll文件丢失”。这个看似小小的文件缺失&#xff0c;却可能像一道无法逾…

如何把视频变成自己的原创?提升视频原创度的7个技巧

在短视频平台发布作品时&#xff0c;时常因为原创问题&#xff0c;而被限流。如何在海量视频中脱颖而出&#xff0c;让自己的作品具有独特性和原创性&#xff0c;是每位创作者都需要思考的问题。本文将详细介绍如何通过一系列前期准备和后期处理技巧&#xff0c;将视频素材转化…

Windows11 24H2 纯净专业工作站版:无捆绑,安心使用!

Windows11系统工作站版集成Windows11系统专业版的所有安全优势和其他丰富特性&#xff0c;全面满足用户的使用需求。今天&#xff0c;系统之家小编给大家带来2024最新的Windows11 24H2纯净专业工作站版系统下载&#xff0c;这个系统版本不包含捆绑流氓软件&#xff0c;系统环境…

洛谷 P7076 [CSP-S2020] 动物园(位运算)

题目传送门 解题思路 可以先开一个 把所有动物的编号或起来。 然后对于每一个 和 &#xff0c;我们可以判断一下这个饲料是否需要购买&#xff08;对于原来有的动物&#xff09;。 如果它原本是不需要购买的&#xff0c;我们可以开一个 来标记这一位。 最后&#xff0c;…

内网渗透-隧道代理转发

文章目录 前言环境搭建工具清单工具使用Frp命令执行实验 Lcx命令执行实验 reGeorg命令执行实验Proxifier ew(EarthWorm)正向代理命令执行实验 反向代理命令执行实验SocksCap netsh命令执行 pingtunnel命令执行实验 ngrok命令执行&&实验 cs命令执行实验 前言 本文章介绍…

如何安装NOI(全国青少年信息学奥林匹克竞赛) Linux环境:详细安装指南

在全国青少年信息学奥林匹克竞赛&#xff08;NOI&#xff09;中&#xff0c;NOI Linux环境是比赛指定的操作系统。该环境基于Ubuntu&#xff0c;专门为编程竞赛设计和优化&#xff0c;包含了C编程和算法竞赛所需的工具和编译器。为了让学生在比赛中熟悉这一环境&#xff0c;了解…

深圳有哪些神仙公司?

前文写了一篇杭州有哪些神仙公司&#xff1f;有读者看完之后&#xff0c;建议出一篇深圳的神仙公司名单&#xff0c;这就安排了。 之前在深圳待过一段时间&#xff0c;整体印象很好&#xff0c;气候宜人&#xff0c;是一座充满活力、创新和机遇的城市。 坐标深圳的小伙伴&…

【C++】线程库常用接口

1.创建线程&#xff0c;等待线程&#xff0c;获取线程id 2.全局变量&#xff0c;局部变量&#xff0c;互斥锁 要让不同的线程访问同一个变量和同一把锁&#xff0c;有两种方法&#xff1a; 2.1方法一 定义全局的变量和全局的锁&#xff0c;这样自然就能访问到。 但全局变量在…

物联网护士站!RFID与传感技术如何提升病患管理智能化?

随着物联网技术的迅速发展&#xff0c;智慧医疗的理念逐渐被广泛应用于医院管理和医疗服务中&#xff0c;成为现代医疗的重要组成部分。通过物联网技术的引入&#xff0c;医院不仅能够实现对患者的智能化医疗&#xff0c;还能高效管理设备和物资&#xff0c;从而推动智慧医疗的…

[C++]使用onnxruntime部署yolov8-cls图像分类onnx模型

如果只需要opencv去部署yolov8分类模型可以参考博文&#xff1a;https://blog.csdn.net/FL1623863129/article/details/142734780 本文和 opencv去部署yolov8分类模型区别是&#xff1a;opencv部署推理核心使用opencv自带api&#xff0c;而本文推理核心用的onnxruntime&#x…

ros2:从github上下载源码进行编译

首先&#xff0c;创建工作空间 # 1. 递归创建工作空间目录 mkdir -p catkin_ws/src # 2. 进入src目录 cd catkin_ws/src然后如果你没有安装git&#xff0c;需要 sudo apt install git然后输入。 git clone https://github.com/6-robot/wpr_simulation.git这时候&#xff0c;…

LeetCode-12. 整数转罗马数字【哈希表 数学 字符串】

LeetCode-12. 整数转罗马数字【哈希表 数学 字符串】 题目描述&#xff1a;解题思路一&#xff1a;贪心解题思路二&#xff1a;背诵版&#xff0c;只需写出1954开头的数字进行贪心即可。解题思路三&#xff1a;暴力匹配 题目描述&#xff1a; 七个不同的符号代表罗马数字&…

拿下奇怪的前端报错:1比特丢失导致的音视频播放时长无限增长-浅析http分片传输核心和一个坑点

问题背景 在一个使用MongoDB GridFS实现文件存储和分片读取的项目中&#xff0c;同事遇到了一个令人困惑的问题&#xff1a;音频文件总是丢失最后几秒&#xff0c;视频文件也出现类似情况。更奇怪的是&#xff0c;播放器显示的总时长为无限大。这个问题困扰了团队成员几天&…

gradle build --offline idea怎么配置 打包命令使用gradle build --offline进行打包怎么操作

两种方式 1&#xff1a;一种命令行执行gradle build --offline 2&#xff1a;一种直接gradle这里单击离线

多模态理论——什么是多模态?多模态的关键技术?

多模态理论 文章目录 多模态理论1.什么是多模态&#xff08;multimodal&#xff09;2.深度学习中的多模态3.多模态学习的关键技术3.1模态表示3.2多模态对齐3.3多模态融合 4.多模态任务 1.什么是多模态&#xff08;multimodal&#xff09; 模态指的是数据或者信息的表现形式&am…

完美收官丨飞易通端到端整体解决方案助力嵌入式技术发展

2024年10月10日&#xff0c;Embedded World 2024展会在美国德克萨斯州奥斯汀完美收官。Embedded World 2024是嵌入式系统领域的全球性盛会&#xff0c;汇聚了来自世界各地的专业人士&#xff0c;促进了嵌入式系统、工业自动化、汽车技术和通信系统领域的合作与发展。 应展会举办…

深入解析 Go 语言中的结构体:从基础用法到高级技巧的全方位指南

结构体&#xff08;Struct&#xff09;是 Go 语言中的一种重要数据类型&#xff0c;能够帮助我们将多个数据组合成一个自定义的类型。与其他编程语言的类&#xff08;Class&#xff09;类似&#xff0c;结构体允许我们定义字段、方法&#xff0c;并灵活操作数据。本文将从基础到…