概述

从服务发现注解

   @EnableDiscoveryClient入手,剖析整个服务发现与注册过程

一,spring-cloud-common包

针对服务发现,本jar包定义了

DiscoveryClient 接口

public interface DiscoveryClient {
/**
* A human readable description of the implementation, used in HealthIndicator
* @return the description
*/
String description();
/**
* @deprecated use the {@link org.springframework.cloud.client.serviceregistry.Registration} bean instead
*
* @return ServiceInstance with information used to register the local service
*/
@Deprecated
ServiceInstance getLocalServiceInstance();
/**
* Get all ServiceInstances associated with a particular serviceId
* @param serviceId the serviceId to query
* @return a List of ServiceInstance
*/
List<ServiceInstance> getInstances(String serviceId);
/**
* @return all known service ids
*/
List<String> getServices(); }

EnableDiscoveryClient注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class) //关键在这句话
public @interface EnableDiscoveryClient {
/**
* If true, the ServiceRegistry will automatically register the local server.
*/
boolean autoRegister() default true;
}
@Import注解:支持导入普通的java类,并将其声明成一个bean
现在看EnableDiscoveryClientImportSelector类实现
@Order(Ordered.LOWEST_PRECEDENCE - 100)     //指定实例化bean的顺序
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector<EnableDiscoveryClient> { @Override
public String[] selectImports(AnnotationMetadata metadata) {
String[] imports = super.selectImports(metadata); AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(getAnnotationClass().getName(), true)); boolean autoRegister = attributes.getBoolean("autoRegister"); if (autoRegister) {
List<String> importsList = new ArrayList<>(Arrays.asList(imports));
importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
imports = importsList.toArray(new String[0]);
} return imports;
}
EnableDiscoveryClientImportSelector类继承SpringFactoryImportSelector类,该类是重点,如下:
public abstract class SpringFactoryImportSelector<T>
implements DeferredImportSelector, BeanClassLoaderAware, EnvironmentAware { private ClassLoader beanClassLoader; private Class<T> annotationClass; private Environment environment; private final Log log = LogFactory.getLog(SpringFactoryImportSelector.class); @SuppressWarnings("unchecked")
protected SpringFactoryImportSelector() {
this.annotationClass = (Class<T>) GenericTypeResolver
.resolveTypeArgument(this.getClass(), SpringFactoryImportSelector.class);
} @Override
public String[] selectImports(AnnotationMetadata metadata) {
if (!isEnabled()) {
return new String[0];
}
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(this.annotationClass.getName(), true)); Assert.notNull(attributes, "No " + getSimpleName() + " attributes found. Is "
+ metadata.getClassName() + " annotated with @" + getSimpleName() + "?"); // Find all possible auto configuration classes, filtering duplicates 重点在这个地方
List<String> factories = new ArrayList<>(new LinkedHashSet<>(SpringFactoriesLoader
.loadFactoryNames(this.annotationClass, this.beanClassLoader))); if (factories.isEmpty() && !hasDefaultFactory()) {
throw new IllegalStateException("Annotation @" + getSimpleName()
+ " found, but there are no implementations. Did you forget to include a starter?");
} if (factories.size() > 1) {
// there should only ever be one DiscoveryClient, but there might be more than
// one factory
log.warn("More than one implementation " + "of @" + getSimpleName()
+ " (now relying on @Conditionals to pick one): " + factories);
} return factories.toArray(new String[factories.size()]);
}

SpringFactoriesLoader调用loadFactoryNames其实加载META-INF/spring.factories下的class。

spring-cloud-netflix-eureka-client\src\main\resources\META-INF\spring.factories中配置:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration org.springframework.cloud.client.discovery.EnableDiscoveryClient=\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration

该类调用了DeferredImportSelector接口,即ImportSelector接口

,继承了selectImport方法,关于ImportSelector的具体作用,参考下面链接

selectImport详解

对于selectImport的调用,是在spring context 包中的ConfigurationClassParser进行解析

先流程走到EurekaClientAutoConfiguration类与EurekaDiscoveryClientConfiguration类

