上篇文章中,我们简单介绍了EurekaServer自动装配及启动流程解析,本篇文章则继续研究EurekaClient的相关代码

老规矩,先看spring.factories文件,其中引入了一个配置类EurekaDiscoveryClientConfigServiceBootstrapConfiguration

@ConditionalOnClass(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false)
@Configuration
@Import({ EurekaDiscoveryClientConfiguration.class,
EurekaClientAutoConfiguration.class })
public class EurekaDiscoveryClientConfigServiceBootstrapConfiguration {
}

上方两个注解则是这个配置类是否能够开启的条件,这里就不再展开,直接看它引入的配置类吧

EurekaDiscoveryClientConfiguration

  1. 细心的读者可能会发现这里又注册了一个Marker类,可以猜测也是某个地方的开关
  2. EurekaClientConfigurationRefresher这个类看名字就知道这是当配置被动态刷新时的一个处理器,这里也不再展开了
  3. EurekaHealthCheckHandlerConfiguration这里面注册了一个Eureka健康检查的处理类,这个健康检查相关的原理分析可以参考这篇文章:SpringBoot健康检查实现原理

EurekaClientAutoConfiguration

这个类里面全是重点,也是我们本文的核心

注解
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@Import(DiscoveryClientOptionalArgsConfiguration.class)
@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = {"org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
"org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration"})

首先可以看到这个类一共包含这些注解,我们来一一解析比较重要的几个注解吧

@Import(DiscoveryClientOptionalArgsConfiguration.class)

引入了两个bean,RestTemplateDiscoveryClientOptionalArgsMutableDiscoveryClientOptionalArgs ,这两个类的作用暂且不说

@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)

刚才说的Marker类的作用出来了

@AutoConfigureBefore

既然必须在这三个类完成自动装配之后才能进行装配,那就代表着这三个类肯定大有用途,研究一下吧

NoopDiscoveryClientAutoConfiguration

故名思意,负责服务发现的类,咱们重点关注一下其中的几个方法

  1. init
@PostConstruct
public void init() {
String host = "localhost";
try {
host = InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException e) {
log.warn("Cannot get host info: (" + e.getMessage() + ")");
}
int port = findPort();
this.serviceInstance = new DefaultServiceInstance(
this.environment.getProperty("spring.application.name", "application"),
host, port, false);
}

这里构造了一个DefaultServiceInstance对象,这个对象包含了当前项目的ip+端口+项目名称

2. 注入beanNoopDiscoveryClient

@Bean
public DiscoveryClient discoveryClient() {
return new NoopDiscoveryClient(this.serviceInstance);
}

再深入看一下这个类

public class NoopDiscoveryClient implements DiscoveryClient {

	public NoopDiscoveryClient(ServiceInstance instance) {
} @Override
public String description() {
return "Spring Cloud No-op DiscoveryClient";
} @Override
public List<ServiceInstance> getInstances(String serviceId) {
return Collections.emptyList();
} @Override
public List<String> getServices() {
return Collections.emptyList();
} }

这个类包含了获取当前实例以及当前服务的方法,但是返回的都是空,那么是不是会在后面的某个地方被覆盖呢?

CommonsClientAutoConfiguration

进去深入了解一下,哎哟,注册了几个bean:DiscoveryClientHealthIndicatorDiscoveryCompositeHealthIndicator。原来是健康检查相关的东西,那就忽略了

ServiceRegistryAutoConfiguration

这个配置类中主要注册了一个bean:ServiceRegistryEndpoint这个类主要是对外提供对与Eureka状态的检查

@ReadOperation
public ResponseEntity getStatus() {
if (this.registration == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("no registration found");
} return ResponseEntity.ok().body(this.serviceRegistry.getStatus(this.registration));
}

而Eureka的状态则是通过serviceRegistry对象获取的,这个对象会再下方详细分析

注册bean

接着来看这个类注入的几个bean

EurekaClientConfigBean
@Bean
@ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
EurekaClientConfigBean client = new EurekaClientConfigBean();
if ("bootstrap".equals(this.env.getProperty("spring.config.name"))) {
client.setRegisterWithEureka(false);
}
return client;
}

这个bean中包含了eureka.client.xxx系列的一些配置,详细的配置信息可以参考这里:https://github.com/shiyujun/syj-study-demo/blob/master/src/main/java/cn/shiyujun/EurekaConfig.md

EurekaInstanceConfigBean

