SpringBoot的Eureka-client分析
虽然本文主要讲的是Eureka,但是绝大部分初级中级读者应该还是从SpringBoot开始上手,因此本部分将讲解SpringBoot下Eureka的启动流程。
首先要注意的是,由于作者不精通SpringBoot,下面断点方法有投机取巧的意味,仅用于拉通对齐端到端主流程,而不能让你深入理解SpringBoot的启动原理
结论
通过进一步的封装,将原生的eureka-client
转换为SpringBoot常用的yaml,通过AutoConfiguration这种注解DSL帮你节约样板代码的配置时间
- SpringBoot-starter等框架大部分均是一层包装,千万不要畏惧
- 善于用工具解决问题,避免把时间耗在Spring的细节中
- 充分利用构造函数来断点分析Bean
预先准备
- 按照网上任意教程搭建Server与Client的环境,比如这里
- 旗舰版IntellijIDEA工具
SpringBoot自动装箱扫盲
@Configuration注解的含义
我们首先复习一下传统Spring的Java注解。除了传统的XML标记Bean以外,专业书籍中更推荐使用JavaConfig配置来代替繁琐的Xml标记
比如在《Spring In Action》中,作者是这样推荐的 "I’d favor the type-safe and more powerful JavaConfig over XML."
通过在传统项目的web.xml(WebApplicationInitializer)
中配置@ComponentScan
或者SpringBoot中配置@SpringBootApplication
进行扫描,带有@Configuration
的Class将被自动注入,比如下面就是一个Bean。
@Configuration
public class WebConfig{
@Bean
public SSOFilter ssofilter(){
....
}
}
@EnableAutoConfiguration的含义
在@SpringBootApplication
注解中,最重要的是EnableAutoConfiguration
这个注解,它就是SpringBoot自动装配各种配置的关键
// EnableAutoConfiguration注解对应的代码
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
...
}
我们对它的最终实现类进行如下断点
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports
可以发现这里调用了SpringFactoriesLoader读取factories并返回了一个数组
org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
SpringFactoriesLoader是老Spring项目中已经存在的工具类(比如Dubbo中对Bean的xsd进行定制以支持Zookeeper),而非SpringBoot新造的轮子。它硬编码了
META-INF/spring.factories
作为properties配置读取并反序列化,并通过反射实例化标记为@Configuration
的JavaConfig—— 此部分可以参考《SpringBoot揭秘: 快速构建微服务》
所以说 ,EnableAutoConfiguration
并不是什么黑科技,它只是反序列化了properties配置并读取JavaConfig,并没有很高深的技巧。
这里就不详细讲了,在本场景中返回了70多个bean,这些将后续被实例化并加载到上下文后进行Refresh。
如果读者有兴趣的话,可以参考mybatis-starter中的配置,这个比Eureka依赖更简单,更加适合入门学习
Eureka都自动注入了啥
Eureka中的AutoConfiguration
我们接着去查看Eureka中的配置文件(spring-cloud-netflix-eureka-client-1.4.2.RELEASE.jar!/META-INF/spring.factories),可以发现内部有如下配置
# 注意: value的前缀被我节约版面干掉了
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
EurekaClientConfigServerAutoConfiguration,\
EurekaDiscoveryClientConfigServiceAutoConfiguration,\
EurekaClientAutoConfiguration,\
RibbonEurekaAutoConfiguration,\
EurekaDiscoveryClientConfiguration
...
这么多配置如何分析呢,对于这类问题我们可以考虑购买高效工具(IntellijIDEA)来提高生产力
首先我们最常见使用的对象是这个
@Autowired
private DiscoveryClient discoveryClient;
使用IDEA的行左边绿色的Bean分析功能,可以发现是CompositeDiscoveryClientAutoConfiguration
注入的。
打开这个Config,在构造compositeDiscoveryClient
下打断点,可以发现实现类分别是EurekaDiscoveryClient
与SimpleDiscoveryClient
,明显前一个才是真正负责RPC业务的客户端。
接着在EurekaDiscoveryClient
的构造函数上打断点,发现构造这个是在EurekaClientAutoConfiguration
被调用,两个入参均为netflix的包名,说明我们已经走到Spring封装的尽头
@Bean
public DiscoveryClient discoveryClient(EurekaInstanceConfig config, EurekaClient client) {
return new EurekaDiscoveryClient(config, client);
}
接下来依次分析config与client的构造函数
EurekaInstanceConfig
第一个比较好分析,它只是一个Properties类
@ConfigurationProperties("eureka.instance")
public class EurekaInstanceConfigBean {
//可以发现这个类就是一个单纯的反序列化类
}
EurekaClient
这部分在断点中为动态代理,实现类是CloudEurekaClient,我们在它的构造函数上打断点
public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
EurekaClientConfig config,
AbstractDiscoveryClientOptionalArgs<?> args,
ApplicationEventPublisher publisher) {
//这里进行了耗时的注册初始化等流程,后续我们会在neflix中单独分析
super(applicationInfoManager, config, args);
this.applicationInfoManager = applicationInfoManager;
this.publisher = publisher;
this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class, "eurekaTransport");
ReflectionUtils.makeAccessible(this.eurekaTransportField);
}
可以发现,在如下位置进行@Lazy
初始化Bean
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration.RefreshableEurekaClientConfiguration#eurekaClient
这里注入了3个类
- ApplicationInfoManager: Spring 直接注入的Singleton
- EurekaClientConfigBean: 反序列化
eureka.client
的配置信息 - EurekaInstanceConfig: 同上,反序列化
eureka.instance
的配置信息
这样EurekaClient的启动流程就分析完了
总结
说白了SpringBoot就是自动帮你反序列化并new出来一个Client,并配置了很多默认值。
关于@EnableDiscoveryClient
注解
读者可能会问:为什么这个注解放到最后写呢,它明明应该是“入口”啊。
关于这个入口,读者可以做一个黑盒实验,把@EnableDiscoveryClient
注解给去掉重新跑。发现无论是否加这个注解,在默认的配置下,都会自动注入EurekaClient
这些Bean,EurekaClient也照样会发送RPC心跳请求。
首先配置日志等级
logging.level.org.springframework.boot.autoconfigure=DEBUG
我们点击进入这个注解,这个注解实际实现类是EnableDiscoveryClientImportSelector
,在这里打上断点
org.springframework.cloud.client.discovery.EnableDiscoveryClientImportSelector#selectImports
然后启动SpringBoot,在Edgware.SR1
版本下,断点进入,返回
org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration
这个类基本啥也没干,只是一个反序列化类
因此网上大部分的Eureka源码分析文章从第一步入口就得出了错误的结论,这个注解与@EnableAutoConfiguration
虽然用的都是一个SpringFactoriesLoader
,但是并不是它帮你组装了各种类,也不是一个开关,返回的并不是同一个值。
因此建议各位读者无论看我的文章还是其他的博文,都要有批判性思维。除了精确校对的技术书籍,博客中的内容都是需要验证的。