[享学Eureka] 三十四、借助Guice的DI依赖管理,轻松实现一键启动Eureka Client端完成服务注册

要么做第一个,要么做最好的一个。

–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/netflix-learning

前言

回想一下,在本系列第一篇文章就有提到过,Eureka它是使用轻量级DI框架:谷歌的Guice来管理其依赖的。通过前面这么多篇文章的学习,有理想相信亲们已经了解了Eureka几乎每个组件的作用以及它们的依赖关系。即使如此,但若现在要你构件其一个完整可用的Eureka Client客户端,你觉得呢?

相信拿到这个“题目”的感觉和我是一样的:我去,这也太麻烦了吧,组件这么多,框架依赖关系还一层一层的错综复杂,难点并不是因为它困难,而是很麻烦。是的,这是每一个稍大型软件均会遇见的难题:类/组件多了后,组织在一起便变成了一大难题,所以需要一个类似于Spring这样的容器进行统一组织、管理依赖那是极好的。Eureka选择了谷歌的轻量级DI框架Guice来化解该难题。本文将尝试使用Guice来自动化管理其各个组件,一键启动 Eureka Client端,让其协调工作起来。亲爱的小摩托从此便手动档升级为自动挡,本文你值得拥有。

说明:在理解了本文之后再去阅读Spring Cloud整合Eureka,那就“易如反掌”了

提示:在阅读本文之前,请务必确保你已经了解Guice是什么,大概怎么玩。参考文章:3分钟带你了解:轻量级依赖注入框架Google Guice【享学Java】


正文

前面文章我书写代码示例的时候,全靠我勤劳的双手,各种new对象,各种组件的构建和组装真的是蛮麻烦的。本文将这些依赖管理交给DI容器,切换成自动挡,一键完成启动。


EurekaModule容器配置类

它是Eureka Client整合Guice的配置类,类比于Spring的@Configuration配置类,容器的启动需要从本类开始。

下面根据此类源码,看看其向容器内放置了哪些组件呢?

public final class EurekaModule extends AbstractModule {

    @Override
    protected void configure() {
        // 实例管理器,需要立马初始化
        bind(ApplicationInfoManager.class).asEagerSingleton();

        // 下面的这些组件,如果你有需要可以自行在其它模块里通过Modules.override()覆盖掉它
        // override these in additional modules if necessary with Modules.override()

		//实例配置:使用的CloudInstanceConfigProvider,也就是CloudInstanceConfig
		// 但是默认配置它的话需要特别注意的是:你必须配置metadata里的instance-id等,或者关闭校验,请看下面的示例就懂了
        bind(EurekaInstanceConfig.class).toProvider(CloudInstanceConfigProvider.class).in(Scopes.SINGLETON);
        //客户端配置:使用的DefaultEurekaClientConfigProvider,也就是DefaultEurekaClientConfig
        // 配置信息来自于:archaius管理的配置文件,如eureka-client.properties
        bind(EurekaClientConfig.class).toProvider(DefaultEurekaClientConfigProvider.class).in(Scopes.SINGLETON);
        
		// InstanceInfo实例的实例化是个复杂过程,交给了EurekaConfigBasedInstanceInfoProvider
		// 它所需要的信息均来自EurekaInstanceConfig配置文件
        bind(InstanceInfo.class).toProvider(EurekaConfigBasedInstanceInfoProvider.class).in(Scopes.SINGLETON);
        
		// 客户端毫无疑问,显然是DiscoveryClient喽
        bind(EurekaClient.class).to(DiscoveryClient.class).in(Scopes.SINGLETON);
        // 竟然EndpointRandomizer也可放置了一个实例,不错哦
        bind(EndpointRandomizer.class).toInstance(ResolverUtils::randomize);
        // 默认使用的是基于Jersey的底层通信
        bind(AbstractDiscoveryClientOptionalArgs.class).to(Jersey1DiscoveryClientOptionalArgs.class).in(Scopes.SINGLETON);
    }

