@LoadBalanced注解原理

1.概述

使用注解就像是在代码中加入了一段魔法,让我们轻而易举的就实现了至关重要的功能。

就像@LoadBalanced一样,将该注解使用在RestTemplate的Bean上,就可以实现负载均衡,就像下面这段代码:

@Configuration
public class CustomConfiguration {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

RestTemplate是Spring提供的一个简单的Rest模板客户端,用来进行API的调用。在不需要负载均衡的时候,可能我们的代码会是这个样子。

public class LoadBalanceTest {

    public void test1() {
        RestTemplate restTemplate = new RestTemplate();
        String result = restTemplate.getForObject("https://www.baidu.com", String.class);
        System.out.println(result);
    }
    
    public void test2() {
        RestTemplate restTemplate = new RestTemplate();
        String result = restTemplate.getForObject("http://localhost:8080/test", String.class);
        System.out.println(result);
    }
}

我们只是添加了一个注解,就实现了负载均衡的功能,得益于Spring最底层实现逻辑的封装,才让我们在开发中使用起来如此的得心应手。此时我们的代码就会变成这个样子,使用http://provider/test,不会直接使用http://ip:port/test的形式,而是使用服务名称,根据服务名称去注册中心获取实际的服务信息,最终再转换为要请求的地址。

@RestController
public class LoadBalanceController {

    @Autowired
    private RestTemplate restTemplate;

    public void test() {
        // provider代表的是服务提供者的spring.application.name的值
        String result = restTemplate.getForObject("http://provider/test", String.class);
        System.out.println(result);
    }
}

2.@LoadBalanced注解分析

下面我们来一步一步揭开@LoadBalanced的庐山真面目。

/**
 * Annotation to mark a RestTemplate or WebClient bean to be configured to use a
 * LoadBalancerClient.
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {

}

其注释中写到,该注解是用来标记要使用LoadBalancerClientRestTemplateWebClient的bean,从而实现负载均衡。

在上述源码中@Qualifier还历历在目,我们知道@Qualifier经常与@Autowired注解成对出现,原因是@Autowired是按bean的类型来装配的,如果SpringIOC容器中,存在两个类型相同的bean时,将无法进行自动装配,这个时候可以结合@Qualifier根据bean的名称来进行装配,到这里就可以明白,@Qualifier起着限定的作用。

因此,@LoadBalanced继承了该特性,这里先埋下一个猜测,SpringCloud的底层封装也会用到该注解,用于对特定的RestTemplate进行处理。

前面注释中提到了LoadBalancerClient,我们先来看看这个接口。

public interface LoadBalancerClient extends ServiceInstanceChooser {

    /**
	 * 根据指定服务名称来执行请求
	 */
    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

    /**
	 * 根据指定服务名称以及指定的服务实例来执行请求
	 */
    <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

    /**
	 * 获得真实的URI,将http://provider/test转换为http://localhost:8080/test
	 */
    URI reconstructURI(ServiceInstance instance, URI original);

}

继续追踪,我们找到了LoadBalancerAutoConfiguration,这就是LoadBalancer的自动配置类。以下为截取的部分源码:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
public class LoadBalancerAutoConfiguration {

    // 果不其然,@LoadBalanced又出现了,这里的目的是为了装配添加了@LoadBalanced注解的RestTemplate的bean
   	@LoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();

    @Autowired(required = false)
    private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
        final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
        return () -> restTemplateCustomizers.ifAvailable(customizers -> {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        });
    }

    @Bean
    @ConditionalOnMissingBean
    public LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) {
        return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
    }

    @Configuration(proxyBeanMethods = false)
    @Conditional(RetryMissingOrDisabledCondition.class)
    static class LoadBalancerInterceptorConfig {

        @Bean
        public LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
                                                               LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
            return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }

    }
}

果不其然,@LoadBalanced出现在了SpringCloud的底层代码中,主要目的是起一个限定的作用,这里只会装配添加了@LoadBalanced的RestTemplate。

到这里,标注有@LoadBalanced注解的RestTemplate的bean已经装配进来,毫无疑问,接下来就是对RestTemplate的请求进行拦截。自然需要为每个RestTemplate设置拦截器。

