Springcloud 微服务实战笔记 Feign

news2025/5/27 9:29:07

优点

基于Netflix Feign实现,整合了Spring cloud Ribbon 和 Spring cloud Hystrix

提供了声明式的WEB服务客户端定义方式

扩展了Spring MVC的注解支持

使用

1、pom导入包:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
    <version>1.4.4.RELEASE</version>
</dependency>

2、接口增加注解: @FeignClient

@FeignClient(name = "spring-cloud-study-demo")
public interface DemoRemoteClient {
	
	@GetMapping("/demo/getData/{uid}")
	public ApiReturnObject getData(@PathVariable(value="uid") String uid,@RequestParam(value="data") String data);

}

注解@FeignClient的name参数配置为服务名(服务提供方可以自己随便写一个)

 

3、Controller直接调用

@RestController
public class FeignController {
	@Resource
	DemoRemoteClient demoRemoteClient;
	
	@GetMapping("/remote/demo/getData/{uid}")
	public ApiReturnObject  basePath(@PathVariable String uid ,String data){
		return demoRemoteClient.getData(uid, data);
	}
}

4、启动类增加注解 @EnableFeignClients

@EnableEurekaClient
@EnableFeignClients
@SpringBootApplication
public class SpringCloudStudyFeignApplication {
	public static void main(String[] args) {
		SpringApplication.run(SpringCloudStudyFeignApplication.class,args);
		System.out.println("http://127.0.0.1:6666/feign/remote/demo/222");
	}
}

 

Ribbon配置

全局配置

直接使用ribbon.key=value的方式设置即可,比如:

ribbon.ConnectTime=500
ribbon.ReadTimeout=5000

指定服务配置

使用@FeignClient注解中的name或则Value属性值来设置对应的Ribbon参数,比如:

SPRING-CLOUD-STUDY-DEMO.ribbon.ConnectTime=500
SPRING-CLOUD-STUDY-DEMO.ribbon.ReadTimeout=5000
SPRING-CLOUD-STUDY-DEMO.ribbon.OkToRetryOnAllOperations=true

重试机制

Spring Cloud Feign中默认实现了请求的重试机制(重试次数参数可配置)

需注意:Ribbon的超时与Hystrix的超时是两个概念。为了能让Ribbon超时重试生效,需要让Hystrix的超时时间设置大于Ribbon的超时时间,否则Hystrix超时后直接熔断了,也就没有重试了。

Hystrix配置

全局配置

Hystrix全局配置与Ribbon全局配置一样,直接使用默认前缀hystrix.xxx.xxx.xx即可配置,比如设置超时时间:

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
hystrix.command.default.execution.timeout.enabled=false ##来关闭熔断功能。

在 对 Hystrix 进 行 配 置 之 前 , 我 们 需 要 确 认feign.hystrix.enabled参数没有被设置为false,否则该参数设置会关闭Feign客户端的Hystrix支持。

禁用Hystrix

  1. 通过feign.hystrix.enabled参数配置,全局配置,默认为false。
  2. 针对某个服务关闭hystrix,需要通过使用@Scope("prototype")注解为指定的客户端配置Feign.Builder实例,详细实现步骤如下所示。
    // 构建一个关闭Hystrix的配置类
    @Configuration
    public class DisableHystrixConfiguration {
      @Bean
      @Scope("prototype")
      public Feign.Builder feignBuilder(){
      return Feign.builder();
    }
    
    }
    // 在HelloService的@FeignClient注解中,通过configuration参数引入上面实现的配置。
    @FeignClient(name="SPRING-CLOUD-STUDY-DEMO",configuration=DisableHystrixConfiguration.class)
    public interface HelloService {
      ...
    }
    

指定命令配置

采用hystrix.command.(commadKey默认为方法名)为前缀来进行配置

服务降级配置

注解@FeignClient包含参数:org.springframework.cloud.openfeign.FeignClient#fallback

fallback参数为实现FeignClient接口实现类class

// FeignClient注解增加fallback参数,配置为当前接口实现类
@FeignClient(name = "SPRING-CLOUD-STUDY-DEMO", fallback = DemoFallback.class)
public interface DemoFeignClient {

    @GetMapping("/demo/hello")
    String hello();
}

// 实现FeignClient接口,重写降级方法
@Component
public class DemoFallback implements DemoFeignClient{

    @Override
    public String hello() {
        return "fallbcak";
    }
}

请求压缩

Spring Cloud Feign支持对请求与响应进行GZIP压缩,以减少通信过程中的性能损耗。我们只需通过下面两个参数设置,就能开启请求与响应的压缩功能:

feign.compression.request.enabled=true
feign.compression.response.enabled=true

指定压缩类型

feign.compression.request.mime.types=text/xml,application/xml,application/json(默认值)

