㈠ Spring自动装配原理
其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件!
点进去,发现还有一个父依赖
这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;
以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;
springboot-boot-starter-xxx :就是spring-boot的场景启动器
SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 starter;
默认的主启动类
但是一个简单的启动类并不简单!我们来分析一下这些注解都干了什么
@SpringBootApplication
作用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;
进入这个注解:可以看到上面还有很多其他注解!
@ComponentScan
这个注解在Spring中很重要 ,它对应XML配置中的元素。
作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
@SpringBootConfiguration
作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;
我们继续进去这个注解查看
这里的 @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件;
里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!
我们回到 SpringBootApplication 注解中继续看。
@EnableAutoConfiguration :开启自动配置功能
以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;
点进注解接续查看:
@AutoConfigurationPackage :自动配置包
@import :Spring底层注解@import , 给容器中导入一个组件
Registrar.class 作用:将主启动类所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;
这个分析完了,退到上一步,继续看
@Import({.class}) :给容器导入组件 ;
:自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:
1、这个类中有一个这样的方法:
2、这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法
3、我们继续点击查看 loadSpringFactories 方法
4、发现一个多次出现的文件:spring.factories,全局搜索它
spring.factories
我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!
可以看到这些一个个的都是javaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下!
所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。
结论:
不简单的方法
我最初以为就是运行了一个main方法,没想到却开启了一个服务;
SpringApplication.run分析
分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行
springApplication这个类主要做了以下四件事情:
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类
run方法流程分析
㈡ SpringBoot自动装配原理
初看@SpringBootApplication有很多的注解组成,其实归纳就是一个"三体"结构,重要的只有三个Annotation:
(1)@Configuration注解
(2)@ComponentScan
(3)@EnableAutoConfiguration
从源码中可以知道,最关键的要属@Import(.class),借助,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。同时借助于Spring框架原有的一个工具类:SpringFactoriesLoader,@EnableAutoConfiguration就可以实现智能的自动配置。
总结 :@EnableAutoConfiguration作用就是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。这些功能配置类要生效的话,会去classpath中找是否有该类的依赖类(也就是pom.xml必须有对应功能的jar包才行)并且配置类里面注入了默认属性值类,功能类可以引用并赋默认值。生成功能类的原则是自定义优先,没有自定义时才会使用自动装配类。
1、从spring-boot-autoconfigure.jar/META-INF/spring.factories中获取redis的相关配置类全限定名(有120多个的配置类)RedisAutoConfiguration,一般一个功能配置类围绕该功能,负责管理创建多个相关的功能类,比如RedisAutoConfiguration负责:JedisConnectionFactory、RedisTemplate、StringRedisTemplate这3个功能类的创建
2、RedisAutoConfiguration配置类生效的一个条件是在classpath路径下有RedisOperations类存在,因此springboot的自动装配机制会会去classpath下去查找对应的class文件。
3.如果pom.xml有对应的jar包,就能匹配到对应依赖class,
4、匹配成功,这个功能配置类才会生效,同时会注入默认的属性配置类@EnableConfigurationProperties(RedisProperties.class)
5.Redis功能配置里面会根据条件生成最终的JedisConnectionFactory、RedisTemplate,并提供了默认的配置形式@ConditionalOnMissingBean(name = "redisTemplate")
6.最终创建好的默认装配类,会通过功能配置类里面的 @Bean注解,注入到IOC当中
7.用户使用,当用户在配置文件中自定义时候就会覆盖默认的配置@ConditionalOnMissingBean(name = "redisTemplate")
1.通过各种注解实现了类与类之间的依赖关系,容器在启动的时候Application.run,会调用.class的selectImports方法(其实是其父类的方法)-- 这里需要注意,调用这个方法之前发生了什么和是在哪里调用这个方法需要进一步的探讨
2.selectImports方法最终会调用SpringFactoriesLoader.loadFactoryNames方法来获取一个全面的常用BeanConfiguration列表
3.loadFactoryNames方法会读取FACTORIES_RESOURCE_LOCATION(也就是spring-boot-autoconfigure.jar 下面的spring.factories),获取到所有的Spring相关的Bean的全限定名ClassName,大概120多个
4.selectImports方法继续调用filter(configurations, autoConfigurationMetadata);这个时候会根据这些BeanConfiguration里面的条件,来一一筛选,最关键的是
@ConditionalOnClass,这个条件注解会去classpath下查找,jar包里面是否有这个条件依赖类,所以必须有了相应的jar包,才有这些依赖类,才会生成IOC环境需要的一些默认配置Bean
5.最后把符合条件的BeanConfiguration注入默认的EnableConfigurationPropertie类里面的属性值,并且注入到IOC环境当中
㈢ 自动装配解决了什么问题
之前提到的@Componen, @Configurationt这些在单纯的写业务的时候需要用到的注解,不过仅限于在当前工程下面处理自己的业务类。
当自己需要写一个框架的时候,必然会引入很多第三方包,那么怎么把第三方jar包(不是源码)自动加入到IOC容器呢,这就是自动装配需要解决的问题。
@EnableAutoConfiguration 里边通过@Import(.class)的这个方法
public String[]selectImports(AnnotationMetadata annotationMetadata) 去搜素jar包中,META-INF文件夹下的xxx.factories来最终加载Configuration
从而实现了我们之前通过@Confituration加载业务类的行为。
㈣ Spring 源码(九)@Autowired注解实现原理(Spring Bean的自动装配)
@Autowired 注解的实现过程,其实就是Spring Bean的自动装配过程。通过看@Autowired源码注释部分我们可以看到 @Autowired 的实现是通过 后置处理器中实现的。
在分析自动装配前我们先来介绍一下上面的这些后置处理器。
BeanPostProcessor有两个方法, 和 。它们分别在任何bean初始化回调之前或之后执行(例如InitializingBean的afterPropertiesSet方法或自定义init-method方法之前或者之后), 在这个时候该bean的属性值已经填充完成了,并且我们返回的bean实例可能已经是原始实例的包装类型了。例如返回一个 FactoryBean 。
继承自 BeanPostProcessor 接口。主要多提供了以下三个方法。
该方法是在Bean实例化目标对象之前调用,返回的Bean对象可以代理目标,从而有效的阻止了目标Bean的默认实例化。
跟进源码我们可以看出,如果此方法返回一个非null对象,则Bean创建过程将被短路。唯一应用的进一步处理是来自已配置BeanPostProcessors的回调。
该方法执行在通过构造函数或工厂方法在实例化bean之后但在发生Spring属性填充(通过显式属性或自动装配)之前执行操作。这是在Spring的自动装配开始之前对给定的bean实例执行自定义字段注入的理想回调。如果该方法返回false,那么它会阻断后续 后置处理器的执行,并且会阻止后续属性填充的执行逻辑。
在工厂将给定属性值应用于给定bean之前,对它们进行后处理。允许检查是否满足所有依赖关系,例如基于bean属性设置器上的“ Required”注解。还允许替换要应用的属性值,通常是通过基于原始PropertyValues创建新的MutablePropertyValues实例,添加或删除特定值来实现。
智能实例化Bean的后处理器,主要提供了三个方法。
预测从此处理器的回调最终返回的bean的类型。
确定使用实例化Bean的构造函数。
获取提早暴露的Bean的引用,提早暴露的Bean就是只完成了实例化,还未完成属性赋值和初始化的Bean。
合并Bean的定义信息的后处理方法,该方法是在Bean的实例化之后设置值之前调用。
后置处理器主要负责对添加了@Autowired和@Value注解的元素实现自动装配。所以找到需要自动装配的元素,其实就是对@Autowired和@Value注解的解析。
后置处理器,找出需要自动装配的元素是在 . 这个方法中实现的,调用链路如下:
从链路上我们可以看到,找到需要自动装配的元素是在 findAutowiringMetadata 方法中实现的,该方法会去调用 buildAutowiringMetadata 方法构建元数据信息。如果注解被加载属性上将会被封装成 AutowiredFieldElement 对象;如果注解加在方法上,那么元素会被封装成 AutowiredMethodElement 对象。这里两个对象的 inject 方法将最后完成属性值的注入,主要区别就是使用反射注入值的方式不一样。源码如下:
寻找需要自动装配过程:
后置处理器注入属性值是在 postProcessPropertyValues 方法中实现的。源码如下:
从这里的源码我们可以看出 AutowiredFieldElement 和 AutowiredMethodElement 完成自动装配都是先去容器中找对应的Bean,然后通过反射将获取到的Bean设置到目标对象中,来完成Bean的自动装配。
我们可以看出Spring Bean的自动装配过程就是:
在自动装配过程中还涉及循环依赖的问题,有兴趣的可以看下这篇文章 Spring 源码(八)循环依赖
㈤ 2020-07-26 带着疑问看源码 -- springboot aop默认采用什么动态代理机制
了解springboot aop的动态代理方式有哪些种类?
有3种,前提均开启spring.aop.auto=true:
1. jdk动态代理:当spring.aop.proxy-target-class=false, 引入了aspectjweaver依赖时生效
2. cglib代理:当spring.aop.proxy-target-class=true, 引入了aspectjweaver依赖时生效
3. 基础代理:当spring.aop.proxy-target-class=true, 若没有aspectjweaver依赖时生效,只作用于框架内部的advisors,
我们既然用springboot那么就采用springboot的AopAutoConfiguration自动配置类来加载aop机制的,内部对@EnableAspectJAutoProxy进行了封装,扩展了一些配置项,同时还提供了ClassProxyingConfiguration配置(下面会讲到).
这个自动装配类会是spring boot框架自动会装配的,所以说默认aop机制是打开的,可以通过配置项:spring.aop.auto=false 手工关闭。
这个配置类会根据spring.aop.proxy-target-class配置项来决定采用jdk动态代理或者cglib动态代理:
注意:配置类生效前提是@ConditionalOnClass(Advice.class),说明只有当引入了依赖项才生效:
而这里推荐使用spring-boot-starter-aop来传递依赖:
这里我们已经看到提供了原生spring的两种代理方式,接着看AopAutoConfiguration源码发现还有个ClassProxyingConfiguration配置类,其生效条件之一是@ConditionalOnMissingClass("org.aspectj.weaver.Advice"),就是当项目里没有aspectjweaver的依赖的时候生效。
我们进入AopConfigUtils.(registry)方法,通过几步调用跳转:
发现会去注册后置处理器,查看源码注释:
表明了只为基础的advisor做动态代理,而忽略应用定义的Advisors,说明项目中我们自定义的切面是不会被AOP代理的。
㈥ nacos原理
nacos目前是集成到spring cloud alibaba里去的,也就是在spring cloud的标准之下实现了一些东西,spring cloud自己是有一个接口,叫做ServiceRegistry,也就是服务注册中心的概念,nacos中有一个它的实现类NacosServiceRegistry,实现了register、deregister、close、setStatus、getStatus之类的方法。
自动装配是一个spring boot的一个概念,自动装配的意思,其实就是说系统启动的时候,自动装配机制会运行,实现一些系统的初始化,自动运行,也就是系统启动时自动去调用NacosServiceRegistry的register方法去进行服务注册。而且除了注册之外,还会通过schele线程池去提交一个定时调度任务,源码如下:
this.exeutorService.schele(new BeatReactor.BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS),这就是一个心跳机制,定时发送心跳给nacos server。
然后会访问nacos server的open api,其实就是http接口,他有一个接口:http://31.208.59.24:8848/nacos/v1/ns/instance?serviceName=xx&ip=xx&port=xx,这么一个东西,也没什么特别的,这里就是访问注册接口罢了
nacos server那里是基于一个ConcurrentHashMap作为注册表来放服务信息的,直接会构造一个Service放到map里,然后对Service去addInstance添加一个实例,本质里面就是在维护信息,同时还会建立定时检查实例心跳的机制。最后还会基于一致性协议,比如说raft协议,去把注册同步给其他节点。
服务发现的本质其实也是nacos server上的一个http接口,就是:http://31.208.59.24:8848/nacos/v1/ns/instance/list?serviceName=xx,就这么一个接口,然后就会启动定时任务,每隔10s拉取一次最新的实例列表,然后服务端还会监听他服务的状态,有异常就会基于UDP协议反向通知客户端这次服务异常变动。
㈦ 彻底搞明白Spring中的自动装配和Autowired
当Spring装配Bean属性时,有时候非常明确,就是需要将某个Bean的引用装配给指定属性。比如,如果我们的应用上下文中只有一个 org.mybatis.spring.SqlSessionFactoryBean 类型的Bean,那么任意一个依赖 SqlSessionFactoryBean 的其他Bean就是需要这个Bean。毕竟这里只有一个 SqlSessionFactoryBean 的Bean。
为了应对这种明确的装配场景,Spring提供了自动装配(autowiring)。与其显式的装配Bean属性,为何不让Spring识别出可以自动装配的场景。
当涉及到自动装配Bean的依赖关系时,Spring有多种处理方式。因此,Spring提供了4种自动装配策略。
Spring在 AutowireCapableBeanFactory 接口中定义了这几种策略。其中, AUTOWIRE_AUTODETECT 被标记为过时方法,在Spring3.0之后已经不再支持。
它的意思是,把与Bean的属性具有相同名字的其他Bean自动装配到Bean的对应属性中。听起来可能比较拗口,我们来看个例子。
首先,在User的Bean中有个属性 Role myRole ,再创建一个Role的Bean,它的名字如果叫myRole,那么在User中就可以使用byName来自动装配。
上面是Bean的定义,再看配置文件。
如上所述,只要属性名称和Bean的名称可以对应,那么在user的Bean中就可以使用byName来自动装配。那么,如果属性名称对应不上呢?
是的,如果不使用属性名称来对应,你也可以选择使用类型来自动装配。它的意思是,把与Bean的属性具有相同类型的其他Bean自动装配到Bean的对应属性中。
还是上面的例子,如果使用byType,Role Bean的ID都可以省去。
它是说,把与Bean的构造器入参具有相同类型的其他Bean自动装配到Bean构造器的对应入参中。值的注意的是, 具有相同类型的其他Bean 这句话说明它在查找入参的时候,还是通过Bean的类型来确定。
构造器中入参的类型为Role
它首先会尝试使用constructor进行自动装配,如果失败再尝试使用byType。不过,它在Spring3.0之后已经被标记为 @Deprecated 。
默认情况下,default-autowire属性被设置为none,标示所有的Bean都不使用自动装配,除非Bean上配置了autowire属性。
如果你需要为所有的Bean配置相同的autowire属性,有个办法可以简化这一操作。
在根元素Beans上增加属性 default-autowire="byType" 。
Spring自动装配的优点不言而喻。但是事实上,在Spring XML配置文件里的自动装配并不推荐使用,其中笔者认为最大的缺点在于不确定性。或者除非你对整个Spring应用中的所有Bean的情况了如指掌,不然随着Bean的增多和关系复杂度的上升,情况可能会很糟糕。
从Spring2.5开始,开始支持使用注解来自动装配Bean的属性。它允许更细粒度的自动装配,我们可以选择性的标注某一个属性来对其应用自动装配。
Spring支持几种不同的应用于自动装配的注解。
我们今天只重点关注Autowired注解,关于它的解析和注入过程,请参考笔者Spring源码系列的文章。 Spring源码分析(二)bean的实例化和IOC依赖注入
使用@Autowired很简单,在需要注入的属性加入注解即可。
不过,使用它有几个点需要注意。
默认情况下,它具有强制契约特性,其所标注的属性必须是可装配的。如果没有Bean可以装配到Autowired所标注的属性或参数中,那么你会看到 NoSuchBeanDefinitionException 的异常信息。
看到上面的源码,我们可以得到这一信息,Bean集合为空不要紧,关键 isRequired 条件不能成立,那么,如果我们不确定属性是否可以装配,可以这样来使用Autowired。
我记得曾经有个面试题是这样问的:Autowired是按照什么策略来自动装配的呢?
关于这个问题,不能一概而论,你不能简单的说按照类型或者按照名称。但可以确定的一点的是,它默认是按照类型来自动装配的,即byType。
关键点 findAutowireCandidates 这个方法。
可以看到它返回的是一个列表,那么就表明,按照类型匹配可能会查询到多个实例。到底应该装配哪个实例呢?我看有的文章里说,可以加注解以此规避。比如 @qulifier、@Primary 等,实际还有个简单的办法。
比如,按照UserService接口类型来装配它的实现类。UserService接口有多个实现类,分为 UserServiceImpl、UserServiceImpl2 。那么我们在注入的时候,就可以把属性名称定义为Bean实现类的名称。
这样的话,Spring会按照byName来进行装配。首先,如果查到类型的多个实例,Spring已经做了判断。
可以看出,如果查到多个实例, determineAutowireCandidate 方法就是关键。它来确定一个合适的Bean返回。其中一部分就是按照Bean的名称来匹配。
最后我们回到问题上,得到的答案就是:@Autowired默认使用byType来装配属性,如果匹配到类型的多个实例,再通过byName来确定Bean。
上面我们已经看到了,通过byType可能会找到多个实例的Bean。然后再通过byName来确定一个合适的Bean,如果通过名称也确定不了呢?
还是 determineAutowireCandidate 这个方法,它还有两种方式来确定。
它的作用是看Bean上是否包含@Primary注解,如果包含就返回。当然了,你不能把多个Bean都设置为@Primary,不然你会得到 这个异常。
你也可以在Bean上配置@Priority注解,它有个int类型的属性value,可以配置优先级大小。数字越小的,就被优先匹配。同样的,你也不能把多个Bean的优先级配置成相同大小的数值,否则 异常照样出来找你。
最后,有一点需要注意。Priority的包在 javax.annotation.Priority; ,如果想使用它还要引入一个坐标。
本章节重点阐述了Spring中的自动装配的几种策略,又通过源码分析了Autowired注解的使用方式。
在Spring3.0之后,有效的自动装配策略分为 byType、byName、constructor 三种方式。注解Autowired默认使用byType来自动装配,如果存在类型的多个实例就尝试使用byName匹配,如果通过byName也确定不了,可以通过Primary和Priority注解来确定。
㈧ 波场的自动归集有源码嘛
有。
波场的自动归集有源码,源码无加密,带说明。自动归集至冷钱包地址,安全可靠。无需向用户或订单地址注入Trx即可完成Trx/TRC20代币的归集。
㈨ “SpringCloud原理”Ribbon核心组件以及运行原理万字源码剖析
大家好,本文我将继续来剖析SpringCloud中负载均衡组件Ribbon的源码。本来我是打算接着OpenFeign动态代理生成文章直接讲Feign是如何整合Ribbon的,但是文章写了一半发现,如果不把Ribbon好好讲清楚,那么有些Ribbon的细节理解起来就很困难,所以我还是打算单独写一篇文章来剖析Ribbon的源码,这样在讲Feign整合Ribbon的时候,我就不再赘述这些细节了。好了,话不多说,直接进入主题。
这是个很简单的东西,就是服务实例数据的封装,里面封装了服务实例的ip和端口之类的,一个服务有很多台机器,那就有很多个Server对象。
ServerList是个接口,泛型是Server,提供了两个方法,都是获取服务实例列表的,这两个方法其实在很多实现类中实现是一样的,没什么区别。这个接口很重要,因为这个接口就是Ribbon获取服务数据的来源接口,Ribbon进行负载均衡的服务列表就是通过这个接口来的,那么可以想一想是不是只要实现这个接口就可以给Ribbon提供服务数据了?事实的确如此,在SpringCloud中,eureka、nacos等注册中心都实现了这个接口,都将注册中心的服务实例数据提供给Ribbon,供Ribbon来进行负载均衡。
通过名字也可以知道,是用来更新服务注册表的数据,他有唯一的实现,就是PollingServerListUpdater,这个类有一个核心的方法,就是start,我们来看一下start的实现。
通过这段方法我们可以看出,首先通过isActive.compareAndSet(false, true)来保证这个方法只会被调用一下,然后封装了一个Runnable,这个Runnable干了一件核心的事,就是调用传入的updateAction的doUpdate方法,然后将Runnable扔到了带定时调度功能的线程池,经过initialDelayMs(默认1s)时间后,会调用一次,之后都是每隔refreshIntervalMs(默认30s)调用一次Runnable的run方法,也就是调用updateAction的doUpdate方法。
所以这个类的核心作用就是每隔30s会调用一次传入的updateAction的doUpdate方法的实现,记住这个结论。
IRule是负责负载均衡的算法的,也就是真正实现负载均衡获取一个服务实例就是这个接口的实现。比如说实现类RandomRule,就是从一堆服务实例中随机选取一个服务实例。
就是一个配置接口,有个默认的实现DefaultClientConfigImpl,通过这个可以获取到一些配置Ribbon的一些配置。
这个接口的作用,对外主要提供了获取服务实例列表和选择服务实例的功能。虽然对外主要提供获取服务的功能,但是在实现的时候,主要是用来协调上面提到的各个核心组件的,使得他们能够协调工作,从而实现对外提供获取服务实例的功能。
这个接口的实现有好几个实现类,但是我讲两个比较重要的。
BaseLoadBalancer
核心属性
allServerList:缓存了所有的服务实例数据
upServerList:缓存了能够使用的服务实例数据。
rule:负载均衡算法组件,默认是RoundRobinRule
核心方法
setRule:这个方法是设置负载均衡算法的,并将当前这个ILoadBalancer对象设置给IRule,从这可以得出一个结论,IRule进行负载均衡的服务实例列表是通过ILoadBalancer获取的,也就是 IRule 和 ILoadBalancer相互引用。setRule(rule)一般是在构造对象的时候会调用。
chooseServer:就是选择一个服务实例,是委派给IRule的choose方法来实现服务实例的选择。
BaseLoadBalancer这个实现类总体来说,已经实现了ILoadBalancer的功能的,所以这个已经基本满足使用了。
说完BaseLoadBalancer这个实现类,接下来说一下DynamicServerListLoadBalancer实现类。DynamicServerListLoadBalancer继承自BaseLoadBalancer,DynamicServerListLoadBalancer主要是对BaseLoadBalancer功能进行扩展。
DynamicServerListLoadBalancer
成员变量
serverListImpl:上面说过,通过这个接口获取服务列表
filter:起到过滤的作用,一般不care
updateAction:是个匿名内部类,实现了doUpdate方法,会调用updateListOfServers方法
serverListUpdater:上面说到过,默认就是唯一的实现类PollingServerListUpdater,也就是每个30s就会调用传入的updateAction的doUpdate方法。
这不是巧了么,serverListUpdater的start方法需要一个updateAction,刚刚好成员变量有个updateAction的匿名内部类的实现,所以serverListUpdater的start方法传入的updateAction的实现其实就是这个匿名内部类。
那么哪里调用了serverListUpdater的start方法传入了updateAction呢?是在构造的时候调用的,具体的调用链路是调用 restOfInit -> (),这里就不贴源码了
所以,其实DynamicServerListLoadBalancer在构造完成之后,默认每隔30s中,就会调用updateAction的匿名内部类的doUpdate方法,从而会调用updateListOfServers。所以我们来看一看 updateListOfServers 方法干了什么。
这个方法实现很简单,就是通过调用 ServerList 的getUpdatedListOfServers获取到一批服务实例数据,然后过滤一下,最后调用updateAllServerList方法,进入updateAllServerList方法。
其实很简单,就是调用每个服务实例的setAlive方法,将isAliveFlag设置成true,然后调用setServersList。setServersList这个方法的主要作用是将服务实例更新到内部的缓存中,也就是上面提到的allServerList和upServerList,这里就不贴源码了。
其实分析完updateListOfServers方法之后,再结合上面源码的分析,我们可以清楚的得出一个结论,那就是默认每隔30s都会重新通过ServerList组件获取到服务实例数据,然后更新到BaseLoadBalancer缓存中,IRule的负载均衡所需的服务实例数据,就是这个内部缓存。
从DynamicServerListLoadBalancer的命名也可以看出,他相对于父类BaseLoadBalancer而言,提供了动态更新内部服务实例列表的功能。
为了便于大家记忆,我画一张图来描述这些组件的关系以及是如何运作的。
说完一些核心的组件,以及他们跟ILoadBalancer的关系之后,接下来就来分析一下,ILoadBalancer是在ribbon中是如何使用的。
ILoadBalancer是一个可以获取到服务实例数据的组件,那么服务实例跟什么有关,那么肯定是跟请求有关,所以在Ribbon中有这么一个抽象类,,这个是用来执行请求的,我们来看一下这个类的构造。
通过上面可以看出,在构造的时候需要传入一个ILoadBalancer。
中有一个方法executeWithLoadBalancer,这个是用来执行传入的请求,以负载均衡的方式。
这个方法构建了一个LoadBalancerCommand,随后调用了submit方法,传入了一个匿名内部类,这个匿名内部类中有这么一行代码很重要。
这行代码是根据给定的一个Server重构了URI,这是什么意思呢?举个例子,在OpenFeign那一篇文章我说过,会根据服务名拼接出类似 http:// ServerA 的地址,那时是没有服务器的ip地址的,只有服务名,假设请求的地址是 http:// ServerA/api/sayHello ,那么reconstructURIWithServer干的一件事就是将ServerA服务名替换成真正的服务所在的机器的ip和端口,假设ServerA所在的一台机器(Server里面封装了某台机器的ip和端口)是192.168.1.101:8088,那么重构后的地址就变成 http:// 192.168.1.101:8088/api/ sayHello ,这样就能发送http请求到ServerA服务所对应的一台服务器了。
之后根据新的地址,调用这个类中的execute方法来执行请求,execute方法是个抽象方法,也就是交给子类实现,子类就可以通过实现这个方法,来发送http请求,实现rpc调用。
那么这台Server是从获取的呢?其实猜猜也知道,肯定是通过ILoadBalancer获取的,因为submit方法比较长,这里我直接贴出submit方法中核心的一部分代码
就是通过selectServer来选择一个Server的,selectServer我就不翻源码了,其实最终还是调用ILoadBalancer的方法chooseServer方法来获取一个服务,之后就会调用上面的说的匿名内部类的方法,重构URI,然后再交由子类的execut方法来实现发送http请求。
所以,通过对的executeWithLoadBalancer方法,我们可以知道,这个抽象类的主要作用就是通过负载均衡算法,找到一个合适的Server,然后将你传入的请求路径 http:// ServerA/api/sayHello 重新构建成类似 http:// 192.168.1.101:8088/api/ sayHello 这样,之后调用子类实现的execut方法,来发送http请求,就是这么简单。
到这里其实Ribbon核心组件和执行原理我就已经说的差不多了,再来画一张图总结一下
说完了Ribbon的一些核心组件和执行原理之后,我们再来看一下在SpringCloud环境下,这些组件到底是用的哪些实现,毕竟有写时接口,有的是抽象类。
Ribbon的自动装配类:RibbonAutoConfiguration,我拎出了核心的源码
RibbonAutoConfiguration配置类上有个@RibbonClients注解,接下来讲解一下这个注解的作用
SpringClientFactory是不是感觉跟OpenFeign中的FeignContext很像,其实两个的作用是一样的,SpringClientFactory也继承了NamedContextFactory,实现了配置隔离,同时也在构造方法中传入了每个容器默认的配置类RibbonClientConfiguration。至于什么是配置隔离,我在OpenFeign那篇文章说过,不清楚的小伙伴可以后台回复feign01即可获得文章链接。
配置优先级问题
优先级最低的就是FeignContext和SpringClientFactory构造时传入的配置类
至于优先级怎么来的,其实是在NamedContextFactory中createContext方法中构建时按照配置的优先级一个一个传进去的。
RibbonClientConfiguration提供的默认的bean
接下来我们看一下RibbonClientConfiguration都提供了哪些默认的bean
配置类对应的bean,这里设置了ConnectTimeout和ReadTimeout都是1s中。
IRule,默认是ZoneAvoidanceRule,这个Rule带有过滤的功能,过滤哪些不可用的分区的服务(这个过滤可以不用care),过滤成功之后,继续采用线性轮询的方式从过滤结果中选择一个出来。至于这个propertiesFactory,可以不用管,这个是默认读配置文件的中的配置,一般不设置,后面看到都不用care。
至于为什么容器选择NacosServerList而不是ConfigurationBasedServerList,主要是因为这个配置类是通过@RibbonClients导入的,也就是比SpringClientFactory导入的RibbonClientConfiguration配置类优先级高。
ServerListUpdater,就是我们剖析的PollingServerListUpdater,默认30s更新一次BaseLoadBalancer内部服务的缓存。
那么在springcloud中,上图就可以加上注册中心。
三、总结
本文剖析了Ribbon这个负载均衡组件中的一些核心组件的源码,并且将这些组件之间的关系一一描述清楚,同时也剖析了在发送请求的时候是如何通过ILoadBalancer获取到一个服务实例,重构URI的过程。希望本篇文章能够让你知道Ribbon是如何工作的。