3.为RestTemplate设置拦截器

那么为什么可以为RestTemplate设置拦截器呢?我们来看看RestTemplate的类图。

从图中可以看出InterceptingHttpAccessor是一个和拦截器有关系类,我们来一探究竟。

是的,在InterceptingHttpAccessor中为我们存储了拦截器列表。

我们在回到LoadBalancerAutoConfiguration中,看看自动配置的LoadBalancerInterceptor

1、创建拦截器

@Configuration(proxyBeanMethods = false)
@Conditional(RetryMissingOrDisabledCondition.class)
static class LoadBalancerInterceptorConfig {

    // 创建负载均衡拦截器
    @Bean
    public LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
                                                           LoadBalancerRequestFactory requestFactory) {
        return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    }

}

2、创建RestTemplateCustomizer

@Configuration(proxyBeanMethods = false)
@Conditional(RetryMissingOrDisabledCondition.class)
static class LoadBalancerInterceptorConfig {
    
	// 创建RestTemplateCustomizer
    @Bean
    @ConditionalOnMissingBean
    public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
        // 利用lambda表达式来创建RestTemplateCustomizer
        return restTemplate -> {
            List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
            list.add(loadBalancerInterceptor);
            restTemplate.setInterceptors(list);
        };
    }

}

RestTemplateCustomizer是一个函数式接口。

public interface RestTemplateCustomizer {

	void customize(RestTemplate restTemplate);

}

3、设置拦截器

获取到所有的RestTemplateCustomizer,并调用其customize()方法,这时会回调步骤2的定义的逻辑。

@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
    final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
    return () -> restTemplateCustomizers.ifAvailable(customizers -> {
        for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
            for (RestTemplateCustomizer customizer : customizers) {
                customizer.customize(restTemplate);
            }
        }
    });
}

我们来看这段回调的具体逻辑,指向了customize()方法。

这里用到了SmartInitializingSingleton,不得不提到bean的初始化过程。

先来看看SmartInitializingSingleton

public interface SmartInitializingSingleton {

	void afterSingletonsInstantiated();

}

再看bean的初始化过程,最后最终源码可得知其调用过程:

refresh()-->finishBeanFactoryInitialization()-->preInstantiateSingletons()

最终完成了对SmartInitializingSingletonafterSingletonsInstantiated()的调用,从而成功为RestTemplate设置了LoadBalancerInterceptor

到目前为止,我们理清楚了对RestTemplate设置拦截器的原理,接下来就是分析如何对RestTemplate进行的请求进行拦截的。

4.RestTemplate调用过程

这里以RestTemplate的get请求为例进行分析:

public void test() {
    // provider代表的是服务提供者的spring.application.name的值
    String result = restTemplate.getForObject("http://provider/test", String.class);
    System.out.println(result);
}

调用流程图

4.1 创建InterceptingClientHttpRequest

1)RestTemplate.doExecute()

获取InterceptingClientHttpRequest

2)调用InterceptingHttpAccessor.getRequestFactory(),获取InterceptingClientHttpRequestFactory

3)调用InterceptingClientHttpRequestFactory.createRequest(),获取InterceptingClientHttpRequest

4.2 执行拦截

1)调用InterceptingClientHttpRequest.executeInternal()

2)将InterceptingClientHttpRequest封装为ServiceRequestWrapper,然后使用LoadBalancerClient执行请求

这里在执行LoadBalancerClient的execute方法时,首先要构造一个LoadBalancerRequest,跟进创建方式时,发现使用lambda表达式进行构建,在调用其方法时,就会进行回调。

public interface LoadBalancerRequest<T> {

	T apply(ServiceInstance instance) throws Exception;

}

构造一个LoadBalancerRequest

4.3 发送请求

1)获取ServiceInstance的真实host、port

当执行完所有的拦截器方法后,就来到了获取真实请求地址的host、port环节