EurekaClientAutoConfiguration详解

  源码如下
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class) // 该注解的参数对应的类必须存在,否则不解析该注解修饰的配置类; @ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true) //属性必须存在,才解析该类
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
CommonsClientAutoConfiguration.class })
@AutoConfigureAfter(name = "org.springframework.cloud.autoconfigure.RefreshAutoConfiguration")
public class EurekaClientAutoConfiguration { @Value("${server.port:${SERVER_PORT:${PORT:8080}}}")
int nonSecurePort; @Value("${management.port:${MANAGEMENT_PORT:${server.port:${SERVER_PORT:${PORT:8080}}}}}")
int managementPort; @Value("${eureka.instance.hostname:${EUREKA_INSTANCE_HOSTNAME:}}")
String hostname; @Autowired
ConfigurableEnvironment env; //环境上下文,即配置文件相关的内容 @Bean
public HasFeatures eurekaFeature() {
return HasFeatures.namedFeature("Eureka Client", EurekaClient.class);
}
// EurekaClientConfigBean: 服务注册类配置,如指定注册中心,以及定义了各种超时时间,比如下线超时时间,注册超时时间等
// 这些注册类配置,以eureka.client为前缀
@Bean
@ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
public EurekaClientConfigBean eurekaClientConfigBean() {
EurekaClientConfigBean client = new EurekaClientConfigBean();
if ("bootstrap".equals(this.env.getProperty("spring.config.name"))) {
// We don't register during bootstrap by default, but there will be another
// chance later.
client.setRegisterWithEureka(false);
}
return client;
}
// EurekaInstanceConfigBean: 服务实例类配置 instance 实例配置 ,包括appname,instanceId,主机名等
// 1:元数据:实例元数据的配置,比如服务名称,实例名称,实例ip,端口,安全通信端口,非安全通信端口,心跳间隔等 此类信息会包装成InstanceInfo 然后传递到服务中心
// 以eureka.instance配置为前缀
@Bean
@ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils) {
EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);
instance.setNonSecurePort(this.nonSecurePort);
instance.setInstanceId(getDefaultInstanceId(this.env)); if (this.managementPort != this.nonSecurePort && this.managementPort != 0) {
if (StringUtils.hasText(this.hostname)) {
instance.setHostname(this.hostname);
}
RelaxedPropertyResolver relaxedPropertyResolver = new RelaxedPropertyResolver(env, "eureka.instance.");
String statusPageUrlPath = relaxedPropertyResolver.getProperty("statusPageUrlPath");
String healthCheckUrlPath = relaxedPropertyResolver.getProperty("healthCheckUrlPath");
if (StringUtils.hasText(statusPageUrlPath)) {
instance.setStatusPageUrlPath(statusPageUrlPath);
}
if (StringUtils.hasText(healthCheckUrlPath)) {
instance.setHealthCheckUrlPath(healthCheckUrlPath);
}
String scheme = instance.getSecurePortEnabled() ? "https" : "http";
instance.setStatusPageUrl(scheme + "://" + instance.getHostname() + ":"
+ this.managementPort + instance.getStatusPageUrlPath());
instance.setHealthCheckUrl(scheme + "://" + instance.getHostname() + ":"
+ this.managementPort + instance.getHealthCheckUrlPath());
}
return instance;
} @Bean
public DiscoveryClient discoveryClient(EurekaInstanceConfig config,
EurekaClient client) {
return new EurekaDiscoveryClient(config, client); //注册服务发现客户端类成bean
} @Bean
@ConditionalOnMissingBean(value = DiscoveryClientOptionalArgs.class, search = SearchStrategy.CURRENT)
public MutableDiscoveryClientOptionalArgs discoveryClientOptionalArgs() {
return new MutableDiscoveryClientOptionalArgs();
}

spring-cloud-netflix-eureka-client的EurekaDiscoveryClilent类只是对我们注解用到的DiscoveryClient接口的实现, 该类中的EurekaClient接口变量才是真正对服务发现实现,即Eureka-client中的EurekaClient接口实现类DiscoveryClient才是真正对发现服务进行了实现

DiscoveryClient类的实现内容:

1:向Eureka server 注册服务实例

2:向Eureka server 服务租约

3:当服务关闭期间,向Eureka server 取消租约

4:查询Eureka server 中的服务实例列表

Eureka client 还需要配置一个Eureka server 的url列表

DiscoveryClient类服务注册关键实现:

