springboot之WebServerInitializedEvent事件

    技术2022-07-11  142

    前言 请先闲暇时间阅读文章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的子类

    从图中我们可以看出,有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获取到了,条件满足,去执行了注册。 各位可以思考,这样是否有意义,让我们猜测下当时作者开发的思路?
    Processed: 0.019, SQL: 9