前面我们知道InterceptingClientHttpRequest被封装到了ServiceRequestWrapper,因此来到了ServiceRequestWrapper.getURI(),实际上使用的LoadBalancerClient.reconstructURI(),前面我们提到过,这个方法就是获取真实URI。

public class ServiceRequestWrapper extends HttpRequestWrapper {

    private final ServiceInstance instance;

    private final LoadBalancerClient loadBalancer;

    public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance, LoadBalancerClient loadBalancer) {
        super(request);
        this.instance = instance;
        this.loadBalancer = loadBalancer;
    }

    @Override
    public URI getURI() {
        // 
        URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
        return uri;
    }

}

5.小结

1、在创建RestTemplate的bean时添加@LoadBalanced注解

2、LoadBalancerAutoConfiguration自动配置筛选出添加@LoadBalanced注解的RestTemplate

3、为RestTemplate设置LoadBalancerInterceptor,目的是使用LoadBalancerClient来发起请求

4、请求过程中,根据负载均衡策略,调用LoadBalancerClient.choose()方法获取最终ServiceInstance

5、根据ServiceInstance获取真实host、port,发起最后请求

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

新手UI设计师必读:火爆海外设计圈的设计资源!

Hello&#xff0c;各位好&#xff01; 作为一名新手UI设计师&#xff0c;你是否无法完全搞清楚某些UI设计的基本原则和概念&#xff1f;你是否为使用哪款设计软件来开启你的设计之路而困扰&#xff1f;你是否想要在线学习设计排版、色彩搭配、形状和线条设计&#xff1f; 今天这…

rabbitMQ的详细介绍

1.概述 RabbitMQ是一个消息中间件:它接受并转发消息。你可以把它当做一个快递站点&#xff0c;当你要发送一个包裹时&#xff0c;你把你的包裹放到快递站&#xff0c;快递员最终会把你的快递送到收件人那里&#xff0c;按照这种逻辑RabbitMQ是一个快递站&#xff0c;一个快递员…

实验记录项目

1. 关于语谱图的通道的均值与方差 2023年 03月 27日 星期一 10:10:22 CST 使用tqwt 对每一份音频进行分解: 每份音频得到三个分量, cA分量: 近似分量,用于表征低频部分; cD分量: 高频分量,  res 剩余分量: 1.1 问题: 对每个分量使用如下变换,  使用Mel,  Shar…

SQL面试必会50题

引用&#xff1a; 视频讲解&#xff1a;https://www.bilibili.com/video/BV1q4411G7Lw/ SQL面试必会50题&#xff1a; https://zhuanlan.zhihu.com/p/43289968 图解SQL面试题&#xff1a;经典50题&#xff1a;https://zhuanlan.zhihu.com/p/38354000 其中重点为&#xff1a;1/2…

ChatGLM本地部署应用的实战方案

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

系统分析师每日练习错题知识点2

嵌入式系统---多核cpu 多核是多微处理器核的简称&#xff0c;是将两个或更多的独立处理器封装在一起&#xff0c;集成在一个电路中。多核处理器是单枚芯片&#xff08;也称为硅核&#xff09;&#xff0c;能够直接插入单一的处理器插槽中&#xff0c;但操作系统会利用所有相关…

Java Web中的ServletContext对象

目录 ServletContext对象 获取上下文初始化参数的相关方法 创建ServletContext对象 1&#xff09;通过 GenericServlet 提供的 getServletContext() 方法 2&#xff09;通过 ServletConfig 提供的 getServletContext() 方法 3&#xff09;通过 HttpSession 提供的 getServletCo…

Apache iotdb-web-workbench 认证绕过漏洞 CVE-2023-24829

漏洞简介影响版本 0.13.0 < 漏洞版本 < 0.13.3漏洞主要来自于 iotdb-web-workbench IoTDB-Workbench是IoTDB的可视化管理工具&#xff0c;可对IoTDB的数据进行增删改查、权限控制等&#xff0c;简化IoTDB的使用及学习成本。iotdb-web-workbench 中存在不正确的身份验证漏…

微服务架构(二)

Sentinel 使用及概念 什么是 Sentinel Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综合性解决方案。它以流量 为切入点, 从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。Sentinel 具有以下特征: 丰富的应用场景&#xff1a;Sentin…