    @Override
    public boolean equals(Object obj) {
        return obj != null && getClass().equals(obj.getClass());
    }
    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
}

建立在已经基本了解Guice的使用的基础上,对此配置类应该是没有疑问的。它控制着容器初始化时向容器内放置的实例:对应类型放入对应实例,也就是默认实例。

如:EurekaInstanceConfig客户端配置默认是CloudInstanceConfigProvider类型(CloudInstanceConfig类型),是基于AWS云环境的配置,一般情况下不会使用它,需要替换~

CloudInstanceConfig和元数据强相关,关于eureka的元数据,后面我会用专门文章介绍如何去使用它,有较多的使用技巧以及黑黑科技


使用Guice启动Eureka Client

有了Guice的基础,使用起来自然不在话下,非常轻松:

@Inject
EurekaClient eurekaClient;

@Test
public void fun2(){
    // 启动DI容器
    Injector injector = Guice.createInjector(new EurekaModule());
    // 让其可以@Inject注入eurekaClient 提供使用
    // 说明:若你只想用纯API方式使用,此句是没有必要写的~~~~~
    injector.injectMembers(this);

    // 可以看到注入的和API获取到的是同一个实例
    EurekaClient injectorInstance = injector.getInstance(EurekaClient.class);
    System.out.println(eurekaClient == injectorInstance);
}

运行程序(注意:此时我没有准备任何配置文件),抛错:

Caused by: java.lang.RuntimeException: Your datacenter is defined as cloud but we are not able to get the amazon metadata to register. 
Set the property eureka.validateInstanceId to false to ignore the metadata call
	at com.netflix.appinfo.RefreshableAmazonInfoProvider.init(RefreshableAmazonInfoProvider.java:58)
	at com.netflix.appinfo.RefreshableAmazonInfoProvider.<init>(RefreshableAmazonInfoProvider.java:33)
	at com.netflix.appinfo.CloudInstanceConfig.<init>(CloudInstanceConfig.java:86)
	at com.netflix.appinfo.CloudInstanceConfig.<init>(CloudInstanceConfig.java:59)
	...

解决默认情况下启动容器报错

报错发生在使用RefreshableAmazonInfoProvider初始化CloudInstanceConfig实例上,代码如下:

RefreshableAmazonInfoProvider:

	private static AmazonInfo init(AmazonInfoConfig amazonInfoConfig, FallbackAddressProvider fallbackAddressProvider) {
		...
		if (info.get(AmazonInfo.MetaDataKey.instanceId) == null) {
			if (amazonInfoConfig.shouldValidateInstanceId()) {
				throw new RuntimeException("Your datacenter is defined as cloud but we are not able to get the amazon metadata to " ... );
			} else {
				...
			}
		}  else { ... }
		...
	}

我们已经知道AmazonInfo它对实例id、ip、hostname等配置都是使用metadata元数据来完成配置的,因此要想正常启动容器不抛错,可有如下两种解决方案:


禁用InstanceId检查

在配置(主配置or专用配置都行)上增加配置类:eureka.validateInstanceId = false,再次运行情况如下:

true

DI容器正常启动(还不能完成自动注册)。


使用自定义的EurekaInstanceConfig实现类

Eureka在Guice容器默认放入的是CloudInstanceConfig,该实例是和AWS绑定的,一般用于较为复杂的云环境。而对于本例使用中,我们可以替换为自定义的(其实也是Eureka内置的)MyDataCenterInstanceConfig即可,做法如下。

自定义一个Module,注入EurekaInstanceConfig的实现为MyDataCenterInstanceConfig这个实现:

private static class MyModule extends AbstractModule{

    @Override
    protected void configure() {
        bind(EurekaInstanceConfig.class).toProvider(MyDataCenterInstanceConfigProvider.class).in(Scopes.SINGLETON);
    }
}

使用我自定义的MyModule覆盖默认的EurekaModule配置类:

@Test
public void fun2() throws InterruptedException {
    // 启动DI容器
    Injector injector = Guice.createInjector(Modules.override(new EurekaModule()).with(new MyModule()));
    // 让其可以@Inject注入eurekaClient 提供使用
    // 说明:若你只想用纯API方式使用,此句是没有必要写的~~~~~
    injector.injectMembers(this);

    // 可以看到注入的和API获取到的是同一个实例
    EurekaClient injectorInstance = injector.getInstance(EurekaClient.class);
    System.out.println(eurekaClient == injectorInstance);

    TimeUnit.MINUTES.sleep(2);
}

配置文件上加入如下配置项:

eureka.serviceUrl.defaultZone=http://localhost:8761/eureka

# 配置当前实例信息
eureka.instanceId = YourBatman
eureka.name = ACCOUNT
eureka.appGroup = USER-CENTER

运行测试程序,可以看到服务端能收到注册信息:

在这里插入图片描述
Client端发送的实例信息截图:

在这里插入图片描述
解释:服务端收到的实例状态是STARTING,是因为InstanceInfo实例是通过EurekaConfigBasedInstanceInfoProvider构建出来的,默认该实例的状态就是STARTING

如果你想它是UP的话,可以增加参数:eureka.traffic.enabled = true这样初始注册上去就是UP啦。详见EurekaConfigBasedInstanceInfoProvider~

需要再次强调的是:Spring Cloud下可没这个“规则”,因为它的InstanceInfo实例是Spring Cloud自己创建、管理的,有一套它自己的逻辑,而不用遵从原生~


InternalEurekaStatusModule

它虽然也是一个Guice的配置类,但是由于已过期,不需要再使用,所以本文略过。


Spring Cloud整合Guice了吗?

答案:完全没有。虽然说Spring和Guice均为DI框架,但是其实他俩是可以完成整合、和谐相处的。但实际上是,在Spring Cloud中使用Eureka时,它完全没有使用到Guice,而是把所有Eureka的组件均交由Spring容器管理,完全的自己组织。

我个人认为这样做是无可厚非的,我大胆猜测了一下SC这么做有如下原因:

  1. Spring家族希望你学习Spring Cloud只需要懂Spring即可,而完全没有必要再去学习另外一个DI框架Guice(我觉得这是最最最重要的原因)
    1. Spring一把全包了,管生管养,管杀管埋
  2. 自身生态考虑。我自己全都能做,为何还需要给“竞品”机会呢,让其死在襁褓里岂不更好(这点原因是我自己YY的)
    1. 毕竟Spring Cloud“号召力”可不小,万一学它的人多了,再加上Google强大的技术基因。。。你懂的
  3. 整合永远停留在适配层面,而所有东西都自己来管理,才更好把握、更好定制、更好扩展、更好引导用户使用我的方式去编码
    1. 最为典型的是:Spring Cloud下的配置项完全是Spring Boot的风格,而去掉了源生风格

总之,我觉得Spring这么做,从用户体验角度来看,是非常非常好的举措,或许也是双赢的方案吧~


总结

关于借助Guice的DI依赖管理,轻松实现一键启动Eureka Client端完成服务注册就先介绍到这了,通过本文示例同前面我书写的示例做对比,你应该体会到了DI依赖管理容器的“威力”。另外,使用Guice启动Eureka Client客户端只是牛刀小试,下面在Spring Cloud下使用Eureka Client才是生产上真正的使用方式,也就是所谓的正确姿势。不过万物皆关联,况且还是近亲呢?下文见吧~
分隔线

声明

原创不易,码字更不易,感谢关注。分享本文到你的朋友圈是被授权的,但拒绝抄袭。【左边扫码加我wx / wx号:fsx641385712】,邀你加入 【Java高工、架构师】 系列纯纯纯技术群,亦可扫码加入我的知识星球【BAT的乌托邦】。
往期精选

发布了403 篇原创文章 · 获赞 923 · 访问量 57万+

猜你喜欢

转载自blog.csdn.net/f641385712/article/details/105310790
今日推荐