前言
请先闲暇时间阅读文章springboot-tomcat服务启动
- 从链接文章中,我们可以发现tomcat启动之后,会发布一个ServletWebServerInitializedEvent事件。
- 熟悉spring事件机制的朋友都会知道,事件event,和监听器Listener是共同存在的,通过观察者模式来完成spring的事件机制。
- 今天,我们就来聊聊这个事件,能涉及到哪些我们想知道的事情
ServletWebServerInitializedEvent
-
看源码得知,就是WebServerInitializedEvent的子类
-
并且在ServletWebServerApplicationContext中,tomcat启动之后,发布事件
@Override protected void finishRefresh() { super.finishRefresh(); WebServer webServer = startWebServer(); if (webServer != null) { publishEvent(new ServletWebServerInitializedEvent(webServer, this)); } }
-
获取到真实的tomcat端口
WebServer webServer; int port = webServer.getPort();
服务启动之后的webServer对象,从该对象#getPort()可以直接获取真实启动的端口。
(可以从ServletWebServerInitializedEvent事件中获取到该对象)
事件对应的监听器
1. ServerPortInfoApplicationContextInitializer
是springboot2.0.0之后,添加一个类
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.addApplicationListener(this);
}
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
String propertyName = "local." + getName(event.getApplicationContext()) + ".port";
setPortProperty(event.getApplicationContext(), propertyName, event.getWebServer().getPort());
}
- 从类名可以推测出,该类是一个ApplicationContextInitializer上下文初始化器,(spring或者springboot在创建出AppcationContext对象之后,都会去获取到容器中所有的ApplicationContextInitializer,然后进行初始化)
- 从该类实现逻辑看,就是将自身添加到applicationContext的监听器中,然后监听WebServerInitializedEvent 事件。并且将实际的tomcat端口存储到local.server.port属性中
2. AbstractAutoServiceRegistration
**可以看出该类,自身是一个WebServerInitializedEvent监听器**
该类是spring-cloud-common中的一个类,作用是: 将自身服务注册到注册中心的Abstract模板类。
从以下代码可以实现功能:就是获取WebServer的端口,然后进行服务注册
@Override
@SuppressWarnings("deprecation")
public void onApplicationEvent(WebServerInitializedEvent event) {
bind(event);
}
@Deprecated
public void bind(WebServerInitializedEvent event) {
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableWebServerApplicationContext) {
if ("management".equals(((ConfigurableWebServerApplicationContext) context)
.getServerNamespace())) {
return;
}
}
this.port.compareAndSet(0, event.getWebServer().getPort());
this.start();
}
public void start() {
if (!isEnabled()) {
if (logger.isDebugEnabled()) {
logger.debug("Discovery Lifecycle disabled. Not starting");
}
return;
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
if (!this.running.get()) {
this.context.publishEvent(
new InstancePreRegisteredEvent(this, getRegistration()));
register();
if (shouldRegisterManagement()) {
registerManagement();
}
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, getConfiguration()));
this.running.compareAndSet(false, true);
}
}
AbstractAutoServiceRegistration的子类![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/c204da42b2a4e27bfc2cd229991e75be.png)
- 从图中我们可以看出,有zookeeper和nacos作为注册中心的子类实现。
- 但是有的同学可能会说,之前常用的Eureak注册中心,不存在吗?当然不是,看如下
- 从上图可以得出,Eureka并没有继承spring-cloud-common的服务注册的规范,而是自己搞了一遍。
熟悉spring的朋友都知道,上图标记的两个接口,SmartLifecycle和SmartApplicationListener都是spring中的拥有生命周期的接口
所以要么是SmartLifecycle中的start进行注册,还是在SmartApplicationListener监听器中进行注册。不过从EurekaAutoServiceRegistration中得出,其实该类是做了服务注册两次调用
@Override
public void start() {
// only set the port if the nonSecurePort or securePort is 0 and this.port != 0
if (this.port.get() != 0) {
if (this.registration.getNonSecurePort() == 0) {
this.registration.setNonSecurePort(this.port.get());
}
if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
this.registration.setSecurePort(this.port.get());
}
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
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);
}
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof WebServerInitializedEvent) {
onApplicationEvent((WebServerInitializedEvent) event);
}
else if (event instanceof ContextClosedEvent) {
onApplicationEvent((ContextClosedEvent) event);
}
}
public void onApplicationEvent(WebServerInitializedEvent event) {
// TODO: take SSL into account
String contextName = event.getApplicationContext().getServerNamespace();
if (contextName == null || !contextName.equals("management")) {
int localPort = event.getWebServer().getPort();
if (this.port.get() == 0) {
log.info("Updating port to " + localPort);
this.port.compareAndSet(0, localPort);
start();
}
}
}
- 得出结论:
- EurekaAutoServiceRegistration所做之事和AbstractAutoServiceRegistration基本一模一样,
到底是谁先,谁后,谁抄袭了谁,不得而知,留给你们自己思考? - 另一点就是EurekaAutoServiceRegistration服务注册借用spring声明周期调用两次,
只不过第一次port为空,条件不满足。
第二次事件监听去注册,此时port获取到了,条件满足,去执行了注册。
各位可以思考,这样是否有意义,让我们猜测下当时作者开发的思路?
- EurekaAutoServiceRegistration所做之事和AbstractAutoServiceRegistration基本一模一样,