10、Eureka教程-ApplicationInfoManager创建过程

零、前言

有几个步骤比较简单,且和主流程关系不大,就一起看了。

一、注册JSON、XML数据流转换器

这里的代码很简单,就是创建了两个转换器,然后保存到了一个底层的PrioritizedList数据结构中。

JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);

public void registerConverter(Converter converter, int priority) {

    if (converterRegistry != null) {

        converterRegistry.registerConverter(converter, priority);
    }
}

private final PrioritizedList converters = new PrioritizedList();

public void registerConverter(Converter converter, int priority) {

    typeToConverterMap.clear();
    converters.add(converter, priority);
}

二、创建ServerCodecs

这里其实就是创建了一个编码解码器。简单看看就好~

ServerCodecs serverCodecs = new DefaultServerCodecs(eurekaServerConfig);

@Inject
public DefaultServerCodecs(EurekaServerConfig serverConfig) {

    this (
            getFullJson(serverConfig),
            CodecWrappers.getCodec(CodecWrappers.JacksonJsonMini.class),
            getFullXml(serverConfig),
            CodecWrappers.getCodec(CodecWrappers.JacksonXmlMini.class)
    );
}

protected DefaultServerCodecs(CodecWrapper fullJsonCodec,
                              CodecWrapper compactJsonCodec,
                              CodecWrapper fullXmlCodec,
                              CodecWrapper compactXmlCodec) {

    this.fullJsonCodec = fullJsonCodec;
    this.compactJsonCodec = compactJsonCodec;
    this.fullXmlCodec = fullXmlCodec;
    this.compactXmlCodec = compactXmlCodec;
}

三、创建ApplicationInfoManager

ApplicationInfoManager就是当前Eureka实例信息的管理器,我们来看看它的逻辑~

ApplicationInfoManager applicationInfoManager = null;
if (eurekaClient == null) {

    EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext())
            ? new CloudInstanceConfig()
            : new MyDataCenterInstanceConfig();

    applicationInfoManager = new ApplicationInfoManager(
            instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get());

    EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig();
    eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig);
} else {

    applicationInfoManager = eurekaClient.getApplicationInfoManager();
}

首先它判断了一下eurekaClient是否存在,如果存在就从eurekaClient中获取,不存在就创建一个,并且创建一个eurekaClient,再把applicationInfoManager设置到eurekaClient里面。这里一开始默认是没有的,所以要先创建。

第一步:它根据Eureka是否部署在云上创建不同的EurekaInstanceConfig实例。这里要吐槽一下,Eureka貌似很多地方都判断了它自己是否部署在亚马逊的AWS云上- -! 但这两个实现类都继承自PropertiesInstanceConfig,所以这里就不纠结细节了。而且根据笔者一层一层找上去,他最终就是读取了Eureka-Client.properties文件里面的配置,自己封装了一下,这里有兴趣得到小伙伴自己看看~ 需要注意的是这里读取的是client的配置信息,那么可以得知applicationInfoManager组装的是Client端的信息,进而可以证明EurekaServer端也是一个EurekaClient端,需要把自己的节点信息同步给其他EurekaServer节点。

第二步:这里创建了一个ApplicationInfoManager,我们可以看到ApplicationInfoManager创建需要两个参数,分别是EurekaInstanceConfigInstanceInfo,一个是Client端配置信息,一个是Client节点本身的信息。

public ApplicationInfoManager(EurekaInstanceConfig config, InstanceInfo instanceInfo) {

    this(config, instanceInfo, null);
}

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构造函数本身是没有什么逻辑的,就是把EurekaInstanceConfigInstanceInfo还有其他信息保存了一下,那么我们回过头看一下InstanceInfo是如何被创建的new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get()

// 这里没什么东西,我们看一下get方法
public EurekaConfigBasedInstanceInfoProvider(EurekaInstanceConfig config) {

    this.config = config;
}

public synchronized InstanceInfo get() {

    if (instanceInfo == null) {

        // Build the lease information to be passed to the server based on config
        LeaseInfo.Builder leaseInfoBuilder = LeaseInfo.Builder.newBuilder()
                .setRenewalIntervalInSecs(config.getLeaseRenewalIntervalInSeconds())
                .setDurationInSecs(config.getLeaseExpirationDurationInSeconds());

        if (vipAddressResolver == null) {

            vipAddressResolver = new Archaius1VipAddressResolver();
        }

        // Builder the instance information to be registered with eureka server
        InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder(vipAddressResolver);

        // set the appropriate id for the InstanceInfo, falling back to datacenter Id if applicable, else hostname
        String instanceId = config.getInstanceId();
        if (instanceId == null || instanceId.isEmpty()) {

            DataCenterInfo dataCenterInfo = config.getDataCenterInfo();
            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())
                .setSecureVIPAddress(config.getSecureVirtualHostName())
                .setHomePageUrl(config.getHomePageUrlPath(), config.getHomePageUrl())
                .setStatusPageUrl(config.getStatusPageUrlPath(), config.getStatusPageUrl())
                .setASGName(config.getASGName())
                .setHealthCheckUrls(config.getHealthCheckUrlPath(),
                        config.getHealthCheckUrl(), config.getSecureHealthCheckUrl());
        // Start off with the STARTING state to avoid traffic
        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);
        }

        // Add any user-specific metadata information
        for (Map.Entry<String, String> mapEntry : config.getMetadataMap().entrySet()) {

            String key = mapEntry.getKey();
            String value = mapEntry.getValue();
            // only add the metadata if the value is present
            if (value != null && !value.isEmpty()) {

                builder.add(key, value);
            }
        }

        instanceInfo = builder.build();
        instanceInfo.setLeaseInfo(leaseInfoBuilder.build());
    }
    return instanceInfo;
}

我们可以看到在get方法中,Eureka使用了构造器模式创建了一个InstanceInfo实例,它在中间设置了InstanceId、AppName、IPAddr等一系列信息,为连接EurekaServer做准备。这里大家可以学习一下,当一个类需要配置大量参数的时候,可以使用构造器模式,减少大量的setXXX代码。

四、总结

那么到这里ApplicationInfoManager就创建完毕了,它本质上封装了EurekaClient的配置信息和Client连接Server的实例信息,作为后续连接使用。继续画个图总结一下~

&nbsp;