基于springboot实现医院信息管理系统【源码+论文】

基于springboot实现医院信管系统演示开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xf…

2023年全国DAMA-CDGP数据治理专家认证线上班招生简章

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

Vue3——v-md-editor(markDown编辑器)使用教程

Vue3——v-md-editor安装使用教程 安装 # 使用 npm npm i kangc/v-md-editor -SEditorMarkdown.vue页面用来封装此编辑器组件 Test.vue作为接受EditorMarkdown.vue的父组件&#xff0c;当测试页使用 路由部分要放入test.vue main.js部分全局引入组件 import EditorMarkdown f…

操作系统权限维持(十一)之Linux系统-SSH Wrapper后门

系列文章 操作系统权限维持&#xff08;一&#xff09;之Windows系统-粘贴键后门 操作系统权限维持&#xff08;二&#xff09;之Windows系统-克隆账号维持后门 操作系统权限维持&#xff08;三&#xff09;之Windows系统-启动项维持后门 操作系统权限维持&#xff08;四&…

算法强化每日一题--字符串中找出连续最长的数字串

hi,大家好,今天为大家带来一道题目 OR59 字符串中找出连续最长的数字串 描述 读入一个字符串str&#xff0c;输出字符串str中的连续最长的数字串 输入描述&#xff1a; 个测试输入包含1个测试用例&#xff0c;一个字符串str&#xff0c;长度不超过255。 输出描述&#xff1a; 在…

北邮22信通:(8)实验1 题目五:大整数加减法(搬运官方代码)

北邮22信通一枚~ 跟随课程进度每周更新数据结构与算法的代码和文章 持续关注作者 解锁更多邮苑信通专属代码~ 上一篇文章&#xff1a; 北邮22信通&#xff1a;&#xff08;7&#xff09;实验1 题目四&#xff1a;一元多项式&#xff08;节省内存版&#xff09;_青山如…

【人人都能读标准】17. 底层算法:ECMAScript的错误处理机制

本文为《人人都能读标准》—— ECMAScript篇的第17篇。我在这个仓库中系统地介绍了标准的阅读规则以及使用方式&#xff0c;并深入剖析了标准对JavaScript核心原理的描述。 我们在11.程序完整执行过程说过&#xff0c;一个程序的运行会经历三个阶段&#xff1a;初始化Realm环境…

MyBatis-面试题

文章目录1.什么是MyBatis?2.#{}和${}的区别是什么&#xff1f;3.MyBatis的一级、二级缓存4.MyBatis的优缺点5.当实体类中的属性名和表中的字段名不一样 &#xff0c;怎么办 &#xff1f;6.模糊查询like语句该怎么写?7.Mybatis是如何进行分页的&#xff1f;分页插件的原理是什…

渗透测试之冰蝎实战

渗透测试之冰蝎实战1.基本使用2.命令执行&虚拟终端3.文件管理4.反弹shell5.内网资产扫描6.内网穿透7.数据库管理“冰蝎”是一款动态二进制加密网站管理客户端 下载地址 1.基本使用 运行冰蝎&#xff0c;打开传输协议&#xff1a; 生成一个php远程马&#xff1a; 点击生成…

【测试基础】之07 linux基础

Linux操作系统Linux操作系统介绍操作系统&#xff1a;管理计算机硬件与软件 资源的计算机程序&#xff0c;同时也是计算机系统的内核与基石。简单地说&#xff0c;操作系统就是出于用户与计算机系统硬件之间用于传递信息的系统程序软件。例如&#xff1a;操作系统会在接收到用户…

金三银四,你准备好面试了吗? (附30w字软件测试面试题总结)

不知不觉&#xff0c;已是3月下旬。最近有很多小伙伴都在跟我谈论春招面试的问题&#xff0c;其实对于面试&#xff0c;我也没有太多的经验&#xff0c;只能默默地把之前整理的软件测试面试题分享给Ta。今天就来大致的梳理一下软件测试的面试体系&#xff08;每一部分最后都有相…
最新文章