总结一句话
用@EnableDiscoveryClient注解客户端-启动类,配合@springbootapplication,完成两个步骤:
自动读取spring-factories文件的全限定类名内容
通过selectImport对这些类进行初始化
背景
spring.factories作用
在maven依赖:
spring-cloud-netflix-eureka-client/2.2.6.RELEASE/spring-cloud-netflix-eureka-client-2.2.6.RELEASE.jar!的 META-INF 目录下,有一个spring.factories文件:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.loadbalancer.LoadBalancerEurekaAutoConfigurationorg.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaConfigServerBootstrapConfiguration
@SpringBootApplication为一个组合注解,通过@EnableAutoConfiguration开启自动装配。
spring.factories
文件位于JAR包的META-INF
目录下。当Spring Boot应用启动时,它会自动扫描所有的
spring.factories
文件,并根据里面的配置加载相应的类。
在上述例子中,当应用启动时,EurekaClientAutoConfiguration 类将会被自动注册为一个bean,并可以通过依赖注入在其他组件中使用。
@SpringBootApplication注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
AutoConfigurationImportSelector资源类
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {// ....protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "+ "are using a custom packaging, make sure that file is correct.");return configurations;}
}
分析:
getCandidateConfigurations:此处是自动导入spring.factories文件的全部实现类
我们从debug可以看到,已经读出了spring.factories的配置类,包括了EurekaClientAutoConfiguration和EurekaDiscoveryClientConfiguration 两个自动装配类。
小结
EurekaClientAutoConfiguration和EurekaDiscoveryClientConfiguration在Spring Cloud Eureka中扮演着不同的角色,它们的主要区别在于功能和职责的不同。
自动化配置类 | 功能 | 职责 |
EurekaClientAutoConfiguration | 配置EurekaClient | 确保了Eureka客户端能够正确地: - 注册到Eureka服务端 - 周期性地发送心跳信息来更新服务租约 - 下线时通知Eureka服务端 - 获取服务实例列表; 更侧重于Eureka客户端的基本配置和功能实现 |
EurekaDiscoveryClientConfiguration | 配置EurekaDiscoveryClient | 创建RefreshScopeRefreshedEvent事件的监听类,用于重启注册; 更多地涉及到服务的自动注册、健康检查以及事件处理等方面 |
这两个配置类共同工作,确保了Spring Boot应用能够顺利地与Eureka服务注册中心进行交互,实现服务的注册和发现。
所以下文我们讨论客户端的4个功能(注册租约、更新租约、下线租约、获取服务列表),都是倾向于分析EurekaClient的代码。
EurekaClientAutoConfiguration自动配置类
自动化装配EurekaClient的相关bean,
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = {"org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration","org.springframework.cloud.autoconfigure.RefreshAutoConfiguration","org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration","org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" })
public class EurekaClientAutoConfiguration {// 【1】EurekaClientConfigBean:Eureka客户端bean,保存了环境配置信息@Bean@ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {return new EurekaClientConfigBean();}//【2】EurekaInstanceConfigBean:Eureka客户端实例bean,保存了实例信息@Bean@ConditionalOnMissingBean(value = EurekaInstanceConfig.class,search = SearchStrategy.CURRENT)public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,ManagementMetadataProvider managementMetadataProvider) {String hostname = getProperty("eureka.instance.hostname");boolean preferIpAddress = Boolean.parseBoolean(getProperty("eureka.instance.prefer-ip-address"));String ipAddress = getProperty("eureka.instance.ip-address");boolean isSecurePortEnabled = Boolean.parseBoolean(getProperty("eureka.instance.secure-port-enabled"));String serverContextPath = env.getProperty("server.servlet.context-path", "/");int serverPort = Integer.parseInt(env.getProperty("server.port", env.getProperty("port", "8080")));Integer managementPort = env.getProperty("management.server.port", Integer.class);String managementContextPath = env.getProperty("management.server.servlet.context-path");Integer jmxPort = env.getProperty("com.sun.management.jmxremote.port",Integer.class);EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);instance.setNonSecurePort(serverPort);instance.setInstanceId(getDefaultInstanceId(env));instance.setPreferIpAddress(preferIpAddress);instance.setSecurePortEnabled(isSecurePortEnabled);if (StringUtils.hasText(ipAddress)) {instance.setIpAddress(ipAddress);}if (isSecurePortEnabled) {instance.setSecurePort(serverPort);}if (StringUtils.hasText(hostname)) {instance.setHostname(hostname);}String statusPageUrlPath = getProperty("eureka.instance.status-page-url-path");String healthCheckUrlPath = getProperty("eureka.instance.health-check-url-path");// ....省略很多配置项赋值setupJmxPort(instance, jmxPort);return instance;}
}
分析:
构造了EurekaInstanceConfigBean
EurekaClientConfigBean是一个配置类,会读取所有eureka.client打头的配置项,例如,eureka.client.serviceUrl.defaultZone=http://localhost:8881/eureka/,http://localhost2:8882/eureka/
构造了EurekaClientConfigBean
EurekaClientConfiguration配置类
EurekaClientAutoConfiguration 内部有一个内部配置类EurekaClientConfiguration,它会继续初始化 CloudEurekaClient 和 ApplicationInfoManager 两个bean。
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingRefreshScope
protected static class EurekaClientConfiguration {// 【3】@Bean(destroyMethod = "shutdown")@ConditionalOnMissingBean(value = EurekaClient.class,search = SearchStrategy.CURRENT)public EurekaClient eurekaClient(ApplicationInfoManager manager,EurekaClientConfig config) {return new CloudEurekaClient(manager, config, this.optionalArgs,this.context);}// 【4】@Bean@ConditionalOnMissingBean(value = ApplicationInfoManager.class,search = SearchStrategy.CURRENT)public ApplicationInfoManager eurekaApplicationInfoManager(EurekaInstanceConfig config) {InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);return new ApplicationInfoManager(config, instanceInfo);}
}
小结
EurekaClientAutoConfiguration构造了EurekaClientConfigBean、EurekaInstanceConfigBean以及EurekaServiceRegistry,之后在这几个对象的基础上进一步构建ApplicationInfoManager、CloudEurekaClient等。
其中ApplicationInfoManager负责变更实例状态并发布StatusChangeEvent事件,而CloudEurekaClient继承了com.netflix.discovery.DiscoveryClient,里头包含了statusChangeListener用于响应StatusChangeEvent,最后触发的是DiscoveryClient.register方法,与远程的Eureka Server通信,同步实例状态。
另外,EurekaClientAutoConfiguration构建的EurekaClient-bean,会被EurekaDiscoveryClientConfiguration构建的EurekaDiscoveryClient,所注入使用
EurekaDiscoveryClientConfiguration自动配置类
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
public class EurekaDiscoveryClientConfiguration {// 【5】@Bean@ConditionalOnMissingBeanpublic EurekaDiscoveryClient discoveryClient(EurekaClient client,EurekaClientConfig clientConfig) {return new EurekaDiscoveryClient(client, clientConfig);}// 【6】@Configuration(proxyBeanMethods = false)@ConditionalOnProperty(value = "eureka.client.healthcheck.enabled", matchIfMissing = false)protected static class EurekaHealthCheckHandlerConfiguration {@Autowired(required = false)private StatusAggregator statusAggregator = new SimpleStatusAggregator();@Bean@ConditionalOnMissingBean(HealthCheckHandler.class)public EurekaHealthCheckHandler eurekaHealthCheckHandler() {return new EurekaHealthCheckHandler(this.statusAggregator);}}
}
分析:
【5】构建EurekaDiscoveryClient,使用了EurekaClient 和 EurekaClientConfig
【6】构建EurekaHealthCheckHandler
至此,完成了EurekaDiscoveryClient的创建