在Eureka注册中心的启动过程中,创建了一个ApplicationInfoManager
对象,这是Eureka中的一个应用信息管理组件:
applicationInfoManager = new ApplicationInfoManager(
instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get());
本章,我们就来看看ApplicationInfoManager和InstanceInfo的底层实现。
一、InstanceInfo
InstanceInfo
代表了Eureka应用实例信息。Eureka-Client 向 Eureka-Server 注册 该对象信息,注册成功后,可以被其它 Eureka-Client 发现 。
1.1 Builder模式
InstanceInfo包含了非常多的参数,它的构造依赖EurekaInstanceConfig。所以,Eureka采用了设计模式里的Builder模式进行InstanceInfo对象的构造,Builder模式非常适合这种复杂对象的构造。
具体的构造由EurekaConfigBasedInstanceInfoProvider
来完成:
new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get()
我们来看下它的get方法:
private final EurekaInstanceConfig config;
private InstanceInfo instanceInfo;
public synchronized InstanceInfo get() {
if (instanceInfo == null) {
// 创建租约信息构建器,并设置属性
LeaseInfo.Builder leaseInfoBuilder = LeaseInfo.Builder.newBuilder()
.setRenewalIntervalInSecs(config.getLeaseRenewalIntervalInSeconds())
.setDurationInSecs(config.getLeaseExpirationDurationInSeconds());
// 创建VIP地址解析器
if (vipAddressResolver == null) {
vipAddressResolver = new Archaius1VipAddressResolver();
}
// 创建应用实例Buidler
InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder(vipAddressResolver);
// 设置应用实例编号
String instanceId = config.getInstanceId();
DataCenterInfo dataCenterInfo = config.getDataCenterInfo();
if (instanceId == null || instanceId.isEmpty()) {
if (dataCenterInfo instanceof UniqueIdentifier) {
instanceId = ((UniqueIdentifier) dataCenterInfo).getId();
} else {
instanceId = config.getHostName(false);
}
}
// 获得主机名
String defaultAddress;
if (config instanceof RefreshableInstanceConfig) {
// Refresh AWS data center info, and return up to date address
defaultAddress = ((RefreshableInstanceConfig) config).resolveDefaultAddress(false);
} else {
defaultAddress = config.getHostName(false);
}
// fail safe
if (defaultAddress == null || defaultAddress.isEmpty()) {
defaultAddress = config.getIpAddress();
}
// 设置应用实例的属性
builder.setNamespace(config.getNamespace())
.setInstanceId(instanceId)
.setAppName(config.getAppname())
.setAppGroupName(config.getAppGroupName())
.setDataCenterInfo(config.getDataCenterInfo())
.setIPAddr(config.getIpAddress())
.setHostName(defaultAddress) // 主机名
.setPort(config.getNonSecurePort())
.enablePort(PortType.UNSECURE, config.isNonSecurePortEnabled())
.setSecurePort(config.getSecurePort())
.enablePort(PortType.SECURE, config.getSecurePortEnabled())
.setVIPAddress(config.getVirtualHostName()) // VIP 地址
.setSecureVIPAddress(config.getSecureVirtualHostName())
.setHomePageUrl(config.getHomePageUrlPath(), config.getHomePageUrl())
.setStatusPageUrl(config.getStatusPageUrlPath(), config.getStatusPageUrl())
.setASGName(config.getASGName())
.setHealthCheckUrls(config.getHealthCheckUrlPath(),
config.getHealthCheckUrl(), config.getSecureHealthCheckUrl());
// 应用初始化后是否开启:不开启-应用实例处于STARTING状态;开启-应用实例处于UP状态
if (!config.isInstanceEnabledOnit()) {
InstanceStatus initialStatus = InstanceStatus.STARTING;
LOG.info("Setting initial instance status as: " + initialStatus);
builder.setStatus(initialStatus);
} else {
LOG.info("Setting initial instance status as: {}. This may be too early for the instance to advertise "
+ "itself as available. You would instead want to control this via a healthcheck handler.",
InstanceStatus.UP);
}
// 设置 应用实例构建器 的 元数据( Metadata )集合
for (Map.Entry<String, String> mapEntry : config.getMetadataMap().entrySet()) {
String key = mapEntry.getKey();
String value = mapEntry.getValue();
builder.add(key, value);
}
// 基于Builder模式,创建应用实例对象
instanceInfo = builder.build();
// 设置应用实例的租约信息
instanceInfo.setLeaseInfo(leaseInfoBuilder.build());
}
return instanceInfo;
}
可以看到,InstanceInfo应用实例对象的创建是基于InstanceInfo.Builder
这个Builder构建器:
public static final class Builder {
//...
private InstanceInfo result;
private final VipAddressResolver vipAddressResolver;
private Builder(InstanceInfo result, VipAddressResolver vipAddressResolver) {
this.vipAddressResolver = vipAddressResolver;
this.result = result;
}
public static Builder newBuilder() {
return new Builder(new InstanceInfo(), LazyHolder.DEFAULT_VIP_ADDRESS_RESOLVER);
}
public static Builder newBuilder(VipAddressResolver vipAddressResolver) {
return new Builder(new InstanceInfo(), vipAddressResolver);
}
//...
}
最终的InstanceInfo应用实例的构造完成是在调用InstanceInfo.Builder.build()
方法时:
public InstanceInfo build() {
if (!isInitialized()) {
throw new IllegalStateException("name is required!");
}
return result;
}
builder对象针对InstanceInfo实例的各种set方法进行了一些处理,去set各种需要的属性和配置,设置完成后就完成了最终的一个复杂InstanceInfo应用实例对象的构造。比如:
public Builder setNamespace(String namespace) {
this.namespace = namespace.endsWith(".")
? namespace
: namespace + ".";
return this;
}
二、ApplicationInfoManager
应用实例InstanceInfo构建完成后,会保存到ApplicationInfoManager中。ApplicationInfoManager,顾名思义,就是一个应用信息管理器。
因为InstanceInfo就是一个单纯的POJO,不带任何业务行为,所以需要一个外部组件对它进行管理,所以就有了ApplicationInfoManager:
public class ApplicationInfoManager {
// 单例
private static ApplicationInfoManager instance = new ApplicationInfoManager(null, null, null);
// 状态变更监听器集合
protected final Map<String, StatusChangeListener> listeners;
// 应用实例状态匹配
private final InstanceStatusMapper instanceStatusMapper;
// 应用实例信息
private InstanceInfo instanceInfo;
// 应用实例配置
private EurekaInstanceConfig config;
//...
public ApplicationInfoManager(EurekaInstanceConfig config, InstanceInfo instanceInfo, OptionalArgs optionalArgs) {
this.config = config;
this.instanceInfo = instanceInfo;
this.listeners = new ConcurrentHashMap<String, StatusChangeListener>();
if (optionalArgs != null) {
this.instanceStatusMapper = optionalArgs.getInstanceStatusMapper();
} else {
this.instanceStatusMapper = NO_OP_MAPPER;
}
// Hack to allow for getInstance() to use the DI'd ApplicationInfoManager
instance = this;
}
//...
}
可以看到,ApplicationInfoManager提供了很多管理InstanceInfo的方法:
三、总结
本章,我介绍了InstanceInfo应用实例,它的特点就是利用了Builder设计模式 进行对象的构造。对于InstanceInfo对象的管理,Eureka则采用了一个ApplicationInfoManager对象。对于InstanceInfo应用实例信息,后面在构造EurekaClient对象时还会用到。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。