这个bean中主要是包含eureka实例(eureka.instance.xxx系列)的一些配置信息,详细的配置信息同上

RefreshableEurekaClientConfiguration.DiscoveryClient
		@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
@org.springframework.cloud.context.config.annotation.RefreshScope
@Lazy
public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config, EurekaInstanceConfig instance) {
manager.getInfo(); // force initialization
return new CloudEurekaClient(manager, config, this.optionalArgs,
this.context);
}

其中CloudEurekaClientDiscoveryClient的子类,而DiscoveryClient则是EurekaClient的核心类

   	public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
EurekaClientConfig config,
AbstractDiscoveryClientOptionalArgs<?> args,
ApplicationEventPublisher publisher) {
//这里会调用父类DiscoveryClient的构造方法
super(applicationInfoManager, config, args);
this.applicationInfoManager = applicationInfoManager;
this.publisher = publisher;
this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class, "eurekaTransport");
ReflectionUtils.makeAccessible(this.eurekaTransportField);
}

父类的构造方法中执行的代码块比较长,一些赋值操作等就忽略了,这里只摘出比较重要的部分

  1. 初始化拉取监控和心跳监控
       if (config.shouldFetchRegistry()) {
this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
} else {
this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
} if (config.shouldRegisterWithEureka()) {
this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
} else {
this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
}
  1. 当当前实例不需要注册到EurekaServer时,构造方法走到这里就结束了

if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
logger.info("Client configured to neither register nor query for data.");
scheduler = null;
heartbeatExecutor = null;
cacheRefreshExecutor = null;
eurekaTransport = null;
instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion());
DiscoveryManager.getInstance().setDiscoveryClient(this);
DiscoveryManager.getInstance().setEurekaClientConfig(config); initTimestampMs = System.currentTimeMillis();
logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
initTimestampMs, this.getApplications().size()); return;
}
  1. 初始化心跳线程和刷新线程以及它们的调度器
  try {
scheduler = Executors.newScheduledThreadPool(2,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build()); heartbeatExecutor = new ThreadPoolExecutor(
1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
.setDaemon(true)
.build()
); cacheRefreshExecutor = new ThreadPoolExecutor(
1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
.setDaemon(true)
.build()
);
  1. 从EurekaServer拉取注册信息
 if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
fetchRegistryFromBackup();
}

这里fetchRegistry是第一次拉取注册信息,如果拉取不成功的话则执行fetchRegistryFromBackup从备份注册中心获取,同样,拉取的信息会放在之后的文章中

  1. 注册之前的扩展点
  if (this.preRegistrationHandler != null) {
this.preRegistrationHandler.beforeRegistration();
}

这里是个空的实现,可以通过实现PreRegistrationHandler接口做些什么操作

6. 向EurekaServer发起注册

 if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
try {
if (!register() ) {
throw new IllegalStateException("Registration error at startup. Invalid server response.");
}
} catch (Throwable th) {
logger.error("Registration error at startup: {}", th.getMessage());
throw new IllegalStateException(th);
}
}

注册方法为register,同样这里先不展开

7. 初始化几个定时任务

initScheduledTasks();

private void initScheduledTasks() {
// 从 EurekaServer 拉取注册信息
if (clientConfig.shouldFetchRegistry()) {
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
} // 向 EurekaServer 发送续租心跳
if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs); scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS); instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
} @Override
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
logger.warn("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
instanceInfoReplicator.onDemandUpdate();
}
}; if (clientConfig.shouldOnDemandUpdateStatusChange()) {
//注册状态监听器
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
} instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}

至此,EurekaClient的自动装配与启动流程就解析完毕了

本文由博客一文多发平台 OpenWrite 发布!