设置请求压缩的大小下限,只有超过这个大小的请求才会对其进行压缩

feign.compression.request.min-request-size=2048(默认值)

工作原理

1、获取feign相关配置信息

我们使用 Feign 的话,会在 Application 的主启动类上,标记 EnableFeignClient 注解,在要调用的接口上标记 FeignClient 注解。 ​在 EnableFeignClients 注解内部,有一个 @Import(FeignClientsRegistrar.class) ,这个类实现了 ImportBeanDefinitionRegistrar 接口,这个的话是 Spring Context 项目下的,所以会在 Spring Boot 项目启动的时候,会调用 FeignClientsRegistrar.registerBeanDefinitions , 扫描 FeiginClient 注解,并设置相关信息,主要实现在org.springframework.cloud.openfeign.FeignClientsRegistrar#registerDefaultConfiguration:

private void registerDefaultConfiguration(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        Map<String, Object> defaultAttrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

        if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
            String name;
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            }
            else {
                name = "default." + metadata.getClassName();
            }
            registerClientConfiguration(registry, name,
                    defaultAttrs.get("defaultConfiguration"));
        }
    }

2、扫描FeignClient注解接口

主要实现org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClients:

public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);

        Set<String> basePackages;

        Map<String, Object> attrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName());
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
                FeignClient.class);
        final Class<?>[] clients = attrs == null ? null
                : (Class<?>[]) attrs.get("clients");
        if (clients == null || clients.length == 0) {
            scanner.addIncludeFilter(annotationTypeFilter);
            basePackages = getBasePackages(metadata);
        }
        else {
            final Set<String> clientClasses = new HashSet<>();
            basePackages = new HashSet<>();
            for (Class<?> clazz : clients) {
                basePackages.add(ClassUtils.getPackageName(clazz));
                clientClasses.add(clazz.getCanonicalName());
            }
            AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
                @Override
                protected boolean match(ClassMetadata metadata) {
                    String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                    return clientClasses.contains(cleaned);
                }
            };
            scanner.addIncludeFilter(
                    new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
        }

        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidateComponents = scanner
                    .findCandidateComponents(basePackage);
            for (BeanDefinition candidateComponent : candidateComponents) {
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    // verify annotated class is an interface
                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                    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);
                    registerClientConfiguration(registry, name,
                            attributes.get("configuration"));

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

最终会调用org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient方法,主要实现:

private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientFactoryBean.class);
        validate(attributes);
        definition.addPropertyValue("url", getUrl(attributes));
        definition.addPropertyValue("path", getPath(attributes));
        String name = getName(attributes);
        definition.addPropertyValue("name", name);
        String contextId = getContextId(attributes);
        definition.addPropertyValue("contextId", contextId);
        definition.addPropertyValue("type", className);
        definition.addPropertyValue("decode404", attributes.get("decode404"));
        definition.addPropertyValue("fallback", attributes.get("fallback"));
        definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

        String alias = contextId + "FeignClient";
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);

        // has a default, won't be null
        boolean primary = (Boolean) attributes.get("primary");

        beanDefinition.setPrimary(primary);

        String qualifier = getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {
            alias = qualifier;
        }

        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                new String[] { alias });
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }

registerFeignClient中通过BeanDefinitionBuilder构建FeignClientFactoryBean(构建Feign的核心),将通过@FeginClient 注解获取到的信息,都设置到这个 definition 中,如图:

3、FeignClientFactoryBean.getObject()方法 动态代理的实现入口

<T> T getTarget() {
        FeignContext context = applicationContext.getBean(FeignContext.class);
        Feign.Builder builder = feign(context);

        if (!StringUtils.hasText(url)) {
            if (!name.startsWith("http")) {
                url = "http://" + name;
            }
            else {
                url = name;
            }
            url += cleanPath();
            return (T) loadBalance(builder, context,
                    new HardCodedTarget<>(type, name, url));
        }
        if (StringUtils.hasText(url) && !url.startsWith("http")) {
            url = "http://" + url;
        }
        String url = this.url + cleanPath();
        Client client = getOptional(context, Client.class);
        if (client != null) {
            if (client instanceof LoadBalancerFeignClient) {
                // not load balancing because we have a url,
                // but ribbon is on the classpath, so unwrap
                client = ((LoadBalancerFeignClient) client).getDelegate();
            }
            if (client instanceof FeignBlockingLoadBalancerClient) {
                // not load balancing because we have a url,
                // but Spring Cloud LoadBalancer is on the classpath, so unwrap
                client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
            }
            builder.client(client);
        }
        Targeter targeter = get(context, Targeter.class);
        return (T) targeter.target(this, builder, context,
                new HardCodedTarget<>(type, name, url));
    }

