
1.一个注解引发的血案
我们跟着这一个注解来看下源码的实现

从这个注解点进去查看:

接下来我们再点击进入@Import(EnableDiscoveryClientImportSelector.class) 这个类中:
这个类从名称上看是要导入什么类
我们如果要查看源码,最好是可以断点进入,这样就可以看到真实的数据是怎样的,比如如下类中,我们可以看到

AnnotationMetadata 的数据就是main函数中挂的所有的注解:

下面的代码就是获取到Eureka的注解,然后查看autoRegister这个值是true还是false,意思就是是否自动注册,然后继续往下看:

if (autoRegister) {
List<String> importsList = new ArrayList<>(Arrays.asList(imports));
importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
imports = importsList.toArray(new String[0]);
} else {
Environment env = getEnvironment();
if(ConfigurableEnvironment.class.isInstance(env)) {
ConfigurableEnvironment configEnv = (ConfigurableEnvironment)env;
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("spring.cloud.service-registry.auto-registration.enabled", false);
MapPropertySource propertySource = new MapPropertySource(
"springCloudDiscoveryClient", map);
configEnv.getPropertySources().addLast(propertySource);
}
}
我们从源码中看到如果自动注册是true,就会手动加入AutoServiceRegistrationConfiguration类:
org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration
然后我们就看下AutoServiceRegistrationConfiguration这个类是干嘛的:
/**
* @author Spencer Gibb
*/
@Configuration
@EnableConfigurationProperties(AutoServiceRegistrationProperties.class)
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
public class AutoServiceRegistrationConfiguration {
}
发现就是一个类,什么都没有,除了一些注解,我们查看一下这个注解:
AutoServiceRegistrationProperties

发现这是一个配置类,既然是配置类,那么肯定是有些地方会引用到这个类,我们查询使用这个类的地方:

然后就看到:
protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry, AutoServiceRegistrationProperties properties) {
this.serviceRegistry = serviceRegistry;
this.properties = properties;
}
发现这个类是把配置给注入进来而已,同时我们发现他同时还注册了一个serviceRegistry,我们查看一下这个类:

发现这个类是一个接口,既然是一个接口,那就会有实现类,我们查看下他的实现类:

我们可以看到他实现了注册的方法:
public void register(EurekaRegistration reg) {
this.maybeInitializeClient(reg);
if (log.isInfoEnabled()) {
log.info("Registering application " + reg.getInstanceConfig().getAppname() + " with eureka with status " + reg.getInstanceConfig().getInitialStatus());
}
reg.getApplicationInfoManager().setInstanceStatus(reg.getInstanceConfig().getInitialStatus());
reg.getHealthCheckHandler().ifAvailable((healthCheckHandler) -> {
reg.getEurekaClient().registerHealthCheck(healthCheckHandler);
});
}
这边我们继续使用打断点的方式来查看:
this.maybeInitializeClient(reg);
断点进去查看:
说明是强制初始化一些代理:

reg.getApplicationInfoManager().getInfo();
我们看到getinfo方法,点进去然后查看到info类,如图:

从断点上的信息来看:

可以发现他是一个Eureka客户端的实例信息。
然后我们就知道了this.maybeInitializeClient(reg);其实就是初始化一些Eureka的实例信息,那么我们继续看注册的代码:
reg.getApplicationInfoManager().setInstanceStatus(reg.getInstanceConfig().getInitialStatus());
reg.getHealthCheckHandler().ifAvailable((healthCheckHandler) -> {
reg.getEurekaClient().registerHealthCheck(healthCheckHandler);
});
继续断点我们看到
reg.getInstanceConfig().getInitialStatus()
这个值是up
这个register查看下来,我们发现就是初始化了实例的信息,但是并没有发现往注册中心发送数据的代码。那我们最终是希望看到这块的代码,所以在debug中找:

查看这个方法的上一层是干嘛的:

查看代码:
if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
this.serviceRegistry.register(this.registration);
this.context.publishEvent(new InstanceRegisteredEvent(this, this.registration.getInstanceConfig()));
this.running.set(true);
}
可以看到register这个执行之后就执行这个代码:
this.context.publishEvent(new InstanceRegisteredEvent(this, this.registration.getInstanceConfig()));
this.running.set(true);
我们查看代码知道this.context 是ApplicationContext context 这个类,springcloud的上下文。
这个上下文发布了一个事件:InstanceRegisteredEvent,从名字上看是 实例已注册事件。
但是我们再去注册中心查看,发现并没有有注册的实例

这就说明在发布之前或之后程序肯定做了什么处理,使得Eureka向注册中心发送了请求。然后this.running.set(true);就把实例的运行状态初始化为true了。
注意:在发布事件之后,服务注册到了注册中心,这中间发生了什么可以参考这个链接:https://mp.weixin.qq.com/s/47TUd96NMz67_PCDyvyInQ
下面我们继续讲客户端注册到注册中心的过程:

boolean register() throws Throwable {
logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
EurekaHttpResponse<Void> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);
throw e;
}
if (logger.isInfoEnabled()) {
logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());
}
return httpResponse.getStatusCode() == 204;
}
我们断点查看下,发现这个class是:SessionedEurekaHttpClient

我们打开SessionedEurekaHttpClient查询register方法,发现在父类中有实现:
EurekaHttpClientDecorator

protected abstract <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor);
@Override
public EurekaHttpResponse<Void> register(final InstanceInfo info) {
return execute(new RequestExecutor<Void>() {
@Override
public EurekaHttpResponse<Void> execute(EurekaHttpClient delegate) {
return delegate.register(info);
}
@Override
public RequestType getRequestType() {
return RequestType.Register;
}
});
}
我们看下这个代码 有一个execute方法,这个方法是一个抽象方法,也就是说每一个子类都会去实现这个方法,然后下面还有一个register方法,这个方法调用了execute,然后还传入了一个delegate 一个代理类。
这里讲到代理类了,就讲下两个设计模式,代理模式和装饰器模式:

关于代理模式想要详细了解的参见链接:
静态代理和动态代理有什么区别?--乐字节java - dirft的文章 - 知乎
https://zhuanlan.zhihu.com/p/159112639

关于装饰器模式如果想详细了解的参见链接:java中什么是装饰者模式? - 不知道的回答 - 知乎
https://www.zhihu.com/question/32007641/answer/54428318
了解完之后大家会产生疑问,代理模式和装饰器模式都是对一个对象的功能增强,那到底区别是什么,在什么场景下用什么模式,下面这个帖子就写的不错:
Java中“装饰模式”和“代理模式”有啥区别? - 海岸红杉的回答 - 知乎
https://www.zhihu.com/question/41988550/answer/567925484
我们继续来看Eureka的源码:

打断点来分析,进入到这个execute方法,首先获取一些时间的值:
if (delay >= currentSessionDurationMs) {
logger.debug("Ending a session and starting anew");
lastReconnectTimeStamp = now;
currentSessionDurationMs = randomizeSessionDuration(sessionDurationMs);
TransportUtils.shutdown(eurekaHttpClientRef.getAndSet(null));
}
如果这个delay值大于currentSessionDurationMs,那么就结束当前的session然后重启一个新的。
然后获取eurekaHttpClient实例,然后调用父类的execute方法。

可以看到这里是在配置的可重复的次数里面来进行一些逻辑。

如果节点中有这么的节点是坏的,那么就再重试一次。在下面的逻辑就是查询出所有坏的和正常的节点。
我们继续看之前的逻辑:

下面就直接进入到最后:

这里就是服务端往注册中心发送请求的源码。
本帖子把源码非常粗糙的过了一遍,因为源码确实非常复杂,我本人也只了解个大概,所以更细节的地方还需要个人仔细debug和查阅资料的。
网友评论