【spring cloud】源码分析(一)
概述
从服务发现注解
@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的调用,是在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】源码分析(一)的更多相关文章
- Spring Cloud源码分析(四)Zuul:核心过滤器
通过之前发布的<Spring Cloud构建微服务架构(五)服务网关>一文,相信大家对于Spring Cloud Zuul已经有了一个基础的认识.通过前文的介绍,我们对于Zuul的第一印象 ...
- Spring Cloud 源码分析之OpenFeign
OpenFeign是一个远程客户端请求代理,它的基本作用是让开发者能够以面向接口的方式来实现远程调用,从而屏蔽底层通信的复杂性,它的具体原理如下图所示. 在今天的内容中,我们需要详细分析OpenFei ...
- 精尽Spring MVC源码分析 - 寻找遗失的 web.xml
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - WebApplicationContext 容器的初始化
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring Boot源码分析 - 序言
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 配置加载
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 日志系统
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - @ConfigurationProperties 注解的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- Spring Security 源码分析(四):Spring Social实现微信社交登录
社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ.人人网.开心网.新浪微博.搜狐微博.腾讯微博.淘宝.豆瓣.MSN.Google等社会化媒体账号登录该网站. 前言 ...
随机推荐
- POJ1161(并查集)
1.题目链接地址 http://poj.org/problem?id=1161 2.源代码 #include <iostream> using namespace std; ]; ]; i ...
- Java虚拟机(一):JVM的运行机制
一.JVM启动流程 通过java +xxx(或javaw)启动java虚拟机 装载配置,会在当前路径中寻找jvm的config配置文件. 根据查找jvm.dll文件.这个文件就是java虚拟机的主要实 ...
- JS 实现 unicode 中文互转
// 转为unicode 编码 function encodeUnicode(str) { var res = []; for ( var i=0; i<str.length; i++ ) { ...
- angular.js简单入门。
小弟刚接触angular js 就写了一个简单的入门.后续慢慢补... 首先看 html 页面. <html> <meta charset="UTF-8"> ...
- javascript使用技巧总结,不断更新...
1.使用a标签来获得当前页面相对地址的绝对地址 function getAbsoluteUrl(url){ var a; if(!a) a = document.createElement('a'); ...
- SpringBoot15 sell02 订单模块
1 订单模块 1.1 MySQL数据表 订单模块涉及到两个数据表: 订单表:主要存储订单相关的基本信息 DROP TABLE IF EXISTS `order_master`; CREATE TABL ...
- jquery dropdownlist.js
$.fn.extend({ SetDict: function (option) { var txtControl = $(this); if (!txtControl.hasClass(" ...
- Entity Framework在不同数据库下的配置
http://blog.csdn.net/weiky626/article/details/17068593 http://blog.csdn.net/niewq/article/details/41 ...
- OpenCV2.3.0在VS中的配置
本文为cherish总结,这里先收藏着了啦啦啦!!! vs中配置时尽量在全局配置中修改,否则每次新建项目都需要重新配置全局配置步骤如下:1.“视图”菜单 -> (其他窗口->)属性管理器 ...
- springboot-条件化注解
在项目中,有时会遇到我们的Configuration.Bean.Service等等的bean组件需要依条件按需加载的情况.那么Spring Boot怎么做的呢?它为此定义了许多有趣的条件,当我们将它们 ...