EurekaClient自动装配及启动流程解析的更多相关文章

  1. EurekaServer自动装配及启动流程解析

    在开始本篇文章之前,我想你对SpringCloud和SpringBoot的基本使用已经比较熟悉了,如果不熟悉的话可以参考我之前写过的文章 本篇文章的源码基于SpringBoot2.0,SpringCl ...

  2. SpringBoot启动流程解析

    写在前面: 由于该系统是底层系统,以微服务形式对外暴露dubbo服务,所以本流程中SpringBoot不基于jetty或者tomcat等容器启动方式发布服务,而是以执行程序方式启动来发布(参考下图ke ...

  3. Arm启动流程解析

    谈到arm的启动流程不得不说的是bootloader,但是我这篇文章主要来谈谈arm启动流程的,所以bootloader只是跟大家简介一下就ok.这篇文章我会谈到以下内容: 1.bootloader简 ...

  4. 史上最全且最简洁易懂的Activity启动流程解析

    Activity的启动流程是一个资深Android工程师必须掌握的内容,也是高职级面试中的高频面试知识点,无论是从事应用层开发,还是Framework开发,其重要性都无需我多言.而要真正理解它,就不可 ...

  5. “无处不在” 的系统核心服务 —— ActivityManagerService 启动流程解析

    本文基于 Android 9.0 , 代码仓库地址 : android_9.0.0_r45 系列文章目录: Java 世界的盘古和女娲 -- Zygote Zygote 家的大儿子 -- System ...

  6. Netty(一):server启动流程解析

    netty作为一个被广泛应用的通信框架,有必要我们多了解一点. 实际上netty的几个重要的技术亮点: 1. reactor的线程模型; 2. 安全有效的nio非阻塞io模型应用; 3. pipeli ...

  7. Spring Boot系列(二):Spring Boot自动装配原理解析

    一.Spring Boot整合第三方组件(Redis为例) 1.加依赖 <!--redis--> <dependency> <groupId>org.springf ...

  8. Spring注解开发系列Ⅴ --- 自动装配&Profile

    自动装配: spring利用依赖注入和DI完成对IOC容器中各个组件的依赖关系赋值.自动装配的优点有: 自动装配可以大大地减少属性和构造器参数的指派. 自动装配也可以在解析对象时更新配置. 自动装配的 ...

  9. SpringBoot自动装配-自定义Start

    SpringBoot自动装配 在没有使用SpringBoot之前,使用ssm时配置redis需要在XML中配置端口号,地址,账号密码,连接池等等,而使用了SpringBoot后只需要在applicat ...

随机推荐

  1. bert剪枝系列——Are Sixteen Heads Really Better than One?

    1,概述 剪枝可以分为两种:一种是无序的剪枝,比如将权重中一些值置为0,这种也称为稀疏化,在实际的应用上这种剪枝基本没有意义,因为它只能压缩模型的大小,但很多时候做不到模型推断加速,而在当今的移动设备 ...

  2. 使用hutool工具类进行导出

    引入依赖为: <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</ ...

  3. day23_7.29 多态和类的内置方法

    一.多态 在现实生活中,多态也会体现.如对于水这种物质,有固态:冰,液态:常态,气态:水蒸气, 在程序中,其官方定义是:多个不同对象可以相应同一方法,产生不同的结果. 而在python中,多态不是一个 ...

  4. Maven打包插件Assembly(七)

    1. 在 dubbo 的 provider 项目(实现类项目dubbo-service-impl)中 pom.xml 配置 assembly插件信息 <!-- 指定项目的打包插件信息 --> ...

  5. WebJars简介 —— 前端资源的jar包形式(以后接触到再深入总结)

    对于日常的web开发而言,像css.js.images.font等静态资源文件管理是非常的混乱的.比如jQuery.Bootstrap.Vue.js等,可能每个框架使用的版本都不一样.一不注意就会出现 ...

  6. Media Formatters(媒体格式化器)

    6.1.1 Internet的媒体类型 媒体类型,也叫做MIME类型,标识了数据的格式.在HTTP中,媒体类型描述了消息体的格式.一个媒体类型由两个字符串组成:类型和子类型.例如: text/html ...

  7. 使用jdk 容器镜像注意默认编码问题

    最近在使用一个开源数据pipeline 处理的工具的时候,发现了jdk容器镜像编码的一些问题 以下是一个简单的描述 问题 使用了streamsets 工具,使用容器运行,默认使用了adoptopenj ...

  8. [LeetCode] 909. Snakes and Ladders 蛇梯棋

    On an N x N board, the numbers from 1 to N*Nare written boustrophedonically starting from the bottom ...

  9. 团队作业第五次—项目冲刺-Day5

    Day5 part1-SCRUM: 项目相关 作业相关 具体描述 所属班级 2019秋福大软件工程实践Z班 作业要求 团队作业第五次-项目冲刺 作业正文 hunter--冲刺集合 团队名称 hunte ...

  10. pytorch 查看中间变量的梯度

    pytorch 为了节省显存,在反向传播的过程中只针对计算图中的叶子结点(leaf variable)保留了梯度值(gradient).但对于开发者来说,有时我们希望探测某些中间变量(intermed ...