方法这一系列执行后,生成动态代理对象,动态代理对象为每个方法都初始化了SynchronousMethodHandler负责处理方法的请求,然后将动态代理对象放入到Spring容器中,当执行Controller执行时候,其实执行的是InvacationHandler的invoke()方法。

在创建SynchronousMethodHandler.Factory时候,发现是与LoadBalancerFeignClient结合

调用动态代理invoke方法时候,根据构造的Map<Method, MethodHandler>找到对应的handler对象,最终调用com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)方法(Ribbon的实现):

 public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
        LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

        try {
            return command.submit(
                new ServerOperation<T>() {
                    @Override
                    public Observable<T> call(Server server) {
                        URI finalUri = reconstructURIWithServer(server, request.getUri());
                        S requestForServer = (S) request.replaceUri(finalUri);
                        try {
                            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                        } 
                        catch (Exception e) {
                            return Observable.error(e);
                        }
                    }
                })
                .toBlocking()
                .single();
        } catch (Exception e) {
            Throwable t = e.getCause();
            if (t instanceof ClientException) {
                throw (ClientException) t;
            } else {
                throw new ClientException(e);
            }
        }
        
    }

到这里其实就是Ribbon的实现了。

参考资料:

《Spring Cloud微服务实战》

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

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

相关文章

系统崩溃无U盘重装Win10系统的方法

用户反映自己电脑上的操作系统出现了崩溃问题&#xff0c;无法通过简单的操作解决问题&#xff0c;想重新安装正常的操作系统&#xff0c;但是没有U盘不知道要怎么操作才能安装好系统&#xff1f;接下来小编带来系统崩溃无U盘重装Win10系统的方法步骤介绍&#xff0c;用户们可以…

Spring Boot 完善订单【五】集成接入支付宝沙箱支付

1.1.什么是沙箱支付 支付宝沙箱支付&#xff08;Alipay Sandbox Payment&#xff09;是支付宝提供的一个模拟支付环境&#xff0c;用于开发和测试支付宝支付功能的开发者工具。在真实的支付宝环境中进行支付开发和测试可能涉及真实资金和真实用户账户&#xff0c;而沙箱环境则提…

7. Mybatis 代码反向生成器(MBG)

一、插件的使用 描述: mybatis generator 的作用是根据数据库自动生成 实体类、Dao接口、Mapper 映射文件。 数据库&#xff1a; create table product( id int primary key AUTO_INCREMENT, pname varchar(20), price double, category_id varchar(32) ); INSERT INTO produ…

B样条曲线

零次 B 样条 F i &#xff0c; 0 ( t ) { 1 t i ≤ t < t i 1 0 o t h e r s \bm{F}_{i&#xff0c;0}(t) \begin{cases} 1 & t_i \leq t <t_{i1} \\ 0 & others \end{cases} Fi&#xff0c;0​(t){10​ti​≤t<ti1​others​ 2. 一次 B 样条&#xff0c;…

node加速镜像源 管理工具nrm安装使用

我们在开发node.js的时候,经常会遇到某些包无法下载, 或者下载太慢, 还有需要加载我们自己是有源中的包的问题, 今天推荐给大家的这款 nrm 镜像源管理工具就是解决这类问题的. 安装 方法也很简单, 执行 npm install nrm -g 就可以安装 # 安装nrm npm install nrm -g# 添加…

LN和BN

假设batch为2&#xff0c;&#xff08;2&#xff0c;3&#xff0c;256&#xff0c;256&#xff09;这样的样本 LN比较直观就是在每个独立的样本上计算均值和方差&#xff0c;然后归一化。&#xff08;2&#xff0c;3&#xff0c;256&#xff0c;256&#xff09; 归一化是将数…

14:00面试,14:06就出来了,问的问题真的变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到5月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

mac远程ssh免密登录

服务器部署经常会登录到远程服务&#xff0c;为方便操作&#xff0c;提高效率对运维人员来说设置免密登录还是很有必要的。其实也是很简单&#xff0c;安以下操作步骤即可。 1、进入到&#xff5e;/.ssh目录下&#xff0c;确认已经生成有公钥与私钥。如果没有请执行发下命令 …

Jetson Xavier NX安装pytorch环境《最全、简洁》

有段时间没有写嵌入式领域相关的文章了&#xff0c;近期也在学习和做嵌入式领域的相关作业&#xff0c;学习了一段时间的Jetson Xavier NX开发套件&#xff0c;总结了Jetson Xavier NX如何安装pytorch环境。文章分类在嵌入式学习&#xff1a; 嵌入式学习 ---《Jetson Xavier NX…

鸿蒙南向开发—OpenHarmony技术编译构建框架

