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下打断点,可以发现实现类分别是EurekaDiscoveryClientSimpleDiscoveryClient,明显前一个才是真正负责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,但是并不是它帮你组装了各种类,也不是一个开关,返回的并不是同一个值。

因此建议各位读者无论看我的文章还是其他的博文,都要有批判性思维。除了精确校对的技术书籍,博客中的内容都是需要验证的。