|
| 1 | +**SpringBoot中文注释项目Github地址:** |
| 2 | + |
| 3 | +https://github.com/yuanmabiji/spring-boot-2.1.0.RELEASE |
| 4 | + |
| 5 | + |
| 6 | + |
| 7 | +本篇接 [SpringBoot事件监听机制源码分析(上) SpringBoot源码(九)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/9%20SpringBoot%E4%BA%8B%E4%BB%B6%E7%9B%91%E5%90%AC%E6%9C%BA%E5%88%B6%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90(%E4%B8%8A)%20SpringBoot%E6%BA%90%E7%A0%81(%E4%B9%9D).md) |
| 8 | + |
| 9 | +# 1 温故而知新 |
| 10 | +温故而知新,我们来简单回顾一下上篇的内容,上一篇我们分析了**SpringBoot启动时广播生命周期事件的原理**,现将关键步骤再浓缩总结下: |
| 11 | + |
| 12 | +1. 为广播SpringBoot内置生命周期事件做前期准备:1)首先加载`ApplicationListener`监听器实现类;2)其次加载SPI扩展类`EventPublishingRunListener`。 |
| 13 | +2. SpringBoot启动时利用`EventPublishingRunListener`广播生命周期事件,然后`ApplicationListener`监听器实现类监听相应的生命周期事件执行一些初始化逻辑的工作。 |
| 14 | +# 2 引言 |
| 15 | +上篇文章的侧重点是分析了SpringBoot启动时广播生命周期事件的原理,此篇文章我们再来详细分析SpringBoot内置的7种生命周期事件的源码。 |
| 16 | +# 3 SpringBoot生命周期事件源码分析 |
| 17 | +分析SpringBoot的生命周期事件,我们先来看一张类结构图: |
| 18 | + |
| 19 | +由上图可以看到事件类之间的关系: |
| 20 | +1. 最顶级的父类是JDK的事件基类`EventObject`; |
| 21 | +2. 然后Spring的事件基类`ApplicationEvent`继承了JDK的事件基类`EventObject`; |
| 22 | +3. 其次SpringBoot的生命周期事件基类`SpringApplicationEvent`继承了Spring的事件基类`ApplicationEvent`; |
| 23 | +4. 最后SpringBoot具体的7个生命周期事件类再继承了SpringBoot的生命周期事件基类`SpringApplicationEvent`。 |
| 24 | + |
| 25 | +# 3.1 JDK的事件基类EventObject |
| 26 | +`EventObject`类是JDK的事件基类,可以说是所有Java事件类的基本,即所有的Java事件类都直接或间接继承于该类,源码如下: |
| 27 | +```java |
| 28 | +// EventObject.java |
| 29 | + |
| 30 | +public class EventObject implements java.io.Serializable { |
| 31 | + |
| 32 | + private static final long serialVersionUID = 5516075349620653480L; |
| 33 | + |
| 34 | + /** |
| 35 | + * The object on which the Event initially occurred. |
| 36 | + */ |
| 37 | + protected transient Object source; |
| 38 | + /** |
| 39 | + * Constructs a prototypical Event. |
| 40 | + * |
| 41 | + * @param source The object on which the Event initially occurred. |
| 42 | + * @exception IllegalArgumentException if source is null. |
| 43 | + */ |
| 44 | + public EventObject(Object source) { |
| 45 | + if (source == null) |
| 46 | + throw new IllegalArgumentException("null source"); |
| 47 | + this.source = source; |
| 48 | + } |
| 49 | + /** |
| 50 | + * The object on which the Event initially occurred. |
| 51 | + * |
| 52 | + * @return The object on which the Event initially occurred. |
| 53 | + */ |
| 54 | + public Object getSource() { |
| 55 | + return source; |
| 56 | + } |
| 57 | + /** |
| 58 | + * Returns a String representation of this EventObject. |
| 59 | + * |
| 60 | + * @return A a String representation of this EventObject. |
| 61 | + */ |
| 62 | + public String toString() { |
| 63 | + return getClass().getName() + "[source=" + source + "]"; |
| 64 | + } |
| 65 | +} |
| 66 | +``` |
| 67 | +可以看到`EventObject`类只有一个属性`source`,这个属性是用来记录最初事件是发生在哪个类,举个栗子,比如在SpringBoot启动过程中会发射`ApplicationStartingEvent`事件,而这个事件最初是在`SpringApplication`类中发射的,因此`source`就是`SpringApplication`对象。 |
| 68 | +# 3.2 Spring的事件基类ApplicationEvent |
| 69 | +`ApplicationEvent`继承了DK的事件基类`EventObject`类,是Spring的事件基类,被所有Spring的具体事件类继承,源码如下: |
| 70 | +```java |
| 71 | +// ApplicationEvent.java |
| 72 | + |
| 73 | +/** |
| 74 | + * Class to be extended by all application events. Abstract as it |
| 75 | + * doesn't make sense for generic events to be published directly. |
| 76 | + * |
| 77 | + * @author Rod Johnson |
| 78 | + * @author Juergen Hoeller |
| 79 | + */ |
| 80 | +public abstract class ApplicationEvent extends EventObject { |
| 81 | + /** use serialVersionUID from Spring 1.2 for interoperability. */ |
| 82 | + private static final long serialVersionUID = 7099057708183571937L; |
| 83 | + /** System time when the event happened. */ |
| 84 | + private final long timestamp; |
| 85 | + /** |
| 86 | + * Create a new ApplicationEvent. |
| 87 | + * @param source the object on which the event initially occurred (never {@code null}) |
| 88 | + */ |
| 89 | + public ApplicationEvent(Object source) { |
| 90 | + super(source); |
| 91 | + this.timestamp = System.currentTimeMillis(); |
| 92 | + } |
| 93 | + /** |
| 94 | + * Return the system time in milliseconds when the event happened. |
| 95 | + */ |
| 96 | + public final long getTimestamp() { |
| 97 | + return this.timestamp; |
| 98 | + } |
| 99 | +} |
| 100 | +``` |
| 101 | +可以看到`ApplicationEvent`有且仅有一个属性`timestamp`,该属性是用来记录事件发生的时间。 |
| 102 | +# 3.3 SpringBoot的事件基类SpringApplicationEvent |
| 103 | +`SpringApplicationEvent`类继承了Spring的事件基类`ApplicationEvent`,是所有SpringBoot内置生命周期事件的父类,源码如下: |
| 104 | +```java |
| 105 | + |
| 106 | +/** |
| 107 | + * Base class for {@link ApplicationEvent} related to a {@link SpringApplication}. |
| 108 | + * |
| 109 | + * @author Phillip Webb |
| 110 | + */ |
| 111 | +@SuppressWarnings("serial") |
| 112 | +public abstract class SpringApplicationEvent extends ApplicationEvent { |
| 113 | + private final String[] args; |
| 114 | + public SpringApplicationEvent(SpringApplication application, String[] args) { |
| 115 | + super(application); |
| 116 | + this.args = args; |
| 117 | + } |
| 118 | + public SpringApplication getSpringApplication() { |
| 119 | + return (SpringApplication) getSource(); |
| 120 | + } |
| 121 | + public final String[] getArgs() { |
| 122 | + return this.args; |
| 123 | + } |
| 124 | +} |
| 125 | +``` |
| 126 | +可以看到`SpringApplicationEvent`有且仅有一个属性`args`,该属性就是SpringBoot启动时的命令行参数即标注`@SpringBootApplication`启动类中`main`函数的参数。 |
| 127 | +# 3.4 SpringBoot具体的生命周期事件类 |
| 128 | +接下来我们再来看一下`SpringBoot`内置生命周期事件即`SpringApplicationEvent`的具体子类们。 |
| 129 | +# 3.4.1 ApplicationStartingEvent |
| 130 | + |
| 131 | +```java |
| 132 | +// ApplicationStartingEvent.java |
| 133 | + |
| 134 | +public class ApplicationStartingEvent extends SpringApplicationEvent { |
| 135 | + public ApplicationStartingEvent(SpringApplication application, String[] args) { |
| 136 | + super(application, args); |
| 137 | + } |
| 138 | +} |
| 139 | +``` |
| 140 | +SpringBoot开始启动时便会发布`ApplicationStartingEvent`事件,其发布时机在环境变量Environment或容器ApplicationContext创建前但在注册`ApplicationListener`具体监听器之后,标志标志`SpringApplication`开始启动。 |
| 141 | +# 3.4.2 ApplicationEnvironmentPreparedEvent |
| 142 | +```java |
| 143 | +// ApplicationEnvironmentPreparedEvent.java |
| 144 | + |
| 145 | +public class ApplicationEnvironmentPreparedEvent extends SpringApplicationEvent { |
| 146 | + private final ConfigurableEnvironment environment; |
| 147 | + /** |
| 148 | + * Create a new {@link ApplicationEnvironmentPreparedEvent} instance. |
| 149 | + * @param application the current application |
| 150 | + * @param args the arguments the application is running with |
| 151 | + * @param environment the environment that was just created |
| 152 | + */ |
| 153 | + public ApplicationEnvironmentPreparedEvent(SpringApplication application, |
| 154 | + String[] args, ConfigurableEnvironment environment) { |
| 155 | + super(application, args); |
| 156 | + this.environment = environment; |
| 157 | + } |
| 158 | + /** |
| 159 | + * Return the environment. |
| 160 | + * @return the environment |
| 161 | + */ |
| 162 | + public ConfigurableEnvironment getEnvironment() { |
| 163 | + return this.environment; |
| 164 | + } |
| 165 | +} |
| 166 | +``` |
| 167 | +可以看到`ApplicationEnvironmentPreparedEvent`事件多了一个`environment`属性,我们不妨想一下,多了`environment`属性的作用是啥? |
| 168 | +答案就是`ApplicationEnvironmentPreparedEvent`事件的`environment`属性作用是利用事件发布订阅机制,相应监听器们可以从`ApplicationEnvironmentPreparedEvent`事件中取出`environment`变量,然后我们可以为`environment`属性增加属性值或读出`environment`变量中的值。 |
| 169 | +> **举个栗子:** `ConfigFileApplicationListener`监听器就是监听了`ApplicationEnvironmentPreparedEvent`事件,然后取出`ApplicationEnvironmentPreparedEvent`事件的`environment`属性,然后再为`environment`属性增加`application.properties`配置文件中的环境变量值。 |
| 170 | +
|
| 171 | +当SpringApplication已经开始启动且环境变量`Environment`已经创建后,并且为环境变量`Environment`配置了命令行和`Servlet`等类型的环境变量后,此时会发布`ApplicationEnvironmentPreparedEvent`事件。 |
| 172 | + |
| 173 | +监听`ApplicationEnvironmentPreparedEvent`事件的第一个监听器是`ConfigFileApplicationListener`,因为是`ConfigFileApplicationListener`监听器还要为环境变量`Environment`增加`application.properties`配置文件中的环境变量;此后还有一些也是监听`ApplicationEnvironmentPreparedEvent`事件的其他监听器监听到此事件时,此时可以说环境变量`Environment`几乎已经完全准备好了。 |
| 174 | +> **思考:** 监听同一事件的监听器们执行监听逻辑时是有顺序的,我们可以想一下这个排序逻辑是什么时候排序的?还有为什么要这样排序呢? |
| 175 | +# 3.4.3 ApplicationContextInitializedEvent |
| 176 | +```java |
| 177 | +// ApplicationContextInitializedEvent.java |
| 178 | + |
| 179 | +public class ApplicationContextInitializedEvent extends SpringApplicationEvent { |
| 180 | + private final ConfigurableApplicationContext context; |
| 181 | + /** |
| 182 | + * Create a new {@link ApplicationContextInitializedEvent} instance. |
| 183 | + * @param application the current application |
| 184 | + * @param args the arguments the application is running with |
| 185 | + * @param context the context that has been initialized |
| 186 | + */ |
| 187 | + public ApplicationContextInitializedEvent(SpringApplication application, |
| 188 | + String[] args, ConfigurableApplicationContext context) { |
| 189 | + super(application, args); |
| 190 | + this.context = context; |
| 191 | + } |
| 192 | + /** |
| 193 | + * Return the application context. |
| 194 | + * @return the context |
| 195 | + */ |
| 196 | + public ConfigurableApplicationContext getApplicationContext() { |
| 197 | + return this.context; |
| 198 | + } |
| 199 | +} |
| 200 | +``` |
| 201 | +可以看到`ApplicationContextInitializedEvent`事件多了个`ConfigurableApplicationContext`类型的`context`属性,`context`属性的作用同样是为了相应监听器可以拿到这个`context`属性执行一些逻辑,具体作用将在`3.4.4`详述。 |
| 202 | + |
| 203 | +`ApplicationContextInitializedEvent`事件在`ApplicationContext`容器创建后,且为`ApplicationContext`容器设置了`environment`变量和执行了`ApplicationContextInitializers`的初始化方法后但在bean定义加载前触发,标志ApplicationContext已经初始化完毕。 |
| 204 | + |
| 205 | +> **扩展:** 可以看到`ApplicationContextInitializedEvent`是在为`context`容器配置`environment`变量后触发,此时`ApplicationContextInitializedEvent`等事件只要有`context`容器的话,那么其他需要`environment`环境变量的监听器只需要从`context`中取出`environment`变量即可,从而`ApplicationContextInitializedEvent`等事件没必要再配置`environment`属性。 |
| 206 | +
|
| 207 | +# 3.4.4 ApplicationPreparedEvent |
| 208 | + |
| 209 | +```java |
| 210 | +// ApplicationPreparedEvent.java |
| 211 | + |
| 212 | +public class ApplicationPreparedEvent extends SpringApplicationEvent { |
| 213 | + private final ConfigurableApplicationContext context; |
| 214 | + /** |
| 215 | + * Create a new {@link ApplicationPreparedEvent} instance. |
| 216 | + * @param application the current application |
| 217 | + * @param args the arguments the application is running with |
| 218 | + * @param context the ApplicationContext about to be refreshed |
| 219 | + */ |
| 220 | + public ApplicationPreparedEvent(SpringApplication application, String[] args, |
| 221 | + ConfigurableApplicationContext context) { |
| 222 | + super(application, args); |
| 223 | + this.context = context; |
| 224 | + } |
| 225 | + /** |
| 226 | + * Return the application context. |
| 227 | + * @return the context |
| 228 | + */ |
| 229 | + public ConfigurableApplicationContext getApplicationContext() { |
| 230 | + return this.context; |
| 231 | + } |
| 232 | +} |
| 233 | +``` |
| 234 | +同样可以看到`ApplicationPreparedEvent`事件多了个`ConfigurableApplicationContext`类型的`context`属性,多了`context`属性的作用是能让监听该事件的监听器们能拿到`context`属性,监听器拿到`context`属性一般有如下作用: |
| 235 | +1. 从事件中取出`context`属性,然后可以增加一些后置处理器,比如`ConfigFileApplicationListener`监听器监听到`ApplicationPreparedEvent`事件后,然后取出`context`变量,通过`context`变量增加了`PropertySourceOrderingPostProcessor`这个后置处理器; |
| 236 | +2. 通过`context`属性取出`beanFactory`容器,然后注册一些`bean`,比如`LoggingApplicationListener`监听器通过`ApplicationPreparedEvent`事件的`context`属性取出`beanFactory`容器,然后注册了`springBootLoggingSystem`这个单例`bean`; |
| 237 | +3. 通过`context`属性取出`Environment`环境变量,然后就可以操作环境变量,比如`PropertiesMigrationListener`。 |
| 238 | + |
| 239 | +`ApplicationPreparedEvent`事件在`ApplicationContext`容器已经完全准备好时但在容器刷新前触发,在这个阶段`bean`定义已经加载完毕还有`environment`已经准备好可以用了。 |
| 240 | +# 3.4.5 ApplicationStartedEvent |
| 241 | +```java |
| 242 | +// ApplicationStartedEvent.java |
| 243 | + |
| 244 | +public class ApplicationStartedEvent extends SpringApplicationEvent { |
| 245 | + private final ConfigurableApplicationContext context; |
| 246 | + /** |
| 247 | + * Create a new {@link ApplicationStartedEvent} instance. |
| 248 | + * @param application the current application |
| 249 | + * @param args the arguments the application is running with |
| 250 | + * @param context the context that was being created |
| 251 | + */ |
| 252 | + public ApplicationStartedEvent(SpringApplication application, String[] args, |
| 253 | + ConfigurableApplicationContext context) { |
| 254 | + super(application, args); |
| 255 | + this.context = context; |
| 256 | + } |
| 257 | + /** |
| 258 | + * Return the application context. |
| 259 | + * @return the context |
| 260 | + */ |
| 261 | + public ConfigurableApplicationContext getApplicationContext() { |
| 262 | + return this.context; |
| 263 | + } |
| 264 | +} |
| 265 | +``` |
| 266 | +`ApplicationStartedEvent`事件将在容器刷新后但`ApplicationRunner`和`CommandLineRunner`的`run`方法执行前触发,标志`Spring`容器已经刷新,此时容器已经准备完毕了。 |
| 267 | + |
| 268 | +> **扩展:** 这里提到了`ApplicationRunner`和`CommandLineRunner`接口有啥作用呢?我们一般会在`Spring`容器刷新完毕后,此时可能有一些系统参数等静态数据需要加载,此时我们就可以实现了`ApplicationRunner`或`CommandLineRunner`接口来实现静态数据的加载。 |
| 269 | +# 3.4.6 ApplicationReadyEvent |
| 270 | +```java |
| 271 | +// ApplicationReadyEvent.java |
| 272 | + |
| 273 | +public class ApplicationReadyEvent extends SpringApplicationEvent { |
| 274 | + private final ConfigurableApplicationContext context; |
| 275 | + /** |
| 276 | + * Create a new {@link ApplicationReadyEvent} instance. |
| 277 | + * @param application the current application |
| 278 | + * @param args the arguments the application is running with |
| 279 | + * @param context the context that was being created |
| 280 | + */ |
| 281 | + public ApplicationReadyEvent(SpringApplication application, String[] args, |
| 282 | + ConfigurableApplicationContext context) { |
| 283 | + super(application, args); |
| 284 | + this.context = context; |
| 285 | + } |
| 286 | + /** |
| 287 | + * Return the application context. |
| 288 | + * @return the context |
| 289 | + */ |
| 290 | + public ConfigurableApplicationContext getApplicationContext() { |
| 291 | + return this.context; |
| 292 | + } |
| 293 | +} |
| 294 | +``` |
| 295 | +`ApplicationReadyEvent`事件在调用完`ApplicationRunner`和`CommandLineRunner`的`run`方法后触发,此时标志`SpringApplication`已经正在运行。 |
| 296 | +# 3.4.7 ApplicationFailedEvent |
| 297 | + |
| 298 | +```java |
| 299 | +// ApplicationFailedEvent.java |
| 300 | + |
| 301 | +public class ApplicationFailedEvent extends SpringApplicationEvent { |
| 302 | + private final ConfigurableApplicationContext context; |
| 303 | + private final Throwable exception; |
| 304 | + /** |
| 305 | + * Create a new {@link ApplicationFailedEvent} instance. |
| 306 | + * @param application the current application |
| 307 | + * @param args the arguments the application was running with |
| 308 | + * @param context the context that was being created (maybe null) |
| 309 | + * @param exception the exception that caused the error |
| 310 | + */ |
| 311 | + public ApplicationFailedEvent(SpringApplication application, String[] args, |
| 312 | + ConfigurableApplicationContext context, Throwable exception) { |
| 313 | + super(application, args); |
| 314 | + this.context = context; |
| 315 | + this.exception = exception; |
| 316 | + } |
| 317 | + /** |
| 318 | + * Return the application context. |
| 319 | + * @return the context |
| 320 | + */ |
| 321 | + public ConfigurableApplicationContext getApplicationContext() { |
| 322 | + return this.context; |
| 323 | + } |
| 324 | + /** |
| 325 | + * Return the exception that caused the failure. |
| 326 | + * @return the exception |
| 327 | + */ |
| 328 | + public Throwable getException() { |
| 329 | + return this.exception; |
| 330 | + } |
| 331 | +} |
| 332 | +``` |
| 333 | +可以看到`ApplicationFailedEvent`事件除了多了一个`context`属性外,还多了一个`Throwable`类型的`exception`属性用来记录SpringBoot启动失败时的异常。 |
| 334 | + |
| 335 | +`ApplicationFailedEvent`事件在SpringBoot启动失败时触发,标志SpringBoot启动失败。 |
| 336 | + |
| 337 | +# 4 小结 |
| 338 | +此篇文章相对简单,对SpringBoot内置的7种生命周期事件进行了详细分析。我们还是引用上篇文章的一张图来回顾一下这些生命周期事件及其用途: |
| 339 | + |
| 340 | + |
| 341 | + |
| 342 | +# 5 写在最后 |
| 343 | + |
| 344 | +由于有一些小伙伴们建议之前有些源码分析文章太长,导致耐心不够,看不下去,因此,之后的源码分析文章如果太长的话,笔者将会考虑拆分为几篇文章,这样就比较短小了,比较容易看完,嘿嘿。 |
| 345 | + |
| 346 | +**【源码笔记】Github地址:** |
| 347 | + |
| 348 | +https://github.com/yuanmabiji/Java-SourceCode-Blogs |
| 349 | + |
| 350 | +**Star搞起来,嘿嘿嘿!** |
| 351 | + |
0 commit comments