概述 OpenHarmony编译子系统是以GN和Ninja构建为基座&#xff0c;对构建和配置粒度进行部件化抽象、对内建模块进行功能增强、对业务模块进行功能扩展的系统&#xff0c;该系统提供以下基本功能&#xff1a; 以部件为最小粒度拼装产品和独立编译。支持轻量、小型、标准三种系…

功能介绍 | 探秘Goby功能世界:点击URL,即刻畅享快速调起之旅!

​​0x01 前言 ​我们从只会点鼠标的猴子到CtrlC,CtrlV来回切换的工具人&#xff0c;但有时候遇到大量需要复制的url界面&#xff0c;真的希望能有一个可以一键整理、一键扫描URL的功能来拯救&#xff01; 好消息是&#xff0c;Goby从客户端版本2.8.6起&#xff0c;官方引入了…

5G阅信助力互联网行业:XX出行-出票通知,案例分析

XX出行日常有大量业务通知短信下发&#xff0c;用户触达频次和用户打开率都比较高&#xff0c;但原短信无法带来附加营销增值&#xff0c;通过阅信增值服务消息将两者结合起来&#xff0c;可实现业务的多渠道引流&#xff0c;开拓了新的渠道和方式。 项目概述&#xff1a; 1. 项…

均匀与准均匀 B样条算法

B 样条曲线的定义 p ( t ) ∑ i 0 n P i F i , k ( t ) p(t) \sum_{i0}{n} P_i F_{i, k}(t) p(t)i0∑​nPi​Fi,k​(t) 方程中 n 1 n1 n1 个控制点&#xff0c; P i P_i Pi​, i 0 , 1 , ⋯ n i0, 1, \cdots n i0,1,⋯n 要用到 n 1 n1 n1 个 k k k 次 B 样条基函数 …

Unity中URP下统一不同平台下的z值

文章目录 前言一、ComputeFogFactor 来计算雾效混合因子二、UNITY_Z_0_FAR_FROM_CLIPSPACE 来统一计算不同平台下的Z值1、DirectX平台2、GL平台下&#xff08;在Unity.2022.LTS下&#xff0c;该功能没有完善)3、Opengl下 前言 在之前的文章中&#xff0c;我们实现了URP下的雾效…

企业培训系统开发:构建灵活高效的学习平台

企业培训系统的开发在当今数字化时代是至关重要的。本文将介绍一些关键技术和代码示例&#xff0c;以帮助您构建一个灵活、高效的企业培训系统。 1. 技术选型 在开始企业培训系统的开发之前&#xff0c;首先需要选择合适的技术栈。以下是一个基本的技术选型示例&#xff1a;…

【mars3d】FixedRoute的circle没有跟polyline贴着模型的解决方案

问题&#xff1a;【mars3d】官网的贴模型示例中&#xff0c;参考api文档增加了circle的配置&#xff0c;但是FixedRoute的circle没有跟polyline贴着模型 circle: { radius: 10, materialType: mars3d.MaterialType.CircleWave, materialOptions: { color: "#ffff00"…

柱面,盘片,盘面,扇面,磁头,磁道,扇区,CHS地址,LAB地址

柱面&#xff0c;盘片&#xff0c;盘面&#xff0c;扇面&#xff0c;磁头&#xff0c;磁道&#xff0c;扇区&#xff0c;CHS地址&#xff0c;LAB地址1 CHS地址 CHS地址指的是柱面&#xff08;Cylinder&#xff09;、磁头&#xff08;Head&#xff09;、扇区&#xff08;Secto…

新年话节能 电梯也减排

小伍恭祝大家2024年元旦快乐&#xff01;&#xff01; 目前&#xff0c;电梯的节能已经得到业界的广泛重视&#xff0c;积极推动相关的节能技术的实施&#xff0c;努力宣传和倡导规范的电梯的使用行为&#xff0c;将极大地改变我国电梯的耗能状况&#xff0c;为节能减排做出较大…

Linux进程管理和计划任务

前言 上篇关于进程管理命令使用说明尚未完结&#xff0c;本篇将继续介绍相关命令以及计划任务管理。 目录 前言 一、控制进程 1. vmstat 2. free 3. iostat 4. iotop/iftop 5. uptime 6. mpstat 7. dstat 8. webadin 9. 服务器五大性能 二、进程管理 1. 手动…

SemCms外贸网站商城系统 SQL注入漏洞复现(CVE-2023-50563)

0x01 产品简介 SemCms是国内团队打造的专门针对外贸网站的开源CMS,主要用于外贸企业,兼容IE,Firefox等主流浏览器。建设商城性质的外贸网站,多语言(小语种)网站。 0x02 漏洞概述 SemCms外贸网站商城系统SEMCMS_Function.php 中的 AID 参数存在SQL注入漏洞,未经身份认…