private void initScheduledTasks() {
int renewalIntervalInSecs;
int expBackOffBound;
if(this.clientConfig.shouldFetchRegistry()) {
renewalIntervalInSecs = this.clientConfig.getRegistryFetchIntervalSeconds();
expBackOffBound = this.clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
this.scheduler.schedule(new TimedSupervisorTask("cacheRefresh", this.scheduler, this.cacheRefreshExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.CacheRefreshThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS);
} if(this.clientConfig.shouldRegisterWithEureka()) {
renewalIntervalInSecs = this.instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
expBackOffBound = this.clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: renew interval is: " + renewalIntervalInSecs);

this.scheduler.schedule(new TimedSupervisorTask("heartbeat", this.scheduler, this.heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.HeartbeatThread(null)), (long)renewalIntervalInSecs, TimeUnit.SECONDS);
            /* 下面这行启动一个定时任务,这个定时任务的执行在后面,作用是向服务端注册 */
this.instanceInfoReplicator = new InstanceInfoReplicator(this, this.instanceInfo, this.clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2);
this.statusChangeListener = new StatusChangeListener() {
public String getId() {
return "statusChangeListener";
} public void notify(StatusChangeEvent statusChangeEvent) {
if(InstanceStatus.DOWN != statusChangeEvent.getStatus() && InstanceStatus.DOWN != statusChangeEvent.getPreviousStatus()) {
DiscoveryClient.logger.info("Saw local status change event {}", statusChangeEvent);
} else {
DiscoveryClient.logger.warn("Saw local status change event {}", statusChangeEvent);
} DiscoveryClient.this.instanceInfoReplicator.onDemandUpdate();
}
};
if(this.clientConfig.shouldOnDemandUpdateStatusChange()) {
this.applicationInfoManager.registerStatusChangeListener(this.statusChangeListener);
} this.instanceInfoReplicator.start(this.clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
} }

查看InstanceInfoReplicator类的实现,这个类是个线程类,查看里面的run方法

 this.discoveryClient.refreshInstanceInfo();
Long next = this.instanceInfo.isDirtyWithTime();
if(next != null) {
this.discoveryClient.register();
this.instanceInfo.unsetIsDirty(next.longValue());
var6 = false;
} else {
var6 = false;
}

这个discoveryclient.register调用了http请求,实现了注册,传入参数是com.netflix.appinfo.instanceInfo对象

【spring cloud】源码分析(一)的更多相关文章

  1. Spring Cloud源码分析(四)Zuul:核心过滤器

    通过之前发布的<Spring Cloud构建微服务架构(五)服务网关>一文,相信大家对于Spring Cloud Zuul已经有了一个基础的认识.通过前文的介绍,我们对于Zuul的第一印象 ...

  2. Spring Cloud 源码分析之OpenFeign

    OpenFeign是一个远程客户端请求代理,它的基本作用是让开发者能够以面向接口的方式来实现远程调用,从而屏蔽底层通信的复杂性,它的具体原理如下图所示. 在今天的内容中,我们需要详细分析OpenFei ...

  3. 精尽Spring MVC源码分析 - 寻找遗失的 web.xml

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  4. 精尽Spring MVC源码分析 - WebApplicationContext 容器的初始化

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  5. 精尽Spring Boot源码分析 - 序言

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  6. 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  7. 精尽Spring Boot源码分析 - 配置加载

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  8. 精尽Spring Boot源码分析 - 日志系统

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  9. 精尽Spring Boot源码分析 - @ConfigurationProperties 注解的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  10. Spring Security 源码分析(四):Spring Social实现微信社交登录

    社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ.人人网.开心网.新浪微博.搜狐微博.腾讯微博.淘宝.豆瓣.MSN.Google等社会化媒体账号登录该网站. 前言 ...

随机推荐

  1. ORACLE和MYSQL函数

    函数 编号 类别 ORACLE MYSQL 注释 1 数字函数 round(1.23456,4) round(1.23456,4) 一样: ORACLE:select round(1.23456,4) ...

  2. 微信小程序API登录凭证(code),获得的用户登录态拥有一定的时效性

    调用接口获取登录凭证(code)进而换取用户登录态信息,包括用户的唯一标识(openid) 及本次登录的 会话密钥(session_key).用户数据的加解密通讯需要依赖会话密钥完成. OBJECT参 ...

  3. Django Rest Framework 4

    目录 一.分页 二.视图 三.路由 四.渲染器 一.分页 试问如果当数据量特别大的时候,你是怎么解决分页的? 方式a.记录当前访问页数的数据id 方式b.最多显示120页等 方式c.只显示上一页,下一 ...

  4. hibernate反向生成奇葩错误

    错误场景 最近搞一个hibernate的项目,由于好久不用hibernate了,稍微有点生疏(自从用了ibatis–>mybatis后).这个项目用了hibernate,和ibatis.myba ...

  5. MySQL分组条件,group by order by limit 顺序

    having 中如果没有用聚合函数(必须sum,min),涉及到的字段名称必须在select 中有对应字段名称才可以,用到聚合函数可以不必在select中有相应字段名称的 limit 2,3:2表示从 ...

  6. xcode添加build phase

    [xcode添加build phase] xcode版本:5.0.2,找了半天,终于找到add build phase的方法,如下图.

  7. FZU2282 Wand

    题意 n个数字,要求至少k个数字位置不变,其余进行错排的方案数 分析 错排公式: D(n)=(n-1)[D(n-2)+D(n-1)]  如果n个数字,i个数字位置不变,其余进行错排的的方案数是C(n, ...

  8. linux常用命令大全(转)好东西要分享

    1.ls命令 就是list的缩写,通过ls 命令不仅可以查看linux文件夹包含的文件,而且可以查看文件权限(包括目录.文件夹.文件权限)查看目录信息等等 常用参数搭配: ls -a 列出目录所有文 ...

  9. 带你剖析WebGis的世界奥秘----瓦片式加载地图(转)

    带你剖析WebGis的世界奥秘----瓦片式加载地图 转:https://zxhtom.oschina.io/zxh/20160805.html  编程  java  2016/08/05 0留言,  ...

  10. Linux常用基本命令 1

    useradd 创建用户. password 修改密码. date 查看时间 man date 帮助文档.f往后翻 b往前翻 q退出.软修改 man hwclock 修改硬件时钟, cal 查看日历 ...