├── Disruptor ├── Disruptor广播模式与执行顺序链源码分析.md ├── README.md ├── images │ ├── img.png │ └── img_1.png └── 初识Disruptor框架.md ├── JDK ├── 1 Java是如何实现自己的SPI机制的? JDK源码(一).md └── README.md ├── JUC ├── Java是如何实现Future模式的?万字详解!.md └── README.md ├── README.md ├── Spring ├── 1 模仿Spring事件机制实现自定义事件驱动编程 Spring源码(一).md ├── 2 Spring是如何实现事件监听机制的? Spring源码(二).md └── README.md ├── SpringBoot ├── 1 如何搭建自己的SpringBoot源码调试环境? SpringBoot源码(一).md ├── 10 SpringBoot内置生命周期事件详解 SpringBoot源码(十).md ├── 2 如何分析SpringBoot源码模块及结构? SpringBoot源码(二).md ├── 3 助力SpringBoot自动配置的条件注解原理揭秘 SpringBoot源码(三).md ├── 4 SpringBoot是如何实现自动配置的? SpringBoot源码(四).md ├── 5 SpringBoot的配置属性值是如何绑定的? SpringBoot源码(五).md ├── 6 SpringBoot内置的各种Starter是怎样构建的? SpringBoot源码(六).md ├── 7 SpringBoot的启动流程是怎样的?SpringBoot源码(七).md ├── 8 SpringApplication对象是如何构建的? SpringBoot源码(八).md ├── 9 SpringBoot事件监听机制源码分析(上) SpringBoot源码(九).md └── README.md ├── 分析开源项目源码,我们该如何入手分析?(授人以渔).md └── 跟大家聊聊我们为什么要学习源码?学习源码对我们有用吗?.md /Disruptor/Disruptor广播模式与执行顺序链源码分析.md: -------------------------------------------------------------------------------- 1 | > 【源码笔记】专注于Java后端系列框架源码分析,Github地址:https://github.com/yuanmabiji/Java-SourceCode-Blogs 2 | 3 | # 1 前言 4 | 5 | 本篇文章开始`Disruptor`的源码分析,理解起来相对比较困难,特别是`Disruptor`的`sequenceBarrier`的理解,`sequenceBarrier`包括生产者与消费者之间的`gatingSequence`以及消费者与消费者之间的`dependentSequence`。此外,`Disruptor`源码中的`sequence`变量也比较多,需要捋清楚各种`sequence`的含义。最后,建议小伙伴们动手调试理解,效果会更好。 6 | 7 | # 2 Disruptor六边形DEMO 8 | 9 | 分析源码前,先来看看`Disruptor`六边形执行器链的`DEMO`。 10 | 11 | ```java 12 | public class LongEventMain 13 | { 14 | private static final int BUFFER_SIZE = 1024; 15 | public static void main(String[] args) throws Exception 16 | { 17 | // 1,构建disruptor 18 | final Disruptor disruptor = new Disruptor( 19 | new LongEventFactory(), 20 | BUFFER_SIZE, 21 | Executors.newFixedThreadPool(5), // 【注意点】线程池需要保证足够的线程:有多少个消费者就要有多少个线程,否则有些消费者将不会执行,生产者可能也会一直阻塞下去 22 | ProducerType.SINGLE, 23 | new YieldingWaitStrategy() 24 | ); 25 | 26 | EventHandler eventHandler1 = new LongEventHandler1(); 27 | EventHandler eventHandler2 = new LongEventHandler2(); 28 | EventHandler eventHandler3 = new LongEventHandler3(); 29 | EventHandler eventHandler4 = new LongEventHandler4(); 30 | EventHandler eventHandler5 = new LongEventHandler5(); 31 | 32 | // 方式1 构建串行执行顺序: 33 | /*disruptor 34 | .handleEventsWith(eventHandler1) 35 | .handleEventsWith(eventHandler2) 36 | .handleEventsWith(eventHandler3) 37 | .handleEventsWith(eventHandler4) 38 | .handleEventsWith(eventHandler5);*/ 39 | 40 | // 方式2 构建并行执行顺序 41 | /*disruptor 42 | .handleEventsWith(eventHandler1, eventHandler2, eventHandler3, eventHandler4, eventHandler5);*/ 43 | 44 | // 方式3 构建菱形执行顺序 45 | /*disruptor.handleEventsWith(eventHandler1, eventHandler2) 46 | .handleEventsWith(eventHandler3);*/ 47 | 48 | // 2,构建eventHandler执行链 49 | // 方式4 构建六边形执行顺序 50 | disruptor.handleEventsWith(eventHandler1, eventHandler3); 51 | disruptor.after(eventHandler1).handleEventsWith(eventHandler2); 52 | disruptor.after(eventHandler3).handleEventsWith(eventHandler4); 53 | disruptor.after(eventHandler2, eventHandler4).handleEventsWith(eventHandler5); 54 | 55 | // 3, 启动disruptor即启动线程池线程执行BatchEventProcessor任务 56 | disruptor.start(); 57 | 58 | // 4,生产者往ringBuffer生产数据并唤醒所有的消费者消费数据 59 | RingBuffer ringBuffer = disruptor.getRingBuffer(); 60 | ByteBuffer bb = ByteBuffer.allocate(8); 61 | bb.putLong(0, 666); 62 | ringBuffer.publishEvent(new LongEventTranslatorOneArg(), bb); 63 | } 64 | 65 | static class LongEventTranslatorOneArg implements EventTranslatorOneArg { 66 | @Override 67 | public void translateTo(LongEvent event, long sequence, ByteBuffer buffer) { 68 | event.set(buffer.getLong(0)); 69 | } 70 | } 71 | 72 | static class LongEvent 73 | { 74 | private long value; 75 | 76 | public void set(long value) 77 | { 78 | this.value = value; 79 | } 80 | 81 | public long get() { 82 | return this.value; 83 | } 84 | } 85 | 86 | static class LongEventFactory implements EventFactory 87 | { 88 | @Override 89 | public LongEvent newInstance() 90 | { 91 | return new LongEvent(); 92 | } 93 | } 94 | 95 | static class LongEventHandler1 implements EventHandler 96 | { 97 | @Override 98 | public void onEvent(LongEvent event, long sequence, boolean endOfBatch) 99 | { 100 | System.out.println("LongEventHandler1-" + event.get() + " executed by " + Thread.currentThread().getName()); 101 | } 102 | } 103 | 104 | static class LongEventHandler2 implements EventHandler 105 | { 106 | @Override 107 | public void onEvent(LongEvent event, long sequence, boolean endOfBatch) 108 | { 109 | System.out.println("LongEventHandler2-" + event.get() + " executed by " + Thread.currentThread().getName()); 110 | } 111 | } 112 | 113 | static class LongEventHandler3 implements EventHandler 114 | { 115 | @Override 116 | public void onEvent(LongEvent event, long sequence, boolean endOfBatch) 117 | { 118 | System.out.println("LongEventHandler3-" + event.get() + " executed by " + Thread.currentThread().getName()); 119 | } 120 | } 121 | 122 | static class LongEventHandler4 implements EventHandler 123 | { 124 | @Override 125 | public void onEvent(LongEvent event, long sequence, boolean endOfBatch) 126 | { 127 | System.out.println("LongEventHandler4-" + event.get() + " executed by " + Thread.currentThread().getName()); 128 | } 129 | } 130 | 131 | static class LongEventHandler5 implements EventHandler 132 | { 133 | @Override 134 | public void onEvent(LongEvent event, long sequence, boolean endOfBatch) 135 | { 136 | System.out.println("LongEventHandler5-" + event.get() + " executed by " + Thread.currentThread().getName()); 137 | } 138 | } 139 | } 140 | ``` 141 | 这个`Demo`也是`Disruptor`广播模式与执行顺序链构建的`Demo`,有以下值得注意的点: 142 | 1. 生产者总是要把`RingBuffer`填充完一圈后才会考虑追赶消费者进度的问题; 143 | 2. 线程池需要保证足够的线程:有多少个消费者就要有多少个线程,否则有些消费者将不会执行(消费者线程起不来),生产者生产完一圈`RingBuffer`后即使有新的数据生产者也会一直阻塞下去; 144 | 3. 消费者执行链中,每个消费者都是独立的消费线程,决定当前消费者消不消费的只有其依赖的消费者有无消费完,消费者进行消费第二个数据时无须等整个执行链执行完才能消费。比如有执行链:A->B-C,生产者在`Ringbuffer`中生产了2个数据,那么消费顺序可能为A->B->C->A->B-C,也可能为A->B-A->B->C->C,也可能为A->A->B->B->C->C等。 145 | 4. 生产者填充完第一圈`Ringbuffer`后,当要追赶消费者消费速度时,此时生产者能否继续生产取决于执行链最后一个消费者的消费速度。比如有执行链:A->B-C,生产者的生产速度取决于消费者C的消费速度。 146 | 147 | # 3 初始化Disruptor实例 148 | 149 | 先来看下前面DEMO中的初始化`Disruptor`实例代码: 150 | 151 | ```java 152 | // 1,构建disruptor 153 | final Disruptor disruptor = new Disruptor( 154 | new LongEventFactory(), 155 | BUFFER_SIZE, 156 | Executors.newFixedThreadPool(5), // 线程池需要保证足够的线程 157 | ProducerType.SINGLE, 158 | new YieldingWaitStrategy() 159 | ); 160 | ``` 161 | 162 | 这句代码最终是给`Disruptor`的`ringBuffer`和`executor`属性赋值: 163 | 164 | ```java 165 | // Disruptor.java 166 | public Disruptor( 167 | final EventFactory eventFactory, 168 | final int ringBufferSize, 169 | final Executor executor, 170 | final ProducerType producerType, 171 | final WaitStrategy waitStrategy) 172 | { 173 | this( 174 | // 创建RingBuffer实例 175 | RingBuffer.create(producerType, eventFactory, ringBufferSize, waitStrategy), 176 | executor); 177 | } 178 | 179 | private Disruptor(final RingBuffer ringBuffer, final Executor executor) 180 | { 181 | this.ringBuffer = ringBuffer; 182 | this.executor = executor; 183 | } 184 | ``` 185 | 186 | 那么`RingBuffer`实例又是如何创建的呢?我们来看下`RingBuffer.create(producerType, eventFactory, ringBufferSize, waitStrategy)`这句源码: 187 | 188 | ```java 189 | // RingBuffer.java 190 | public static RingBuffer create( 191 | final ProducerType producerType, 192 | final EventFactory factory, 193 | final int bufferSize, 194 | final WaitStrategy waitStrategy) 195 | { 196 | switch (producerType) 197 | { 198 | case SINGLE: 199 | return createSingleProducer(factory, bufferSize, waitStrategy); 200 | case MULTI: 201 | return createMultiProducer(factory, bufferSize, waitStrategy); 202 | default: 203 | throw new IllegalStateException(producerType.toString()); 204 | } 205 | } 206 | ``` 207 | 208 | 首先会根据`producerType`来创建不同的`Producer`,以创建`SingleProducerSequencer`实例为例进去源码看下: 209 | 210 | ```java 211 | // RingBuffer.java 212 | public static RingBuffer createSingleProducer( 213 | final EventFactory factory, 214 | final int bufferSize, 215 | final WaitStrategy waitStrategy) 216 | { 217 | // 1,创建SingleProducerSequencer实例 218 | SingleProducerSequencer sequencer = new SingleProducerSequencer(bufferSize, waitStrategy); 219 | // 2,创建RingBuffer实例 220 | return new RingBuffer<>(factory, sequencer); 221 | } 222 | ``` 223 | 224 | ## 3.1 创建SingleProducerSequencer实例 225 | 226 | 首先创建了`SingleProducerSequencer`实例,给`SingleProducerSequencer`实例的`bufferSize`和`waitStrategy`赋初值; 227 | 228 | ```java 229 | // AbstractSequencer.java 230 | // SingleProducerSequencer父类 231 | public AbstractSequencer(int bufferSize, WaitStrategy waitStrategy) 232 | { 233 | this.bufferSize = bufferSize; 234 | this.waitStrategy = waitStrategy; 235 | } 236 | ``` 237 | 238 | 此外,创建`SingleProducerSequencer`实例时还初始化了一个成员变量`cursor`: 239 | 240 | ```java 241 | protected final Sequence cursor = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); 242 | ``` 243 | 244 | 即给`cursor`赋值了一个`Sequence`实例对象,`Sequence`是标识`RingBuffer`环形数组的下标,同时生产者和消费者也会同时维护各自的`Sequence`。最重要的是,**`Sequence`通过填充CPU缓存行避免了伪共享带来的性能损耗**,来看下其填充缓存行源码: 245 | 246 | ```java 247 | // Sequence.java 248 | class LhsPadding 249 | { 250 | // 左填充 251 | protected long p1, p2, p3, p4, p5, p6, p7; 252 | } 253 | 254 | class Value extends LhsPadding 255 | { 256 | // Sequence值 257 | protected volatile long value; 258 | } 259 | 260 | class RhsPadding extends Value 261 | { 262 | // 右填充 263 | protected long p9, p10, p11, p12, p13, p14, p15; 264 | } 265 | 266 | public class Sequence extends RhsPadding 267 | { 268 | // ... 269 | } 270 | ``` 271 | 272 | 273 | 274 | ## 3.2 创建RingBuffer实例 275 | 276 | 然后核心是创建`RingBuffer`实例,看看最终创建`RingBuffer`实例源码: 277 | 278 | ```java 279 | // RingBuffer.java 280 | RingBufferFields( // RingBufferFields为RingBuffer父类 281 | final EventFactory eventFactory, 282 | final Sequencer sequencer) 283 | { 284 | this.sequencer = sequencer; 285 | this.bufferSize = sequencer.getBufferSize(); 286 | 287 | if (bufferSize < 1) 288 | { 289 | throw new IllegalArgumentException("bufferSize must not be less than 1"); 290 | } 291 | if (Integer.bitCount(bufferSize) != 1) 292 | { 293 | throw new IllegalArgumentException("bufferSize must be a power of 2"); 294 | } 295 | 296 | this.indexMask = bufferSize - 1; 297 | // 【重要特性】内存预加载,内存池机制 298 | this.entries = (E[]) new Object[sequencer.getBufferSize() + 2 * BUFFER_PAD]; 299 | fill(eventFactory); 300 | } 301 | ``` 302 | 303 | 可以看到先前创建的`SingleProducerSequencer`实例作为构造参数传入给了`RingBuffer`实例的`sequencer`属性赋初值,然后最重要的是在创建`RingBuffer`实例时,会为`RingBuffer`的环形数组提前填充`Event`对象,即**内存池机制**: 304 | 305 | ```java 306 | // RingBuffer.java 307 | private void fill(final EventFactory eventFactory) 308 | { 309 | for (int i = 0; i < bufferSize; i++) 310 | { 311 | entries[BUFFER_PAD + i] = eventFactory.newInstance(); 312 | } 313 | } 314 | ``` 315 | 316 | 内存池机制好处: 317 | 318 | 1. 提前创建好复用的对象,减少程序运行时因为创建对象而浪费性能,其实也是一种空间换时间的思想; 319 | 2. 因为环形数组对象可复用,从而避免GC来提高性能。 320 | 321 | # 4 构建执行顺序链 322 | 323 | ```java 324 | // 2,构建eventHandler执行链:构建六边形执行顺序 325 | disruptor.handleEventsWith(eventHandler1, eventHandler3); 326 | disruptor.after(eventHandler1).handleEventsWith(eventHandler2); 327 | disruptor.after(eventHandler3).handleEventsWith(eventHandler4); 328 | disruptor.after(eventHandler2, eventHandler4).handleEventsWith(eventHandler5); 329 | ``` 330 | 331 | ![](https://common-ymbj.oss-cn-beijing.aliyuncs.com/Disruptor/2/1.png) 332 | 333 | 再来看看`Disruptor`构建执行顺序链相关源码: 334 | 335 | 先来看看`disruptor.handleEventsWith(eventHandler1, eventHandler3);`源码: 336 | 337 | ```java 338 | // Disruptor.java 339 | public final EventHandlerGroup handleEventsWith(final EventHandler... handlers) 340 | { 341 | return createEventProcessors(new Sequence[0], handlers); 342 | } 343 | 344 | EventHandlerGroup createEventProcessors( 345 | final Sequence[] barrierSequences, 346 | final EventHandler[] eventHandlers) 347 | { 348 | checkNotStarted(); 349 | // 根据eventHandlers长度来创建多少个消费者Sequence实例,注意这个processorSequences是传递到EventHandlerGroup用于构建执行顺序链用的, 350 | // 比如有执行顺序链:A->B,那么A的sequenct即processorSequences会作为B节点的barrierSequences即dependencySequence 351 | final Sequence[] processorSequences = new Sequence[eventHandlers.length]; 352 | // 新建了一个ProcessingSequenceBarrier实例返回 353 | // ProcessingSequenceBarrier实例作用:序号屏障,通过追踪生产者的cursorSequence和每个消费者( EventProcessor) 354 | // 的sequence的方式来协调生产者和消费者之间的数据交换进度 355 | final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences);// 如果构建执行顺序链比如A->B,那么barrierSequences是A消费者的sequence;如果是A,C->B,那么barrierSequences是A和C消费者的sequence 356 | 357 | for (int i = 0, eventHandlersLength = eventHandlers.length; i < eventHandlersLength; i++) 358 | { 359 | final EventHandler eventHandler = eventHandlers[i]; 360 | // 有多少个eventHandlers就创建多少个BatchEventProcessor实例(消费者), 361 | // 但需要注意的是同一批次的每个BatchEventProcessor实例共用同一个SequenceBarrier实例 362 | final BatchEventProcessor batchEventProcessor = 363 | new BatchEventProcessor<>(ringBuffer, barrier, eventHandler); 364 | 365 | if (exceptionHandler != null) 366 | { 367 | batchEventProcessor.setExceptionHandler(exceptionHandler); 368 | } 369 | // 将batchEventProcessor, eventHandler, barrier封装成EventProcessorInfo实例并加入到ConsumerRepository相关集合 370 | // ConsumerRepository作用:提供存储机制关联EventHandlers和EventProcessors 371 | consumerRepository.add(batchEventProcessor, eventHandler, barrier); // // 如果构建执行顺序链比如A->B,那么B消费者也一样会加入consumerRepository的相关集合 372 | // 获取到每个消费的消费sequece并赋值给processorSequences数组 373 | // 即processorSequences[i]引用了BatchEventProcessor的sequence实例, 374 | // 但processorSequences[i]又是构建生产者gatingSequence和消费者执行器链dependentSequence的来源 375 | processorSequences[i] = batchEventProcessor.getSequence(); 376 | } 377 | // 总是拿执行器链最后一个消费者的sequence作为生产者的gateingSequence 378 | updateGatingSequencesForNextInChain(barrierSequences, processorSequences); 379 | // 最终返回封装了Disruptor、ConsumerRepository和消费者sequence数组processorSequences的EventHandlerGroup对象实例返回 380 | return new EventHandlerGroup<>(this, consumerRepository, processorSequences); 381 | } 382 | ``` 383 | 384 | 构建`Disruptor`执行顺序链的核心逻辑就在这段源码中,我们缕一缕核心逻辑: 385 | 386 | 1. 有多少个`eventHandlers`就创建多少个`BatchEventProcessor`实例(消费者),`BatchEventProcessor`消费者其实就是一个实现`Runnable`接口的线程实例; 387 | 2. 每个`BatchEventProcessor`实例(消费者)拥有前一个消费者的`sequence`作为其`sequenceBarrier`即`dependentSequence`; 388 | 3. 当前消费者的`sequence`通过`EventHandlerGroup`这个载体来传递给下一个消费者作为其`sequenceBarrier`即`dependentSequence`。 389 | 390 | 再来看看`diruptor.after(eventHandler1)`源码: 391 | 392 | ```java 393 | // Disruptor.java 394 | public final EventHandlerGroup after(final EventHandler... handlers) 395 | { 396 | // 获取指定的EventHandler的消费者sequence并赋值给sequences数组, 397 | // 然后重新新建一个EventHandlerGroup实例返回(封装了前面的指定的消费者sequence被赋值 398 | // 给了EventHandlerGroup的成员变量数组sequences,用于后面指定执行顺序用) 399 | final Sequence[] sequences = new Sequence[handlers.length]; 400 | for (int i = 0, handlersLength = handlers.length; i < handlersLength; i++) 401 | { 402 | sequences[i] = consumerRepository.getSequenceFor(handlers[i]); 403 | } 404 | 405 | return new EventHandlerGroup<>(this, consumerRepository, sequences); 406 | } 407 | ``` 408 | 409 | 这段源码做的事情也是将当前消费者`sequence`封装进`EventHandlerGroup`,从而可以通过这个载体来传递给下一个消费者作为其`sequenceBarrier`即`dependentSequence`。 410 | 411 | 最终构建的最终`sequence`依赖关系如下图,看到这个图不禁让我想起`AQS`的线程等待链即CLH锁的变相实现,附上文章链接,有兴趣的读者可以比对理解。[AQS基础——多图详解CLH锁的原理与实现](https://mp.weixin.qq.com/s/xBw7koGuZtqU8imZ9_JzDA) 412 | 413 | ![](https://common-ymbj.oss-cn-beijing.aliyuncs.com/Disruptor/2/20220404235504.png) 414 | 415 | # 5 启动Disruptor实例 416 | 417 | ```java 418 | // 3, 启动disruptor即启动线程池线程执行BatchEventProcessor任务 419 | disruptor.start(); 420 | ``` 421 | 422 | 我们再来看看` disruptor.start()`这句源码: 423 | 424 | ```java 425 | // Disruptor.java 426 | public RingBuffer start() 427 | { 428 | checkOnlyStartedOnce(); 429 | // 遍历每一个BatchEventProcessor消费者(线程)实例,并把该消费者线程实例跑起来 430 | for (final ConsumerInfo consumerInfo : consumerRepository) 431 | { 432 | consumerInfo.start(executor); 433 | } 434 | 435 | return ringBuffer; 436 | } 437 | ``` 438 | 439 | 其实这里做的事情无非就是遍历每个消费者线程实例,然后启动每个消费者线程实例`BatchEventProcessor`,其中`BatchEventProcessor`被封装进`ConsumerInfo`实例。还没生产数据就启动消费线程的话,此时消费者会根据阻塞策略`WaitStrategy`进行阻塞。 440 | 441 | # 6 生产消费数据 442 | 443 | ## 6.1 生产者生产数据 444 | 445 | ```java 446 | // 4,生产者往ringBuffer生产数据并唤醒所有的消费者消费数据 447 | RingBuffer ringBuffer = disruptor.getRingBuffer(); 448 | ByteBuffer bb = ByteBuffer.allocate(8); 449 | bb.putLong(0, 666); 450 | ringBuffer.publishEvent(new LongEventTranslatorOneArg(), bb); 451 | ``` 452 | 453 | 生产者生产数据的源码在`ringBuffer.publishEvent(new LongEventTranslatorOneArg(), bb);`中。 454 | 455 | ```java 456 | // RingBuffer.java 457 | public void publishEvent(final EventTranslatorOneArg translator, final A arg0) 458 | { 459 | // 【1】获取下一个RingBuffer中需填充数据的event对象的序号,对应生产者 460 | final long sequence = sequencer.next(); 461 | // 【2】转换数据格式并生产数据并唤醒消费者 462 | translateAndPublish(translator, sequence, arg0); 463 | } 464 | ``` 465 | 466 | ### 6.1.1 生产者获取RingBuffer的sequence 467 | 468 | 先来看下单生产者获取`sequence`的源码: 469 | 470 | ```java 471 | // SingleProducerSequencer.java 472 | public long next(final int n) 473 | { 474 | if (n < 1 || n > bufferSize) 475 | { 476 | throw new IllegalArgumentException("n must be > 0 and < bufferSize"); 477 | } 478 | // 总是拿到生产者已生产的当前序号 479 | long nextValue = this.nextValue; 480 | // 获取要生产的下n个序号 481 | long nextSequence = nextValue + n; 482 | // 生产者总是先有bufferSize个坑可以填,所以nextSequence - bufferSize 483 | long wrapPoint = nextSequence - bufferSize; 484 | // 拿到上一次的GatingSequence,因为是缓存,这里不是最新的 485 | long cachedGatingSequence = this.cachedValue; 486 | // 如果生产者生产超过了消费者消费速度,那么这里自旋等待,这里的生产者生产的下标wrapPoint是已经绕了RingBuffer一圈的了哈 487 | if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue) 488 | { 489 | cursor.setVolatile(nextValue); // StoreLoad fence 490 | 491 | long minSequence; 492 | // 自旋等待,其中gatingSequences是前面构建执行顺序链时的最后一个消费者的sequence 493 | while (wrapPoint > (minSequence = Util.getMinimumSequence(gatingSequences, nextValue))) 494 | { 495 | LockSupport.parkNanos(1L); // TODO: Use waitStrategy to spin? 496 | } 497 | 498 | this.cachedValue = minSequence; 499 | } 500 | // 将获取的nextSequence赋值给生产者当前值nextValue 501 | this.nextValue = nextSequence; 502 | 503 | return nextSequence; 504 | } 505 | ``` 506 | 507 | 这段源码相对较难,我们缕一缕: 508 | 509 | 1. 生产者把第一圈`RingBuffer`的坑填完后,此时生产者进入`RingBuffer`第2圈,如果消费者消费速度过慢,此时生产者很可能会追上消费者,如果追上消费者那么就让生产者自旋等待; 510 | 511 | 2. 第1点的**如果消费者消费速度过慢**,对于构建了一个过滤器链的消费者中,那么指的是哪个消费者呢?指的就是执行器链最后执行的那个消费者,`gatingSequences`就是执行器链最后执行的那个消费者的`sequence`;**这个`gatingSequences`其实就是防止生产者追赶消费者的`sequenceBarrier`**; 512 | 513 | ![](https://common-ymbj.oss-cn-beijing.aliyuncs.com/Disruptor/2/20220404235652.png) 514 | 515 | 3. 生产者总是先把第一圈`RingBuffer`填满后,才会考虑追赶消费者的问题,因此才有`wrapPoint > cachedGatingSequence`的评判条件。 516 | 517 | 前面是单生产者获取`sequence`的源码,对于多生产者`MultiProducerSequencer`的源码逻辑也是类似,只不过将生产者当前值`cursor`和`cachedGatingSequence`用了CAS操作而已,防止多线程问题。 518 | 519 | 520 | 521 | ### 6.1.2 生产者生产数据并唤醒消费者 522 | 523 | 再来看看` translateAndPublish(translator, sequence, arg0)`源码: 524 | 525 | ```java 526 | // RingBuffer.java 527 | private void translateAndPublish(final EventTranslatorOneArg translator, final long sequence, final A arg0) 528 | { 529 | try 530 | { 531 | // 【1】将相应数据arg0转换为相应的Eevent数据,其中get(sequence)会从RingBuffer数组对象池中取出一个对象,而非新建 532 | translator.translateTo(get(sequence), sequence, arg0); 533 | } 534 | finally 535 | { 536 | // 【2】发布该序号说明已经生产完毕供消费者使用 537 | sequencer.publish(sequence); 538 | } 539 | } 540 | 541 | 542 | 543 | // SingleProducerSequencer.java 544 | public void publish(final long sequence) 545 | { 546 | // 【1】给生产者cursor游标赋值新的sequence,说明该sequenc对应的对象数据已经填充(生产)完毕 547 | cursor.set(sequence);// 这个cursor即生产者生产时移动的游标,是AbstractSequencer的成员变量 548 | // 【2】根据阻塞策略将所有消费者唤醒 549 | // 注意:这个waitStrategy实例是所有消费者和生产者共同引用的 550 | waitStrategy.signalAllWhenBlocking(); 551 | } 552 | 553 | ``` 554 | 555 | 生产者生产数据并唤醒消费者的注释已经写得很清楚了,这里需要注意的点: 556 | 557 | 1. `cursor`才是生产者生产数据的当前下标,消费者消费速度有无追赶上生产者就是拿消费者的消费`sequence`跟生产者的`cursor`比较的,因此生产者生产数据完成后需要给`cursor`赋值; 558 | 2. `waitStrategy`策略对象时跟消费者共用的,这样才能线程间实现阻塞唤醒逻辑。 559 | 560 | ## 6.2 消费者消费数据 561 | 562 | 前面第4节启动`Disruptor`实例中讲到,其实就是开启各个消费者实例`BatchEventProcessor`线程,我们看看其`run`方法中的核心逻辑即`processEvents`源码: 563 | 564 | ```java 565 | // BatchEventProcessor.java 566 | private void processEvents() 567 | { 568 | T event = null; 569 | // nextSequence:消费者要消费的下一个序号 570 | long nextSequence = sequence.get() + 1L; // 【重要】每一个消费者都是从0开始消费,各个消费者维护各自的sequence 571 | // 消费者线程一直在while循环中不断获取生产者数据 572 | while (true) 573 | { 574 | try 575 | { 576 | // 拿到当前生产者的生产序号 577 | final long availableSequence = sequenceBarrier.waitFor(nextSequence); 578 | if (batchStartAware != null) 579 | { 580 | batchStartAware.onBatchStart(availableSequence - nextSequence + 1); 581 | } 582 | // 如果消费者要消费的下一个序号小于生产者的当前生产序号,那么消费者则进行消费 583 | // 这里有一个亮点:就是消费者会一直循环消费直至到达当前生产者生产的序号 584 | while (nextSequence <= availableSequence) 585 | { 586 | event = dataProvider.get(nextSequence); 587 | eventHandler.onEvent(event, nextSequence, nextSequence == availableSequence); 588 | nextSequence++; 589 | } 590 | // 消费完后设置当前消费者的消费进度,这点很重要 591 | // 【1】如果当前消费者是执行链的最后一个消费者,那么其sequence则是生产者的gatingSequence,因为生产者就是拿要生产的下一个sequence跟gatingSequence做比较的哈 592 | // 【2】如果当前消费者不是执行器链的最后一个消费者,那么其sequence作为后面消费者的dependentSequence 593 | sequence.set(availableSequence); 594 | } 595 | catch (final TimeoutException e) 596 | { 597 | notifyTimeout(sequence.get()); 598 | } 599 | catch (final AlertException ex) 600 | { 601 | if (running.get() != RUNNING) 602 | { 603 | break; 604 | } 605 | } 606 | catch (final Throwable ex) 607 | { 608 | handleEventException(ex, nextSequence, event); 609 | sequence.set(nextSequence); 610 | nextSequence++; 611 | } 612 | } 613 | } 614 | ``` 615 | 616 | 617 | 618 | 消费者线程起来后,然后进入死循环,持续不断从生产者处**批量**获取可用的序号,如果获取到可用序号后,那么遍历所有可用序号,然后调用`eventHandler`的`onEvent`方法消费数据,`onEvent`方法写的是消费者的业务逻辑。消费完后再设置当前消费者的消费进度,这点很重要,用于构建`sequenceBarrier`包括`gatingSequence`和`dependentSequence`。 619 | 620 | 621 | 622 | 下面再来看看消费者是怎么获取可用的序号的,继续看`sequenceBarrier.waitFor(nextSequence)`源码: 623 | 624 | ```java 625 | // ProcessingSequenceBarrier.java 626 | 627 | public long waitFor(final long sequence) 628 | throws AlertException, InterruptedException, TimeoutException 629 | { 630 | checkAlert(); 631 | // availableSequence:获取生产者生产后可用的序号 632 | // sequence:消费者要消费的下一个序号 633 | // cursorSequence:生产者生产数据时的当前序号 634 | // dependentSequence:第一个消费者即前面不依赖任何消费者的消费者,dependentSequence就是生产者游标; 635 | // 有依赖其他消费者的消费者,dependentSequence就是依赖的消费者的sequence 636 | long availableSequence = waitStrategy.waitFor(sequence, cursorSequence, dependentSequence, this); 637 | 638 | if (availableSequence < sequence) 639 | { 640 | return availableSequence; 641 | } 642 | // 这个主要是针对多生产者的情形 643 | return sequencer.getHighestPublishedSequence(sequence, availableSequence); 644 | } 645 | ``` 646 | 647 | 可以看到`ProcessingSequenceBarrier`封装了`WaitStrategy`等待策略实例,此时消费者获取下一批可用序号的逻辑又封装在了`WaitStrategy`的`waitFor`方法中,以`BlockingWaitStrategy`为例来其实现逻辑: 648 | 649 | ```java 650 | // BlockingWaitStrategy.java 651 | 652 | public long waitFor(long sequence, Sequence cursorSequence, Sequence dependentSequence, SequenceBarrier barrier) 653 | throws AlertException, InterruptedException 654 | { 655 | long availableSequence; 656 | // cursorSequence:生产者的序号 657 | // 第一重条件判断:如果消费者消费速度大于生产者生产速度(即消费者要消费的下一个数据已经大于生产者生产的数据时),那么消费者等待一下 658 | if (cursorSequence.get() < sequence) 659 | { 660 | lock.lock(); 661 | try 662 | { 663 | while (cursorSequence.get() < sequence) 664 | { 665 | barrier.checkAlert(); 666 | processorNotifyCondition.await(); 667 | } 668 | } 669 | finally 670 | { 671 | lock.unlock(); 672 | } 673 | } 674 | // 第一重条件判断:自旋等待 675 | // 即当前消费者线程要消费的下一个sequence大于其前面执行链路(若有依赖关系)的任何一个消费者最小sequence(dependentSequence.get()),那么这个消费者要自旋等待, 676 | // 直到前面执行链路(若有依赖关系)的任何一个消费者最小sequence(dependentSequence.get())已经大于等于当前消费者的sequence时,说明前面执行链路的消费者已经消费完了 677 | while ((availableSequence = dependentSequence.get()) < sequence) 678 | { 679 | barrier.checkAlert(); 680 | ThreadHints.onSpinWait(); 681 | } 682 | 683 | return availableSequence; 684 | } 685 | ``` 686 | 687 | 可以看到,消费者获取下一批可用消费序号时,此时要经过两重判断: 688 | 689 | 1. 第一重判断:**消费者消费的序号不能超过当前生产者消费当前生产的序号**,否则消费者就阻塞等待;当然,这里因为是`BlockingWaitStrategy`等待策略的实现,如果是其他策略,比如`BusySpinWaitStrategy`和`YieldingWaitStrategy`的话,这里消费者是不会阻塞等待的,而是自旋,因此这也是其无锁化的实现了,但就是很耗CPU而已; 690 | 2. 第二重判断:**消费者消费的序号不能超过其前面依赖的消费消费的序号**,否则其自旋等待。因为这里是消费者等消费者,按理说前面消费者应该会很快处理完,所以不用阻塞等待;但是消费者等待生产者的话,如果生产者没生产数据的话,消费者还是自旋等待的话会比较浪费CPU,所以对于`BlockingWaitStrategy`策略,是阻塞等待了。 691 | 692 | # 7 WaitStrategy等待策略 693 | 694 | 最后,再来看下`WaitStrategy`有哪些实现类: 695 | 696 | ![](https://common-ymbj.oss-cn-beijing.aliyuncs.com/Disruptor/2/20220404185331.png) 697 | 698 | 可以看到消费者的`WaitStrategy`等待策略有8种实现类,可以分为有锁和无锁两大类,然后每一种都有其适用的场合,没有最好的`WaitStrategy`等待策略,只有适合自己应用场景的等待策略。因为其源码不是很难,这里不再逐一分析。 699 | 700 | 701 | 702 | > `disruptor`中文源码注释地址:https://github.com/yuanmabiji/disruptor 703 | -------------------------------------------------------------------------------- /Disruptor/README.md: -------------------------------------------------------------------------------- 1 | 2 | 【**源码笔记**】专注于Java后端系列框架的源码分析。若觉得源码分析文章不错,欢迎Star哦。 3 | 4 | 5 | ================**Disruptor源码专题持续更新中...**==================== 6 | 7 | #### 目录 8 | 9 | 1. [初识Disruptor框架!](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/Disruptor/初识Disruptor框架.md) 10 | 2. 持续更新中... 11 | 12 | * Disruptor源码解析项目(带中文注释):https://github.com/yuanmabiji/disruptor 13 | * 更多源码分析文章请跳转至:https://github.com/yuanmabiji/Java-SourceCode-Blogs 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Disruptor/images/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanmabiji/Java-SourceCode-Blogs/50ab54c1ee4b02b86657717699e247f15b925249/Disruptor/images/img.png -------------------------------------------------------------------------------- /Disruptor/images/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuanmabiji/Java-SourceCode-Blogs/50ab54c1ee4b02b86657717699e247f15b925249/Disruptor/images/img_1.png -------------------------------------------------------------------------------- /Disruptor/初识Disruptor框架.md: -------------------------------------------------------------------------------- 1 | >最近工作中参与了一个随机数分发平台的设计,考虑如何才能实现该平台的高并发性能,在技术实现选型中首先参考了百度的[uid-generator](https://github.com/baidu/uid-generator),其采用了双`RingBuffer`的实现形式,估计uid-generator的双`RingBuffer`也是借鉴了Disruptor的实现思想吧。因此,本系列文章我们一起来探究学习下2011年获得了Duke’s 程序框架创新奖的`Disruptor`框架。 2 | 3 | 4 | # 1 前言 5 | Martin Fowler在自己网站上写了一篇LMAX架构的文章,LMAX是一种运行在JVM平台上的新型零售金融交易平台,该平台能够以很低的延迟产生大量交易,大量交易是多少呢?单个线程达到了每秒处理6百万订单的TPS,虽然业务逻辑是纯内存操作,但每秒处理6百万订单的TPS已经高的惊人了。那么,是什么支撑了LMAX单个线程能达到每秒处理6百万订单呢?答案就是`Disruptor`。 6 | 7 | `Disruptor`是一个开源的并发框架,其于2011年获得了Duke’s 程序框架创新奖,采用事件源驱动方式,能够在无锁的情况下实现网络的Queue并发操作。 8 | 9 | # 2 Disruptor框架简介 10 | 11 | `Disruptor`框架内部核心的数据结构是`Ring Buffer`,`Ring Buffer`是一个环形的数组,`Disruptor`框架以`Ring Buffer`为核心实现了异步事件处理的高性能架构;JDK的`BlockingQueue`相信大家都用过,其是一个阻塞队列,内部通过锁机制实现生产者和消费者之间线程的同步。跟`BlockingQueue`一样,`Disruptor`框架也是围绕`Ring Buffer`实现生产者和消费者之间数据的交换,只不过`Disruptor`框架性能更高,笔者曾经在同样的环境下拿`Disruptor`框架跟`ArrayBlockingQueue`做过性能测试,`Disruptor`框架处理数据的性能比`ArrayBlockingQueue`的快几倍。 12 | 13 | `Disruptor`框架性能为什么会更好呢?其有以下特点: 14 | 15 | 1. 预加载内存可以理解为使用了内存池; 16 | 2. 无锁化 17 | 3. 单线程写 18 | 4. 消除伪共享 19 | 5. 使用内存屏障 20 | 6. 序号栅栏机制 21 | 22 | 23 | 24 | # 3 相关概念 25 | 26 | ![img.png](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/Disruptor/images/img.png?raw=true) 27 | 28 | 29 | **Disruptor**:是使用`Disruptor`框架的核心类,持有`RingBuffer`、消费者线程池、消费者集合`ConsumerRepository`和消费者异常处理器`ExceptionHandler`等引用; 30 | 31 | **Ring Buffer**: `RingBuffer`处于`Disruptor`框架的中心位置,其是一个环形数组,环形数组的对象采用预加载机制创建且能重用,是生产者和消费者之间交换数据的桥梁,其持有`Sequencer`的引用; 32 | 33 | 34 | **Sequencer**: `Sequencer`是`Disruptor`框架的核心,实现了所有并发算法,用于生产者和消费者之间快速、正确地传递数据,其有两个实现类`SingleProducerSequencer`和`MultiProducerSequencer`。 35 | 36 | **Sequence**:`Sequence`被用来标识`Ring Buffer`和消费者`Event Processor`的处理进度,每个消费者`Event Processor`和`Ring Buffer`本身都分别维护了一个`Sequence`,支持并发操作和顺序写,其也通过填充缓存行的方式来消除伪共享从而提高性能。 37 | 38 | **Sequence Barrier**:`Sequence Barrier`即为序号屏障,通过追踪生产者的`cursorSequence`和每个消费者(` EventProcessor`)的`sequence`的方式来协调生产者和消费者之间的数据交换进度,其实现类`ProcessingSequenceBarrier`持有的`WaitStrategy`等待策略类是实现序号屏障的核心。 39 | 40 | **Wait Strategy**:`Wait Strategy`是决定消费者如何等待生产者的策略方式,当消费者消费速度过快时,此时是不是要让消费者等待下,此时消费者等待是通过锁的方式实现还是无锁的方式实现呢? 41 | 42 | **Event Processor**:`Event Processor`可以理解为消费者线程,该线程会一直从`Ring Buffer`获取数据来消费数据,其有两个核心实现类:`BatchEventProcessor`和`WorkProcessor`。 43 | 44 | **Event Handler**:`Event Handler`可以理解为消费者实现业务逻辑的`Handler`,被`BatchEventProcessor`类引用,在`BatchEventProcessor`线程的死循环中不断从`Ring Buffer`获取数据供`Event Handler`消费。 45 | 46 | **Producer**:生产者,一般用`RingBuffer.publishEvent`来生产数据。 47 | 48 | 49 | 50 | 51 | 52 | # 4 入门DEMO 53 | ```java 54 | // LongEvent.java 55 | public class LongEvent 56 | { 57 | private long value; 58 | 59 | public void set(long value) 60 | { 61 | this.value = value; 62 | } 63 | 64 | public long get() { 65 | return this.value; 66 | } 67 | } 68 | ``` 69 | 70 | ```java 71 | // LongEventFactory.java 72 | public class LongEventFactory implements EventFactory 73 | { 74 | @Override 75 | public LongEvent newInstance() 76 | { 77 | return new LongEvent(); 78 | } 79 | } 80 | ``` 81 | 82 | ```java 83 | // LongEventHandler.java 84 | public class LongEventHandler implements EventHandler 85 | { 86 | @Override 87 | public void onEvent(LongEvent event, long sequence, boolean endOfBatch) 88 | { 89 | System.out.println(new Date() + ":Event-" + event.get()); 90 | } 91 | } 92 | ``` 93 | 94 | ```java 95 | // LongEventTranslatorOneArg.java 96 | public class LongEventTranslatorOneArg implements EventTranslatorOneArg { 97 | @Override 98 | public void translateTo(LongEvent event, long sequence, ByteBuffer buffer) { 99 | event.set(buffer.getLong(0)); 100 | } 101 | } 102 | ``` 103 | 104 | ```java 105 | // LongEventMain.java 106 | public class LongEventMain 107 | { 108 | public static void main(String[] args) throws Exception 109 | { 110 | int bufferSize = 1024; 111 | final Disruptor disruptor = new Disruptor( 112 | new LongEventFactory(), 113 | bufferSize, 114 | Executors.newSingleThreadExecutor(), 115 | ProducerType.SINGLE, 116 | new YieldingWaitStrategy() 117 | ); 118 | 119 | disruptor.handleEventsWith(new LongEventHandler()); 120 | disruptor.start(); 121 | 122 | 123 | RingBuffer ringBuffer = disruptor.getRingBuffer(); 124 | ByteBuffer bb = ByteBuffer.allocate(8); 125 | for (long l = 0; true; l++) 126 | { 127 | bb.putLong(0, l); 128 | ringBuffer.publishEvent(new LongEventTranslatorOneArg(), bb); 129 | Thread.sleep(1000); 130 | } 131 | } 132 | } 133 | ``` 134 | 输出结果: 135 | 136 | 137 | ![img_1.png](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/Disruptor/images/img_1.png?raw=true) 138 | 139 | 参考:https://lmax-exchange.github.io/disruptor/user-guide/index.html 140 | 141 | 142 | 143 | **若您觉得不错,请无情的转发和点赞吧!** 144 | 145 | 【源码笔记】Github地址: 146 | 147 | https://github.com/yuanmabiji/Java-SourceCode-Blogs 148 | 149 | -------------------------------------------------------------------------------- /JDK/1 Java是如何实现自己的SPI机制的? JDK源码(一).md: -------------------------------------------------------------------------------- 1 | **注:该源码分析对应JDK版本为1.8** 2 | 3 | # 1 引言 4 | 5 | 这是【源码笔记】的JDK源码解读的第一篇文章,本篇我们来探究Java的SPI机制的相关源码。 6 | 7 | # 2 什么是SPI机制 8 | 9 | 那么,什么是SPI机制呢? 10 | 11 | SPI是Service Provider Interface 的简称,即**服务提供者接口**的意思。根据字面意思我们可能还有点困惑,SPI说白了就是一种扩展机制,我们在相应配置文件中定义好某个接口的实现类,然后再根据这个接口去这个配置文件中加载这个实例类并实例化,其实SPI就是这么一个东西。说到SPI机制,我们最常见的就是Java的SPI机制,此外,还有Dubbo和SpringBoot自定义的SPI机制。 12 | 13 | 有了SPI机制,那么就为一些框架的灵活扩展提供了可能,而不必将框架的一些实现类写死在代码里面。 14 | 15 | 那么,某些框架是如何利用SPI机制来做到灵活扩展的呢?下面举几个栗子来阐述下: 16 | 17 | 1. **JDBC驱动加载案例**:利用Java的SPI机制,我们可以根据不同的数据库厂商来引入不同的JDBC驱动包; 18 | 2. **SpringBoot的SPI机制**:我们可以在`spring.factories`中加上我们自定义的自动配置类,事件监听器或初始化器等; 19 | 3. **Dubbo的SPI机制**:Dubbo更是把SPI机制应用的**淋漓尽致**,Dubbo基本上自身的每个功能点都提供了扩展点,比如提供了集群扩展,路由扩展和负载均衡扩展等差不多接近30个扩展点。如果Dubbo的某个内置实现不符合我们的需求,那么我们只要利用其SPI机制将我们的实现替换掉Dubbo的实现即可。 20 | 21 | 上面的三个栗子先让我们直观感受下某些框架利用SPI机制是如何做到灵活扩展的。 22 | 23 | 24 | # 3 如何使用Java的SPI? 25 | 26 | 我们先来看看如何使用Java自带的SPI。 27 | 先定义一个`Developer`接口 28 | 29 | ```java 30 | // Developer.java 31 | 32 | package com.ymbj.spi; 33 | 34 | public interface Developer { 35 | void sayHi(); 36 | } 37 | ``` 38 | 39 | 再定义两个`Developer`接口的两个实现类: 40 | 41 | ```java 42 | // JavaDeveloper.java 43 | 44 | package com.ymbj.spi; 45 | 46 | public class JavaDeveloper implements Developer { 47 | 48 | @Override 49 | public void sayHi() { 50 | System.out.println("Hi, I am a Java Developer."); 51 | } 52 | } 53 | ``` 54 | 55 | ```java 56 | // PythonDeveloper.java 57 | 58 | package com.ymbj.spi; 59 | 60 | public class PythonDeveloper implements Developer { 61 | 62 | @Override 63 | public void sayHi() { 64 | System.out.println("Hi, I am a Python Developer."); 65 | } 66 | } 67 | ``` 68 | 69 | 然后再在项目`resources`目录下新建一个`META-INF/services`文件夹,然后再新建一个以`Developer`接口的全限定名命名的文件,文件内容为: 70 | 71 | ```java 72 | // com.ymbj.spi.Developer文件 73 | 74 | com.ymbj.spi.JavaDeveloper 75 | com.ymbj.spi.PythonDeveloper 76 | ``` 77 | 78 | 最后我们再新建一个测试类`JdkSPITest`: 79 | 80 | ```java 81 | // JdkSPITest.java 82 | 83 | public class JdkSPITest { 84 | 85 | @Test 86 | public void testSayHi() throws Exception { 87 | ServiceLoader serviceLoader = ServiceLoader.load(Developer.class); 88 | serviceLoader.forEach(Developer::sayHi); 89 | } 90 | } 91 | ``` 92 | 93 | 运行上面那个测试类,运行成功结果如下截图所示: 94 | 95 | ![](https://user-gold-cdn.xitu.io/2020/3/28/1712015422d3ad09?w=438&h=81&f=png&s=5007) 96 | 97 | 由上面简单的Demo我们知道了如何使用Java的SPI机制来实现扩展点加载,下面推荐一篇文章[JAVA拾遗--关于SPI机制](https://mp.weixin.qq.com/s/Y-PFZwzSORsznJYRfiM3DA),通过这篇文章,相信大家对Java的SPI会有一个比较深刻的理解,特别是JDBC加载驱动这方面。 98 | 99 | # 4 Java的SPI机制的源码解读 100 | 101 | 通过前面扩展`Developer`接口的简单Demo,我们看到Java的SPI机制实现跟`ServiceLoader`这个类有关,那么我们先来看下`ServiceLoader`的类结构代码: 102 | 103 | ```java 104 | // ServiceLoader实现了【Iterable】接口 105 | public final class ServiceLoader 106 | implements Iterable{ 107 | private static final String PREFIX = "META-INF/services/"; 108 | // The class or interface representing the service being loaded 109 | private final Class service; 110 | // The class loader used to locate, load, and instantiate providers 111 | private final ClassLoader loader; 112 | // The access control context taken when the ServiceLoader is created 113 | private final AccessControlContext acc; 114 | // Cached providers, in instantiation order 115 | private LinkedHashMap providers = new LinkedHashMap<>(); 116 | // The current lazy-lookup iterator 117 | private LazyIterator lookupIterator; 118 | // 构造方法 119 | private ServiceLoader(Class svc, ClassLoader cl) { 120 | service = Objects.requireNonNull(svc, "Service interface cannot be null"); 121 | loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; 122 | acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; 123 | reload(); 124 | } 125 | 126 | // ...暂时省略相关代码 127 | 128 | // ServiceLoader的内部类LazyIterator,实现了【Iterator】接口 129 | // Private inner class implementing fully-lazy provider lookup 130 | private class LazyIterator 131 | implements Iterator{ 132 | Class service; 133 | ClassLoader loader; 134 | Enumeration configs = null; 135 | Iterator pending = null; 136 | String nextName = null; 137 | 138 | private LazyIterator(Class service, ClassLoader loader) { 139 | this.service = service; 140 | this.loader = loader; 141 | } 142 | // 覆写Iterator接口的hasNext方法 143 | public boolean hasNext() { 144 | // ...暂时省略相关代码 145 | } 146 | // 覆写Iterator接口的next方法 147 | public S next() { 148 | // ...暂时省略相关代码 149 | } 150 | // 覆写Iterator接口的remove方法 151 | public void remove() { 152 | // ...暂时省略相关代码 153 | } 154 | 155 | } 156 | 157 | // 覆写Iterable接口的iterator方法,返回一个迭代器 158 | public Iterator iterator() { 159 | // ...暂时省略相关代码 160 | } 161 | 162 | // ...暂时省略相关代码 163 | 164 | } 165 | ``` 166 | 167 | 可以看到,`ServiceLoader`实现了`Iterable`接口,覆写其`iterator`方法能产生一个迭代器;同时`ServiceLoader`有一个内部类`LazyIterator`,而`LazyIterator`又实现了`Iterator`接口,说明`LazyIterator`是一个迭代器。 168 | 169 | ## 4.1 ServiceLoader.load方法,为加载服务提供者实现类做前期准备 170 | 171 | 那么我们现在开始探究Java的SPI机制的源码, 172 | 先来看`JdkSPITest`的第一句代码`ServiceLoader serviceLoader = ServiceLoader.load(Developer.class);`中的`ServiceLoader.load(Developer.class);`的源码: 173 | 174 | ```java 175 | // ServiceLoader.java 176 | 177 | public static ServiceLoader load(Class service) { 178 | //获取当前线程上下文类加载器 179 | ClassLoader cl = Thread.currentThread().getContextClassLoader(); 180 | // 将service接口类和线程上下文类加载器作为参数传入,继续调用load方法 181 | return ServiceLoader.load(service, cl); 182 | } 183 | ``` 184 | 185 | 我们再来看下`ServiceLoader.load(service, cl);`方法: 186 | 187 | ```java 188 | // ServiceLoader.java 189 | 190 | public static ServiceLoader load(Class service, 191 | ClassLoader loader) 192 | { 193 | // 将service接口类和线程上下文类加载器作为构造参数,新建了一个ServiceLoader对象 194 | return new ServiceLoader<>(service, loader); 195 | } 196 | ``` 197 | 198 | 继续看`new ServiceLoader<>(service, loader);`是如何构建的? 199 | 200 | ```java 201 | // ServiceLoader.java 202 | 203 | private ServiceLoader(Class svc, ClassLoader cl) { 204 | service = Objects.requireNonNull(svc, "Service interface cannot be null"); 205 | loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; 206 | acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; 207 | reload(); 208 | } 209 | ``` 210 | 211 | 可以看到在构建`ServiceLoader`对象时除了给其成员属性赋值外,还调用了`reload`方法: 212 | 213 | ```java 214 | // ServiceLoader.java 215 | 216 | public void reload() { 217 | providers.clear(); 218 | lookupIterator = new LazyIterator(service, loader); 219 | } 220 | ``` 221 | 222 | 可以看到在`reload`方法中又新建了一个`LazyIterator`对象,然后赋值给`lookupIterator`。 223 | 224 | ```java 225 | // ServiceLoader$LazyIterator.java 226 | 227 | private LazyIterator(Class service, ClassLoader loader) { 228 | this.service = service; 229 | this.loader = loader; 230 | } 231 | ``` 232 | 233 | 可以看到在构建`LazyIterator`对象时,也只是给其成员变量`service`和`loader`属性赋值呀,我们一路源码跟下来,也没有看到去`META-INF/services`文件夹加载`Developer`接口的实现类!这就奇怪了,我们都被`ServiceLoader`的`load`方法名骗了。 234 | 235 | 还记得分析前面的代码时新建了一个`LazyIterator`对象吗?`Lazy`顾名思义是**懒**的意思,`Iterator`就是迭代的意思。我们此时猜测那么`LazyIterator`对象的作用应该就是在迭代的时候再去加载`Developer`接口的实现类了。 236 | 237 | ## 4.2 ServiceLoader.iterator方法,实现服务提供者实现类的懒加载 238 | 239 | 我们现在再来看`JdkSPITest`的第二句代码`serviceLoader.forEach(Developer::sayHi);`,执行这句代码后最终会调用`serviceLoader`的`iterator`方法: 240 | 241 | ```java 242 | // serviceLoader.java 243 | 244 | public Iterator iterator() { 245 | return new Iterator() { 246 | 247 | Iterator> knownProviders 248 | = providers.entrySet().iterator(); 249 | 250 | public boolean hasNext() { 251 | if (knownProviders.hasNext()) 252 | return true; 253 | // 调用lookupIterator即LazyIterator的hasNext方法 254 | // 可以看到是委托给LazyIterator的hasNext方法来实现 255 | return lookupIterator.hasNext(); 256 | } 257 | 258 | public S next() { 259 | if (knownProviders.hasNext()) 260 | return knownProviders.next().getValue(); 261 | // 调用lookupIterator即LazyIterator的next方法 262 | // 可以看到是委托给LazyIterator的next方法来实现 263 | return lookupIterator.next(); 264 | } 265 | 266 | public void remove() { 267 | throw new UnsupportedOperationException(); 268 | } 269 | 270 | }; 271 | } 272 | ``` 273 | 274 | 可以看到调用`serviceLoader`的`iterator`方法会返回一个匿名的迭代器对象,而这个匿名迭代器对象其实相当于一个门面类,其覆写的`hasNext`和`next`方法又分别委托`LazyIterator`的`hasNext`和`next`方法来实现了。 275 | 276 | 我们继续调试,发现接下来会进入`LazyIterator`的`hasNext`方法: 277 | 278 | ```java 279 | // serviceLoader$LazyIterator.java 280 | 281 | public boolean hasNext() { 282 | if (acc == null) { 283 | // 调用hasNextService方法 284 | return hasNextService(); 285 | } else { 286 | PrivilegedAction action = new PrivilegedAction() { 287 | public Boolean run() { return hasNextService(); } 288 | }; 289 | return AccessController.doPrivileged(action, acc); 290 | } 291 | } 292 | ``` 293 | 294 | 继续跟进`hasNextService`方法: 295 | 296 | ```java 297 | // serviceLoader$LazyIterator.java 298 | 299 | private boolean hasNextService() { 300 | if (nextName != null) { 301 | return true; 302 | } 303 | if (configs == null) { 304 | try { 305 | // PREFIX = "META-INF/services/" 306 | // service.getName()即接口的全限定名 307 | // 还记得前面的代码构建LazyIterator对象时已经给其成员属性service赋值吗 308 | String fullName = PREFIX + service.getName(); 309 | // 加载META-INF/services/目录下的接口文件中的服务提供者类 310 | if (loader == null) 311 | configs = ClassLoader.getSystemResources(fullName); 312 | else 313 | // 还记得前面的代码构建LazyIterator对象时已经给其成员属性loader赋值吗 314 | configs = loader.getResources(fullName); 315 | } catch (IOException x) { 316 | fail(service, "Error locating configuration files", x); 317 | } 318 | } 319 | while ((pending == null) || !pending.hasNext()) { 320 | if (!configs.hasMoreElements()) { 321 | return false; 322 | } 323 | // 返回META-INF/services/目录下的接口文件中的服务提供者类并赋值给pending属性 324 | pending = parse(service, configs.nextElement()); 325 | } 326 | // 然后取出一个全限定名赋值给LazyIterator的成员变量nextName 327 | nextName = pending.next(); 328 | return true; 329 | } 330 | ``` 331 | 332 | 可以看到在执行`LazyIterator`的`hasNextService`方法时最终将去`META-INF/services/`目录下加载接口文件的内容即加载服务提供者实现类的全限定名,然后取出一个服务提供者实现类的全限定名赋值给`LazyIterator`的成员变量`nextName`。到了这里,我们就明白了`LazyIterator`的作用真的是懒加载,在用到的时候才会去加载。 333 | 334 | > **思考**:为何这里要用懒加载呢?懒加载的思想是怎样的呢?懒加载有啥好处呢?你还能举出其他懒加载的案例吗? 335 | 336 | 同样,执行完`LazyIterator`的`hasNext`方法后,会继续执行`LazyIterator`的`next`方法: 337 | 338 | ```java 339 | // serviceLoader$LazyIterator.java 340 | 341 | public S next() { 342 | if (acc == null) { 343 | // 调用nextService方法 344 | return nextService(); 345 | } else { 346 | PrivilegedAction action = new PrivilegedAction() { 347 | public S run() { return nextService(); } 348 | }; 349 | return AccessController.doPrivileged(action, acc); 350 | } 351 | } 352 | ``` 353 | 354 | 我们继续跟进`nextService`方法: 355 | 356 | ```java 357 | // serviceLoader$LazyIterator.java 358 | 359 | private S nextService() { 360 | if (!hasNextService()) 361 | throw new NoSuchElementException(); 362 | // 还记得在hasNextService方法中为nextName赋值过服务提供者实现类的全限定名吗 363 | String cn = nextName; 364 | nextName = null; 365 | Class c = null; 366 | try { 367 | // 【1】去classpath中根据传入的类加载器和服务提供者实现类的全限定名去加载服务提供者实现类 368 | c = Class.forName(cn, false, loader); 369 | } catch (ClassNotFoundException x) { 370 | fail(service, 371 | "Provider " + cn + " not found"); 372 | } 373 | if (!service.isAssignableFrom(c)) { 374 | fail(service, 375 | "Provider " + cn + " not a subtype"); 376 | } 377 | try { 378 | // 【2】实例化刚才加载的服务提供者实现类,并进行转换 379 | S p = service.cast(c.newInstance()); 380 | // 【3】最终将实例化后的服务提供者实现类放进providers集合 381 | providers.put(cn, p); 382 | return p; 383 | } catch (Throwable x) { 384 | fail(service, 385 | "Provider " + cn + " could not be instantiated", 386 | x); 387 | } 388 | throw new Error(); // This cannot happen 389 | } 390 | ``` 391 | 392 | 可以看到`LazyIterator`的`nextService`方法最终将实例化之前加载的服务提供者实现类,并放进`providers`集合中,随后再调用服务提供者实现类的方法(比如这里指`JavaDeveloper`的`sayHi`方法)。注意,这里是加载一个服务提供者实现类后,若`main`函数中有调用该服务提供者实现类的方法的话,紧接着会调用其方法;然后继续实例化下一个服务提供者类。 393 | 394 | > **设计模式**:可以看到,Java的SPI机制实现代码中应用了迭代器模式,迭代器模式屏蔽了各种存储对象的内部结构差异,提供一个统一的视图来遍历各个存储对象(存储对象可以为集合,数组等)。`java.util.Iterator`也是迭代器模式的实现:同时Java的各个集合类一般实现了`Iterable`接口,实现了其`iterator`方法从而获得`Iterator`接口的实现类对象(一般为集合内部类),然后再利用`Iterator`对象的实现类的`hasNext`和`next`方法来遍历集合元素。 395 | 396 | 397 | # 5 JDBC驱动加载源码解读 398 | 前面分析了Java的SPI机制的源码实现,现在我们再来看下Java的SPI机制的实际案例的应用。 399 | 400 | 我们都知道,JDBC驱动加载是Java的SPI机制的典型应用案例。JDBC主要提供了一套接口规范,而这套规范的api在java的核心库(`rt.jar`)中实现,而不同的数据库厂商只要编写符合这套JDBC接口规范的驱动代码,那么就可以用Java语言来连接数据库了。 401 | 402 | java的核心库(`rt.jar`)中跟JDBC驱动加载的最核心的接口和类分别是`java.sql.Driver`接口和`java.sql.DriverManager`类,其中`java.sql.Driver`是各个数据库厂商的驱动类要实现的接口,而`DriverManager`是用来管理数据库的驱动类的,值得注意的是`DriverManager`这个类有一个`registeredDrivers`集合属性,用来存储数据库的驱动类。 403 | ```java 404 | // DriverManager.java 405 | 406 | // List of registered JDBC drivers 407 | private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>(); 408 | ``` 409 | 410 | 这里以加载Mysql驱动为例来分析JDBC驱动加载的源码。 411 | 412 | 我们的项目引入`mysql-connector-java`依赖(这里的版本是`5.1.47`)后,那么Mysql的驱动实现类文件如下图所示: 413 | 414 | 415 | ![](https://user-gold-cdn.xitu.io/2020/3/28/17121285dd574a92?w=1425&h=382&f=png&s=103626) 416 | 可以看到Mysql的驱动包中有两个`Driver`驱动类,分别是`com.mysql.jdbc.Driver`和`com.mysql.fabric.jdbc.FabricMySQLDriver`,默认情况下一般我们只用到前者。 417 | 418 | ## 5.1 利用Java的SPI加载Mysql的驱动类 419 | 420 | 那么接下来我们就来探究下JDBC驱动加载的代码是如何实现的。 421 | 422 | 先来看一下一个简单的JDBC的测试代码: 423 | ```java 424 | // JdbcTest.java 425 | 426 | public class JdbcTest { 427 | public static void main(String[] args) { 428 | Connection connection = null; 429 | Statement statement = null; 430 | ResultSet rs = null; 431 | 432 | try { 433 | // 注意:在JDBC 4.0规范中,这里可以不用再像以前那样编写显式加载数据库的代码了 434 | // Class.forName("com.mysql.jdbc.Driver"); 435 | // 获取数据库连接,注意【这里将会加载mysql的驱动包】 436 | /***************【主线,切入点】****************/ 437 | connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "123456"); 438 | // 创建Statement语句 439 | statement = connection.createStatement(); 440 | // 执行查询语句 441 | rs = statement.executeQuery("select * from user"); 442 | // 遍历查询结果集 443 | while(rs.next()){ 444 | String name = rs.getString("name"); 445 | System.out.println(name); 446 | } 447 | } catch(Exception e) { 448 | e.printStackTrace(); 449 | } finally { 450 | // ...省略释放资源的代码 451 | } 452 | } 453 | } 454 | ``` 455 | 在`JdbcTest`的`main`函数调用`DriverManager`的`getConnection`方法时,此时必然会先执行`DriverManager`类的静态代码块的代码,然后再执行`getConnection`方法,那么先来看下`DriverManager`的静态代码块: 456 | ```java 457 | // DriverManager.java 458 | 459 | static { 460 | // 加载驱动实现类 461 | loadInitialDrivers(); 462 | println("JDBC DriverManager initialized"); 463 | } 464 | ``` 465 | 继续跟进`loadInitialDrivers`的代码: 466 | ```java 467 | // DriverManager.java 468 | 469 | private static void loadInitialDrivers() { 470 | String drivers; 471 | try { 472 | drivers = AccessController.doPrivileged(new PrivilegedAction() { 473 | public String run() { 474 | return System.getProperty("jdbc.drivers"); 475 | } 476 | }); 477 | } catch (Exception ex) { 478 | drivers = null; 479 | } 480 | AccessController.doPrivileged(new PrivilegedAction() { 481 | public Void run() { 482 | // 来到这里,是不是感觉似曾相识,对,没错,我们在前面的JdkSPITest代码中执行过下面的两句代码 483 | // 这句代码前面已经分析过,这里不会真正加载服务提供者实现类 484 | // 而是实例化一个ServiceLoader对象且实例化一个LazyIterator对象用于懒加载 485 | ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class); 486 | // 调用ServiceLoader的iterator方法,在迭代的同时,也会去加载并实例化META-INF/services/java.sql.Driver文件 487 | // 的com.mysql.jdbc.Driver和com.mysql.fabric.jdbc.FabricMySQLDriver两个驱动类 488 | /****************【主线,重点关注】**********************/ 489 | Iterator driversIterator = loadedDrivers.iterator(); 490 | try{ 491 | while(driversIterator.hasNext()) { 492 | driversIterator.next(); 493 | } 494 | } catch(Throwable t) { 495 | // Do nothing 496 | } 497 | return null; 498 | } 499 | }); 500 | 501 | println("DriverManager.initialize: jdbc.drivers = " + drivers); 502 | 503 | if (drivers == null || drivers.equals("")) { 504 | return; 505 | } 506 | String[] driversList = drivers.split(":"); 507 | println("number of Drivers:" + driversList.length); 508 | for (String aDriver : driversList) { 509 | try { 510 | println("DriverManager.Initialize: loading " + aDriver); 511 | Class.forName(aDriver, true, 512 | ClassLoader.getSystemClassLoader()); 513 | } catch (Exception ex) { 514 | println("DriverManager.Initialize: load failed: " + ex); 515 | } 516 | } 517 | } 518 | ``` 519 | 在上面的代码中,我们可以看到Mysql的驱动类加载主要是利用Java的SPI机制实现的,即利用`ServiceLoader`来实现加载并实例化Mysql的驱动类。 520 | 521 | ## 5.2 注册Mysql的驱动类 522 | 523 | 那么,上面的代码只是Mysql驱动类的加载和实例化,**那么,驱动类又是如何被注册进`DriverManager`的`registeredDrivers`集合的呢?** 524 | 525 | 这时,我们注意到`com.mysql.jdbc.Driver`类里面也有个静态代码块,即实例化该类时肯定会触发该静态代码块代码的执行,那么我们直接看下这个静态代码块做了什么事情: 526 | ```java 527 | // com.mysql.jdbc.Driver.java 528 | 529 | // Register ourselves with the DriverManager 530 | static { 531 | try { 532 | // 将自己注册进DriverManager类的registeredDrivers集合 533 | java.sql.DriverManager.registerDriver(new Driver()); 534 | } catch (SQLException E) { 535 | throw new RuntimeException("Can't register driver!"); 536 | } 537 | } 538 | ``` 539 | 可以看到,原来就是Mysql驱动类`com.mysql.jdbc.Driver`在实例化的时候,利用执行其静态代码块的时机时将自己注册进`DriverManager`的`registeredDrivers`集合中。 540 | 541 | 好,继续跟进`DriverManager`的`registerDriver`方法: 542 | ```java 543 | // DriverManager.java 544 | 545 | public static synchronized void registerDriver(java.sql.Driver driver) 546 | throws SQLException { 547 | // 继续调用registerDriver方法 548 | registerDriver(driver, null); 549 | } 550 | 551 | public static synchronized void registerDriver(java.sql.Driver driver, 552 | DriverAction da) 553 | throws SQLException { 554 | 555 | /* Register the driver if it has not already been added to our list */ 556 | if(driver != null) { 557 | // 将driver驱动类实例注册进registeredDrivers集合 558 | registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); 559 | } else { 560 | // This is for compatibility with the original DriverManager 561 | throw new NullPointerException(); 562 | } 563 | println("registerDriver: " + driver); 564 | } 565 | ``` 566 | 分析到了这里,我们就明白了Java的SPI机制是如何加载Mysql的驱动类的并如何将Mysql的驱动类注册进`DriverManager`的`registeredDrivers`集合中的。 567 | 568 | ## 5.3 使用之前注册的Mysql驱动类连接数据库 569 | 570 | **既然Mysql的驱动类已经被注册进来了,那么何时会被用到呢?** 571 | 572 | 我们要连接Mysql数据库,自然需要用到Mysql的驱动类,对吧。此时我们回到JDBC的测试代码`JdbcTest`类的`connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "123456");`这句代码中,看一下`getConnection`的源码: 573 | 574 | ```java 575 | // DriverManager.java 576 | 577 | @CallerSensitive 578 | public static Connection getConnection(String url, 579 | String user, String password) throws SQLException { 580 | java.util.Properties info = new java.util.Properties(); 581 | 582 | if (user != null) { 583 | info.put("user", user); 584 | } 585 | if (password != null) { 586 | info.put("password", password); 587 | } 588 | // 继续调用getConnection方法来连接数据库 589 | return (getConnection(url, info, Reflection.getCallerClass())); 590 | } 591 | ``` 592 | 继续跟进`getConnection`方法: 593 | ```java 594 | // DriverManager.java 595 | 596 | private static Connection getConnection( 597 | String url, java.util.Properties info, Class caller) throws SQLException { 598 | 599 | ClassLoader callerCL = caller != null ? caller.getClassLoader() : null; 600 | synchronized(DriverManager.class) { 601 | // synchronize loading of the correct classloader. 602 | if (callerCL == null) { 603 | callerCL = Thread.currentThread().getContextClassLoader(); 604 | } 605 | } 606 | if(url == null) { 607 | throw new SQLException("The url cannot be null", "08001"); 608 | } 609 | println("DriverManager.getConnection(\"" + url + "\")"); 610 | // Walk through the loaded registeredDrivers attempting to make a connection. 611 | // Remember the first exception that gets raised so we can reraise it. 612 | SQLException reason = null; 613 | // 遍历registeredDrivers集合,注意之前加载的Mysql驱动类实例被注册进这个集合 614 | for(DriverInfo aDriver : registeredDrivers) { 615 | // If the caller does not have permission to load the driver then 616 | // skip it. 617 | // 判断有无权限 618 | if(isDriverAllowed(aDriver.driver, callerCL)) { 619 | try { 620 | println(" trying " + aDriver.driver.getClass().getName()); 621 | // 利用Mysql驱动类来连接数据库 622 | /*************【主线,重点关注】*****************/ 623 | Connection con = aDriver.driver.connect(url, info); 624 | // 只要连接上,那么加载的其余驱动类比如FabricMySQLDriver将会忽略,因为下面直接返回了 625 | if (con != null) { 626 | // Success! 627 | println("getConnection returning " + aDriver.driver.getClass().getName()); 628 | return (con); 629 | } 630 | } catch (SQLException ex) { 631 | if (reason == null) { 632 | reason = ex; 633 | } 634 | } 635 | 636 | } else { 637 | println(" skipping: " + aDriver.getClass().getName()); 638 | } 639 | 640 | } 641 | 642 | // if we got here nobody could connect. 643 | if (reason != null) { 644 | println("getConnection failed: " + reason); 645 | throw reason; 646 | } 647 | 648 | println("getConnection: no suitable driver found for "+ url); 649 | throw new SQLException("No suitable driver found for "+ url, "08001"); 650 | } 651 | ``` 652 | 可以看到,`DriverManager`的`getConnection`方法会从`registeredDrivers`集合中拿出刚才加载的Mysql驱动类来连接数据库。 653 | 654 | 好了,到了这里,JDBC驱动加载的源码就基本分析完了。 655 | # 6 线程上下文类加载器 656 | 前面基本分析完了JDBC驱动加载的源码,但是还有一个很重要的知识点还没讲解,那就是破坏类加载机制的双亲委派模型的**线程上下文类加载器**。 657 | 658 | 我们都知道,JDBC规范的相关类(比如前面的`java.sql.Driver`和`java.sql.DriverManager`)都是在Jdk的`rt.jar`包下,意味着这些类将由启动类加载器(BootstrapClassLoader)加载;而Mysql的驱动类由外部数据库厂商实现,当驱动类被引进项目时也是位于项目的`classpath`中,此时启动类加载器肯定是不可能加载这些驱动类的呀,此时该怎么办? 659 | 660 | 由于类加载机制的双亲委派模型在这方面的缺陷,因此只能打破双亲委派模型了。因为项目`classpath`中的类是由应用程序类加载器(AppClassLoader)来加载,所以我们可否"逆向"让启动类加载器委托应用程序类加载器去加载这些外部数据库厂商的驱动类呢?如果可以,我们怎样才能做到让启动类加载器委托应用程序类加载器去加载 661 | `classpath`中的类呢? 662 | 663 | 答案肯定是可以的,我们可以将应用程序类加载器设置进线程里面,即线程里面新定义一个类加载器的属性`contextClassLoader`,然后在某个时机将应用程序类加载器设置进线程的`contextClassLoader`这个属性里面,如果没有设置的话,那么默认就是应用程序类加载器。然后启动类加载器去加载`java.sql.Driver`和`java.sql.DriverManager`等类时,同时也会从当前线程中取出`contextClassLoader`即应用程序类加载器去`classpath`中加载外部厂商提供的JDBC驱动类。因此,通过破坏类加载机制的双亲委派模型,利用**线程上下文类加载器**完美的解决了该问题。 664 | 665 | 此时我们再回过头来看下**在加载Mysql驱动时是什么时候获取的线程上下文类加载器呢?** 666 | 667 | 答案就是在`DriverManager`的`loadInitialDrivers`方法调用了`ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);`这句代码,而取出线程上下文类加载器就是在`ServiceLoader`的`load`方法中取出: 668 | ```java 669 | 670 | public static ServiceLoader load(Class service) { 671 | // 取出线程上下文类加载器取出的是contextClassLoader,而contextClassLoader装的应用程序类加载器 672 | ClassLoader cl = Thread.currentThread().getContextClassLoader(); 673 | // 把刚才取出的线程上下文类加载器作为参数传入,用于后去加载classpath中的外部厂商提供的驱动类 674 | return ServiceLoader.load(service, cl); 675 | } 676 | ``` 677 | 因此,到了这里,我们就明白了线程上下文类加载器在加载JDBC驱动包中充当的作用了。此外,我们应该知道,Java的绝大部分涉及SPI的加载都是利用线程上下文类加载器来完成的,比如JNDI,JCE,JBI等。 678 | 679 | > **扩展**:打破类加载机制的双亲委派模型的还有代码的热部署等,另外,Tomcat的类加载机制也值得一读。 680 | 681 | 注:若有些小伙伴对类加载机制的双亲委派模型不清楚的话,推荐[完全理解双亲委派模型与自定义 ClassLoader](https://mp.weixin.qq.com/s/Hy4OSYI8_s0tlEmMfZr-UQ)这篇文了解下。 682 | # 7 扩展:Dubbo的SPI机制 683 | 前面也讲到Dubbo框架身上处处是SPI机制的应用,可以说处处都是扩展点,真的是把SPI机制应用的淋漓尽致。但是Dubbo没有采用默认的Java的SPI机制,而是自己实现了一套SPI机制。 684 | 685 | 那么,**Dubbo为什么没有采用Java的SPI机制呢?** 686 | 687 | 原因主要有两个: 688 | 689 | 1. Java的SPI机制会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源; 690 | 2. Java的SPI机制没有Ioc和AOP的支持,因此Dubbo用了自己的SPI机制:增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。 691 | 692 | 由于以上原因,Dubbo自定义了一套SPI机制,用于加载自己的扩展点。关于Dubbo的SPI机制这里不再详述,感兴趣的小伙伴们可以去Dubbo官网看看是如何扩展Dubbo的SPI的?还有其官网也有Duboo的SPI的源码分析文章。 693 | # 8 小结 694 | 695 | 好了,Java的SPI机制就解读到这里了,先将前面的知识点再总结下: 696 | 1. Java的SPI机制的使用; 697 | 2. Java的SPI机制的原理; 698 | 3. JDBC驱动的加载原理; 699 | 4. 线程上下文类加载器在JDBC驱动加载中的作用; 700 | 5. 简述了Duboo的SPI机制。 701 | 702 | **原创不易,帮忙点个赞呗!** 703 | 704 | 由于笔者水平有限,若文中有错误还请指出,谢谢。 705 | 706 | **参考**: 707 | 708 | 1,http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html 709 | 710 | 2,《深入理解Java虚拟机》 711 | -------------------------------------------------------------------------------- /JDK/README.md: -------------------------------------------------------------------------------- 1 | 2 | 【**源码笔记**】专注于Java后端系列框架的源码分析。若觉得源码分析文章不错,欢迎Star哦。 3 | 4 | 5 | ================**JDK源码专题持续更新中...**==================== 6 | 7 | #### 目录 8 | 9 | 1. [Java是如何实现自己的SPI机制的? JDK源码(一)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/JDK/1%20Java%E6%98%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%B7%B1%E7%9A%84SPI%E6%9C%BA%E5%88%B6%E7%9A%84%EF%BC%9F%20JDK%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%80%EF%BC%89.md) 10 | 2. 持续更新中... 11 | 12 | * 更多源码分析文章请跳转至:https://github.com/yuanmabiji/Java-SourceCode-Blogs 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /JUC/Java是如何实现Future模式的?万字详解!.md: -------------------------------------------------------------------------------- 1 | JDK1.8源码分析项目(中文注释)Github地址: 2 | 3 | https://github.com/yuanmabiji/jdk1.8-sourcecode-blogs 4 | # 1 Future是什么? 5 | 先举个例子,我们平时网购买东西,下单后会生成一个订单号,然后商家会根据这个订单号发货,发货后又有一个快递单号,然后快递公司就会根据这个快递单号将网购东西快递给我们。在这一过程中,这一系列的单号都是我们收货的重要凭证。 6 | 7 | 因此,JDK的Future就类似于我们网购买东西的单号,当我们执行某一耗时的任务时,我们可以另起一个线程异步去执行这个耗时的任务,同时我们可以干点其他事情。当事情干完后我们再根据future这个"单号"去提取耗时任务的执行结果即可。因此Future也是多线程中的一种应用模式。 8 | 9 | > **扩展**: 说起多线程,那么Future又与Thread有什么区别呢?最重要的区别就是Thread是没有返回结果的,而Future模式是有返回结果的。 10 | 11 | # 2 如何使用Future 12 | 前面搞明白了什么是Future,下面我们再来举个简单的例子看看如何使用Future。 13 | 14 | 假如现在我们要打火锅,首先我们要准备两样东西:把水烧开和准备食材。因为烧开水是一个比较漫长的过程(相当于耗时的业务逻辑),因此我们可以一边烧开水(相当于另起一个线程),一边准备火锅食材(主线程),等两者都准备好了我们就可以开始打火锅了。 15 | 16 | ```java 17 | // DaHuoGuo.java 18 | 19 | public class DaHuoGuo { 20 | public static void main(String[] args) throws Exception { 21 | FutureTask futureTask = new FutureTask<>(new Callable() { 22 | @Override 23 | public String call() throws Exception { 24 | System.out.println(Thread.currentThread().getName() + ":" + "开始烧开水..."); 25 | // 模拟烧开水耗时 26 | Thread.sleep(2000); 27 | System.out.println(Thread.currentThread().getName() + ":" + "开水已经烧好了..."); 28 | return "开水"; 29 | } 30 | }); 31 | 32 | Thread thread = new Thread(futureTask); 33 | thread.start(); 34 | 35 | // do other thing 36 | System.out.println(Thread.currentThread().getName() + ":" + " 此时开启了一个线程执行future的逻辑(烧开水),此时我们可以干点别的事情(比如准备火锅食材)..."); 37 | // 模拟准备火锅食材耗时 38 | Thread.sleep(3000); 39 | System.out.println(Thread.currentThread().getName() + ":" + "火锅食材准备好了"); 40 | String shicai = "火锅食材"; 41 | 42 | // 开水已经稍好,我们取得烧好的开水 43 | String boilWater = futureTask.get(); 44 | 45 | System.out.println(Thread.currentThread().getName() + ":" + boilWater + "和" + shicai + "已经准备好,我们可以开始打火锅啦"); 46 | } 47 | } 48 | ``` 49 | 执行结果如下截图,符合我们的预期: 50 | ![](https://user-gold-cdn.xitu.io/2020/6/21/172d728d9ad5b1f1?w=1008&h=150&f=png&s=25755) 51 | 52 | 从以上代码中可以看到,我们使用Future主要有以下步骤: 53 | 1. 新建一个`Callable`匿名函数实现类对象,我们的业务逻辑在`Callable`的`call`方法中实现,其中Callable的泛型是返回结果类型; 54 | 2. 然后把`Callable`匿名函数对象作为`FutureTask`的构造参数传入,构建一个`futureTask`对象; 55 | 3. 然后再把`futureTask`对象作为`Thread`构造参数传入并开启这个线程执行去执行业务逻辑; 56 | 4. 最后我们调用`futureTask`对象的`get`方法得到业务逻辑执行结果。 57 | 58 | 可以看到跟Future使用有关的JDK类主要有`FutureTask`和`Callable`两个,下面主要对`FutureTask`进行源码分析。 59 | > **扩展**: 还有一种使用`Future`的方式是将`Callable`实现类提交给线程池执行的方式,这里不再介绍,自行百度即可。 60 | 61 | # 3 FutureTask类结构分析 62 | 我们先来看下`FutureTask`的类结构: 63 | 64 | ![](https://user-gold-cdn.xitu.io/2020/6/21/172d73948efa22e2?w=299&h=299&f=png&s=8011) 65 | 可以看到`FutureTask`实现了`RunnableFuture`接口,而`RunnableFuture`接口又继承了`Future`和`Runnable`接口。因为`FutureTask`间接实现了`Runnable`接口,因此可以作为任务被线程`Thread`执行;此外,**最重要的一点**就是`FutureTask`还间接实现了`Future`接口,因此还可以获得任务执行的结果。下面我们就来简单看看这几个接口的相关`api`。 66 | ```java 67 | // Runnable.java 68 | 69 | @FunctionalInterface 70 | public interface Runnable { 71 | // 执行线程任务 72 | public abstract void run(); 73 | } 74 | ``` 75 | `Runnable`没啥好说的,相信大家都已经很熟悉了。 76 | ```java 77 | // Future.java 78 | 79 | public interface Future { 80 | /** 81 | * 尝试取消线程任务的执行,分为以下几种情况: 82 | * 1)如果线程任务已经完成或已经被取消或其他原因不能被取消,此时会失败并返回false; 83 | * 2)如果任务还未开始执行,此时执行cancel方法,那么任务将被取消执行,此时返回true;TODO 此时对应任务状态state的哪种状态???不懂!! 84 | * 3)如果任务已经开始执行,那么mayInterruptIfRunning这个参数将决定是否取消任务的执行。 85 | * 这里值得注意的是,cancel(true)实质并不能真正取消线程任务的执行,而是发出一个线程 86 | * 中断的信号,一般需要结合Thread.currentThread().isInterrupted()来使用。 87 | */ 88 | boolean cancel(boolean mayInterruptIfRunning); 89 | /** 90 | * 判断任务是否被取消,在执行任务完成前被取消,此时会返回true 91 | */ 92 | boolean isCancelled(); 93 | /** 94 | * 这个方法不管任务正常停止,异常还是任务被取消,总是返回true。 95 | */ 96 | boolean isDone(); 97 | /** 98 | * 获取任务执行结果,注意是阻塞等待获取任务执行结果。 99 | */ 100 | V get() throws InterruptedException, ExecutionException; 101 | /** 102 | * 获取任务执行结果,注意是阻塞等待获取任务执行结果。 103 | * 只不过在规定的时间内未获取到结果,此时会抛出超时异常 104 | */ 105 | V get(long timeout, TimeUnit unit) 106 | throws InterruptedException, ExecutionException, TimeoutException; 107 | } 108 | ``` 109 | `Future`接口象征着异步执行任务的结果即执行一个耗时任务完全可以另起一个线程执行,然后此时我们可以去做其他事情,做完其他事情我们再调用`Future.get()`方法获取结果即可,此时若异步任务还没结束,此时会一直阻塞等待,直到异步任务执行完获取到结果。 110 | 111 | ```java 112 | // RunnableFuture.java 113 | 114 | public interface RunnableFuture extends Runnable, Future { 115 | /** 116 | * Sets this Future to the result of its computation 117 | * unless it has been cancelled. 118 | */ 119 | void run(); 120 | } 121 | ``` 122 | `RunnableFuture`是`Future`和`Runnable`接口的组合,即这个接口表示又可以被线程异步执行,因为实现了`Runnable`接口,又可以获得线程异步任务的执行结果,因为实现了`Future`接口。因此解决了`Runnable`异步任务没有返回结果的缺陷。 123 | 124 | 接下来我们来看下`FutureTask`,`FutureTask`实现了`RunnableFuture`接口,因此是`Future`和`Runnable`接口的具体实现类,是一个可被取消的异步线程任务,提供了`Future`的基本实现,即异步任务执行后我们能够获取到异步任务的执行结果,是我们接下来分析的重中之重。`FutureTask`可以包装一个`Callable`和`Runnable`对象,此外,`FutureTask`除了可以被线程执行外,还可以被提交给线程池执行。 125 | 126 | 我们先看下`FutureTask`类的`api`,其中重点方法已经红框框出。 127 | 128 | ![](https://user-gold-cdn.xitu.io/2020/6/25/172e98c0611e7c60?w=389&h=429&f=png&s=27506) 129 | 上图中`FutureTask`的`run`方法是被线程异步执行的方法,`get`方法即是取得异步任务执行结果的方法,还有`cancel`方法是取消任务执行的方法。接下来我们主要对这三个方法进行重点分析。 130 | > **思考**: 131 | 1. `FutureTask`覆写的`run`方法的返回类型依然是`void`,表示没有返回值,那么`FutureTask`的`get`方法又是如何获得返回值的呢? 132 | 2. `FutureTask`的`cancel`方法能真正取消线程异步任务的执行么?什么情况下能取消? 133 | 134 | 因为`FutureTask`异步任务执行结果还跟`Callable`接口有关,因此我们再来看下`Callable`接口: 135 | ```java 136 | // Callable.java 137 | 138 | @FunctionalInterface 139 | public interface Callable { 140 | /** 141 | * Computes a result, or throws an exception if unable to do so. 142 | */ 143 | V call() throws Exception; 144 | } 145 | ``` 146 | 我们都知道,`Callable`接口和`Runnable`接口都可以被提交给线程池执行,唯一不同的就是`Callable`接口是有返回结果的,其中的泛型`V`就是返回结果,而`Runnable`接口是没有返回结果的。 147 | > **思考**: 一般情况下,`Runnable`接口实现类才能被提交给线程池执行,为何`Callable`接口实现类也可以被提交给线程池执行?想想线程池的`submit`方法内部有对`Callable`做适配么? 148 | 149 | # 4 FutureTask源码分析 150 | 151 | ## 4.1 FutureTask成员变量 152 | 我们首先来看下`FutureTask`的成员变量有哪些,理解这些成员变量对后面的源码分析非常重要。 153 | ```java 154 | // FutureTask.java 155 | 156 | /** 封装的Callable对象,其call方法用来执行异步任务 */ 157 | private Callable callable; 158 | /** 在FutureTask里面定义一个成员变量outcome,用来装异步任务的执行结果 */ 159 | private Object outcome; // non-volatile, protected by state reads/writes 160 | /** 用来执行callable任务的线程 */ 161 | private volatile Thread runner; 162 | /** 线程等待节点,reiber stack的一种实现 */ 163 | private volatile WaitNode waiters; 164 | /** 任务执行状态 */ 165 | private volatile int state; 166 | 167 | // Unsafe mechanics 168 | private static final sun.misc.Unsafe UNSAFE; 169 | // 对应成员变量state的偏移地址 170 | private static final long stateOffset; 171 | // 对应成员变量runner的偏移地址 172 | private static final long runnerOffset; 173 | // 对应成员变量waiters的偏移地址 174 | private static final long waitersOffset; 175 | ``` 176 | 这里我们要重点关注下`FutureTask`的`Callable`成员变量,因为`FutureTask`的异步任务最终是委托给`Callable`去实现的。 177 | 178 | > **思考**: 179 | 1. `FutureTask`的成员变量`runner`,`waiters`和`state`都被`volatile`修饰,我们可以思考下为什么这三个成员变量需要被`volatile`修饰,而其他成员变量又不用呢?`volatile`关键字的作用又是什么呢? 180 | 2. 既然已经定义了成员变量`runner`,`waiters`和`state`了,此时又定义了`stateOffset`,`runnerOffset`和`waitersOffset`变量分别对应`runner`,`waiters`和`state`的偏移地址,为何要多此一举呢? 181 | 182 | 我们再来看看`stateOffset`,`runnerOffset`和`waitersOffset`变量这三个变量的初始化过程: 183 | ```java 184 | // FutureTask.java 185 | 186 | static { 187 | try { 188 | UNSAFE = sun.misc.Unsafe.getUnsafe(); 189 | Class k = FutureTask.class; 190 | stateOffset = UNSAFE.objectFieldOffset 191 | (k.getDeclaredField("state")); 192 | runnerOffset = UNSAFE.objectFieldOffset 193 | (k.getDeclaredField("runner")); 194 | waitersOffset = UNSAFE.objectFieldOffset 195 | (k.getDeclaredField("waiters")); 196 | } catch (Exception e) { 197 | throw new Error(e); 198 | } 199 | } 200 | ``` 201 | ## 4.2 FutureTask的状态变化 202 | 前面讲了`FutureTask`的成员变量,有一个表示**状态**的成员变量`state`我们要重点关注下,`state`变量表示任务执行的状态。 203 | ```java 204 | // FutureTask.java 205 | 206 | /** 任务执行状态 */ 207 | private volatile int state; 208 | /** 任务新建状态 */ 209 | private static final int NEW = 0; 210 | /** 任务正在完成状态,是一个瞬间过渡状态 */ 211 | private static final int COMPLETING = 1; 212 | /** 任务正常结束状态 */ 213 | private static final int NORMAL = 2; 214 | /** 任务执行异常状态 */ 215 | private static final int EXCEPTIONAL = 3; 216 | /** 任务被取消状态,对应cancel(false) */ 217 | private static final int CANCELLED = 4; 218 | /** 任务中断状态,是一个瞬间过渡状态 */ 219 | private static final int INTERRUPTING = 5; 220 | /** 任务被中断状态,对应cancel(true) */ 221 | private static final int INTERRUPTED = 6; 222 | ``` 223 | 可以看到任务状态变量`state`有以上7种状态,0-6分别对应着每一种状态。任务状态一开始是`NEW`,然后由`FutureTask`的三个方法`set`,`setException`和`cancel`来设置状态的变化,其中状态变化有以下四种情况: 224 | 225 | 1. `NEW -> COMPLETING -> NORMAL`:这个状态变化表示异步任务的正常结束,其中`COMPLETING`是一个瞬间临时的过渡状态,由`set`方法设置状态的变化; 226 | 2. `NEW -> COMPLETING -> EXCEPTIONAL`:这个状态变化表示异步任务执行过程中抛出异常,由`setException`方法设置状态的变化; 227 | 3. `NEW -> CANCELLED`:这个状态变化表示被取消,即调用了`cancel(false)`,由`cancel`方法来设置状态变化; 228 | 4. `NEW -> INTERRUPTING -> INTERRUPTED`:这个状态变化表示被中断,即调用了`cancel(true)`,由`cancel`方法来设置状态变化。 229 | 230 | 231 | 232 | 233 | 234 | ## 4.3 FutureTask构造函数 235 | `FutureTask`有两个构造函数,我们分别来看看: 236 | ```java 237 | // FutureTask.java 238 | 239 | // 第一个构造函数 240 | public FutureTask(Callable callable) { 241 | if (callable == null) 242 | throw new NullPointerException(); 243 | this.callable = callable; 244 | this.state = NEW; // ensure visibility of callable 245 | } 246 | ``` 247 | 可以看到,这个构造函数在我们前面举的“打火锅”的例子代码中有用到,就是`Callable`成员变量赋值,在异步执行任务时再调用`Callable.call`方法执行异步任务逻辑。此外,此时给任务状态`state`赋值为`NEW`,表示任务新建状态。 248 | 249 | 我们再来看下`FutureTask`的另外一个构造函数: 250 | ```java 251 | // FutureTask.java 252 | 253 | // 另一个构造函数 254 | public FutureTask(Runnable runnable, V result) { 255 | this.callable = Executors.callable(runnable, result); 256 | this.state = NEW; // ensure visibility of callable 257 | } 258 | ``` 259 | 这个构造函数在执行`Executors.callable(runnable, result)`时是通过适配器`RunnableAdapter`来将`Runnable`对象`runnable`转换成`Callable`对象,然后再分别给`callable`和`state`变量赋值。 260 | 261 | **注意**,这里我们需要记住的是`FutureTask`新建时,此时的任务状态`state`是`NEW`就好了。 262 | ## 4.4 FutureTask.run方法,用来执行异步任务 263 | 前面我们有讲到`FutureTask`间接实现了`Runnable`接口,覆写了`Runnable`接口的`run`方法,因此该覆写的`run`方法是提交给线程来执行的,同时,该`run`方法正是执行异步任务逻辑的方法,那么,执行完`run`方法又是如何保存异步任务执行的结果的呢? 264 | 265 | 我们现在着重来分析下`run`方法: 266 | ```java 267 | // FutureTask.java 268 | 269 | public void run() { 270 | // 【1】,为了防止多线程并发执行异步任务,这里需要判断线程满不满足执行异步任务的条件,有以下三种情况: 271 | // 1)若任务状态state为NEW且runner为null,说明还未有线程执行过异步任务,此时满足执行异步任务的条件, 272 | // 此时同时调用CAS方法为成员变量runner设置当前线程的值; 273 | // 2)若任务状态state为NEW且runner不为null,任务状态虽为NEW但runner不为null,说明有线程正在执行异步任务, 274 | // 此时不满足执行异步任务的条件,直接返回; 275 | // 1)若任务状态state不为NEW,此时不管runner是否为null,说明已经有线程执行过异步任务,此时没必要再重新 276 | // 执行一次异步任务,此时不满足执行异步任务的条件; 277 | if (state != NEW || 278 | !UNSAFE.compareAndSwapObject(this, runnerOffset, 279 | null, Thread.currentThread())) 280 | return; 281 | try { 282 | // 拿到之前构造函数传进来的callable实现类对象,其call方法封装了异步任务执行的逻辑 283 | Callable c = callable; 284 | // 若任务还是新建状态的话,那么就调用异步任务 285 | if (c != null && state == NEW) { 286 | // 异步任务执行结果 287 | V result; 288 | // 异步任务执行成功还是始遍标志 289 | boolean ran; 290 | try { 291 | // 【2】,执行异步任务逻辑,并把执行结果赋值给result 292 | result = c.call(); 293 | // 若异步任务执行过程中没有抛出异常,说明异步任务执行成功,此时设置ran标志为true 294 | ran = true; 295 | } catch (Throwable ex) { 296 | result = null; 297 | // 异步任务执行过程抛出异常,此时设置ran标志为false 298 | ran = false; 299 | // 【3】设置异常,里面也设置state状态的变化 300 | setException(ex); 301 | } 302 | // 【3】若异步任务执行成功,此时设置异步任务执行结果,同时也设置状态的变化 303 | if (ran) 304 | set(result); 305 | } 306 | } finally { 307 | // runner must be non-null until state is settled to 308 | // prevent concurrent calls to run() 309 | // 异步任务正在执行过程中,runner一直是非空的,防止并发调用run方法,前面有调用cas方法做判断的 310 | // 在异步任务执行完后,不管是正常结束还是异常结束,此时设置runner为null 311 | runner = null; 312 | // state must be re-read after nulling runner to prevent 313 | // leaked interrupts 314 | // 线程执行异步任务后的任务状态 315 | int s = state; 316 | // 【4】如果执行了cancel(true)方法,此时满足条件, 317 | // 此时调用handlePossibleCancellationInterrupt方法处理中断 318 | if (s >= INTERRUPTING) 319 | handlePossibleCancellationInterrupt(s); 320 | } 321 | } 322 | ``` 323 | 可以看到执行异步任务的`run`方法主要分为以下四步来执行: 324 | 1. **判断线程是否满足执行异步任务的条件**:为了防止多线程并发执行异步任务,这里需要判断线程满不满足执行异步任务的条件; 325 | 2. **若满足条件,执行异步任务**:因为异步任务逻辑封装在`Callable.call`方法中,此时直接调用`Callable.call`方法执行异步任务,然后返回执行结果; 326 | 3. **根据异步任务的执行情况做不同的处理**:1) 若异步任务执行正常结束,此时调用`set(result);`来设置任务执行结果;2)若异步任务执行抛出异常,此时调用`setException(ex);`来设置异常,详细分析请见`4.4.1小节`; 327 | 4. **异步任务执行完后的善后处理工作**:不管异步任务执行成功还是失败,若其他线程有调用`FutureTask.cancel(true)`,此时需要调用`handlePossibleCancellationInterrupt`方法处理中断,详细分析请见`4.4.2小节`。 328 | 329 | 这里**值得注意**的是判断线程满不满足执行异步任务条件时,`runner`是否为`null`是调用`UNSAFE`的`CAS`方法`compareAndSwapObject`来判断和设置的,同时`compareAndSwapObject`是通过成员变量`runner`的偏移地址`runnerOffset`来给`runner`赋值的,此外,成员变量`runner`被修饰为`volatile`是在多线程的情况下, 一个线程的`volatile`修饰变量的设值能够立即刷进主存,因此值便可被其他线程可见。 330 | 331 | ### 4.4.1 FutureTask的set和setException方法 332 | 下面我们来看下当异步任务执行正常结束时,此时会调用`set(result);`方法: 333 | ```java 334 | // FutureTask.java 335 | 336 | protected void set(V v) { 337 | // 【1】调用UNSAFE的CAS方法判断任务当前状态是否为NEW,若为NEW,则设置任务状态为COMPLETING 338 | // 【思考】此时任务不能被多线程并发执行,什么情况下会导致任务状态不为NEW? 339 | // 答案是只有在调用了cancel方法的时候,此时任务状态不为NEW,此时什么都不需要做, 340 | // 因此需要调用CAS方法来做判断任务状态是否为NEW 341 | if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { 342 | // 【2】将任务执行结果赋值给成员变量outcome 343 | outcome = v; 344 | // 【3】将任务状态设置为NORMAL,表示任务正常结束 345 | UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state 346 | // 【4】调用任务执行完成方法,此时会唤醒阻塞的线程,调用done()方法和清空等待线程链表等 347 | finishCompletion(); 348 | } 349 | } 350 | ``` 351 | 可以看到当异步任务正常执行结束后,且异步任务没有被`cancel`的情况下,此时会做以下事情:将任务执行结果保存到`FutureTask`的成员变量`outcome`中的,赋值结束后会调用`finishCompletion`方法来唤醒阻塞的线程(哪里来的阻塞线程?后面会分析),**值得注意**的是这里对应的任务状态变化是**NEW -> COMPLETING -> NORMAL**。 352 | 353 | 354 | 我们继续来看下当异步任务执行过程中抛出异常,此时会调用`setException(ex);`方法。 355 | ```java 356 | // FutureTask.java 357 | 358 | protected void setException(Throwable t) { 359 | // 【1】调用UNSAFE的CAS方法判断任务当前状态是否为NEW,若为NEW,则设置任务状态为COMPLETING 360 | // 【思考】此时任务不能被多线程并发执行,什么情况下会导致任务状态不为NEW? 361 | // 答案是只有在调用了cancel方法的时候,此时任务状态不为NEW,此时什么都不需要做, 362 | // 因此需要调用CAS方法来做判断任务状态是否为NEW 363 | if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { 364 | // 【2】将异常赋值给成员变量outcome 365 | outcome = t; 366 | // 【3】将任务状态设置为EXCEPTIONAL 367 | UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state 368 | // 【4】调用任务执行完成方法,此时会唤醒阻塞的线程,调用done()方法和清空等待线程链表等 369 | finishCompletion(); 370 | } 371 | } 372 | ``` 373 | 可以看到`setException(Throwable t)`的代码逻辑跟前面的`set(V v)`几乎一样,不同的是任务执行过程中抛出异常,此时是将异常保存到`FutureTask`的成员变量`outcome`中,还有,**值得注意**的是这里对应的任务状态变化是**NEW -> COMPLETING -> EXCEPTIONAL**。 374 | 375 | 因为异步任务不管正常还是异常结束,此时都会调用`FutureTask`的`finishCompletion`方法来唤醒唤醒阻塞的线程,这里阻塞的线程是指我们调用`Future.get`方法时若异步任务还未执行完,此时该线程会阻塞。 376 | ```java 377 | // FutureTask.java 378 | 379 | private void finishCompletion() { 380 | // assert state > COMPLETING; 381 | // 取出等待线程链表头节点,判断头节点是否为null 382 | // 1)若线程链表头节点不为空,此时以“后进先出”的顺序(栈)移除等待的线程WaitNode节点 383 | // 2)若线程链表头节点为空,说明还没有线程调用Future.get()方法来获取任务执行结果,固然不用移除 384 | for (WaitNode q; (q = waiters) != null;) { 385 | // 调用UNSAFE的CAS方法将成员变量waiters设置为空 386 | if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) { 387 | for (;;) { 388 | // 取出WaitNode节点的线程 389 | Thread t = q.thread; 390 | // 若取出的线程不为null,则将该WaitNode节点线程置空,且唤醒正在阻塞的该线程 391 | if (t != null) { 392 | q.thread = null; 393 | //【重要】唤醒正在阻塞的该线程 394 | LockSupport.unpark(t); 395 | } 396 | // 继续取得下一个WaitNode线程节点 397 | WaitNode next = q.next; 398 | // 若没有下一个WaitNode线程节点,说明已经将所有等待的线程唤醒,此时跳出for循环 399 | if (next == null) 400 | break; 401 | // 将已经移除的线程WaitNode节点的next指针置空,此时好被垃圾回收 402 | q.next = null; // unlink to help gc 403 | // 再把下一个WaitNode线程节点置为当前线程WaitNode头节点 404 | q = next; 405 | } 406 | break; 407 | } 408 | } 409 | // 不管任务正常执行还是抛出异常,都会调用done方法 410 | done(); 411 | // 因为异步任务已经执行完且结果已经保存到outcome中,因此此时可以将callable对象置空了 412 | callable = null; // to reduce footprint 413 | } 414 | ``` 415 | `finishCompletion`方法的作用就是不管异步任务正常还是异常结束,此时都要唤醒且移除线程等待链表的等待线程节点,这个链表实现的是一个是`Treiber stack`,因此唤醒(移除)的顺序是"后进先出"即后面先来的线程先被先唤醒(移除),关于这个线程等待链表是如何成链的,后面再继续分析。 416 | 417 | ### 4.4.2 FutureTask的handlePossibleCancellationInterrupt方法 418 | 在`4.4小节`分析的`run`方法里的最后有一个`finally`块,此时若任务状态`state >= INTERRUPTING`,此时说明有其他线程执行了`cancel(true)`方法,此时需要让出`CPU`执行的时间片段给其他线程执行,我们来看下具体的源码: 419 | ```java 420 | // FutureTask.java 421 | 422 | private void handlePossibleCancellationInterrupt(int s) { 423 | // It is possible for our interrupter to stall before getting a 424 | // chance to interrupt us. Let's spin-wait patiently. 425 | // 当任务状态是INTERRUPTING时,此时让出CPU执行的机会,让其他线程执行 426 | if (s == INTERRUPTING) 427 | while (state == INTERRUPTING) 428 | Thread.yield(); // wait out pending interrupt 429 | 430 | // assert state == INTERRUPTED; 431 | 432 | // We want to clear any interrupt we may have received from 433 | // cancel(true). However, it is permissible to use interrupts 434 | // as an independent mechanism for a task to communicate with 435 | // its caller, and there is no way to clear only the 436 | // cancellation interrupt. 437 | // 438 | // Thread.interrupted(); 439 | } 440 | ``` 441 | > **思考**: 为啥任务状态是`INTERRUPTING`时,此时就要让出CPU执行的时间片段呢?还有为什么要在义务任务执行后才调用`handlePossibleCancellationInterrupt`方法呢? 442 | 443 | ## 4.5 FutureTask.get方法,获取任务执行结果 444 | 445 | ```java 446 | 前面我们起一个线程在其`run`方法中执行异步任务后,此时我们可以调用`FutureTask.get`方法来获取异步任务执行的结果。 447 | 448 | // FutureTask.java 449 | 450 | public V get() throws InterruptedException, ExecutionException { 451 | int s = state; 452 | // 【1】若任务状态<=COMPLETING,说明任务正在执行过程中,此时可能正常结束,也可能遇到异常 453 | if (s <= COMPLETING) 454 | s = awaitDone(false, 0L); 455 | // 【2】最后根据任务状态来返回任务执行结果,此时有三种情况:1)任务正常执行;2)任务执行异常;3)任务被取消 456 | return report(s); 457 | } 458 | ``` 459 | 可以看到,如果任务状态`state<=COMPLETING`,说明异步任务正在执行过程中,此时会调用`awaitDone`方法阻塞等待;当任务执行完后,此时再调用`report`方法来报告任务结果,此时有三种情况:1)任务正常执行;2)任务执行异常;3)任务被取消。 460 | 461 | ### 4.5.1 FutureTask.awaitDone方法 462 | `FutureTask.awaitDone`方法会阻塞获取异步任务执行结果的当前线程,直到异步任务执行完成。 463 | ```java 464 | // FutureTask.java 465 | 466 | private int awaitDone(boolean timed, long nanos) 467 | throws InterruptedException { 468 | // 计算超时结束时间 469 | final long deadline = timed ? System.nanoTime() + nanos : 0L; 470 | // 线程链表头节点 471 | WaitNode q = null; 472 | // 是否入队 473 | boolean queued = false; 474 | // 死循环 475 | for (;;) { 476 | // 如果当前获取任务执行结果的线程被中断,此时移除该线程WaitNode链表节点,并抛出InterruptedException 477 | if (Thread.interrupted()) { 478 | removeWaiter(q); 479 | throw new InterruptedException(); 480 | } 481 | 482 | int s = state; 483 | // 【5】如果任务状态>COMPLETING,此时返回任务执行结果,其中此时任务可能正常结束(NORMAL),可能抛出异常(EXCEPTIONAL) 484 | // 或任务被取消(CANCELLED,INTERRUPTING或INTERRUPTED状态的一种) 485 | if (s > COMPLETING) { 486 | // 【问】此时将当前WaitNode节点的线程置空,其中在任务结束时也会调用finishCompletion将WaitNode节点的thread置空, 487 | // 这里为什么又要再调用一次q.thread = null;呢? 488 | // 【答】因为若很多线程来获取任务执行结果,在任务执行完的那一刻,此时获取任务的线程要么已经在线程等待链表中,要么 489 | // 此时还是一个孤立的WaitNode节点。在线程等待链表中的的所有WaitNode节点将由finishCompletion来移除(同时唤醒)所有 490 | // 等待的WaitNode节点,以便垃圾回收;而孤立的线程WaitNode节点此时还未阻塞,因此不需要被唤醒,此时只要把其属性置为 491 | // null,然后其有没有被谁引用,因此可以被GC。 492 | if (q != null) 493 | q.thread = null; 494 | // 【重要】返回任务执行结果 495 | return s; 496 | } 497 | // 【4】若任务状态为COMPLETING,此时说明任务正在执行过程中,此时获取任务结果的线程需让出CPU执行时间片段 498 | else if (s == COMPLETING) // cannot time out yet 499 | Thread.yield(); 500 | // 【1】若当前线程还没有进入线程等待链表的WaitNode节点,此时新建一个WaitNode节点,并把当前线程赋值给WaitNode节点的thread属性 501 | else if (q == null) 502 | q = new WaitNode(); 503 | // 【2】若当前线程等待节点还未入线程等待队列,此时加入到该线程等待队列的头部 504 | else if (!queued) 505 | queued = UNSAFE.compareAndSwapObject(this, waitersOffset, 506 | q.next = waiters, q); 507 | // 若有超时设置,那么处理超时获取任务结果的逻辑 508 | else if (timed) { 509 | nanos = deadline - System.nanoTime(); 510 | if (nanos <= 0L) { 511 | removeWaiter(q); 512 | return state; 513 | } 514 | LockSupport.parkNanos(this, nanos); 515 | } 516 | // 【3】若没有超时设置,此时直接阻塞当前线程 517 | else 518 | LockSupport.park(this); 519 | } 520 | } 521 | ``` 522 | `FutureTask.awaitDone`方法主要做的事情总结如下: 523 | 524 | 0. 首先`awaitDone`方法里面是一个死循环; 525 | 1. 若获取结果的当前线程被其他线程中断,此时移除该线程WaitNode链表节点,并抛出InterruptedException; 526 | 2. 如果任务状态`state>COMPLETING`,此时返回任务执行结果; 527 | 3. 若任务状态为`COMPLETING`,此时获取任务结果的线程需让出CPU执行时间片段; 528 | 4. 若`q == null`,说明当前线程还未设置到`WaitNode`节点,此时新建`WaitNode`节点并设置其`thread`属性为当前线程; 529 | 5. 若`queued==false`,说明当前线程`WaitNode`节点还未加入线程等待链表,此时加入该链表的头部; 530 | 6. 当`timed`设置为true时,此时该方法具有超时功能,关于超时的逻辑这里不详细分析; 531 | 7. 当前面6个条件都不满足时,此时阻塞当前线程。 532 | 533 | 我们分析到这里,可以直到执行异步任务只能有一个线程来执行,而获取异步任务结果可以多线程来获取,当异步任务还未执行完时,此时获取异步任务结果的线程会加入线程等待链表中,然后调用调用`LockSupport.park(this);`方法阻塞当前线程。直到异步任务执行完成,此时会调用`finishCompletion`方法来唤醒并移除线程等待链表的每个`WaitNode`节点,这里这里唤醒(移除)`WaitNode`节点的线程是从链表头部开始的,前面我们也已经分析过。 534 | 535 | 还有一个特别需要注意的就是`awaitDone`方法里面是一个死循环,当一个获取异步任务的线程进来后可能会多次进入多个条件分支执行不同的业务逻辑,也可能只进入一个条件分支。下面分别举两种可能的情况进行说明: 536 | 537 | **情况1**: 538 | 当获取异步任务结果的线程进来时,此时异步任务还未执行完即`state=NEW`且没有超时设置时: 539 | 1. **第一次循环**:此时`q = null`,此时进入上面代码标号`【1】`的判断分支,即为当前线程新建一个`WaitNode`节点; 540 | 2. **第二次循环**:此时`queued = false`,此时进入上面代码标号`【2】`的判断分支,即将之前新建的`WaitNode`节点加入线程等待链表中; 541 | 3. **第三次循环**:此时进入上面代码标号`【3】`的判断分支,即阻塞当前线程; 542 | 4. **第四次循环**:加入此时异步任务已经执行完,此时进入上面代码标号`【5】`的判断分支,即返回异步任务执行结果。 543 | 544 | **情况2**: 545 | 当获取异步任务结果的线程进来时,此时异步任务已经执行完即`state>COMPLETING`且没有超时设置时,此时直接进入上面代码标号`【5】`的判断分支,即直接返回异步任务执行结果即可,也不用加入线程等待链表了。 546 | ### 4.5.2 FutureTask.report方法 547 | 在`get`方法中,当异步任务执行结束后即不管异步任务正常还是异常结束,亦或是被`cancel`,此时获取异步任务结果的线程都会被唤醒,因此会继续执行`FutureTask.report`方法报告异步任务的执行情况,此时可能会返回结果,也可能会抛出异常。 548 | ```java 549 | // FutureTask.java 550 | 551 | private V report(int s) throws ExecutionException { 552 | // 将异步任务执行结果赋值给x,此时FutureTask的成员变量outcome要么保存着 553 | // 异步任务正常执行的结果,要么保存着异步任务执行过程中抛出的异常 554 | Object x = outcome; 555 | // 【1】若异步任务正常执行结束,此时返回异步任务执行结果即可 556 | if (s == NORMAL) 557 | return (V)x; 558 | // 【2】若异步任务执行过程中,其他线程执行过cancel方法,此时抛出CancellationException异常 559 | if (s >= CANCELLED) 560 | throw new CancellationException(); 561 | // 【3】若异步任务执行过程中,抛出异常,此时将该异常转换成ExecutionException后,重新抛出。 562 | throw new ExecutionException((Throwable)x); 563 | } 564 | ``` 565 | ## 4.6 FutureTask.cancel方法,取消执行任务 566 | 我们最后再来看下`FutureTask.cancel`方法,我们一看到`FutureTask.cancel`方法,肯定一开始就天真的认为这是一个可以取消异步任务执行的方法,如果我们这样认为的话,只能说我们猜对了一半。 567 | ```java 568 | // FutureTask.java 569 | 570 | public boolean cancel(boolean mayInterruptIfRunning) { 571 | // 【1】判断当前任务状态,若state == NEW时根据mayInterruptIfRunning参数值给当前任务状态赋值为INTERRUPTING或CANCELLED 572 | // a)当任务状态不为NEW时,说明异步任务已经完成,或抛出异常,或已经被取消,此时直接返回false。 573 | // TODO 【问题】此时若state = COMPLETING呢?此时为何也直接返回false,而不能发出中断异步任务线程的中断信号呢?? 574 | // TODO 仅仅因为COMPLETING是一个瞬时态吗??? 575 | // b)当前仅当任务状态为NEW时,此时若mayInterruptIfRunning为true,此时任务状态赋值为INTERRUPTING;否则赋值为CANCELLED。 576 | if (!(state == NEW && 577 | UNSAFE.compareAndSwapInt(this, stateOffset, NEW, 578 | mayInterruptIfRunning ? INTERRUPTING : CANCELLED))) 579 | return false; 580 | try { // in case call to interrupt throws exception 581 | // 【2】如果mayInterruptIfRunning为true,此时中断执行异步任务的线程runner(还记得执行异步任务时就把执行异步任务的线程就赋值给了runner成员变量吗) 582 | if (mayInterruptIfRunning) { 583 | try { 584 | Thread t = runner; 585 | if (t != null) 586 | // 中断执行异步任务的线程runner 587 | t.interrupt(); 588 | } finally { // final state 589 | // 最后任务状态赋值为INTERRUPTED 590 | UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED); 591 | } 592 | } 593 | // 【3】不管mayInterruptIfRunning为true还是false,此时都要调用finishCompletion方法唤醒阻塞的获取异步任务结果的线程并移除线程等待链表节点 594 | } finally { 595 | finishCompletion(); 596 | } 597 | // 返回true 598 | return true; 599 | } 600 | ``` 601 | 以上代码中,当异步任务状态`state != NEW`时,说明异步任务已经正常执行完或已经异常结束亦或已经被`cancel`,此时直接返回`false`;当异步任务状态`state = NEW`时,此时又根据`mayInterruptIfRunning`参数是否为`true`分为以下两种情况: 602 | 603 | 1. 当`mayInterruptIfRunning = false`时,此时任务状态`state`直接被赋值为`CANCELLED`,此时不会对执行异步任务的线程发出中断信号,**值得注意**的是这里对应的任务状态变化是**NEW -> CANCELLED**。 604 | 2. 当`mayInterruptIfRunning = true`时,此时会对执行异步任务的线程发出中断信号,**值得注意**的是这里对应的任务状态变化是**NEW -> INTERRUPTING -> INTERRUPTED**。 605 | 606 | 最后不管`mayInterruptIfRunning`为`true`还是`false`,此时都要调用`finishCompletion`方法唤醒阻塞的获取异步任务结果的线程并移除线程等待链表节点。 607 | 608 | 从`FutureTask.cancel`源码中我们可以得出答案,该方法并不能真正中断正在执行异步任务的线程,只能对执行异步任务的线程发出中断信号。如果执行异步任务的线程处于`sleep`、`wait`或`join`的状态中,此时会抛出`InterruptedException`异常,该线程可以被中断;此外,如果异步任务需要在`while`循环执行的话,此时可以结合以下代码来结束异步任务线程,即执行异步任务的线程被中断时,此时`Thread.currentThread().isInterrupted()`返回`true`,不满足`while`循环条件因此退出循环,结束异步任务执行线程,如下代码: 609 | ``` 610 | public Integer call() throws Exception { 611 | while (!Thread.currentThread().isInterrupted()) { 612 | // 业务逻辑代码 613 | System.out.println("running..."); 614 | 615 | } 616 | return 666; 617 | } 618 | ``` 619 | 620 | **注意**:调用了`FutureTask.cancel`方法,只要返回结果是`true`,假如异步任务线程虽然不能被中断,即使异步任务线程正常执行完毕,返回了执行结果,此时调用`FutureTask.get`方法也不能够获取异步任务执行结果,此时会抛出`CancellationException`异常。请问知道这是为什么吗? 621 | 622 | 因为调用了`FutureTask.cancel`方法,只要返回结果是`true`,此时的任务状态为`CANCELLED`或`INTERRUPTED`,同时必然会执行`finishCompletion`方法,而`finishCompletion`方法会唤醒获取异步任务结果的线程等待列表的线程,而获取异步任务结果的线程唤醒后发现状态`s >= CANCELLED`,此时就会抛出`CancellationException`异常了。 623 | 624 | # 5 总结 625 | 626 | 好了,本篇文章对`FutureTask`的源码分析就到此结束了,下面我们再总结下`FutureTask`的实现逻辑: 627 | 1. 我们实现`Callable`接口,在覆写的`call`方法中定义需要执行的业务逻辑; 628 | 2. 然后把我们实现的`Callable`接口实现对象传给`FutureTask`,然后`FutureTask`作为异步任务提交给线程执行; 629 | 3. 最重要的是`FutureTask`内部维护了一个状态`state`,任何操作(异步任务正常结束与否还是被取消)都是围绕着这个状态进行,并随时更新`state`任务的状态; 630 | 4. 只能有一个线程执行异步任务,当异步任务执行结束后,此时可能正常结束,异常结束或被取消。 631 | 5. 可以多个线程并发获取异步任务执行结果,当异步任务还未执行完,此时获取异步任务的线程将加入线程等待列表进行等待; 632 | 6. 当异步任务线程执行结束后,此时会唤醒获取异步任务执行结果的线程,注意唤醒顺序是"后进先出"即后面加入的阻塞线程先被唤醒。 633 | 7. 当我们调用`FutureTask.cancel`方法时并不能真正停止执行异步任务的线程,只是发出中断线程的信号。但是只要`cancel`方法返回`true`,此时即使异步任务能正常执行完,此时我们调用`get`方法获取结果时依然会抛出`CancellationException`异常。 634 | 635 | > **扩展**: 前面我们提到了`FutureTask`的`runner`,`waiters`和`state`都是用`volatile`关键字修饰,说明这三个变量都是多线程共享的对象(成员变量),会被多线程操作,此时用`volatile`关键字修饰是为了一个线程操作`volatile`属性变量值后,能够及时对其他线程可见。此时多线程操作成员变量仅仅用了`volatile`关键字仍然会有线程安全问题的,而此时Doug Lea老爷子没有引入任何线程锁,而是采用了`Unsafe`的`CAS`方法来代替锁操作,确保线程安全性。 636 | 637 | # 6 分析FutureTask源码,我们能学到什么? 638 | 639 | 我们分析源码的目的是什么?除了弄懂`FutureTask`的内部实现原理外,我们还要借鉴大佬写写框架源码的各种技巧,只有这样,我们才能成长。 640 | 641 | 分析了`FutureTask`源码,我们可以从中学到: 642 | 1. 利用`LockSupport`来实现线程的阻塞\唤醒机制; 643 | 2. 利用`volatile`和`UNSAFE`的`CAS`方法来实现线程共享变量的无锁化操作; 644 | 3. 若要编写超时异常的逻辑可以参考`FutureTask`的`get(long timeout, TimeUnit unit)`的实现逻辑; 645 | 4. 多线程获取某一成员变量结果时若需要等待时的线程等待链表的逻辑实现; 646 | 5. 某一异步任务在某一时刻只能由单一线程执行的逻辑实现; 647 | 6. `FutureTask`中的任务状态`satate`的变化处理的逻辑实现。 648 | 7. ... 649 | 650 | 以上列举的几点都是我们可以学习参考的地方。 651 | 652 | 653 | **若您觉得不错,请无情的转发和点赞吧!** 654 | 655 | 【源码笔记】Github地址: 656 | 657 | https://github.com/yuanmabiji/Java-SourceCode-Blogs 658 | 659 | ------------------------------------------------------------------------------- 660 | 公众号【源码笔记】,专注于Java后端系列框架的源码分析。 661 | 662 | ![](https://user-gold-cdn.xitu.io/2020/6/26/172ec7a63ebdf26f?w=498&h=143&f=png&s=23420) -------------------------------------------------------------------------------- /JUC/README.md: -------------------------------------------------------------------------------- 1 | 2 | 【**源码笔记**】专注于Java后端系列框架的源码分析。若觉得源码分析文章不错,欢迎Star哦。 3 | 4 | 5 | ================**JUC源码专题持续更新中...**==================== 6 | 7 | #### 目录 8 | 9 | 1. [Java是如何实现Future模式的?万字详解!](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/JUC/Java%E6%98%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0Future%E6%A8%A1%E5%BC%8F%E7%9A%84%EF%BC%9F%E4%B8%87%E5%AD%97%E8%AF%A6%E8%A7%A3%EF%BC%81.md) 10 | 2. 持续更新中... 11 | 12 | * 更多源码分析文章请跳转至:https://github.com/yuanmabiji/Java-SourceCode-Blogs 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 【**源码笔记**】专注于Java后端系列框架的源码分析。若觉得源码分析文章不错,欢迎Star哦。 2 | 3 | 公众号: 4 | 5 | ![img_1.png](https://common-ymbj.oss-cn-beijing.aliyuncs.com/wxgzh_qrcode.PNG) 6 | 7 | **温馨提示**:github上前期文章图片失效了,如果想阅读前期文章的话还请移步公众号阅读哦。 8 | 9 | 【源码笔记】计划每周持续推出一篇Java后端框架源码系列的文章,随着时间的积累,Java后端源码分析文章肯定会越来越多,越来越丰富哦,敬请关注。 10 | 11 | ### 目录 12 | 13 | ===================**源码阅读感悟&&阅读技巧**====================== 14 | 1. [跟大家聊聊我们为什么要学习源码?学习源码对我们有用吗?](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/%E8%B7%9F%E5%A4%A7%E5%AE%B6%E8%81%8A%E8%81%8A%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%AD%A6%E4%B9%A0%E6%BA%90%E7%A0%81%EF%BC%9F%E5%AD%A6%E4%B9%A0%E6%BA%90%E7%A0%81%E5%AF%B9%E6%88%91%E4%BB%AC%E6%9C%89%E7%94%A8%E5%90%97%EF%BC%9F.md) 15 | 2. [分析开源项目源码,我们该如何入手分析?(授人以渔)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/%E5%88%86%E6%9E%90%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E6%BA%90%E7%A0%81%EF%BC%8C%E6%88%91%E4%BB%AC%E8%AF%A5%E5%A6%82%E4%BD%95%E5%85%A5%E6%89%8B%E5%88%86%E6%9E%90%EF%BC%9F%EF%BC%88%E6%8E%88%E4%BA%BA%E4%BB%A5%E6%B8%94%EF%BC%89.md) 16 | 17 | ================**Disruptor源码专题持续更新中...**==================== 18 | 1. [初识Disruptor框架!](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/Disruptor/初识Disruptor框架.md) 19 | 2. [Disruptor广播模式与执行顺序链源码分析](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/Disruptor/Disruptor广播模式与执行顺序链源码分析.md) 20 | 3. 持续更新中... 21 | * Disruptor源码分析专题:https://github.com/yuanmabiji/Java-SourceCode-Blogs/tree/master/Disruptor 22 | * Disruptor源码解析项目(带中文注释):https://github.com/yuanmabiji/disruptor 23 | 24 | ================**JUC源码专题持续更新中...**==================== 25 | 1. [Java是如何实现Future模式的?万字详解!](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/JUC/Java%E6%98%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0Future%E6%A8%A1%E5%BC%8F%E7%9A%84%EF%BC%9F%E4%B8%87%E5%AD%97%E8%AF%A6%E8%A7%A3%EF%BC%81.md) 26 | 2. 持续更新中... 27 | * JUC源码分析专题:https://github.com/yuanmabiji/Java-SourceCode-Blogs/tree/master/JUC 28 | * JUC源码解析项目(带中文注释):https://github.com/yuanmabiji/jdk1.8-sourcecode-blogs 29 | 30 | ================**SpringBoot源码专题持续更新中...**==================== 31 | 1. [如何搭建自己的SpringBoot源码调试环境? SpringBoot源码(一)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/1%20%E5%A6%82%E4%BD%95%E6%90%AD%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84SpringBoot%E6%BA%90%E7%A0%81%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83%EF%BC%9F%20%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%80%EF%BC%89.md) 32 | 2. [如何分析SpringBoot源码模块及结构? SpringBoot源码(二)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/2%20%E5%A6%82%E4%BD%95%E5%88%86%E6%9E%90SpringBoot%E6%BA%90%E7%A0%81%E6%A8%A1%E5%9D%97%E5%8F%8A%E7%BB%93%E6%9E%84%EF%BC%9F%20%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E4%BA%8C%EF%BC%89.md) 33 | 3. [助力SpringBoot自动配置的条件注解原理揭秘 SpringBoot源码(三)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/3%20%E5%8A%A9%E5%8A%9BSpringBoot%E8%87%AA%E5%8A%A8%E9%85%8D%E7%BD%AE%E7%9A%84%E6%9D%A1%E4%BB%B6%E6%B3%A8%E8%A7%A3%E5%8E%9F%E7%90%86%E6%8F%AD%E7%A7%98%20%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%89%EF%BC%89.md) 34 | 4. [SpringBoot是如何实现自动配置的? SpringBoot源码(四)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/4%20SpringBoot%E6%98%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E9%85%8D%E7%BD%AE%E7%9A%84%EF%BC%9F%20%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E5%9B%9B%EF%BC%89.md) 35 | 5. [SpringBoot的配置属性值是如何绑定的? SpringBoot源码(五)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/5%20SpringBoot%E7%9A%84%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%80%BC%E6%98%AF%E5%A6%82%E4%BD%95%E7%BB%91%E5%AE%9A%E7%9A%84%EF%BC%9F%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E4%BA%94%EF%BC%89.md) 36 | 6. [SpringBoot内置的各种Starter是怎样构建的? SpringBoot源码(六)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/6%20SpringBoot%E5%86%85%E7%BD%AE%E7%9A%84%E5%90%84%E7%A7%8DStarter%E6%98%AF%E6%80%8E%E6%A0%B7%E6%9E%84%E5%BB%BA%E7%9A%84%EF%BC%9F%20%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E5%85%AD%EF%BC%89.md) 37 | 7. [SpringBoot的启动流程是怎样的?SpringBoot源码(七)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/7%20SpringBoot%E7%9A%84%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84%EF%BC%9FSpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%83%EF%BC%89.md) 38 | 8. [SpringApplication对象是如何构建的? SpringBoot源码(八)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/8%20SpringApplication%E5%AF%B9%E8%B1%A1%E6%98%AF%E5%A6%82%E4%BD%95%E6%9E%84%E5%BB%BA%E7%9A%84%EF%BC%9F%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E5%85%AB%EF%BC%89.md) 39 | 9. [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) 40 | 10. [SpringBoot内置生命周期事件详解 SpringBoot源码(十)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/10%20SpringBoot%E5%86%85%E7%BD%AE%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E4%BA%8B%E4%BB%B6%E8%AF%A6%E8%A7%A3%20%20SpringBoot%E6%BA%90%E7%A0%81(%E5%8D%81).md) 41 | 11. 持续更新中... 42 | * SpringBoot源码分析专题:https://github.com/yuanmabiji/Java-SourceCode-Blogs/tree/master/SpringBoot 43 | * SpringBoot源码解析项目(带中文注释):https://github.com/yuanmabiji/spring-boot-2.1.0.RELEASE 44 | 45 | 46 | ================**Spring5源码专题持续更新中...**==================== 47 | 1. [模仿Spring事件机制实现自定义事件驱动编程 Spring源码(一)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/Spring/1%20%E6%A8%A1%E4%BB%BFSpring%E4%BA%8B%E4%BB%B6%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8%E7%BC%96%E7%A8%8B%20Spring%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%80%EF%BC%89.md) 48 | 2. [Spring是如何实现事件监听机制的? Spring源码(二)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/Spring/2%20Spring%E6%98%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E4%BA%8B%E4%BB%B6%E7%9B%91%E5%90%AC%E6%9C%BA%E5%88%B6%E7%9A%84%EF%BC%9F%20%20Spring%E6%BA%90%E7%A0%81%EF%BC%88%E4%BA%8C%EF%BC%89.md) 49 | 3. 持续更新中... 50 | * Spring5源码分析专题:https://github.com/yuanmabiji/Java-SourceCode-Blogs/tree/master/Spring 51 | * Spring5源码解析项目(带中文注释):待提供 52 | 53 | 54 | ================**JDK源码专题持续更新中...**==================== 55 | 1. [Java是如何实现自己的SPI机制的? JDK源码(一)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/JDK/1%20Java%E6%98%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%B7%B1%E7%9A%84SPI%E6%9C%BA%E5%88%B6%E7%9A%84%EF%BC%9F%20JDK%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%80%EF%BC%89.md) 56 | 2. 持续更新中... 57 | * JDK源码分析专题:https://github.com/yuanmabiji/Java-SourceCode-Blogs/tree/master/JDK 58 | * JDK源码解析项目(带中文注释):https://github.com/yuanmabiji/jdk1.8-sourcecode-blogs 59 | 60 | 61 | ================**TODO LIST**==================== 62 | 63 | * SpringMVC 64 | * Mybatis 65 | * Dubbo 66 | * Netty 67 | * RocketMQ 68 | * SpringCloud 69 | * Shiro 70 | * Tomcat 71 | * Seata 72 | * JUC 73 | * Zookeeper 74 | * ..... 75 | 76 | -------------------------------------------------------------------------------- /Spring/1 模仿Spring事件机制实现自定义事件驱动编程 Spring源码(一).md: -------------------------------------------------------------------------------- 1 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)**注意:spring源码分析文章对应spring版本为 5.1.x** 2 | 3 | # 1,概述 4 | 5 | 要想理解spring的事件机制,我觉得首先自己动手去撸一套简单的自定义事件驱动编程demo还是非常有必要滴,因为这样有助于理解spring事件机制。当然,这里也是模仿spring的事件机制的代码,不过下面看代码实现时可以先抛开spring的事件机制相关代码,将注意力集中到这个简单demo上即可。 6 | 7 | 在看这个自定义事件驱动编程时,首先要熟悉观察者设计模式,因为事件驱动编程可以说是观察者(发布订阅)模式的具体实现。推荐我的另一篇翻译的博文:[观察者模式--设计模式(一)](https://blog.csdn.net/biaolianlao0449/article/details/104246763),有需要的小伙伴们可以先学习下哈。 8 | 9 | 下面正式开始手撸代码实现,首先先放上下面代码的github地址: 10 | 11 | https://github.com/jinyue233/java-demo/tree/master/spring5-demo/src/main/java/com/jinyue/spring/event/mockspringevent 12 | 13 | # 2,自定义事件驱动编程 14 | 15 | 因为这篇文章是spring事件机制的前置文章,因此这里自定义实现一个模拟容器(可以理解为spring容器,servltet容器等)的生命周期事件的简单demo。 16 | 17 | ## 2.1 事件 18 | 19 | 先来看一下事件的整体架构图,让大家先有对事件有一个整体的认识,如下图: 20 | 21 | ![img](https://user-gold-cdn.xitu.io/2020/2/11/1703314099225c59?w=637&h=332&f=png&s=24496)![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 22 | 23 | **1,Event接口** 24 | 25 | 面向接口编程,首先先定义一个Event接口,该接口没有任何方法,可以说是事件的标志接口。 26 | 27 | ``` 28 | public interface Event extends Serializable { 29 | } 30 | ``` 31 | 32 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 33 | 34 | **2,AbstractContextEvent** 35 | 36 | AbstractContextEvent是容器事件的基本抽象类,因为事件也可以携带数据,因此这里定义了一个timestamp属性,用来记录事件的发生时间。 37 | 38 | ``` 39 | public class AbstractContextEvent implements Event { 40 | private static final long serialVersionUID = -6159391039546783871L; 41 | 42 | private final long timestamp = System.currentTimeMillis(); 43 | 44 | public final long getTimestamp() { 45 | return this.timestamp; 46 | } 47 | } 48 | ``` 49 | 50 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 51 | 52 | **3,ContextStartEvent** 53 | 54 | ContextStartEvent事件是AbstractContextEvent具体实现类,容器开始启动时触发,这里为了demo简单,这里不再定义任何事件逻辑,只是代表容器启动时的一个标志事件类。 55 | 56 | ``` 57 | public class ContextStartEvent extends AbstractContextEvent { 58 | } 59 | ``` 60 | 61 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 62 | 63 | 64 | 65 | **4,ContextRunningEvent** 66 | 67 | 68 | 69 | ContextRunningEvent事件是AbstractContextEvent具体实现类,容器启动后时触发,这里为了demo简单,这里不再定义任何事件逻辑,只是代表容器启动运行时时的一个标志事件类。 70 | 71 | ``` 72 | public class ContextRunningEvent extends AbstractContextEvent { 73 | } 74 | ``` 75 | 76 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 77 | 78 | **5,ContextDestroyEvent** 79 | 80 | 81 | 82 | ContextDestroyEvent事件是AbstractContextEvent具体实现类,容器销毁时触发,这里为了demo简单,这里不再定义任何事件逻辑,只是代表容器销毁时的一个标志事件类。 83 | 84 | ``` 85 | public class ContextDestroyEvent extends AbstractContextEvent { 86 | } 87 | ``` 88 | 89 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 90 | 91 | ## 2.2 事件监听器 92 | 93 | 先来看一下事件监听器的整体架构图,让大家先有对事件监听器有一个整体的认识,如下图: 94 | 95 | ![img](https://user-gold-cdn.xitu.io/2020/2/11/1703314096a6a5e1?w=758&h=289&f=png&s=25133)![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 96 | 97 | 其中EventListener是所有事件监听器的基类接口,,是事件监听器的标志类接口,被所有具体的事件监听器实现。然后ContextListener接口是容器事件监听器接口,继承了EventListener,主要定义了如下事件监听方法: 98 | 99 | ``` 100 | void onApplicationEvent(T event); 101 | ``` 102 | 103 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 104 | 105 | 然后ContextListener接口被三个具体的容器生命周期事件监听器实现,分别是ContextStartEventListener(监听容器启动时的ContextStartEvent),ContextRunningEventListener(监听容器启动后运行时的ContextRunningEvent)和ContextDestroyEventListener(监听容器销毁时的ContextDestroyEvent)。 106 | 107 | 下面看具体的代码实现: 108 | 109 | ``` 110 | public interface EventListener { 111 | } 112 | 113 | 114 | public interface ContextListener extends EventListener { 115 | /** 116 | * Handle an application event. 117 | * @param event the event to respond to 118 | */ 119 | void onApplicationEvent(T event); 120 | } 121 | 122 | 123 | public class ContextStartEventListener implements ContextListener { 124 | /** 125 | * Handle an application event. 126 | * 127 | * @param event the event to respond to 128 | */ 129 | public void onApplicationEvent(AbstractContextEvent event) { 130 | if (event instanceof ContextStartEvent) { 131 | System.out.println("容器启动。。。,启动时间为:" + event.getTimestamp()); 132 | } 133 | } 134 | } 135 | 136 | public class ContextRunningEventListener implements ContextListener { 137 | /** 138 | * Handle an application event. 139 | * 140 | * @param event the event to respond to 141 | */ 142 | public void onApplicationEvent(AbstractContextEvent event) { 143 | if (event instanceof ContextRunningEvent) { 144 | System.out.println("容器开始运行。。。"); 145 | try { 146 | Thread.sleep(3000); 147 | System.out.println("容器运行结束。。。"); 148 | } catch (InterruptedException e) { 149 | e.printStackTrace(); 150 | } 151 | } 152 | } 153 | } 154 | 155 | public class ContextDestroyEventListener implements ContextListener { 156 | /** 157 | * Handle an application event. 158 | * 159 | * @param event the event to respond to 160 | */ 161 | public void onApplicationEvent(AbstractContextEvent event) { 162 | if (event instanceof ContextDestroyEvent) { 163 | System.out.println("容器销毁。。。,销毁时间为:" + event.getTimestamp()); 164 | } 165 | } 166 | } 167 | ``` 168 | 169 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 170 | 171 | ## 2.3 事件发布器 172 | 173 | 先看下类图: 174 | 175 | ![img](https://user-gold-cdn.xitu.io/2020/2/11/1703314099136a60?w=538&h=63&f=png&s=7799)![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 176 | 177 | ApplicationEventMulticaster是发布事件的父类接口,主要定义了增加,删除,获取等操作事件监听器的的方法接口,此外,还定义了一个发布事件的方法。 178 | 179 | SimpleApplicationEventMulticaster是ApplicationEventMulticaster事件发布器接口的默认实现类,主要承担发布事件的功能。其内部维护了一个事件监听器列表contextListeners,当发布事件时会遍历这些列表,然后再向每个监听器发布事件,通过设置async属性来决定同步广播事件还是异步广播事件。 180 | 181 | 下面看看实现代码: 182 | 183 | ``` 184 | public interface ApplicationEventMulticaster { 185 | void addContextListener(ContextListener listener); 186 | 187 | void removeContextListener(ContextListener listener); 188 | 189 | void removeAllListeners(); 190 | 191 | void multicastEvent(AbstractContextEvent event); 192 | 193 | } 194 | 195 | 196 | 197 | public class SimpleApplicationEventMulticaster implements ApplicationEventMulticaster { 198 | // 是否异步发布事件 199 | private boolean async = false; 200 | // 线程池 201 | private Executor taskExecutor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new LinkedBlockingQueue()); 202 | // 事件监听器列表 203 | private List> contextListeners = new ArrayList>(); 204 | 205 | 206 | public void addContextListener(ContextListener listener) { 207 | contextListeners.add(listener); 208 | } 209 | 210 | public void removeContextListener(ContextListener listener) { 211 | contextListeners.remove(listener); 212 | } 213 | 214 | public void removeAllListeners() { 215 | contextListeners.clear(); 216 | } 217 | 218 | public void multicastEvent(AbstractContextEvent event) { 219 | doMulticastEvent(contextListeners, event); 220 | } 221 | 222 | private void doMulticastEvent(List> contextListeners, AbstractContextEvent event) { 223 | for (ContextListener contextListener : contextListeners) { 224 | // 异步广播事件 225 | if (async) { 226 | taskExecutor.execute(() -> invokeListener(contextListener, event)); 227 | // new Thread(() -> invokeListener(contextListener, event)).start(); 228 | // 同步发布事件,阻塞的方式 229 | } else { 230 | invokeListener(contextListener, event); 231 | } 232 | } 233 | } 234 | 235 | private void invokeListener(ContextListener contextListener, AbstractContextEvent event) { 236 | contextListener.onApplicationEvent(event); 237 | } 238 | 239 | public void setAsync(boolean async) { 240 | this.async = async; 241 | } 242 | } 243 | ``` 244 | 245 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 246 | 247 | ## 2.4 测试自定义的容器生命周期事件 248 | 249 | 那么直接上测试代码,下面只演示同步发布事件的功能: 250 | 251 | ``` 252 | public class MockSpringEventTest { 253 | 254 | @Test 255 | public void testContextLifecycleEventInSync() { 256 | // 新建SimpleApplicationEventMulticaster对象,并添加容器生命周期监听器 257 | ApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(); 258 | eventMulticaster.addContextListener(new ContextStartEventListener()); 259 | eventMulticaster.addContextListener(new ContextRunningEventListener()); 260 | eventMulticaster.addContextListener(new ContextDestroyEventListener()); 261 | // 发射容器启动事件ContextStartEvent 262 | eventMulticaster.multicastEvent(new ContextStartEvent()); 263 | // 发射容器正在运行事件ContextRunningEvent 264 | eventMulticaster.multicastEvent(new ContextRunningEvent()); 265 | // 发射容器正在运行事件ContextDestroyEvent 266 | eventMulticaster.multicastEvent(new ContextDestroyEvent()); 267 | } 268 | 269 | @Test 270 | public void testContextLifecycleEventInAsync() throws InterruptedException { 271 | // 新建SimpleApplicationEventMulticaster对象,并添加容器生命周期监听器 272 | ApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(); 273 | eventMulticaster.addContextListener(new ContextStartEventListener()); 274 | eventMulticaster.addContextListener(new ContextRunningEventListener()); 275 | eventMulticaster.addContextListener(new ContextDestroyEventListener()); 276 | 277 | ((SimpleApplicationEventMulticaster) eventMulticaster).setAsync(true); 278 | 279 | // 发射容器启动事件ContextStartEvent 280 | eventMulticaster.multicastEvent(new ContextStartEvent()); 281 | // 发射容器正在运行事件ContextRunningEvent 282 | eventMulticaster.multicastEvent(new ContextRunningEvent()); 283 | // 发射容器正在运行事件ContextDestroyEvent 284 | eventMulticaster.multicastEvent(new ContextDestroyEvent()); 285 | // 这里没明白在没有用CountDownLatch的情况下为何主线程退出,非后台线程的子线程也会退出???为了测试,所有先用CountDownLatch锁住main线程先 286 | // 经过测试,原来是因为用了junit的方法,test方法线程退出后,test方法线程产生的非后台线程也随之退出,而下面的main方法启动的非后台线程则不会 287 | // TODO 这是为什么呢???难道是A子线程(非main线程)启动的B子线程会随着A子线程退出而退出?还没验证 288 | CountDownLatch countDownLatch = new CountDownLatch(1); 289 | countDownLatch.await(); 290 | 291 | } 292 | 293 | public static void main(String[] args) throws InterruptedException { 294 | // 新建SimpleApplicationEventMulticaster对象,并添加容器生命周期监听器 295 | ApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(); 296 | eventMulticaster.addContextListener(new ContextStartEventListener()); 297 | eventMulticaster.addContextListener(new ContextRunningEventListener()); 298 | eventMulticaster.addContextListener(new ContextDestroyEventListener()); 299 | 300 | ((SimpleApplicationEventMulticaster) eventMulticaster).setAsync(true); 301 | 302 | // 发射容器启动事件ContextStartEvent 303 | eventMulticaster.multicastEvent(new ContextStartEvent()); 304 | // 发射容器正在运行事件ContextRunningEvent 305 | eventMulticaster.multicastEvent(new ContextRunningEvent()); 306 | // 发射容器正在运行事件ContextDestroyEvent 307 | eventMulticaster.multicastEvent(new ContextDestroyEvent()); 308 | 309 | } 310 | } 311 | ``` 312 | 313 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 314 | 315 | 通过运行测试方法testContextLifecycleEventInSync(),运行结果如下截图: 316 | 317 | ![img](https://user-gold-cdn.xitu.io/2020/2/11/1703314099333a6b?w=621&h=128&f=png&s=12253)![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 318 | 319 | # 3,结语 320 | 321 | 好了,自定义事件驱动编程的简单demo就已经实现了。 322 | 323 | 这只是spring事件机制源码分析的前置文章,真正的源码分析请见下一篇博文: 324 | 325 | **[Spring事件相关类关系源码解析--Spring的事件机制源码分析(二](https://blog.csdn.net/biaolianlao0449/article/details/104246732)**) 326 | 327 | **原创不易,帮忙点个赞呗。** 328 | 329 | \---------------------------------------------------------------------- 330 | 331 | 微信公众号:**源码笔记** 332 | 探讨更多源码知识,关注“源码笔记”微信公众号,每周持续推出SpringBoot,Spring,Mybatis,Dubbo,RocketMQ,Jdk 和Netty等源码系列文章。 333 | 334 | ![img](https://user-gold-cdn.xitu.io/2020/2/15/17046e9b1cf0506e?w=258&h=258&f=jpeg&s=26882) -------------------------------------------------------------------------------- /Spring/2 Spring是如何实现事件监听机制的? Spring源码(二).md: -------------------------------------------------------------------------------- 1 | **注意:该源码分析对应版本为spring5.1.x** 2 | 3 | # 1,概述 4 | 5 | 本篇开始分析Spring的事件机制源码,因为Spring的事件机制实质是观察者(发布订阅)模式的实现,因此要想搞清楚Spring的事件机制,因此得知道观察者模式是什么。 6 | 7 | 同时,[本文接模仿Spring事件机制实现自定义事件驱动编程--Spring的事件机制源码分析(一)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/Spring/1%20%E6%A8%A1%E4%BB%BFSpring%E4%BA%8B%E4%BB%B6%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8%E7%BC%96%E7%A8%8B%20Spring%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%80%EF%BC%89.md)一文,前面自己实现了一个简单的事件驱动编程的简单demo后,那么此时分析Spring的事件机制源码就简单多了。 8 | 9 | 在开始正题前,先聊聊研究源码的感受:研究源码前那么必须先搞清楚类与类之间的关系,比如某个接口有哪些实现类,某个父类有哪些子类,子类与子类之间的关系,这些类之间的关系捋清楚了,那么再下手研究源码就容易很多。总之不能一下子就进入源码的某个细节,这样子就会造成只见冰山一角而看不到全貌的感觉。 10 | 11 | 好了,下面开始进入正题,开始学习Spring的事件机制。因为编码一般都是面向接口编程,那么我们先从事件机制的相关接口或抽象类开始分析。 12 | 13 | Spring事件机制涉及的重要的类主要有以下四个: 14 | 15 | - ApplicationEvent:事件,该抽象类是所有Spring事件的父类,可携带数据比如事件发生时间timestamp。 16 | - ApplicationListener:事件监听器,该接口被所有的事件监听器实现,基于标准的java的EventListener接口实现观察者模式。 17 | - ApplicationEventMulticaster:事件管理者,管理监听器和发布事件,ApplicationContext通过委托ApplicationEventMulticaster来 发布事件 18 | - ApplicationEventPublisher:事件发布者,该接口封装了事件有关的公共方法,作为ApplicationContext的超级街廓,也是委托 ApplicationEventMulticaster完成事件发布。 19 | 20 | # 2,Spring事件涉及类源码分析 21 | 22 | 事件相关的主要接口类上面已经介绍完毕,下面来看下每个接口及其子类之间的关系。 23 | 24 | ## 2.1 ApplicationEvent 25 | 26 | 首先看下类图如下: 27 | 28 | ![img](https://user-gold-cdn.xitu.io/2020/2/11/170323d4f7b2ad93?w=1556&h=467&f=png&s=68821)![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 29 | 30 | **图1** 31 | 32 | 其接口代码如下: 33 | 34 | ``` 35 | // 事件抽象类,这个是所有Spring事件的父类 36 | public abstract class ApplicationEvent extends EventObject { 37 | 38 | /** use serialVersionUID from Spring 1.2 for interoperability. */ 39 | private static final long serialVersionUID = 7099057708183571937L; 40 | 41 | /** System time when the event happened. */ 42 | private final long timestamp; 43 | 44 | 45 | /** 46 | * Create a new ApplicationEvent. 47 | * @param source the object on which the event initially occurred (never {@code null}) 48 | */ 49 | public ApplicationEvent(Object source) { 50 | super(source); 51 | this.timestamp = System.currentTimeMillis(); 52 | } 53 | 54 | 55 | /** 56 | * Return the system time in milliseconds when the event happened. 57 | */ 58 | public final long getTimestamp() { 59 | return this.timestamp; 60 | } 61 | 62 | } 63 | ``` 64 | 65 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 66 | 67 | ApplicationEvent类定义了一些属性比如timestamp,这个表示事件的发生时间,因此可以通过事件来传递一些参数。 68 | 69 | 图1是ApplicationEvent部分重要的子类关系图,其中ApplicationEvent最重要的子类是ApplicationContextEvent抽象类,ApplicationContextEvent是spring容器Context生命周期事件的基类,ApplicationContextEvent的有四个子类,如下: 70 | 71 | - ContextRefreshedEvent:当spring容器context刷新时触发 72 | - ContextStartedEvent:当spring容器context启动后触发 73 | - ContextStoppedEvent:当spring容器context停止时触发 74 | - ContextClosedEvent:当spring容器context关闭时触发,容器被关闭时,其管理的所有单例Bean都被销毁。 75 | 76 | 以上四个事件就是spring容器生命周期的四个事件,当每个事件触发时,相关的监听器就会监听到相应事件,然后触发onApplicationEvent方法,此时就可以做一些容器,同时这些容器事件跟spring的后置处理器一样,留给用户扩展自定义逻辑,作为暴露的扩展点。 77 | 78 | 以ContextRefreshedEvent事件为例讲解下相关监听类,通过idea全局搜索"(ContextRefreshedEvent"关键字,得到以下截图: 79 | 80 | ![img](https://user-gold-cdn.xitu.io/2020/2/11/170323d4f7d2676b?w=1916&h=286&f=png&s=94668)![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 81 | 82 | 从上图可以看到spring-webmvc模块的FrameworkServlet,spring-context模块的ScheduledAnnotationBeanPostProcessor,和spring-jms模块的JmsListenerEndpointRegistry等类订阅了ContextRefreshedEvent事件,那么在容器刷新的时候这几个类将会监听到ContextRefreshedEvent事件,执行一些初始化逻辑。这一块后面有时间再研究,TODO。 83 | 84 | 下面粘贴下ApplicationContextEvent的四个子类的实现代码,基本都是继承ApplicationContextEvent父类,没有什么逻辑,更多是一个生命周期事件的标志类。 85 | 86 | ``` 87 | public class ContextRefreshedEvent extends ApplicationContextEvent { 88 | 89 | // 当springcontext已经被初始化或者刷新的时候,创建该事件 90 | public ContextRefreshedEvent(ApplicationContext source) { 91 | super(source); 92 | } 93 | 94 | } 95 | 96 | public class ContextStartedEvent extends ApplicationContextEvent { 97 | 98 | // 当springContext已经启动的时候,创建该事件 99 | public ContextStartedEvent(ApplicationContext source) { 100 | super(source); 101 | } 102 | 103 | } 104 | 105 | public class ContextStoppedEvent extends ApplicationContextEvent { 106 | 107 | // 当springContext已经停止时创建该事件 108 | public ContextStoppedEvent(ApplicationContext source) { 109 | super(source); 110 | } 111 | 112 | } 113 | 114 | public class ContextClosedEvent extends ApplicationContextEvent { 115 | 116 | // 当springContext关闭时创建该事件 117 | public ContextClosedEvent(ApplicationContext source) { 118 | super(source); 119 | } 120 | 121 | } 122 | ``` 123 | 124 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 125 | 126 | ## 2.2 ApplicationListener 127 | 128 | ``` 129 | @FunctionalInterface 130 | public interface ApplicationListener extends EventListener { 131 | 132 | /** 133 | * Handle an application event. 134 | * @param event the event to respond to 135 | */ 136 | void onApplicationEvent(E event); 137 | 138 | } 139 | ``` 140 | 141 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 142 | 143 | ApplicationListener是所有事件监听器的父接口,事件监听器监听某个事件必须要实现该接口。这里值得注意的是ApplicationListener接口的参数化类型,这样的话具体的监听器实现该接口时可以指定特定的事件类,当传入的事件向下转型时不是该特定的事件时,此时会抛出类转换异常。不过一般使用的时候会先判断下该事件类型是否属于某种事件,然后再执行相关逻辑,如下代码: 144 | 145 | ``` 146 | @Override 147 | public void onApplicationEvent(ApplicationEvent event) { 148 | if (event instanceof ApplicationStartingEvent) { 149 | onApplicationStartingEvent((ApplicationStartingEvent) event); 150 | } 151 | else if (event instanceof ApplicationEnvironmentPreparedEvent) { 152 | onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); 153 | } 154 | else if (event instanceof ApplicationPreparedEvent) { 155 | onApplicationPreparedEvent((ApplicationPreparedEvent) event); 156 | } 157 | else if (event instanceof ContextClosedEvent 158 | && ((ContextClosedEvent) event).getApplicationContext().getParent() == null) { 159 | onContextClosedEvent(); 160 | } 161 | else if (event instanceof ApplicationFailedEvent) { 162 | onApplicationFailedEvent(); 163 | } 164 | } 165 | ``` 166 | 167 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 168 | 169 | 由于ApplicationListener接口的具体实现类太多,因此就不贴类关系图了。 170 | 171 | ## 2.3 ApplicationEventMulticaster 172 | 173 | 首先看下类图, 174 | 175 | ![img](https://user-gold-cdn.xitu.io/2020/2/11/170323d4f7f69bef?w=622&h=362&f=png&s=40755)![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 176 | 177 | ApplicationEventMulticaster接口功能主要用来广播事件给所有listener,主要定义了增删改监听器和广播事件的接口方法,代码如下: 178 | 179 | ``` 180 | public interface ApplicationEventMulticaster { 181 | void addApplicationListener(ApplicationListener var1); 182 | 183 | void addApplicationListenerBean(String var1); 184 | 185 | void removeApplicationListener(ApplicationListener var1); 186 | 187 | void removeApplicationListenerBean(String var1); 188 | 189 | void removeAllListeners(); 190 | 191 | void multicastEvent(ApplicationEvent var1); 192 | 193 | void multicastEvent(ApplicationEvent var1, @Nullable ResolvableType var2); 194 | } 195 | ``` 196 | 197 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 198 | 199 | AbstractApplicationEventMulticaster是ApplicationEventMulticaster接口的抽象实现,提供最基本的监听器注册的方法。注册监听器时一般不允许相同监听器注册多个实例,因此使用Set集合,用于去重。然后实现广播事件的具体实现没有在这里实现,而是交给子类SimpleApplicationEventMulticaster去实现。 200 | 201 | AbstractApplicationEventMulticaster抽象类的关键代码如下: 202 | 203 | ``` 204 | // AbstractApplicationEventMulticaster.java 205 | 206 | /** 207 | * 获取事件监听器的帮助类,拥有Set>属性 208 | */ 209 | private final ListenerRetriever defaultRetriever = new ListenerRetriever(false); 210 | /** 211 | * ListenerRetriever缓存 212 | * key:ListenerCacheKey value:ListenerRetriever 213 | */ 214 | final Map retrieverCache = new ConcurrentHashMap<>(64); 215 | 216 | // 添加spring监听器到ListenerRetriever的applicationListeners集合中 217 | public void addApplicationListener(ApplicationListener listener) { 218 | synchronized (this.retrievalMutex) { 219 | // Explicitly remove target for a proxy, if registered already, 220 | // in order to avoid double invocations of the same listener. 221 | Object singletonTarget = AopProxyUtils.getSingletonTarget(listener); 222 | if (singletonTarget instanceof ApplicationListener) { 223 | this.defaultRetriever.applicationListeners.remove(singletonTarget); 224 | } 225 | this.defaultRetriever.applicationListeners.add(listener); 226 | this.retrieverCache.clear(); 227 | } 228 | } 229 | 230 | // 移除监听器 231 | public void removeApplicationListener(ApplicationListener listener) { 232 | synchronized (this.retrievalMutex) { 233 | this.defaultRetriever.applicationListeners.remove(listener); 234 | this.retrieverCache.clear(); 235 | } 236 | } 237 | 238 | // 移除所有监听器 239 | public void removeAllListeners() { 240 | synchronized (this.retrievalMutex) { 241 | this.defaultRetriever.applicationListeners.clear(); 242 | this.defaultRetriever.applicationListenerBeans.clear(); 243 | this.retrieverCache.clear(); 244 | } 245 | } 246 | 247 | // 利用defaultRetriever得到所有的监听器 248 | protected Collection> getApplicationListeners() { 249 | synchronized (this.retrievalMutex) { 250 | return this.defaultRetriever.getApplicationListeners(); 251 | } 252 | } 253 | 254 | 255 | 256 | ``` 257 | 258 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 259 | 260 | 根据上面代码,大家注意到了AbstractApplicationEventMulticaster的增加,删除和后去listeners是委托给其内部类ListenerRetriever去获取的,因为ListenerRetriever内部维护了监听器的集合Set>。下面看看ListenerRetriever这个内部类关键代码: 261 | 262 | ``` 263 | private class ListenerRetriever { 264 | /** 265 | * 监听器集合 266 | */ 267 | public final Set> applicationListeners = new LinkedHashSet<>(); 268 | 269 | public final Set applicationListenerBeans = new LinkedHashSet<>(); 270 | 271 | private final boolean preFiltered; 272 | 273 | public ListenerRetriever(boolean preFiltered) { 274 | this.preFiltered = preFiltered; 275 | } 276 | 277 | /** 278 | * 获取所有的spring监听器 279 | * @return 280 | */ 281 | public Collection> getApplicationListeners() { 282 | List> allListeners = new ArrayList<>( 283 | this.applicationListeners.size() + this.applicationListenerBeans.size()); 284 | allListeners.addAll(this.applicationListeners); 285 | if (!this.applicationListenerBeans.isEmpty()) { 286 | BeanFactory beanFactory = getBeanFactory(); 287 | for (String listenerBeanName : this.applicationListenerBeans) { 288 | try { 289 | ApplicationListener listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); 290 | if (this.preFiltered || !allListeners.contains(listener)) { 291 | allListeners.add(listener); 292 | } 293 | } 294 | catch (NoSuchBeanDefinitionException ex) { 295 | // Singleton listener instance (without backing bean definition) disappeared - 296 | // probably in the middle of the destruction phase 297 | } 298 | } 299 | } 300 | if (!this.preFiltered || !this.applicationListenerBeans.isEmpty()) { 301 | AnnotationAwareOrderComparator.sort(allListeners); 302 | } 303 | return allListeners; 304 | } 305 | } 306 | ``` 307 | 308 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 309 | 310 | 下面再来看下承担广播事件的SimpleApplicationEventMulticaster类的关键代码: 311 | 312 | ``` 313 | public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster { 314 | // 执行广播异步事件的线程 315 | @Nullable 316 | private Executor taskExecutor; 317 | // 广播异步事件的线程时出现异常时的处理器 318 | @Nullable 319 | private ErrorHandler errorHandler; 320 | 321 | 322 | /** 323 | * Create a new SimpleApplicationEventMulticaster. 324 | */ 325 | public SimpleApplicationEventMulticaster() { 326 | } 327 | 328 | 329 | @Override 330 | public void multicastEvent(ApplicationEvent event) { 331 | multicastEvent(event, resolveDefaultEventType(event)); 332 | } 333 | 334 | @Override 335 | public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { 336 | ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); 337 | // 获取执行异步任务的线程池,这里异步要外部指定一个线程池,注入进来 338 | Executor executor = getTaskExecutor(); 339 | // 遍历每一个spring事件监听器 340 | for (ApplicationListener listener : getApplicationListeners(event, type)) { 341 | // 若外部指定的线程池不为null,则异步广播事件 342 | if (executor != null) { 343 | executor.execute(() -> invokeListener(listener, event)); 344 | } 345 | // executor为空,则单线程同步广播事件 346 | else { 347 | invokeListener(listener, event); 348 | } 349 | } 350 | } 351 | 352 | protected void invokeListener(ApplicationListener listener, ApplicationEvent event) { 353 | ErrorHandler errorHandler = getErrorHandler(); 354 | // errorHandler不为空的情况下,则会进入try...catch..代码块,这里会对异步广播事件发生的异常进行处理 355 | if (errorHandler != null) { 356 | try { 357 | // 这里真正执行广播事件的逻辑 358 | doInvokeListener(listener, event); 359 | } 360 | catch (Throwable err) { 361 | // 处理异常 362 | errorHandler.handleError(err); 363 | } 364 | } 365 | // errorHandler为空的情况下,则不对出现的异常进行处理 366 | else { 367 | doInvokeListener(listener, event); 368 | } 369 | } 370 | 371 | @SuppressWarnings({"rawtypes", "unchecked"}) 372 | private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { 373 | try { 374 | // 回调监听器onApplicationEvent方法,执行监听逻辑 375 | listener.onApplicationEvent(event); 376 | } 377 | catch (ClassCastException ex) { 378 | // 若出现异常,这里打印一些日志或将异常继续跑出去 379 | String msg = ex.getMessage(); 380 | if (msg == null || matchesClassCastMessage(msg, event.getClass())) { 381 | // Possibly a lambda-defined listener which we could not resolve the generic event type for 382 | // -> let's suppress the exception and just log a debug message. 383 | Log logger = LogFactory.getLog(getClass()); 384 | if (logger.isTraceEnabled()) { 385 | logger.trace("Non-matching event type for listener: " + listener, ex); 386 | } 387 | } 388 | else { 389 | throw ex; 390 | } 391 | } 392 | } 393 | 394 | } 395 | ``` 396 | 397 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 398 | 399 | SimpleApplicationEventMulticaster是ApplicationEventMulticaster的实现类,承担广播所有事件给注册的spring监听器, 让监听器自己去决定哪些事件是自己感兴趣的,监听器们将会执行instanof来判断是否是自己感兴趣的事件。默认情况下,所有监听器将会在调用线程中即单线程中同步阻塞执行,因此,若监听器数量过多或某个监听器执行时间过长 这将会导致spring容器启动时间过长。不过SimpleApplicationEventMulticaster也提供了异步广播时间的功能,通过taskExecutor来获取线程池,然后多线程广播事件,此外其还维护了一个errorHandler对象属性,异常处理器,errorHandler主要用来当异步广播事件时,若监听器执行异常时,此时利用其来处理catch住的异常。 400 | 401 | ## 2.4 ApplicationEventPublisher 402 | 403 | 同样,先来看下下面的类关系图 404 | 405 | ![img](https://user-gold-cdn.xitu.io/2020/2/11/170323d4f833f8be?w=1359&h=648&f=png&s=84805)![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 406 | 407 | 可以看出所有Spring容器的父类接口ApplicationContext继承了ApplicationEventPublisher这个接口,因此spring容器一般是具有广播事件的功能。 408 | 409 | 下面来看下ApplicationEventPublisher的接口类代码: 410 | 411 | ``` 412 | @FunctionalInterface 413 | public interface ApplicationEventPublisher { 414 | default void publishEvent(ApplicationEvent event) { 415 | this.publishEvent((Object)event); 416 | } 417 | 418 | void publishEvent(Object event); 419 | } 420 | ``` 421 | 422 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 423 | 424 | 该接口封装了发布事件的公共方法,作为ApplicationContext的超级接口,同事也是委托ApplicationEventMulticaster完成事件发布。 425 | 426 | 下面再来看下Spring容器实现了ApplicationEventPublisher接口后是如何来发布事件的,此时得先来看下spring容器的父类接口ApplicationContext,因为该接口继承了ApplicationEventPublisher接口,因此让spring容器具有了发布事件的功能。 427 | 428 | ``` 429 | public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, 430 | MessageSource, ApplicationEventPublisher, ResourcePatternResolver { 431 | 432 | // 省略接口方法 433 | 434 | } 435 | ``` 436 | 437 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 438 | 439 | 那么spring容器是如何来发布事件的呢?前面已经讲过ApplicationEventMulticaster接口,没错,spring容器context正是委托其来实现发布事件的功能。因为AbstractApplicationContext实现了ConfigurableApplicationContext接口,通过该接口最终实现了ApplicationEventPublisher接口,spring容器发布事件的方法封装在AbstractApplicationContext的publishEvent方法中, 440 | 441 | 下面直接看下相关代码: 442 | 443 | ``` 444 | public abstract class AbstractApplicationContext extends DefaultResourceLoader 445 | implements ConfigurableApplicationContext { 446 | /** 447 | * 父类context 448 | */ 449 | @Nullable 450 | private ApplicationContext parent; 451 | 452 | /** 453 | * 在multicaster setup前,发布事件 454 | */ 455 | @Nullable 456 | private Set earlyApplicationEvents; 457 | 458 | // 发布事件给所有事件监听器, 459 | protected void publishEvent(Object event, @Nullable ResolvableType eventType) { 460 | Assert.notNull(event, "Event must not be null"); 461 | 462 | // Decorate event as an ApplicationEvent if necessary 463 | ApplicationEvent applicationEvent; 464 | if (event instanceof ApplicationEvent) { 465 | applicationEvent = (ApplicationEvent) event; 466 | } 467 | else { 468 | applicationEvent = new PayloadApplicationEvent<>(this, event); 469 | if (eventType == null) { 470 | eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType(); 471 | } 472 | } 473 | 474 | // Multicast right now if possible - or lazily once the multicaster is initialized 475 | if (this.earlyApplicationEvents != null) { 476 | this.earlyApplicationEvents.add(applicationEvent); 477 | } 478 | else { 479 | getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); 480 | } 481 | 482 | // Publish event via parent context as well... 483 | if (this.parent != null) { 484 | if (this.parent instanceof AbstractApplicationContext) { 485 | ((AbstractApplicationContext) this.parent).publishEvent(event, eventType); 486 | } 487 | else { 488 | this.parent.publishEvent(event); 489 | } 490 | } 491 | } 492 | } 493 | ``` 494 | 495 | ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) 496 | 497 | 最后,弄清楚该源码机制后,自己再动手实操一下,推荐阅读下面的实操文章: 498 | 499 | [spring 自定义事件发布及监听(简单实例)](https://www.cnblogs.com/xinde123/p/8918714.html) 500 | 501 | 502 | 503 | 小结:这篇文章是本人第二篇源码解析的文章,写作速度仍然很慢,希望以后思路捋清楚后能快点写完,加油。 504 | 505 | 506 | 507 | 参考: 508 | 509 | https://spring.io/docs 510 | 511 | -------------------------------------------------------------------------------- /Spring/README.md: -------------------------------------------------------------------------------- 1 | 【**源码笔记**】专注于Java后端系列框架的源码分析。若觉得源码分析文章不错,欢迎Star哦。 2 | 3 | 4 | ================**Spring5源码专题持续更新中...**==================== 5 | 6 | #### 目录 7 | 8 | 1. [模仿Spring事件机制实现自定义事件驱动编程 Spring源码(一)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/Spring/1%20%E6%A8%A1%E4%BB%BFSpring%E4%BA%8B%E4%BB%B6%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8%E7%BC%96%E7%A8%8B%20Spring%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%80%EF%BC%89.md) 9 | 2. [Spring是如何实现事件监听机制的? Spring源码(二)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/Spring/2%20Spring%E6%98%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E4%BA%8B%E4%BB%B6%E7%9B%91%E5%90%AC%E6%9C%BA%E5%88%B6%E7%9A%84%EF%BC%9F%20%20Spring%E6%BA%90%E7%A0%81%EF%BC%88%E4%BA%8C%EF%BC%89.md) 10 | 3. 持续更新中... 11 | 12 | * 更多源码分析文章请跳转至:https://github.com/yuanmabiji/Java-SourceCode-Blogs 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /SpringBoot/1 如何搭建自己的SpringBoot源码调试环境? SpringBoot源码(一).md: -------------------------------------------------------------------------------- 1 | 2 | ## 1 前言 3 | 这是SpringBoot2.1源码分析专题的第一篇文章,主要讲如何来搭建我们的源码阅读调试环境。如果有经验的小伙伴们可以略过此篇文章。 4 | ## 2 环境安装要求 5 | * IntelliJ IDEA 6 | * JDK1.8 7 | * Maven3.5以上 8 | 9 | ## 3 从github上将SpringBoot源码项目下载下来 10 | 首先提供**SpringBoot2.1.0**的github地址: 11 | https://github.com/spring-projects/spring-boot/tree/v2.1.0.RELEASE 12 | 13 | 因为要进行阅读源码和分析源码项目,我们是不是要在里面写一些注释帮助我们阅读理解源码,因此需要将SpringBoot源码项目fork到自己的github仓库中,然后再利用**git clone url**命令将已经fork到自己github仓库的SpringBoot源码拉取下来即可。 14 | 但由于以上方式往往很慢,通常会超时,所以笔者直接将SpringBoot项目直接下载下来,然后再导入IDEA中。 15 | 16 | ![](https://user-gold-cdn.xitu.io/2020/2/23/1706fb164630bfac?w=1310&h=484&f=png&s=70931) 17 | ## 4 将SpringBoot源码项目导入到IDEA中 18 | 将刚才下载的spring-boot2.1.0.RELEASE项目选择maven方式导入到IDEA中,然后一直next即可导入完成,注意选择JDK版本是1.8,maven版本是3.5+。 19 | 20 | ![](https://user-gold-cdn.xitu.io/2020/2/23/1706fb50079899ac?w=734&h=266&f=png&s=12373) 21 | 此时下载maven依赖是一个漫长的等待过程,建议maven没有配置(阿-里-云)仓库的小伙伴们配置一下,这样下载速度会快很多。参考[配置maven使用(阿-里-云)仓库](https://blog.csdn.net/zhuzj12345/article/details/93200211)进行配置即可。 22 | ## 5 编译构建SpringBoot源码项目 23 | 此时导入项目后,我们进行编译构建SpringBoot源码项目了,在构建之前做两个配置: 24 | 1. 我们要禁用maven的代码检查,在根pom.xml中增加一下配置即可,如下图: 25 | > ![](https://user-gold-cdn.xitu.io/2020/2/23/1706fbc5725ffaf8?w=947&h=303&f=png&s=33333) 26 | 2. 可能有的小伙伴们的pom.xml文件的project标签上显示`java.lang.OutOfMemoryError`错误,这是因为IDEA里的Maven的importer设置的JVM最大堆内存过小而导致的,如下图,此时可参考[Maven依赖包导入错误(IntelliJ IDEA)](https://blog.csdn.net/w605283073/article/details/85107497)解决即可。 27 | > ![](https://user-gold-cdn.xitu.io/2020/2/23/1706fc20d5eaba6b?w=688&h=254&f=png&s=98291) 28 | 29 | 进行了上面的两点配置后,此时我们就可以直接执行以下maven命令来编译构建源码项目了。 30 | ``` 31 | mvn clean install -DskipTests -Pfast 32 | ``` 33 | 34 | ![](https://user-gold-cdn.xitu.io/2020/2/23/1706fc5696712841?w=697&h=92&f=png&s=8216) 35 | 此时又是漫长的等待,我这里等待5分钟左右就显示构建成功了,如下图: 36 | ![](https://user-gold-cdn.xitu.io/2020/2/23/1706fdcd5edaceb9?w=890&h=563&f=png&s=56801) 37 | ## 6 运行SpringBoot自带的sample 38 | 因为SpringBoot源码中的spring-boot-samples模块自带了很多DEMO样例,我们可以利用其中的一个sample来测试运行刚刚构建的springboot源码项目即可。但此时发现spring-boot-samples模块是灰色的,如下图: 39 | ![](https://user-gold-cdn.xitu.io/2020/2/23/1706fca29948aa2a?w=300&h=136&f=png&s=5880) 40 | 这是因为spring-boot-samples模块没有被添加到根pom.xml中,此时将其添加到根pom.xml中即可,增加如下配置,如下图: 41 | ![](https://user-gold-cdn.xitu.io/2020/2/23/1706fcb839241e47?w=815&h=330&f=png&s=34161) 42 | 此时我们挑选spring-boot-samples模块下的spring-boot-sample-tomcat样例项目来测试好了,此时启动`SampleTomcatApplication`的`main`函数,启动成功界面如下: 43 | 44 | ![](https://user-gold-cdn.xitu.io/2020/2/23/1706fde9a5073f65?w=1843&h=444&f=png&s=130677) 45 | 然后我们再在浏览器发送一个HTTP请求,此时可以看到服务端成功返回响应,说明此时SpringBoot源码环境就已经构建成功了,接下来我们就可以进行调试了,如下图: 46 | 47 | ![](https://user-gold-cdn.xitu.io/2020/2/23/1706fd98c2621c7b?w=411&h=161&f=png&s=9614) 48 | 49 | ## 7 动手实践环节 50 | 前面已经成功构建了SpringBoot的源码阅读环境,小伙伴们记得自己动手搭建一套属于自己的SpringBoot源码调试环境哦,阅读源码动手调试很重要,嘿嘿。 51 | 52 | **下节预告**: 53 | 我们该如何去分析SpringBoot源码涉及模块及结构?--SpringBoot源码(二) 54 | 55 | 56 | **原创不易,帮忙Star一下呗**! 57 | 58 | 注:该源码分析对应SpringBoot版本为**2.1.0.RELEASE**,本文对应的SpringBoot源码解析项目github地址:https://github.com/yuanmabiji/spring-boot-2.1.0.RELEASE 59 | 60 | -------------------------------------------------------------------------------- /SpringBoot/10 SpringBoot内置生命周期事件详解 SpringBoot源码(十).md: -------------------------------------------------------------------------------- 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 | ![](https://user-gold-cdn.xitu.io/2020/5/2/171d3520a8eec9ee?w=1172&h=626&f=png&s=56346) 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 | ![](https://user-gold-cdn.xitu.io/2020/5/2/171d300d55cc4470?w=796&h=769&f=png&s=378851) 341 | 342 | # 5 写在最后 343 | 344 | 由于有一些小伙伴们建议之前有些源码分析文章太长,导致耐心不够,看不下去,因此,之后的源码分析文章如果太长的话,笔者将会考虑拆分为几篇文章,这样就比较短小了,比较容易看完,嘿嘿。 345 | 346 | **【源码笔记】Github地址:** 347 | 348 | https://github.com/yuanmabiji/Java-SourceCode-Blogs 349 | 350 | **Star搞起来,嘿嘿嘿!** 351 | 352 | -------------------------------------------------------------------------------- /SpringBoot/2 如何分析SpringBoot源码模块及结构? SpringBoot源码(二).md: -------------------------------------------------------------------------------- 1 | ## 1 前言 2 | 本篇接 3 | [如何搭建自己的SpringBoot源码调试环境?--SpringBoot源码(一)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/1%20%E5%A6%82%E4%BD%95%E6%90%AD%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84SpringBoot%E6%BA%90%E7%A0%81%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83%EF%BC%9F%20%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%80%EF%BC%89.md)。 4 | 5 | 前面搭建好了自己本地的SpringBoot源码调试环境后,此时我们不要急着下手进入到具体的源码调试细节中,**刚开始阅读源码,此时我们一定要对项目结构等有一个整体的认识,然后再进行源码分析调试**。推荐阅读下笔者之前写的的[分析开源项目源码,我们该如何入手分析?](https://juejin.im/post/5e4d06b451882549670673c5)一文,干货满满哦。 6 | 7 | ## 2 SpringBoot源码模块一览 8 | 我们先来对SpringBoot的源码模块来一个大致的了解,如下图: 9 | 10 | ![](https://user-gold-cdn.xitu.io/2020/2/23/1707109ab6ff4c1f?w=431&h=273&f=png&s=17190) 11 | 从上图可以看到,主要有以下四个模块: 12 | * **spring-boot-project**:整个SpringBoot框架全部功能在这个模块实现,SpringBoot项目95%的代码都在这里实现,源码总共有25万行左右。 13 | * **Spring-boot-samples**:这个是SpringBoot给小伙伴们赠送的福利,里面包含了各种各样使用SpringBoot的简单demo,我们调试阅读源码的时候可以充分利用该模块。 14 | * **Spring-boot-sample-invoker**:这个模块应该是跟sample模块有关,注意根pom.xml中有这么一句话:`Samples are built via the invoker plugin`,该模块无代码。 15 | * **Spring-boot-tests**:这个模块SpringBoot的测试模块,跟部署测试和集成测试有关。 16 | 17 | 因为SpringBoot的全部功能在spring-boot-project模块实现,因此下面重点来介绍下 spring-boot-project 模块。 18 | ## 3 spring-boot-project源码模块详解 19 | 先来看下spring-boot-project整体模块结构,如下图,然后我们再逐个来介绍: 20 | 21 | ![](https://user-gold-cdn.xitu.io/2020/2/23/170711f4a6a14a10?w=300&h=347&f=png&s=13577) 22 | #### 1) spring-boot-parent 23 | 这个模块没有代码,是spring-boot模块的父项目,被其他子模块继承。 24 | #### 2) spring-boot 25 | 这个模块是SpringBoot项目的核心,可以说一些基础核心的功能都在这里实现,为SpringBoot的其他模块组件功能提供了支持,主要包括以下核心功能: 26 | * `SpringApplication`类,这个是SpringBoot的启动类,提供了一个静态的`run`方法来启动程序,该类主要用来创建并且刷新Spring容器`ApplicationContext`. 27 | * 支持选择不同的容器比如Tomcat,Jetty等来作为应用的嵌入容器,这个是SpringBoot的新特性之一。 28 | * 外部配置支持,这个指的是我们执行`java -jar xxx.jar`命令时可以带一些参数,比如执行`java -jar demo.jar --server.port=8888`来将应用端口修改为8888. 29 | * 该模块内置了一些SpringBoot启动时的生命周期事件和一些容器初始化器(`ApplicationContext` initializers),来执行一些SpringBoot启动时的初始化逻辑。 30 | 31 | #### 3) spring-boot-autoconfigure 32 | 这个模块跟SpringBoot的自动配置有关,也是SpringBoot的新特性之一。比如SpringBoot能基于类路径来自动配置某个项目模块,自动配置最为关键的注解是`@EnableAutoConfiguration`,这个注解能触发Spring上下文的自动配置。另外一个重要的注解是`@Conditional`。 33 | > 举个栗子,若`HSQLDB`在项目的类路径中,且我们没有配置任何其他数据库的连接,此时自动配置就会自动根据类路径来创建相应的`bean`。 34 | 35 | 除了根据类路径来进行自动配置外,还有根据容器中是否存在某个bean等方式来进行自动配置,这里不会进入到具体细节中。 36 | #### 4) spring-boot-starters 37 | 这个模块是跟SpringBoot的起步依赖有关,也是SpringBoot的新特性之一。SpringBoot通过提供众多起步依赖降低项目依赖的复杂度。起步依赖其实就是利用maven项目模型将其他相关的依赖给聚合起来,里面各种依赖的版本号都给定义好,避免用户在引入依赖时出现各种版本冲突,方便了我们的使用。 38 | > 举个栗子,我们要用到activemq时,此时可以直接引入`spring-boot-starter-activemq`起步依赖即可,若SpringBoot官网或第三方组织没有提供相应的SpringBoot起步依赖时,此时我们可以进行定制自己的起步依赖。 39 | 40 | 注意,该模块没有代码,主要是通过maven的pom.xml来组织各种依赖。 41 | 42 | 43 | #### 5) spring-boot-cli 44 | Spring Boot CLI是一个命令行工具,如果您想使用Spring快速开发,可以使用它。它允许您运行Groovy脚本,这意味着您有一个熟悉的类似Java的语法,而没有那么多样板代码。您还可以引导一个新项目或编写自己的命令。 45 | #### 6) spring-boot-actuator 46 | 这个跟SpringBoot的监控有关,也是SpringBoot的新特性之一。可以通过HTTP端点或JMX等来管理和监控应用。审计、运行状况和度量收集可以自动应用到应用程序。这个监控模块是开箱即用的,提供了一系列端点包括`HealthEndpoint`, `EnvironmentEndpoint`和`BeansEndpoint`等端点。 47 | #### 7) spring-boot-actuator-autoconfigure 48 | 这个模块为监控模块提供自动配置的功能,通常也是根据类路径来进行配置。比如`Micrometer`存在于类路径中,那么将会自动配置`MetricsEndpoint`。 49 | #### 8) spring-boot-test 50 | 这个模式是spring-boot的跟测试有关的模块,包含了一些帮助我们测试的核心类和注解(比如`@SpringBootTest`)。 51 | 52 | #### 9) spring-boot-dependencies 53 | 这个模块也没有代码,主要是定义了一些SpringBoot的maven相关的一些依赖及其版本。 54 | #### 10) spring-boot-devtools 55 | 这个模块跟SpringBoot的热部署有关,即修改代码后无需重启应用即生效。 56 | #### 11) spring-boot-docs 57 | 这个模块应该是跟文档相关的模块。 58 | #### 12) spring-boot-properties-migrator 59 | 看到 migrator 这个单词,估计就是跟项目迁移有关,没有去细 60 | 究。 61 | #### 13) spring-boot-test-autoconfigure 62 | 这个模块一看就是跟SpringBoot的测试的自动配置有关。 63 | #### 14) spring-boot-tools 64 | 这个模块一看就是SpringBoot的工具相关的模块,提供了加载,maven插件,metadata和后置处理相关的支持。 65 | 66 | 上面介绍了这么多spring-boot模块下的子模块,不用慌,我们要进行解读的模块不多,我们真正要看的模块有`spring-boot`,`spring-boot-autoconfigure`,`spring-boot-starters`和`spring-boot-actuator`模块。 67 | 68 | ## 5 用一个思维导图来总结下SpringBoot源码项目的脉络 69 | 70 | ![](https://user-gold-cdn.xitu.io/2020/2/23/170728e220dc58a9?w=1247&h=749&f=png&s=281752) 71 | ## 6 SpringBoot模块之间的pom关系详解 72 | 前面弄清楚了SpringBoot的各个模块的具体功能,此时我们来看下SpringBoot模块的pom之间的关系是怎样的,因为项目是通过maven构建的,因此还是有必要去研究下这块关系滴。 73 | 74 | 先看SpringBoot源码项目的pom关系,如下图: 75 | 76 | ![](https://user-gold-cdn.xitu.io/2020/2/23/1707242f908d6dde?w=1122&h=565&f=png&s=51064) 77 | 根据上图可得出以下结论: 78 | * `spring-boot-build(pom.xml)`是项目的根pom,其子pom有`spring-boot-project(pom.xml)`和`spring-boot-dependencies(pom.xml)`; 79 | * `spring-boot-dependencies(pom.xml)`主要定义了SpringBoot项目的各种依赖及其版本,其子pom有`spring-boot-parent(pom.xml)`和`spring-boot-starter-parent(pom.xml)`; 80 | * `spring-boot-project(pom.xml)`起到聚合module的作用,其子模块并不继承于它,而是继承于`spring-boot-parent(pom.xml)`; 81 | * `spring-boot-parent(pom.xml)`是`spring-boot-project(pom.xml)`的子module,但继承的父pom为`spring-boot-dependencies(pom.xml)`,其定义了一些properties等相关的东西。其子pom为`spring-boot-project(pom.xml)`的子module(注意除去`spring-boot-dependencies(pom.xml)`),比如有`spring-boot(pom.xml)`,`spring-boot-starters(pom.xml)`和`spring-boot-actuator(pom.xml)`等; 82 | * `spring-boot-starters(pom.xml)`是所有具体起步依赖的父pom,其子pom有`spring-boot-starter-data-jdbc(pom.xml)`和`spring-boot-starter-data-redis(pom.xml)`等。 83 | * `spring-boot-starter-parent(pom.xml)`,是我们的所有具体SpringBoot项目的父pom,比如SpringBoot自带的样例的`spring-boot-samples(pom.xml)`是继承于它的。 84 | 85 | SpringBoot的各模块之间的pom关系有点复杂,确实有点绕,如果看完上面的图片和解释还是不太清楚的话,建议小伙伴们自己打开idea的项目,逐个去捋一下。总之记得SpringBoot的一些父pom无非是做了一些版本管理,聚合模块之间的事情。 86 | 87 | ## 5 小结 88 | 好了,前面已经把SpringBoot源码项目的各个模块的功能和模块pom之间的关系给捋清楚了,总之刚开始分析项目源码,有一个整体的大局观很重要。 89 | 90 | 本来下节想先写SpringBoot的启动流程分析的,但由于之前研究过启动流程,所以就把启动流程分析放后点写了。下一节先对SpringBoot的新特性--自动配置的源码撸起来,因此下一节让我们先来揭开SpringBoot自动配置背后神秘的面纱吧,嘿嘿🤭。 91 | 92 | **下节预告**: 93 | SpringBoot自动配置的相关原理搞起来 94 | 95 | 96 | **原创不易,帮忙Star一下呗**! 97 | 98 | 注:该源码分析对应SpringBoot版本为**2.1.0.RELEASE**,本文对应的SpringBoot源码解析项目github地址:https://github.com/yuanmabiji/spring-boot-2.1.0.RELEASE 99 | 100 | 101 | 参考: 102 | 103 | 1,https://github.com/spring-projects/spring-boot/tree/v2.1.0.RELEASE 104 | 105 | 2,https://docs.spring.io/spring-boot/docs/1.5.2.RELEASE/reference/htmlsingle/#cli 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /SpringBoot/6 SpringBoot内置的各种Starter是怎样构建的? SpringBoot源码(六).md: -------------------------------------------------------------------------------- 1 | 2 | # 1 温故而知新 3 | 本篇接 [外部配置属性值是如何被绑定到XxxProperties类属性上的?--SpringBoot源码(五)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/5%20SpringBoot%E7%9A%84%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%80%BC%E6%98%AF%E5%A6%82%E4%BD%95%E7%BB%91%E5%AE%9A%E7%9A%84%EF%BC%9F%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E4%BA%94%EF%BC%89.md) 4 | 5 | 温故而知新,我们来简单回顾一下上篇的内容,上一篇我们分析了SpringBoot**外部配置属性值是如何被绑定到XxxProperties类属性上**的相关源码,现将外部属性绑定的重要步骤总结如下: 6 | 1. 首先是`@EnableConfigurationProperties`注解`import`了`EnableConfigurationPropertiesImportSelector`后置处理器; 7 | 2. `EnableConfigurationPropertiesImportSelector`后置处理器又向`Spring`容器中注册了`ConfigurationPropertiesBeanRegistrar`和`ConfigurationPropertiesBindingPostProcessorRegistrar`这两个`bean`; 8 | 3. 其中`ConfigurationPropertiesBeanRegistrar`向`Spring`容器中注册了`XxxProperties`类型的`bean`;`ConfigurationPropertiesBindingPostProcessorRegistrar`向`Spring`容器中注册了`ConfigurationBeanFactoryMetadata`和`ConfigurationPropertiesBindingPostProcessor`两个后置处理器; 9 | 4. `ConfigurationBeanFactoryMetadata`后置处理器在初始化`bean` `factory`时将`@Bean`注解的元数据存储起来,以便在后续的外部配置属性绑定的相关逻辑中使用; 10 | 5. `ConfigurationPropertiesBindingPostProcessor`后置处理器将外部配置属性值绑定到`XxxProperties`类属性的逻辑委托给`ConfigurationPropertiesBinder`对象,然后`ConfigurationPropertiesBinder`对象又最终将属性绑定的逻辑委托给`Binder`对象来完成。 11 | 12 | 可见,重要的是上面的**第5步**。 13 | 14 | # 2 引言 15 | 我们都知道,SpringBoot内置了各种`Starter`起步依赖,我们使用非常方便,大大减轻了我们的开发工作。有了`Starter`起步依赖,我们不用去考虑这个项目需要什么库,这个库的`groupId`和`artifactId`是什么?更不用担心引入这个版本的库后会不会跟其他依赖有没有冲突。 16 | > **举个栗子**:现在我们想开发一个web项目,那么只要引入`spring-boot-starter-web`这个起步依赖就可以了,不用考虑要引入哪些版本的哪些依赖了。像以前我们还要考虑引入哪些依赖库,比如要引入`spring-web`和`spring-webmvc`依赖等;此外,还要考虑引入这些库的哪些版本才不会跟其他库冲突等问题。 17 | 18 | 那么我们今天暂时不分析SpringBoot自动配置的源码,由于起步依赖跟自动配置的关系是如影随形的关系,因此本篇先站在maven项目构建的角度来宏观分析下我们平时使用的**SpringBoot内置的各种`Starter`是怎样构建的?** 19 | 20 | # 3 Maven传递依赖的optional标签 21 | 在分析SpringBoot内置的各种`Starter`构建原理前,我们先来认识下Maven的`optional`标签,因为这个标签起到至关重要的作用。 22 | Maven的`optional`标签表示可选依赖即不可传递的意思,下面直接举个栗子来说明。 23 | 24 | 比如有`A`,`B`和`C`三个库,`C`依赖`B`,`B`依赖`A`。下面看下这三个库的`pom.xml`文件: 25 | ```java 26 | // A的pom.xml 27 | 28 | 29 | 32 | 33 | com.ymbj 34 | A 35 | 1.0-SNAPSHOT 36 | 37 | 38 | ``` 39 | ```java 40 | 41 | 42 | 43 | 46 | 47 | com.ymbj 48 | B 49 | 1.0-SNAPSHOT 50 | 51 | 52 | 53 | 54 | com.ymbj 55 | A 56 | 1.0-SNAPSHOT 57 | true 58 | 59 | 60 | 61 | 62 | ``` 63 | ```java 64 | 65 | 66 | 69 | 70 | com.ymbj 71 | C 72 | 1.0-SNAPSHOT 73 | 74 | 75 | 76 | com.ymbj 77 | B 78 | 1.0-SNAPSHOT 79 | 80 | 81 | 82 | 83 | ``` 84 | 上面三个`A`,`B`和`C`库的`pom.xml`可知,`B`库依赖`A`库,然后`C`库又依赖了`B`库,那么请想一下,**Maven打包构建`C`库后,`A`库有没有被引进来?** 85 | 86 | 答案肯定是**没有**,因为`B`库引入`A`库依赖时使用了`true`,即将Maven的`optional`标签值设为了`true`,此时`C`库再引入`B`库依赖时,`A`库是不会被引入到`C`库的。 87 | 88 | 同时跟Maven传递依赖有关的还有一个`exclusions`标签,这个表示将某个库的某个子依赖排除掉,这里不再详述。 89 | # 4 SpringBoot内置的各种Starter是怎样构建的? 90 | 我们现在来探究SpringBoot内置的各种`Starter`到底是怎样构建的呢? 91 | 92 | 还记得[如何分析SpringBoot源码模块及结构?](https://juejin.im/post/5e521a2fe51d4526f55f014a)这篇文章分析的SpringBoot内部的模块之间的关系吗?先来回顾一下SpringBoot源码内部模块图: 93 | 94 | ![](https://user-gold-cdn.xitu.io/2020/3/14/170d85d8243e32e0?w=447&h=516&f=png&s=291414) 95 |
图1
96 | 97 | 我们都知道,SpringBoot的`Starter`的构建的原理实质就是自动配置,因此由图1可以看到SpringBoot源码项目内部跟`Starter`及其自动配置有关的模块有四个:`spring-boot-starters`,`spring-boot-actuator-autoconfigure`,`spring-boot-autoconfigure`和`spring-boot-test-autoconfigure`。 每个模块的作用请看[如何分析SpringBoot源码模块及结构?](https://juejin.im/post/5e521a2fe51d4526f55f014a)这篇文章,这里不再赘述。 98 | 99 | 那么,`spring-boot-starters`模块跟后面三个自动配置有关的模块`xxx-autoconfigure`模块的关系是怎样的呢? 100 | 101 | 此时我们先来看看`spring-boot-starters`模块里面的结构是怎样的? 102 | 103 | 104 | ![](https://user-gold-cdn.xitu.io/2020/3/15/170db7f1bbb88508?w=429&h=743&f=png&s=164239) 105 |
图2
106 | 107 | 由图2可以看到`spring-boot-starters`模块包含了SpringBoot内置的各种`starter`:`spring-boot-starter-xxx`。由于SpringBoot内置的各种`starter`太多,以我们常用的`spring-boot-starter-web`起步依赖来探究好了。 108 | 109 | 110 | 111 | 112 | 113 | 我们首先看下`spring-boot-starter-web`模块内部结构: 114 | 115 | 116 | ![](https://user-gold-cdn.xitu.io/2020/3/15/170db907457d9085?w=503&h=139&f=png&s=8107) 117 |
图3
118 | 119 | 可以看到`spring-boot-starter-web`模块里面只有`.flattened-pom.xml`和`pom.xml`文件,**而没有任何代码**!有点出乎我们意料。我们都知道若要用到SpringBoot的web功能时引入`spring-boot-starter-web`起步依赖即可,而现在`spring-boot-starter-web`模块里面没有一行代码,那么`spring-boot-starter-web`究竟是如何构建的呢?会不会跟图1所示的`spring-boot-autoconfigure`自动配置模块有关? 120 | 121 | 此时我们就需要看下`spring-boot-starter-web`模块的`pom.xml`文件内容: 122 | 123 | ![](https://user-gold-cdn.xitu.io/2020/3/15/170db9ac9d262916?w=1168&h=909&f=png&s=119294) 124 |
图4
125 | 126 | 由图4可以看到,`spring-boot-starter-web`模块依赖了`spring-boot-starter`,`spring-boot-starter-tomcat`,`spring-web`和`spring-webmvc`等模块,居然没有依赖`spring-boot-autoconfigure`自动配置模块! 127 | 128 | 由于`spring-boot-starter-web`模块肯定跟`spring-boot-autoconfigure`自动配置模块有关,所以`spring-boot-starter-web`模块肯定是间接依赖了`spring-boot-autoconfigure`自动配置模块。 129 | 130 | 图4标有标注"重点关注"的`spring-boot-starter`模块是绝大部分`spring-boot-starter-xxx`模块依赖的基础模块,是核心的`Starter`,包括了自动配置,日志和`YAML`支持。我们此时来关注下`spring-boot-starter`的`pom.xml`文件,也许其依赖了了`spring-boot-autoconfigure`自动配置模块。 131 | 132 | ![](https://user-gold-cdn.xitu.io/2020/3/15/170dbb8e85825a92?w=1147&h=916&f=png&s=119226) 133 |
图5
134 | 135 | 由图5可以看到,我们前面的猜想没有错,**正是`spring-boot-starter`模块依赖了`spring-boot-autoconfigure`自动配置模块!**因此,到了这里我们就可以得出结论了:`spring-boot-starter-web`模块没有一行代码,但是其通过`spring-boot-starter`模块**间接**依赖了`spring-boot-autoconfigure`自动配置模块,从而实现了其起步依赖的功能。 136 | 137 | 此时我们再来看下`spring-boot-autoconfigure`自动配置模块的内部包结构: 138 | 139 | ![](https://user-gold-cdn.xitu.io/2020/3/15/170dbc82e99ec94a?w=415&h=839&f=png&s=85066) 140 |
图6
141 | 142 | 由图6红框处,我们可以知道`spring-boot-starter-web`起步依赖的自动配置功能原来是由`spring-boot-autoconfigure`模块的`web`包下的类实现的。 143 | 144 | 到了这里`spring-boot-starter-web`起步依赖的构建基本原理我们就搞清楚了,但是还有一个特别重要的关键点我们还没Get到。这个关键点跟Maven的`optional`标签有的作用有关。 145 | 146 | 为了Get到这个点,我们先来思考一个问题:平时我们开发`web`项目为什么引入了`spring-boot-starter-web`这个起步依赖后,`spring-boot-autoconfigure`模块的`web`相关的自动配置类就会起自动起作用呢? 147 | 148 | 我们应该知道,某个自动配置类起作用往往是由于`classpath`中存在某个类,这里以`DispatcherServletAutoConfiguration`这个自动配置类为切入点去Get这个点好了。 149 | 先看下`DispatcherServletAutoConfiguration`能够自动配置的条件是啥? 150 | 151 | ![](https://user-gold-cdn.xitu.io/2020/3/15/170dbd6188c1804b?w=702&h=194&f=png&s=26215) 152 |
图7
153 | 154 | 由图7所示,`DispatcherServletAutoConfiguration`能够自动配置的条件之一是`@ConditionalOnClass(DispatcherServlet.class)`,即只有`classpath`中存在`DispatcherServlet.class`这个类,那么`DispatcherServletAutoConfiguration`自动配置相关逻辑才能起作用。 155 | 156 | 而`DispatcherServlet`这个类是在`spring-webmvc`这个依赖库中的,如下图所示: 157 | 158 | ![](https://user-gold-cdn.xitu.io/2020/3/15/170dbdabe203fc0f?w=500&h=385&f=png&s=22361) 159 |
图8
160 | 161 | 此时我们再看下`spring-boot-autoconfigure`模块的`pom.xml`文件引入`spring-webmvc`这个依赖的情况: 162 | 163 | ![](https://user-gold-cdn.xitu.io/2020/3/15/170dbddef7bc7cdc?w=722&h=203&f=png&s=18827) 164 |
图9
165 | 166 | 由图9所示,`spring-boot-autoconfigure`模块引入的`spring-webmvc`这个依赖时`optional`被设置为`true`,原来是可选依赖。即`spring-webmvc`这个依赖库只会被导入到`spring-boot-autoconfigure`模块中,而不会被导入到间接依赖`spring-boot-autoconfigure`模块的`spring-boot-starter-web`这个起步依赖中。 167 | 168 | 此时,我们再来看看`spring-boot-starter-web`的`pom.xml`文件的依赖情况: 169 | ![](https://user-gold-cdn.xitu.io/2020/3/15/170dbeea30725cc1?w=944&h=748&f=png&s=91061) 170 |
图10
171 | 172 | 由图10所示,`spring-boot-starter-web`起步依赖**显式**引入了`spring-webmvc`这个依赖库,即引入`spring-webmvc` 时没有`optional`这个标签,又因为`DispatcherServlet`这个类是在`spring-webmvc`这个依赖库中的,从而`classpath`中存在`DispatcherServlet`这个类,因此`DispatcherServletAutoConfiguration`这个自动配置类就生效了。当然,`web`相关的其他自动配置类生效也是这个原理。 173 | 174 | 至此,我们也明白了`spring-boot-autoconfigure`模块为什么要把引入的`spring-webmvc`这个依赖作为可选依赖了,其目的就是为了在`spring-boot-starter-web`起步依赖中能显式引入`spring-webmvc`这个依赖(这个起决定性作用),从而我们开发web项目只要引入了`spring-boot-starter-web`起步依赖,那么web相关的自动配置类就生效,从而可以开箱即用​这个就是`spring-boot-starter-web`这个起步依赖的构建原理了。 175 | 176 | 前面提到的`spring-boot-starter-actuator`,`spring-boot-starter-test`及其他内置的`spring-boot-starter-xxx`的起步依赖的构建原理也是如此,只不过`spring-boot-starter-actuator`依赖的是`spring-boot-actuator-autoconfigure`,`spring-boot-starter-test`依赖的是`spring-boot-test-autoconfigure`模块罢了,这里不再详述。 177 | 178 | > **思考**:`spring-boot-actuator-autoconfigure`的`pom.xml`文件引入了20多个可选依赖,而为什么`spring-boot-starter-actuator`起步依赖只引入了`micrometer-core`这个依赖呢? 179 | 180 | 181 | # 5 模仿SpringBoot包结构自定义一个Starter 182 | 前面分析了SpringBoot内置的各种`Starter`的构建原理,理论联系实践,那么如果能够动手实践一下自定义`Starter`那就更好了。 183 | 184 | 下面提供一个自定义`Starter`的一个简单`Demo`,这个`Demo`完全模仿`SpringBoot`内置`Starter`的内部包结构来编写,对于进一步了解SpringBoot内置的各种`Starter`的构建原理很有帮助。 185 | 186 | 下面是这个`Demo`的github地址,推荐给有兴趣的小伙伴们。 187 | [模仿springboot内部结构自定义Starter](https://github.com/jinyue233/mock-spring-boot-autoconfiguration)。此外,如何自定义一个`Starter`,可以参考下Mybatis的[spring-boot-starter](https://github.com/mybatis/spring-boot-starter)是如何编写的。 188 | # 6 小结 189 | 好了,SpringBoot内置的各种`Starter`的构建原理分析就到此结束了,现将关键点总结下: 190 | 191 | 1. `spring-boot-starter-xxx`起步依赖没有一行代码,而是直接或间接依赖了`xxx-autoconfigure`模块,而`xxx-autoconfigure`模块承担了`spring-boot-starter-xxx`起步依赖自动配置的实现; 192 | 2. `xxx-autoconfigure`自动配置模块引入了一些可选依赖,这些可选依赖不会被传递到`spring-boot-starter-xxx`起步依赖中,这是起步依赖构建的**关键点**; 193 | 3. `spring-boot-starter-xxx`起步依赖**显式**引入了一些对自动配置起作用的可选依赖; 194 | 4. 经过前面3步的准备,我们项目只要引入了某个起步依赖后,就可以开箱即用了,而不用手动去创建一些`bean`等。 195 | 196 | **原创不易,帮忙Star一下呗**! 197 | 198 | 由于笔者水平有限,若文中有错误还请指出,谢谢。 199 | 200 | 注:该源码分析对应SpringBoot版本为**2.1.0.RELEASE**,本文对应的SpringBoot源码解析项目github地址:https://github.com/yuanmabiji/spring-boot-2.1.0.RELEASE 201 | 202 | 203 | 参考: 204 | 1,[Maven 依赖传递性透彻理解](https://dayarch.top/p/maven-dependency-optional-transitive.html) 205 | 206 | -------------------------------------------------------------------------------- /SpringBoot/7 SpringBoot的启动流程是怎样的?SpringBoot源码(七).md: -------------------------------------------------------------------------------- 1 | 2 | # 1 温故而知新 3 | 本篇接 [SpringBoot内置的各种Starter是怎样构建的? SpringBoot源码(六)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/6%20SpringBoot%E5%86%85%E7%BD%AE%E7%9A%84%E5%90%84%E7%A7%8DStarter%E6%98%AF%E6%80%8E%E6%A0%B7%E6%9E%84%E5%BB%BA%E7%9A%84%EF%BC%9F%20%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E5%85%AD%EF%BC%89.md) 4 | 5 | 温故而知新,我们来简单回顾一下上篇的内容,上一篇我们分析了**SpringBoot内置的各种Starter是怎样构建的?**,现将关键点重新回顾总结下: 6 | 7 | 1. `spring-boot-starter-xxx`起步依赖没有一行代码,而是直接或间接依赖了`xxx-autoconfigure`模块,而`xxx-autoconfigure`模块承担了`spring-boot-starter-xxx`起步依赖自动配置的实现; 8 | 2. `xxx-autoconfigure`自动配置模块引入了一些可选依赖,这些可选依赖不会被传递到`spring-boot-starter-xxx`起步依赖中,这是起步依赖构建的**关键点**; 9 | 3. `spring-boot-starter-xxx`起步依赖**显式**引入了一些对自动配置起作用的可选依赖,因此会触发 `xxx-autoconfigure`自动配置的逻辑(比如创建某些符合条件的配置`bean`); 10 | 4. 经过前面3步的准备,我们项目只要引入了某个起步依赖后,就可以开箱即用了,而不用手动去创建一些`bean`等。 11 | 12 | # 2 引言 13 | 本来这篇文章会继续SpringBoot自动配置的源码分析的,想分析下`spring-boot-starter-web`的自动配置的源码是怎样的的。但是考虑到`spring-boot-starter-web`的自动配置逻辑跟内置`Tomcat`等有关,因此想以后等分析了SpringBoot的内置`Tomcat`的相关源码后再来继续分析`spring-boot-starter-web`的自动配置的源码。 14 | 15 | 因此,本篇我们来探究下**SpringBoot的启动流程是怎样的?** 16 | # 3 如何编写一个SpringBoot启动类 17 | 我们都知道,我们运行一个SpringBoot项目,引入相关`Starters`和相关依赖后,再编写一个启动类,然后在这个启动类标上`@SpringBootApplication`注解,然后就可以启动运行项目了,如下代码: 18 | ```java 19 | //MainApplication.java 20 | 21 | @SpringBootApplication 22 | public class MainApplication { 23 | public static void main(String[] args) { 24 | SpringApplication.run(MainApplication.class, args); 25 | } 26 | } 27 | ``` 28 | 如上代码,我们在`MainApplication`启动类上标注了`@SpringBootApplication`注解,然后在`main`函数中调用`SpringApplication.run(MainApplication.class, args);`这句代码就完成了SpringBoot的启动流程,非常简单。 29 | # 4 @SpringBootApplication 30 | 现在我们来分析下标注在启动类上的`@SpringBootApplication`注解,直接上源码: 31 | ```java 32 | // SpringBootApplication.java 33 | 34 | @Target(ElementType.TYPE) 35 | @Retention(RetentionPolicy.RUNTIME) 36 | @Documented 37 | @Inherited 38 | @SpringBootConfiguration 39 | @EnableAutoConfiguration 40 | @ComponentScan(excludeFilters = { // TODO 这两个排除过滤器TypeExcludeFilter和AutoConfigurationExcludeFilter暂不知道啥作用 41 | @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), 42 | @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) 43 | public @interface SpringBootApplication { 44 | // 等同于EnableAutoConfiguration注解的exclude属性 45 | @AliasFor(annotation = EnableAutoConfiguration.class) 46 | Class[] exclude() default {}; 47 | // 等同于EnableAutoConfiguration注解的excludeName属性 48 | @AliasFor(annotation = EnableAutoConfiguration.class) 49 | String[] excludeName() default {}; 50 | // 等同于ComponentScan注解的basePackages属性 51 | @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") 52 | String[] scanBasePackages() default {}; 53 | // 等同于ComponentScan注解的basePackageClasses属性 54 | @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") 55 | Class[] scanBasePackageClasses() default {}; 56 | } 57 | ``` 58 | 59 | 可以看到,`@SpringBootApplication`注解是一个组合注解,主要由`@SpringBootConfiguration`,`@EnableAutoConfiguration`和`@ComponentScan`这三个注解组合而成。 60 | 61 | 因此`@SpringBootApplication`注解主要作为一个配置类,能够触发包扫描和自动配置的逻辑,从而使得SpringBoot的相关`bean`被注册进Spring容器。 62 | # 5 SpringBoot的启动流程是怎样的? 63 | 接下来是本篇的重点,我们来分析下**SpringBoot的启动流程是怎样的?** 64 | 65 | 我们接着来看前面`main`函数里的`SpringApplication.run(MainApplication.class, args);`这句代码,那么`SpringApplication`这个类是干嘛的呢? 66 | 67 | `SpringApplication`类是用来启动SpringBoot项目的,可以在java的`main`方法中启动,目前我们知道这些就足够了。下面看下`SpringApplication.run(MainApplication.class, args);`这句代码的源码: 68 | ```java 69 | // SpringApplication.java 70 | 71 | // run方法是一个静态方法,用于启动SpringBoot 72 | public static ConfigurableApplicationContext run(Class primarySource, 73 | String... args) { 74 | // 继续调用静态的run方法 75 | return run(new Class[] { primarySource }, args); 76 | } 77 | ``` 78 | 在上面的静态`run`方法里又继续调用另一个静态`run`方法: 79 | ```java 80 | // SpringApplication.java 81 | 82 | // run方法是一个静态方法,用于启动SpringBoot 83 | public static ConfigurableApplicationContext run(Class[] primarySources, 84 | String[] args) { 85 | // 构建一个SpringApplication对象,并调用其run方法来启动 86 | return new SpringApplication(primarySources).run(args); 87 | } 88 | ``` 89 | 如上代码,可以看到构建了一个`SpringApplication`对象,然后再调用其`run`方法来启动SpringBoot项目。关于`SpringApplication`对象是如何构建的,我们后面再分析,现在直接来看下启动流程的源码: 90 | ```java 91 | // SpringApplication.java 92 | 93 | public ConfigurableApplicationContext run(String... args) { 94 | // new 一个StopWatch用于统计run启动过程花了多少时间 95 | StopWatch stopWatch = new StopWatch(); 96 | // 开始计时 97 | stopWatch.start(); 98 | ConfigurableApplicationContext context = null; 99 | // exceptionReporters集合用来存储异常报告器,用来报告SpringBoot启动过程的异常 100 | Collection exceptionReporters = new ArrayList<>(); 101 | // 配置headless属性,即“java.awt.headless”属性,默认为ture 102 | // 其实是想设置该应用程序,即使没有检测到显示器,也允许其启动.对于服务器来说,是不需要显示器的,所以要这样设置. 103 | configureHeadlessProperty(); 104 | // 【1】从spring.factories配置文件中加载到EventPublishingRunListener对象并赋值给SpringApplicationRunListeners 105 | // EventPublishingRunListener对象主要用来发射SpringBoot启动过程中内置的一些生命周期事件,标志每个不同启动阶段 106 | SpringApplicationRunListeners listeners = getRunListeners(args); 107 | // 启动SpringApplicationRunListener的监听,表示SpringApplication开始启动。 108 | // 》》》》》发射【ApplicationStartingEvent】事件 109 | listeners.starting(); 110 | try { 111 | // 创建ApplicationArguments对象,封装了args参数 112 | ApplicationArguments applicationArguments = new DefaultApplicationArguments( 113 | args); 114 | // 【2】准备环境变量,包括系统变量,环境变量,命令行参数,默认变量,servlet相关配置变量,随机值, 115 | // JNDI属性值,以及配置文件(比如application.properties)等,注意这些环境变量是有优先级的 116 | // 》》》》》发射【ApplicationEnvironmentPreparedEvent】事件 117 | ConfigurableEnvironment environment = prepareEnvironment(listeners, 118 | applicationArguments); 119 | // 配置spring.beaninfo.ignore属性,默认为true,即跳过搜索BeanInfo classes. 120 | configureIgnoreBeanInfo(environment); 121 | // 【3】控制台打印SpringBoot的bannner标志 122 | Banner printedBanner = printBanner(environment); 123 | // 【4】根据不同类型创建不同类型的spring applicationcontext容器 124 | // 因为这里是servlet环境,所以创建的是AnnotationConfigServletWebServerApplicationContext容器对象 125 | context = createApplicationContext(); 126 | // 【5】从spring.factories配置文件中加载异常报告期实例,这里加载的是FailureAnalyzers 127 | // 注意FailureAnalyzers的构造器要传入ConfigurableApplicationContext,因为要从context中获取beanFactory和environment 128 | exceptionReporters = getSpringFactoriesInstances( 129 | SpringBootExceptionReporter.class, 130 | new Class[] { ConfigurableApplicationContext.class }, context); // ConfigurableApplicationContext是AnnotationConfigServletWebServerApplicationContext的父接口 131 | // 【6】为刚创建的AnnotationConfigServletWebServerApplicationContext容器对象做一些初始化工作,准备一些容器属性值等 132 | // 1)为AnnotationConfigServletWebServerApplicationContext的属性AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner设置environgment属性 133 | // 2)根据情况对ApplicationContext应用一些相关的后置处理,比如设置resourceLoader属性等 134 | // 3)在容器刷新前调用各个ApplicationContextInitializer的初始化方法,ApplicationContextInitializer是在构建SpringApplication对象时从spring.factories中加载的 135 | // 4)》》》》》发射【ApplicationContextInitializedEvent】事件,标志context容器被创建且已准备好 136 | // 5)从context容器中获取beanFactory,并向beanFactory中注册一些单例bean,比如applicationArguments,printedBanner 137 | // 6)TODO 加载bean到application context,注意这里只是加载了部分bean比如mainApplication这个bean,大部分bean应该是在AbstractApplicationContext.refresh方法中被加载?这里留个疑问先 138 | // 7)》》》》》发射【ApplicationPreparedEvent】事件,标志Context容器已经准备完成 139 | prepareContext(context, environment, listeners, applicationArguments, 140 | printedBanner); 141 | // 【7】刷新容器,这一步至关重要,以后会在分析Spring源码时详细分析,主要做了以下工作: 142 | // 1)在context刷新前做一些准备工作,比如初始化一些属性设置,属性合法性校验和保存容器中的一些早期事件等; 143 | // 2)让子类刷新其内部bean factory,注意SpringBoot和Spring启动的情况执行逻辑不一样 144 | // 3)对bean factory进行配置,比如配置bean factory的类加载器,后置处理器等 145 | // 4)完成bean factory的准备工作后,此时执行一些后置处理逻辑,子类通过重写这个方法来在BeanFactory创建并预准备完成以后做进一步的设置 146 | // 在这一步,所有的bean definitions将会被加载,但此时bean还不会被实例化 147 | // 5)执行BeanFactoryPostProcessor的方法即调用bean factory的后置处理器: 148 | // BeanDefinitionRegistryPostProcessor(触发时机:bean定义注册之前)和BeanFactoryPostProcessor(触发时机:bean定义注册之后bean实例化之前) 149 | // 6)注册bean的后置处理器BeanPostProcessor,注意不同接口类型的BeanPostProcessor;在Bean创建前后的执行时机是不一样的 150 | // 7)初始化国际化MessageSource相关的组件,比如消息绑定,消息解析等 151 | // 8)初始化事件广播器,如果bean factory没有包含事件广播器,那么new一个SimpleApplicationEventMulticaster广播器对象并注册到bean factory中 152 | // 9)AbstractApplicationContext定义了一个模板方法onRefresh,留给子类覆写,比如ServletWebServerApplicationContext覆写了该方法来创建内嵌的tomcat容器 153 | // 10)注册实现了ApplicationListener接口的监听器,之前已经有了事件广播器,此时就可以派发一些early application events 154 | // 11)完成容器bean factory的初始化,并初始化所有剩余的单例bean。这一步非常重要,一些bean postprocessor会在这里调用。 155 | // 12)完成容器的刷新工作,并且调用生命周期处理器的onRefresh()方法,并且发布ContextRefreshedEvent事件 156 | refreshContext(context); 157 | // 【8】执行刷新容器后的后置处理逻辑,注意这里为空方法 158 | afterRefresh(context, applicationArguments); 159 | // 停止stopWatch计时 160 | stopWatch.stop(); 161 | // 打印日志 162 | if (this.logStartupInfo) { 163 | new StartupInfoLogger(this.mainApplicationClass) 164 | .logStarted(getApplicationLog(), stopWatch); 165 | } 166 | // 》》》》》发射【ApplicationStartedEvent】事件,标志spring容器已经刷新,此时所有的bean实例都已经加载完毕 167 | listeners.started(context); 168 | // 【9】调用ApplicationRunner和CommandLineRunner的run方法,实现spring容器启动后需要做的一些东西比如加载一些业务数据等 169 | callRunners(context, applicationArguments); 170 | } 171 | // 【10】若启动过程中抛出异常,此时用FailureAnalyzers来报告异常 172 | // 并》》》》》发射【ApplicationFailedEvent】事件,标志SpringBoot启动失败 173 | catch (Throwable ex) { 174 | handleRunFailure(context, ex, exceptionReporters, listeners); 175 | throw new IllegalStateException(ex); 176 | } 177 | 178 | try { 179 | // 》》》》》发射【ApplicationReadyEvent】事件,标志SpringApplication已经正在运行即已经成功启动,可以接收服务请求了。 180 | listeners.running(context); 181 | } 182 | // 若出现异常,此时仅仅报告异常,而不会发射任何事件 183 | catch (Throwable ex) { 184 | handleRunFailure(context, ex, exceptionReporters, null); 185 | throw new IllegalStateException(ex); 186 | } 187 | // 【11】最终返回容器 188 | return context; 189 | } 190 | ``` 191 | 如上代码就是SpringBoot的启动流程了,其中注释也非常详细,主要步骤也已经标注`【x】`,现将主要步骤总结如下: 192 | 193 | 1. 从`spring.factories`配置文件中**加载`EventPublishingRunListener`对象**,该对象拥有`SimpleApplicationEventMulticaster`属性,即在SpringBoot启动过程的不同阶段用来发射内置的生命周期事件; 194 | 2. **准备环境变量**,包括系统变量,环境变量,命令行参数,默认变量,`servlet`相关配置变量,随机值以及配置文件(比如`application.properties`)等; 195 | 3. 控制台**打印SpringBoot的`bannner`标志**; 196 | 4. **根据不同类型环境创建不同类型的`applicationcontext`容器**,因为这里是`servlet`环境,所以创建的是`AnnotationConfigServletWebServerApplicationContext`容器对象; 197 | 5. 从`spring.factories`配置文件中**加载`FailureAnalyzers`对象**,用来报告SpringBoot启动过程中的异常; 198 | 6. **为刚创建的容器对象做一些初始化工作**,准备一些容器属性值等,对`ApplicationContext`应用一些相关的后置处理和调用各个`ApplicationContextInitializer`的初始化方法来执行一些初始化逻辑等; 199 | 7. **刷新容器**,这一步至关重要。比如调用`bean factory`的后置处理器,注册`BeanPostProcessor`后置处理器,初始化事件广播器且广播事件,初始化剩下的单例`bean`和SpringBoot创建内嵌的`Tomcat`服务器等等重要且复杂的逻辑都在这里实现,主要步骤可见代码的注释,关于这里的逻辑会在以后的spring源码分析专题详细分析; 200 | 8. **执行刷新容器后的后置处理逻辑**,注意这里为空方法; 201 | 9. **调用`ApplicationRunner`和`CommandLineRunner`的run方法**,我们实现这两个接口可以在spring容器启动后需要的一些东西比如加载一些业务数据等; 202 | 10. **报告启动异常**,即若启动过程中抛出异常,此时用`FailureAnalyzers`来报告异常; 203 | 11. 最终**返回容器对象**,这里调用方法没有声明对象来接收。 204 | 205 | 当然在SpringBoot启动过程中,每个不同的启动阶段会分别发射不同的内置生命周期事件,比如在准备`environment`前会发射`ApplicationStartingEvent`事件,在`environment`准备好后会发射`ApplicationEnvironmentPreparedEvent`事件,在刷新容器前会发射`ApplicationPreparedEvent`事件等,总之SpringBoot总共内置了7个生命周期事件,除了标志SpringBoot的不同启动阶段外,同时一些监听器也会监听相应的生命周期事件从而执行一些启动初始化逻辑。 206 | 207 | 208 | # 6 小结 209 | 好了,SpringBoot的启动流程就已经分析完了,这篇内容主要让我们对SpringBoot的启动流程有一个整体的认识,现在还没必要去深究每一个细节,以免丢了**主线**,现在我们对SpringBoot的启动流程有一个整体的认识即可,关于启动流程的一些重要步骤我们会在以后的源码分析中来深究。 210 | 211 | **原创不易,帮忙Star一下呗**! 212 | 213 | 由于笔者水平有限,若文中有错误还请指出,谢谢。 214 | 215 | 注:该源码分析对应SpringBoot版本为**2.1.0.RELEASE**,本文对应的SpringBoot源码解析项目github地址:https://github.com/yuanmabiji/spring-boot-2.1.0.RELEASE 216 | -------------------------------------------------------------------------------- /SpringBoot/8 SpringApplication对象是如何构建的? SpringBoot源码(八).md: -------------------------------------------------------------------------------- 1 | 2 | 本篇接 [SpringBoot的启动流程是怎样的?SpringBoot源码(七)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/7%20SpringBoot%E7%9A%84%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84%EF%BC%9FSpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%83%EF%BC%89.md) 3 | 4 | # 1 温故而知新 5 | 6 | 温故而知新,我们来简单回顾一下上篇的内容,上一篇我们分析了**SpringBoot的启动流程**,现将关键步骤再浓缩总结下: 7 | 8 | 1. 构建`SpringApplication`对象,用于启动SpringBoot; 9 | 2. 从`spring.factories`配置文件中加载`EventPublishingRunListener`对象用于在不同的启动阶段发射不同的生命周期事件; 10 | 3. 准备环境变量,包括系统变量,环境变量,命令行参数及配置文件(比如`application.properties`)等; 11 | 4. 创建容器`ApplicationContext`; 12 | 5. 为第4步创建的容器对象做一些初始化工作,准备一些容器属性值等,同时调用各个`ApplicationContextInitializer`的初始化方法来执行一些初始化逻辑等; 13 | 6. 刷新容器,这一步至关重要,是重点中的重点,太多复杂逻辑在这里实现; 14 | 7. 调用`ApplicationRunner`和`CommandLineRunner`的run方法,可以实现这两个接口在容器启动后来加载一些业务数据等; 15 | 16 | 在SpringBoot启动过程中,每个不同的启动阶段会分别发射不同的内置生命周期事件,然后相应的监听器会监听这些事件来执行一些初始化逻辑工作比如`ConfigFileApplicationListener`会监听`onApplicationEnvironmentPreparedEvent`事件来加载环境变量等。 17 | 18 | # 2 引言 19 | 20 | 上篇文章在讲解SpringBoot的启动流程中,我们有看到新建了一个`SpringApplication`对象用来启动SpringBoot项目。那么,我们今天就来看看`SpringApplication`对象的构建过程,同时讲解一下SpringBoot自己实现的SPI机制。 21 | 22 | # 3 SpringApplication对象的构建过程 23 | 24 | 本小节开始讲解`SpringApplication`对象的构造过程,因为一个对象的构造无非就是在其构造函数里给它的一些成员属性赋值,很少包含其他额外的业务逻辑(当然有时候我们可能也会在构造函数里开启一些线程啥的)。那么,我们先来看下构造`SpringApplication`对象时需要用到的一些成员属性哈: 25 | 26 | ```java 27 | // SpringApplication.java 28 | 29 | /** 30 | * SpringBoot的启动类即包含main函数的主类 31 | */ 32 | private Set> primarySources; 33 | /** 34 | * 包含main函数的主类 35 | */ 36 | private Class mainApplicationClass; 37 | /** 38 | * 资源加载器 39 | */ 40 | private ResourceLoader resourceLoader; 41 | /** 42 | * 应用类型 43 | */ 44 | private WebApplicationType webApplicationType; 45 | /** 46 | * 初始化器 47 | */ 48 | private List> initializers; 49 | /** 50 | * 监听器 51 | */ 52 | private List> listeners; 53 | ``` 54 | 55 | 可以看到构建`SpringApplication`对象时主要是给上面代码中的六个成员属性赋值,现在我接着来看`SpringApplication`对象的构造过程。 56 | 57 | 我们先回到上一篇文章讲解的构建`SpringApplication`对象的代码处: 58 | 59 | ```java 60 | // SpringApplication.java 61 | 62 | // run方法是一个静态方法,用于启动SpringBoot 63 | public static ConfigurableApplicationContext run(Class[] primarySources, 64 | String[] args) { 65 | // 构建一个SpringApplication对象,并调用其run方法来启动 66 | return new SpringApplication(primarySources).run(args); 67 | } 68 | ``` 69 | 70 | 跟进`SpringApplication`的构造函数中: 71 | 72 | ```java 73 | // SpringApplication.java 74 | 75 | public SpringApplication(Class... primarySources) { 76 | // 继续调用SpringApplication另一个构造函数 77 | this(null, primarySources); 78 | } 79 | ``` 80 | 81 | 继续跟进`SpringApplication`另一个构造函数: 82 | 83 | ```java 84 | // SpringApplication.java 85 | 86 | public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { 87 | // 【1】给resourceLoader属性赋值,注意传入的resourceLoader参数为null 88 | this.resourceLoader = resourceLoader; 89 | Assert.notNull(primarySources, "PrimarySources must not be null"); 90 | // 【2】给primarySources属性赋值,传入的primarySources其实就是SpringApplication.run(MainApplication.class, args);中的MainApplication.class 91 | this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); 92 | // 【3】给webApplicationType属性赋值,根据classpath中存在哪种类型的类来确定是哪种应用类型 93 | this.webApplicationType = WebApplicationType.deduceFromClasspath(); 94 | // 【4】给initializers属性赋值,利用SpringBoot自定义的SPI从spring.factories中加载ApplicationContextInitializer接口的实现类并赋值给initializers属性 95 | setInitializers((Collection) getSpringFactoriesInstances( 96 | ApplicationContextInitializer.class)); 97 | // 【5】给listeners属性赋值,利用SpringBoot自定义的SPI从spring.factories中加载ApplicationListener接口的实现类并赋值给listeners属性 98 | setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 99 | // 【6】给mainApplicationClass属性赋值,即这里要推断哪个类调用了main函数,然后再赋值给mainApplicationClass属性,用于后面启动流程中打印一些日志。 100 | this.mainApplicationClass = deduceMainApplicationClass(); 101 | } 102 | ``` 103 | 104 | 可以看到构建`SpringApplication`对象时其实就是给前面讲的6个`SpringApplication`类的成员属性赋值而已,做一些初始化工作: 105 | 106 | 1. **给`resourceLoader`属性赋值**,`resourceLoader`属性,资源加载器,此时传入的`resourceLoader`参数为`null`; 107 | 2. **给`primarySources`属性赋值**,`primarySources`属性`即SpringApplication.run(MainApplication.class,args);`中传入的`MainApplication.class`,该类为SpringBoot项目的启动类,主要通过该类来扫描`Configuration`类加载`bean`; 108 | 3. **给`webApplicationType`属性赋值**,`webApplicationType`属性,代表应用类型,根据`classpath`存在的相应`Application`类来判断。因为后面要根据`webApplicationType`来确定创建哪种`Environment`对象和创建哪种`ApplicationContext`,详细分析请见后面的`第3.1小节`; 109 | 4. **给`initializers`属性赋值**,`initializers`属性为`List>`集合,利用SpringBoot的SPI机制从`spring.factories`配置文件中加载,后面在初始化容器的时候会应用这些初始化器来执行一些初始化工作。因为SpringBoot自己实现的SPI机制比较重要,因此独立成一小节来分析,详细分析请见后面的`第4小节`; 110 | 5. **给`listeners`属性赋值**,`listeners`属性为`List>`集合,同样利用利用SpringBoot的SPI机制从`spring.factories`配置文件中加载。因为SpringBoot启动过程中会在不同的阶段发射一些事件,所以这些加载的监听器们就是来监听SpringBoot启动过程中的一些生命周期事件的; 111 | 6. **给`mainApplicationClass`属性赋值**,`mainApplicationClass`属性表示包含`main`函数的类,即这里要推断哪个类调用了`main`函数,然后把这个类的全限定名赋值给`mainApplicationClass`属性,用于后面启动流程中打印一些日志,详细分析见后面的`第3.2小节`。 112 | 113 | # 3.1 推断项目应用类型 114 | 115 | 我们接着分析构造`SpringApplication`对象的第`【3】`步`WebApplicationType.deduceFromClasspath();`这句代码: 116 | 117 | ```java 118 | // WebApplicationType.java 119 | 120 | public enum WebApplicationType { 121 | // 普通的应用 122 | NONE, 123 | // Servlet类型的web应用 124 | SERVLET, 125 | // Reactive类型的web应用 126 | REACTIVE; 127 | 128 | private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet", 129 | "org.springframework.web.context.ConfigurableWebApplicationContext" }; 130 | private static final String WEBMVC_INDICATOR_CLASS = "org.springframework." 131 | + "web.servlet.DispatcherServlet"; 132 | private static final String WEBFLUX_INDICATOR_CLASS = "org." 133 | + "springframework.web.reactive.DispatcherHandler"; 134 | private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer"; 135 | private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext"; 136 | private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext"; 137 | 138 | static WebApplicationType deduceFromClasspath() { 139 | // 若classpath中不存在"org.springframework." + "web.servlet.DispatcherServlet"和"org.glassfish.jersey.servlet.ServletContainer" 140 | // 则返回WebApplicationType.REACTIVE,表明是reactive应用 141 | if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) 142 | && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) 143 | && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { 144 | return WebApplicationType.REACTIVE; 145 | } 146 | // 若{ "javax.servlet.Servlet", 147 | // "org.springframework.web.context.ConfigurableWebApplicationContext" } 148 | // 都不存在在classpath,则说明是不是web应用 149 | for (String className : SERVLET_INDICATOR_CLASSES) { 150 | if (!ClassUtils.isPresent(className, null)) { 151 | return WebApplicationType.NONE; 152 | } 153 | } 154 | // 最终返回普通的web应用 155 | return WebApplicationType.SERVLET; 156 | } 157 | } 158 | ``` 159 | 160 | 如上代码,根据`classpath`判断应用类型,即通过反射加载`classpath`判断指定的标志类存在与否来分别判断是`Reactive`应用,`Servlet`类型的web应用还是普通的应用。 161 | 162 | # 3.2 推断哪个类调用了main函数 163 | 164 | 我们先跳过构造`SpringApplication`对象的第`【4】`步和第`【5】`步,先来分析构造`SpringApplication`对象的第`【6】`步`this.mainApplicationClass = deduceMainApplicationClass();`这句代码: 165 | 166 | ```java 167 | // SpringApplication.java 168 | 169 | private Class deduceMainApplicationClass() { 170 | try { 171 | // 获取StackTraceElement对象数组stackTrace,StackTraceElement对象存储了调用栈相关信息(比如类名,方法名等) 172 | StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); 173 | // 遍历stackTrace数组 174 | for (StackTraceElement stackTraceElement : stackTrace) { 175 | // 若stackTraceElement记录的调用方法名等于main 176 | if ("main".equals(stackTraceElement.getMethodName())) { 177 | // 那么就返回stackTraceElement记录的类名即包含main函数的类名 178 | return Class.forName(stackTraceElement.getClassName()); 179 | } 180 | } 181 | } 182 | catch (ClassNotFoundException ex) { 183 | // Swallow and continue 184 | } 185 | return null; 186 | } 187 | ``` 188 | 189 | 可以看到`deduceMainApplicationClass`方法的主要作用就是从`StackTraceElement`调用栈数组中获取哪个类调用了`main`方法,然后再返回赋值给`mainApplicationClass`属性,然后用于后面启动流程中打印一些日志。 190 | 191 | # 4 SpringBoot的SPI机制原理解读 192 | 193 | 由于SpringBoot的SPI机制是一个很重要的知识点,因此这里单独一小节来分析。我们都知道,SpringBoot没有使用Java的SPI机制(Java的SPI机制可以看看笔者的[Java是如何实现自己的SPI机制的?](https://juejin.im/post/5e7c26a76fb9a009a441757c),真的是干货满满),而是自定义实现了一套自己的SPI机制。SpringBoot利用自定义实现的SPI机制可以加载初始化器实现类,监听器实现类和自动配置类等等。如果我们要添加自动配置类或自定义监听器,那么我们很重要的一步就是在`spring.factories`中进行配置,然后才会被SpringBoot加载。 194 | 195 | 好了,那么接下来我们就来重点分析下**SpringBoot是如何是实现自己的SPI机制的**。 196 | 197 | 这里接第3小节的构造`SpringApplication`对象的第`【4】`步和第`【5】`步代码,因为第`【4】`步和第`【5】`步都是利用SpringBoot的SPI机制来加载扩展实现类,因此这里只分析第`【4】`步的`setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));`这句代码,看看`getSpringFactoriesInstances`方法中SpringBoot是如何实现自己的一套SPI来加载`ApplicationContextInitializer`初始化器接口的扩展实现类的? 198 | 199 | ```java 200 | // SpringApplication.java 201 | 202 | private Collection getSpringFactoriesInstances(Class type) { 203 | // 继续调用重载的getSpringFactoriesInstances方法进行加载 204 | return getSpringFactoriesInstances(type, new Class[] {}); 205 | } 206 | ``` 207 | 208 | 继续跟进重载的`getSpringFactoriesInstances`方法: 209 | 210 | ```java 211 | // SpringApplication.java 212 | 213 | private Collection getSpringFactoriesInstances(Class type, 214 | Class[] parameterTypes, Object... args) { 215 | // 【1】获得类加载器 216 | ClassLoader classLoader = getClassLoader(); 217 | // Use names and ensure unique to protect against duplicates 218 | // 【2】将接口类型和类加载器作为参数传入loadFactoryNames方法,从spring.factories配置文件中进行加载接口实现类 219 | Set names = new LinkedHashSet<>( 220 | SpringFactoriesLoader.loadFactoryNames(type, classLoader)); 221 | // 【3】实例化从spring.factories中加载的接口实现类 222 | List instances = createSpringFactoriesInstances(type, parameterTypes, 223 | classLoader, args, names); 224 | // 【4】进行排序 225 | AnnotationAwareOrderComparator.sort(instances); 226 | // 【5】返回加载并实例化好的接口实现类 227 | return instances; 228 | } 229 | ``` 230 | 231 | 可以看到,SpringBoot自定义实现的SPI机制代码中最重要的是上面代码的`【1】`,`【2】`,`【3】`步,这3步下面分别进行重点分析。 232 | 233 | # 4.1 获得类加载器 234 | 235 | 还记得[Java是如何实现自己的SPI机制的?](https://juejin.im/post/5e7c26a76fb9a009a441757c)这篇文章中Java的SPI机制默认是利用线程上下文类加载器去加载扩展类的,那么,**SpringBoot自己实现的SPI机制又是利用哪种类加载器去加载`spring.factories`配置文件中的扩展实现类呢?** 236 | 237 | 我们直接看第`【1】`步的`ClassLoader classLoader = getClassLoader();`这句代码,先睹为快: 238 | 239 | ```java 240 | // SpringApplication.java 241 | 242 | public ClassLoader getClassLoader() { 243 | // 前面在构造SpringApplicaiton对象时,传入的resourceLoader参数是null,因此不会执行if语句里面的逻辑 244 | if (this.resourceLoader != null) { 245 | return this.resourceLoader.getClassLoader(); 246 | } 247 | // 获取默认的类加载器 248 | return ClassUtils.getDefaultClassLoader(); 249 | } 250 | ``` 251 | 252 | 继续跟进`getDefaultClassLoader`方法: 253 | 254 | ```java 255 | // ClassUtils.java 256 | 257 | public static ClassLoader getDefaultClassLoader() { 258 | ClassLoader cl = null; 259 | try { 260 | // 【重点】获取线程上下文类加载器 261 | cl = Thread.currentThread().getContextClassLoader(); 262 | } 263 | catch (Throwable ex) { 264 | // Cannot access thread context ClassLoader - falling back... 265 | } 266 | // 这里的逻辑不会执行 267 | if (cl == null) { 268 | // No thread context class loader -> use class loader of this class. 269 | cl = ClassUtils.class.getClassLoader(); 270 | if (cl == null) { 271 | // getClassLoader() returning null indicates the bootstrap ClassLoader 272 | try { 273 | cl = ClassLoader.getSystemClassLoader(); 274 | } 275 | catch (Throwable ex) { 276 | // Cannot access system ClassLoader - oh well, maybe the caller can live with null... 277 | } 278 | } 279 | } 280 | // 返回刚才获取的线程上下文类加载器 281 | return cl; 282 | } 283 | ``` 284 | 285 | 可以看到,原来SpringBoot的SPI机制中也是用线程上下文类加载器去加载`spring.factories`文件中的扩展实现类的! 286 | 287 | # 4.2 加载spring.factories配置文件中的SPI扩展类 288 | 289 | 我们再来看下第`【2】`步中的`SpringFactoriesLoader.loadFactoryNames(type, classLoader)`这句代码是如何加载`spring.factories`配置文件中的SPI扩展类的? 290 | 291 | ```java 292 | // SpringFactoriesLoader.java 293 | 294 | public static List loadFactoryNames(Class factoryClass, @Nullable ClassLoader classLoader) { 295 | // factoryClass即SPI接口,比如ApplicationContextInitializer,EnableAutoConfiguration等接口 296 | String factoryClassName = factoryClass.getName(); 297 | // 【主线,重点关注】继续调用loadSpringFactories方法加载SPI扩展类 298 | return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); 299 | } 300 | ``` 301 | 302 | 继续跟进`loadSpringFactories`方法: 303 | 304 | ```java 305 | // SpringFactoriesLoader.java 306 | 307 | /** 308 | * The location to look for factories. 309 | *

Can be present in multiple JAR files. 310 | */ 311 | public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; 312 | 313 | private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) { 314 | // 以classLoader作为键先从缓存中取,若能取到则直接返回 315 | MultiValueMap result = cache.get(classLoader); 316 | if (result != null) { 317 | return result; 318 | } 319 | // 若缓存中无记录,则去spring.factories配置文件中获取 320 | try { 321 | // 这里加载所有jar包中包含"MATF-INF/spring.factories"文件的url路径 322 | Enumeration urls = (classLoader != null ? 323 | classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : 324 | ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); 325 | result = new LinkedMultiValueMap<>(); 326 | // 遍历urls路径,将所有spring.factories文件的键值对(key:SPI接口类名 value:SPI扩展类名) 327 | // 加载放到 result集合中 328 | while (urls.hasMoreElements()) { 329 | // 取出一条url 330 | URL url = urls.nextElement(); 331 | // 将url封装到UrlResource对象中 332 | UrlResource resource = new UrlResource(url); 333 | // 利用PropertiesLoaderUtils的loadProperties方法将spring.factories文件键值对内容加载进Properties对象中 334 | Properties properties = PropertiesLoaderUtils.loadProperties(resource); 335 | // 遍历刚加载的键值对properties对象 336 | for (Map.Entry entry : properties.entrySet()) { 337 | // 取出SPI接口名 338 | String factoryClassName = ((String) entry.getKey()).trim(); 339 | // 遍历SPI接口名对应的实现类即SPI扩展类 340 | for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { 341 | // SPI接口名作为key,SPI扩展类作为value放入result中 342 | result.add(factoryClassName, factoryName.trim()); 343 | } 344 | } 345 | } 346 | // 以classLoader作为key,result作为value放入cache缓存 347 | cache.put(classLoader, result); 348 | // 最终返回result对象 349 | return result; 350 | } 351 | catch (IOException ex) { 352 | throw new IllegalArgumentException("Unable to load factories from location [" + 353 | FACTORIES_RESOURCE_LOCATION + "]", ex); 354 | } 355 | } 356 | ``` 357 | 358 | 如上代码,`loadSpringFactories`方法主要做的事情就是利用之前获取的线程上下文类加载器将`classpath`中的所有`spring.factories`配置文件中所有SPI接口的所有扩展实现类给加载出来,然后放入缓存中。**注意**,这里是一次性加载所有的SPI扩展实现类哈,所以之后根据SPI接口就直接从缓存中获取SPI扩展类了,就不用再次去`spring.factories`配置文件中获取SPI接口对应的扩展实现类了。比如之后的获取`ApplicationListener`,`FailureAnalyzer`和`EnableAutoConfiguration`接口的扩展实现类都直接从缓存中获取即可。 359 | 360 | > **思考1:** 这里为啥要一次性从`spring.factories`配置文件中获取所有的扩展类放入缓存中呢?而不是每次都是根据SPI接口去`spring.factories`配置文件中获取呢? 361 | 362 | > **思考2:** 还记得之前讲的SpringBoot的自动配置源码时提到的`AutoConfigurationImportFilter`这个接口的作用吗?现在我们应该能更清楚的理解这个接口的作用了吧。 363 | 364 | 365 | 将所有的SPI扩展实现类加载出来后,此时再调用`getOrDefault(factoryClassName, Collections.emptyList())`方法根据SPI接口名去筛选当前对应的扩展实现类,比如这里传入的`factoryClassName`参数名为`ApplicationContextInitializer`接口,那么这个接口将会作为`key`从刚才缓存数据中取出`ApplicationContextInitializer`接口对应的SPI扩展实现类。其中从`spring.factories`中获取的`ApplicationContextInitializer`接口对应的所有SPI扩展实现类如下图所示: 366 | 367 | ![](https://user-gold-cdn.xitu.io/2020/3/31/1712c91d81a89901?w=713&h=199&f=png&s=21757) 368 | 369 | # 4.3 实例化从spring.factories中加载的SPI扩展类 370 | 371 | 前面从`spring.factories`中获取到`ApplicationContextInitializer`接口对应的所有SPI扩展实现类后,此时会将这些SPI扩展类进行实例化。 372 | 373 | 此时我们再来看下前面的第`【3】`步的实例化代码: 374 | `List instances = createSpringFactoriesInstances(type, parameterTypes, 375 | classLoader, args, names);`。 376 | 377 | ```java 378 | // SpringApplication.java 379 | 380 | private List createSpringFactoriesInstances(Class type, 381 | Class[] parameterTypes, ClassLoader classLoader, Object[] args, 382 | Set names) { 383 | // 新建instances集合,用于存储稍后实例化后的SPI扩展类对象 384 | List instances = new ArrayList<>(names.size()); 385 | // 遍历name集合,names集合存储了所有SPI扩展类的全限定名 386 | for (String name : names) { 387 | try { 388 | // 根据全限定名利用反射加载类 389 | Class instanceClass = ClassUtils.forName(name, classLoader); 390 | // 断言刚才加载的SPI扩展类是否属于SPI接口类型 391 | Assert.isAssignable(type, instanceClass); 392 | // 获得SPI扩展类的构造器 393 | Constructor constructor = instanceClass 394 | .getDeclaredConstructor(parameterTypes); 395 | // 实例化SPI扩展类 396 | T instance = (T) BeanUtils.instantiateClass(constructor, args); 397 | // 添加进instances集合 398 | instances.add(instance); 399 | } 400 | catch (Throwable ex) { 401 | throw new IllegalArgumentException( 402 | "Cannot instantiate " + type + " : " + name, ex); 403 | } 404 | } 405 | // 返回 406 | return instances; 407 | } 408 | ``` 409 | 410 | 上面代码很简单,主要做的事情就是实例化SPI扩展类。 411 | 好了,SpringBoot自定义的SPI机制就已经分析完了。 412 | 413 | > **思考3:** SpringBoot为何弃用Java的SPI而自定义了一套SPI? 414 | 415 | # 5 小结 416 | 417 | 好了,本片就到此结束了,先将前面的知识点再总结下: 418 | 419 | 1. 分析了`SpringApplication`对象的构造过程; 420 | 2. 分析了SpringBoot自己实现的一套SPI机制。 421 | 422 | # 6 有感而发 423 | 424 | 从自己2月开始写源码分析文章以来,也认识了一些技术大牛,从他们身上看到,越厉害的人越努力。回想一下,自己现在知识面也很窄,更重要的是对自己所涉及的技术没有深度,一句话概括,还很菜,而看到比自己厉害的大牛们都还那么拼,自己有啥理由不努力呢?很喜欢丁威老师的一句话:"**唯有坚持不懈**"。然后自己一步一个脚印,相信自己能取得更大的进步,继续加油。 425 | 426 | 427 | 428 | **原创不易,帮忙Star一下呗**! 429 | 430 | 由于笔者水平有限,若文中有错误还请指出,谢谢。 431 | 432 | 注:该源码分析对应SpringBoot版本为**2.1.0.RELEASE**,本文对应的SpringBoot源码解析项目github地址:https://github.com/yuanmabiji/spring-boot-2.1.0.RELEASE 433 | 434 | -------------------------------------------------------------------------------- /SpringBoot/9 SpringBoot事件监听机制源码分析(上) SpringBoot源码(九).md: -------------------------------------------------------------------------------- 1 | **SpringBoot中文注释项目Github地址:** 2 | 3 | https://github.com/yuanmabiji/spring-boot-2.1.0.RELEASE 4 | 5 | 6 | 7 | 本篇接 [SpringApplication对象是如何构建的? SpringBoot源码(八)](https://juejin.im/post/5e82bac9518825737a314096) 8 | 9 | # 1 温故而知新 10 | 温故而知新,我们来简单回顾一下上篇的内容,上一篇我们分析了**SpringApplication对象的构建过程及SpringBoot自己实现的一套SPI机制**,现将关键步骤再浓缩总结下: 11 | 1. `SpringApplication`对象的构造过程其实就是给`SpringApplication`类的**6**个成员变量赋值; 12 | 2. SpringBoot通过以下步骤实现自己的SPI机制: 13 | * 1)首先获取线程上下文类加载器; 14 | * 2)然后利用上下文类加载器从`spring.factories`配置文件中**加载所有的SPI扩展实现类并放入缓存中**; 15 | * 3)根据SPI接口从缓存中取出相应的SPI扩展实现类; 16 | * 4)实例化从缓存中取出的SPI扩展实现类并返回。 17 | 18 | # 2 引言 19 | 在SpringBoot启动过程中,每个不同的启动阶段会分别广播不同的内置生命周期事件,然后相应的监听器会监听这些事件来执行一些初始化逻辑工作比如`ConfigFileApplicationListener`会监听`onApplicationEnvironmentPreparedEvent`事件来加载配置文件`application.properties`的环境变量等。 20 | 21 | 因此本篇内容将来分析下SpringBoot的事件监听机制的源码。 22 | 23 | # 3 SpringBoot广播内置生命周期事件流程分析 24 | 为了探究SpringBoot广播内置生命周期事件流程,我们再来回顾一下SpringBoot的启动流程代码: 25 | ```java 26 | // SpringApplication.java 27 | 28 | public ConfigurableApplicationContext run(String... args) { 29 | StopWatch stopWatch = new StopWatch(); 30 | stopWatch.start(); 31 | ConfigurableApplicationContext context = null; 32 | Collection exceptionReporters = new ArrayList<>(); 33 | configureHeadlessProperty(); 34 | // 【0】新建一个SpringApplicationRunListeners对象用于发射SpringBoot启动过程中的生命周期事件 35 | SpringApplicationRunListeners listeners = getRunListeners(args); 36 | // 【1】》》》》》发射【ApplicationStartingEvent】事件,标志SpringApplication开始启动 37 | listeners.starting(); 38 | try { 39 | ApplicationArguments applicationArguments = new DefaultApplicationArguments( 40 | args); 41 | // 【2】》》》》》发射【ApplicationEnvironmentPreparedEvent】事件,此时会去加载application.properties等配置文件的环境变量,同时也有标志环境变量已经准备好的意思 42 | ConfigurableEnvironment environment = prepareEnvironment(listeners, 43 | applicationArguments); 44 | configureIgnoreBeanInfo(environment); 45 | Banner printedBanner = printBanner(environment); 46 | context = createApplicationContext(); 47 | exceptionReporters = getSpringFactoriesInstances( 48 | SpringBootExceptionReporter.class, 49 | new Class[] { ConfigurableApplicationContext.class }, context); 50 | // 【3】》》》》》发射【ApplicationContextInitializedEvent】事件,标志context容器被创建且已准备好 51 | // 【4】》》》》》发射【ApplicationPreparedEvent】事件,标志Context容器已经准备完成 52 | prepareContext(context, environment, listeners, applicationArguments, 53 | printedBanner); 54 | refreshContext(context); 55 | afterRefresh(context, applicationArguments); 56 | stopWatch.stop(); 57 | if (this.logStartupInfo) { 58 | new StartupInfoLogger(this.mainApplicationClass) 59 | .logStarted(getApplicationLog(), stopWatch); 60 | } 61 | // 【5】》》》》》发射【ApplicationStartedEvent】事件,标志spring容器已经刷新,此时所有的bean实例都已经加载完毕 62 | listeners.started(context); 63 | callRunners(context, applicationArguments); 64 | } 65 | // 【6】》》》》》发射【ApplicationFailedEvent】事件,标志SpringBoot启动失败 66 | catch (Throwable ex) { 67 | handleRunFailure(context, ex, exceptionReporters, listeners); 68 | throw new IllegalStateException(ex); 69 | } 70 | try { 71 | // 【7】》》》》》发射【ApplicationReadyEvent】事件,标志SpringApplication已经正在运行即已经成功启动,可以接收服务请求了。 72 | listeners.running(context); 73 | } 74 | catch (Throwable ex) { 75 | handleRunFailure(context, ex, exceptionReporters, null); 76 | throw new IllegalStateException(ex); 77 | } 78 | return context; 79 | } 80 | ``` 81 | 可以看到SpringBoot在启动过程中首先会先新建一个`SpringApplicationRunListeners`对象用于发射SpringBoot启动过程中的各种生命周期事件,比如发射`ApplicationStartingEvent`,`ApplicationEnvironmentPreparedEvent`和`ApplicationContextInitializedEvent`等事件,然后相应的监听器会执行一些SpringBoot启动过程中的初始化逻辑。那么,监听这些SpringBoot的生命周期事件的监听器们是何时被加载实例化的呢?还记得上篇文章在分析`SpringApplication`的构建过程吗?没错,这些执行初始化逻辑的监听器们正是在`SpringApplication`的构建过程中根据`ApplicationListener`接口去`spring.factories`配置文件中加载并实例化的。 82 | # 3.1 为广播SpringBoot内置生命周期事件做前期准备 83 | # 3.1.1 加载ApplicationListener监听器实现类 84 | 我们再来回顾下[SpringApplication对象是如何构建的? SpringBoot源码(八)](https://juejin.im/post/5e82bac9518825737a314096)一文中讲到在构建`SpringApplication`对象时的`setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));`这句代码。 85 | 86 | 这句代码做的事情就是从`spring.factories`中加载出`ApplicationListener`事件监听接口的SPI扩展实现类然后添加到`SpringApplication`对象的`listeners`集合中,用于后续监听SpringBoot启动过程中的事件,来执行一些初始化逻辑工作。 87 | 88 | SpringBoot启动时的具体监听器们都实现了`ApplicationListener`接口,其在`spring.factories`部分配置如下: 89 | 90 | ![](https://user-gold-cdn.xitu.io/2020/4/12/1716f1b4e4687069?w=1150&h=498&f=png&s=75210) 91 | 92 | 不过在调试时,会从所有的spring.factories配置文件中加载监听器,最终加载了10个监听器。如下图: 93 | 94 | 95 | ![](https://user-gold-cdn.xitu.io/2020/4/13/17170eb8ae034bbd?w=712&h=584&f=png&s=53731) 96 | 97 | # 3.1.2 加载SPI扩展类EventPublishingRunListener 98 | 前面讲到,在SpringBoot的启动过程中首先会先新建一个`SpringApplicationRunListeners`对象用于发射SpringBoot启动过程中的生命周期事件,即我们现在来看下`SpringApplicationRunListeners listeners = getRunListeners(args);`这句代码: 99 | 100 | ```java 101 | // SpringApplication.java 102 | 103 | private SpringApplicationRunListeners getRunListeners(String[] args) { 104 | // 构造一个由SpringApplication.class和String[].class组成的types 105 | Class[] types = new Class[] { SpringApplication.class, String[].class }; 106 | // 1) 根据SpringApplicationRunListener接口去spring.factories配置文件中加载其SPI扩展实现类 107 | // 2) 构建一个SpringApplicationRunListeners对象并返回 108 | return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( 109 | SpringApplicationRunListener.class, types, this, args)); 110 | } 111 | 112 | ``` 113 | 114 | 我们将重点放到`getSpringFactoriesInstances( 115 | SpringApplicationRunListener.class, types, this, args)`这句代码,`getSpringFactoriesInstances`这个方法我们已经很熟悉,在上一篇分析SpringBoot的SPI机制时已经详细分析过这个方法。可以看到SpringBoot此时又是根据`SpringApplicationRunListener`这个SPI接口去`spring.factories`中加载相应的SPI扩展实现类,我们直接去`spring.factories`中看看`SpringApplicationRunListener`有哪些SPI实现类: 116 | 117 | ![](https://user-gold-cdn.xitu.io/2020/4/18/1718d054c88bebb9?w=692&h=117&f=png&s=13088) 118 | 由上图可以看到,`SpringApplicationRunListener`只有`EventPublishingRunListener`这个SPI实现类 119 | `EventPublishingRunListener`这个哥们在SpringBoot的启动过程中尤其重要,由其在SpringBoot启动过程的不同阶段发射不同的SpringBoot的生命周期事件,**即`SpringApplicationRunListeners`对象没有承担广播事件的职责,而最终是委托`EventPublishingRunListener`这个哥们来广播事件的。** 120 | 121 | 因为从`spring.factories`中加载`EventPublishingRunListener`类后还会实例化该类,那么我们再跟进`EventPublishingRunListener`的源码,看看其是如何承担发射SpringBoot生命周期事件这一职责的? 122 | ```java 123 | // EventPublishingRunListener.java 124 | 125 | public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { 126 | 127 | private final SpringApplication application; 128 | 129 | private final String[] args; 130 | /** 131 | * 拥有一个SimpleApplicationEventMulticaster事件广播器来广播事件 132 | */ 133 | private final SimpleApplicationEventMulticaster initialMulticaster; 134 | 135 | public EventPublishingRunListener(SpringApplication application, String[] args) { 136 | this.application = application; 137 | this.args = args; 138 | // 新建一个事件广播器SimpleApplicationEventMulticaster对象 139 | this.initialMulticaster = new SimpleApplicationEventMulticaster(); 140 | // 遍历在构造SpringApplication对象时从spring.factories配置文件中获取的事件监听器 141 | for (ApplicationListener listener : application.getListeners()) { 142 | // 将从spring.factories配置文件中获取的事件监听器们添加到事件广播器initialMulticaster对象的相关集合中 143 | this.initialMulticaster.addApplicationListener(listener); 144 | } 145 | } 146 | 147 | @Override 148 | public int getOrder() { 149 | return 0; 150 | } 151 | // 》》》》》发射【ApplicationStartingEvent】事件 152 | @Override 153 | public void starting() { 154 | this.initialMulticaster.multicastEvent( 155 | new ApplicationStartingEvent(this.application, this.args)); 156 | } 157 | // 》》》》》发射【ApplicationEnvironmentPreparedEvent】事件 158 | @Override 159 | public void environmentPrepared(ConfigurableEnvironment environment) { 160 | this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent( 161 | this.application, this.args, environment)); 162 | } 163 | // 》》》》》发射【ApplicationContextInitializedEvent】事件 164 | @Override 165 | public void contextPrepared(ConfigurableApplicationContext context) { 166 | this.initialMulticaster.multicastEvent(new ApplicationContextInitializedEvent( 167 | this.application, this.args, context)); 168 | } 169 | // 》》》》》发射【ApplicationPreparedEvent】事件 170 | @Override 171 | public void contextLoaded(ConfigurableApplicationContext context) { 172 | for (ApplicationListener listener : this.application.getListeners()) { 173 | if (listener instanceof ApplicationContextAware) { 174 | ((ApplicationContextAware) listener).setApplicationContext(context); 175 | } 176 | context.addApplicationListener(listener); 177 | } 178 | this.initialMulticaster.multicastEvent( 179 | new ApplicationPreparedEvent(this.application, this.args, context)); 180 | } 181 | // 》》》》》发射【ApplicationStartedEvent】事件 182 | @Override 183 | public void started(ConfigurableApplicationContext context) { 184 | context.publishEvent( 185 | new ApplicationStartedEvent(this.application, this.args, context)); 186 | } 187 | // 》》》》》发射【ApplicationReadyEvent】事件 188 | @Override 189 | public void running(ConfigurableApplicationContext context) { 190 | context.publishEvent( 191 | new ApplicationReadyEvent(this.application, this.args, context)); 192 | } 193 | // 》》》》》发射【ApplicationFailedEvent】事件 194 | @Override 195 | public void failed(ConfigurableApplicationContext context, Throwable exception) { 196 | ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, 197 | this.args, context, exception); 198 | if (context != null && context.isActive()) { 199 | // Listeners have been registered to the application context so we should 200 | // use it at this point if we can 201 | context.publishEvent(event); 202 | } 203 | else { 204 | // An inactive context may not have a multicaster so we use our multicaster to 205 | // call all of the context's listeners instead 206 | if (context instanceof AbstractApplicationContext) { 207 | for (ApplicationListener listener : ((AbstractApplicationContext) context) 208 | .getApplicationListeners()) { 209 | this.initialMulticaster.addApplicationListener(listener); 210 | } 211 | } 212 | this.initialMulticaster.setErrorHandler(new LoggingErrorHandler()); 213 | this.initialMulticaster.multicastEvent(event); 214 | } 215 | } 216 | 217 | // ...省略非关键代码 218 | } 219 | 220 | ``` 221 | 222 | 可以看到`EventPublishingRunListener`类实现了`SpringApplicationRunListener`接口,`SpringApplicationRunListener`接口定义了SpringBoot启动时发射生命周期事件的接口方法,而`EventPublishingRunListener`类正是通过实现`SpringApplicationRunListener`接口的`starting`,`environmentPrepared`和`contextPrepared`等方法来广播SpringBoot不同的生命周期事件,我们直接看下`SpringApplicationRunListener`接口源码好了: 223 | ```java 224 | // SpringApplicationRunListener.java 225 | 226 | public interface SpringApplicationRunListener { 227 | 228 | void starting(); 229 | 230 | void environmentPrepared(ConfigurableEnvironment environment); 231 | 232 | void contextPrepared(ConfigurableApplicationContext context); 233 | 234 | void contextLoaded(ConfigurableApplicationContext context); 235 | 236 | void started(ConfigurableApplicationContext context); 237 | 238 | void running(ConfigurableApplicationContext context); 239 | 240 | void failed(ConfigurableApplicationContext context, Throwable exception); 241 | } 242 | ``` 243 | 我们再接着分析`EventPublishingRunListener`这个类,可以看到其有一个重要的成员属性`initialMulticaster`,该成员属性是`SimpleApplicationEventMulticaster`类对象,该类正是承担了广播SpringBoot启动时生命周期事件的职责,**即`EventPublishingRunListener`对象没有承担广播事件的职责,而最终是委托`SimpleApplicationEventMulticaster`这个哥们来广播事件的。** 从`EventPublishingRunListener`的源码中也可以看到在`starting`,`environmentPrepared`和`contextPrepared`等方法中也正是通过调用`SimpleApplicationEventMulticaster`类对象的`multicastEvent`方法来广播事件的。 244 | 245 | > **思考** SpringBoot启动过程中发射事件时事件广播者是层层委托职责的,起初由`SpringApplicationRunListeners`对象承担,然后`SpringApplicationRunListeners`对象将广播事件职责委托给`EventPublishingRunListener`对象,最终`EventPublishingRunListener`对象将广播事件的职责委托给`SimpleApplicationEventMulticaster`对象。**为什么要层层委托这么做呢?** 这个值得大家思考。 246 | 247 | 前面讲到从`spring.factories`中加载出`EventPublishingRunListener`类后会实例化,而实例化必然会通过`EventPublishingRunListener`的构造函数来进行实例化,因此我们接下来分析下`EventPublishingRunListener`的构造函数源码: 248 | ```java 249 | // EventPublishingRunListener.java 250 | 251 | public EventPublishingRunListener(SpringApplication application, String[] args) { 252 | this.application = application; 253 | this.args = args; 254 | // 新建一个事件广播器SimpleApplicationEventMulticaster对象 255 | this.initialMulticaster = new SimpleApplicationEventMulticaster(); 256 | // 遍历在构造SpringApplication对象时从spring.factories配置文件中获取的事件监听器 257 | for (ApplicationListener listener : application.getListeners()) { 258 | // 将从spring.factories配置文件中获取的事件监听器们添加到事件广播器initialMulticaster对象的相关集合中 259 | this.initialMulticaster.addApplicationListener(listener); 260 | } 261 | } 262 | ``` 263 | 可以看到在`EventPublishingRunListener`的构造函数中有一个`for`循环会遍历之前从`spring.factories`中加载的监听器们,然后添加到集合中缓存起来,用于以后广播各种事件时直接从这个集合中取出来即可,而不用再去`spring.factories`中加载,提高效率。 264 | 265 | # 3.2 广播SpringBoot的内置生命周期事件 266 | 从`spring.factories`配置文件中加载并实例化`EventPublishingRunListener`对象后,那么在在SpringBoot的启动过程中会发射一系列SpringBoot内置的生命周期事件,我们再来回顾下SpringBoot启动过程中的源码: 267 | ```java 268 | // SpringApplication.java 269 | 270 | public ConfigurableApplicationContext run(String... args) { 271 | StopWatch stopWatch = new StopWatch(); 272 | stopWatch.start(); 273 | ConfigurableApplicationContext context = null; 274 | Collection exceptionReporters = new ArrayList<>(); 275 | configureHeadlessProperty(); 276 | // 【0】新建一个SpringApplicationRunListeners对象用于发射SpringBoot启动过程中的生命周期事件 277 | SpringApplicationRunListeners listeners = getRunListeners(args); 278 | // 【1】》》》》》发射【ApplicationStartingEvent】事件,标志SpringApplication开始启动 279 | listeners.starting(); 280 | try { 281 | ApplicationArguments applicationArguments = new DefaultApplicationArguments( 282 | args); 283 | // 【2】》》》》》发射【ApplicationEnvironmentPreparedEvent】事件,此时会去加载application.properties等配置文件的环境变量,同时也有标志环境变量已经准备好的意思 284 | ConfigurableEnvironment environment = prepareEnvironment(listeners, 285 | applicationArguments); 286 | configureIgnoreBeanInfo(environment); 287 | Banner printedBanner = printBanner(environment); 288 | context = createApplicationContext(); 289 | exceptionReporters = getSpringFactoriesInstances( 290 | SpringBootExceptionReporter.class, 291 | new Class[] { ConfigurableApplicationContext.class }, context); 292 | // 【3】》》》》》发射【ApplicationContextInitializedEvent】事件,标志context容器被创建且已准备好 293 | // 【4】》》》》》发射【ApplicationPreparedEvent】事件,标志Context容器已经准备完成 294 | prepareContext(context, environment, listeners, applicationArguments, 295 | printedBanner); 296 | refreshContext(context); 297 | afterRefresh(context, applicationArguments); 298 | stopWatch.stop(); 299 | if (this.logStartupInfo) { 300 | new StartupInfoLogger(this.mainApplicationClass) 301 | .logStarted(getApplicationLog(), stopWatch); 302 | } 303 | // 【5】》》》》》发射【ApplicationStartedEvent】事件,标志spring容器已经刷新,此时所有的bean实例都已经加载完毕 304 | listeners.started(context); 305 | callRunners(context, applicationArguments); 306 | } 307 | // 【6】》》》》》发射【ApplicationFailedEvent】事件,标志SpringBoot启动失败 308 | catch (Throwable ex) { 309 | handleRunFailure(context, ex, exceptionReporters, listeners); 310 | throw new IllegalStateException(ex); 311 | } 312 | try { 313 | // 【7】》》》》》发射【ApplicationReadyEvent】事件,标志SpringApplication已经正在运行即已经成功启动,可以接收服务请求了。 314 | listeners.running(context); 315 | } 316 | catch (Throwable ex) { 317 | handleRunFailure(context, ex, exceptionReporters, null); 318 | throw new IllegalStateException(ex); 319 | } 320 | return context; 321 | } 322 | ``` 323 | 可以看到在SpringBoot的启动过程中总共会发射7种不同类型的生命周期事件,来标志SpringBoot的不同启动阶段,同时,这些生命周期事件的监听器们也会执行一些启动过程中的初始化逻辑,关于这些监听器的初始化逻辑将在下一篇内容中会分析。以下是SpringBoot启动过程中要发射的事件类型,其中`ApplicationFailedEvent`在SpringBoot启动过程中遇到异常才会发射: 324 | 1. `ApplicationStartingEvent` 325 | 2. `ApplicationEnvironmentPreparedEvent` 326 | 3. `ApplicationContextInitializedEvent` 327 | 4. `ApplicationPreparedEvent` 328 | 5. `ApplicationStartedEvent` 329 | 6. `ApplicationFailedEvent` 330 | 7. `ApplicationReadyEvent` 331 | 332 | 我们以`listeners.starting();`这句代码为例,看看`EventPublishingRunListener`对象发射事件的源码: 333 | ```java 334 | // SpringApplicationRunListeners.java 335 | 336 | public void starting() { 337 | // 遍历listeners集合,这里实质取出的就是刚才从spring.factories中取出的SPI实现类EventPublishingRunListener 338 | // 而EventPublishingRunListener对象承担了SpringBoot启动过程中负责广播不同的生命周期事件 339 | for (SpringApplicationRunListener listener : this.listeners) { 340 | // 调用EventPublishingRunListener的starting方法来广播ApplicationStartingEvent事件 341 | listener.starting(); 342 | } 343 | } 344 | ``` 345 | 继续跟进`listener.starting();`的源码: 346 | ```java 347 | EventPublishingRunListener.java 348 | 349 | // 》》》》》发射【ApplicationStartingEvent】事件 350 | public void starting() { 351 | // EventPublishingRunListener对象将发布ApplicationStartingEvent这件事情委托给了initialMulticaster对象 352 | // 调用initialMulticaster的multicastEvent方法来发射ApplicationStartingEvent事件 353 | this.initialMulticaster.multicastEvent( 354 | new ApplicationStartingEvent(this.application, this.args)); 355 | } 356 | ``` 357 | 可以看到,`EventPublishingRunListener`对象将发布`ApplicationStartingEvent`这件事情委托给了`SimpleApplicationEventMulticaster`对象`initialMulticaster`, 358 | ,而`initialMulticaster`对象最终会调用其`multicastEvent`方法来发射`ApplicationStartingEvent`事件。关于`SimpleApplicationEventMulticaster`类如何广播事件,笔者已经在[Spring是如何实现事件监听机制的? Spring源码(二)](https://juejin.im/post/5e421bfc6fb9a07cd80f1354)这篇文章已经详细分析,这里不再赘述。 359 | 360 | 关于SpringBoot启动过程中发射其他生命周期事件的源码这里不再分析 361 | 362 | # 4 SpringBoot的内置生命周期事件总结 363 | 好了,前面已经分析了SpringBoot启动过程中要发射的各种生命周期事件,下面列一个表格总结下: 364 | 365 | ![](https://user-gold-cdn.xitu.io/2020/4/18/1718d7dce64e30d2?w=881&h=870&f=png&s=88780) 366 | 367 | 368 | # 5 小结 369 | SpringBoot启动过程中广播生命周期事件的源码分析就到此结束了,下一篇会继续介绍监听这些生命周期事件的监听器们。我们再回顾本篇内容总结下关键点: 370 | 371 | SpringBoot启动过程中会发射7种类型的生命周期事件,标志不同的启动阶段,然后相应的监听器会监听这些事件来执行一些初始化逻辑工作。 372 | 373 | **【源码笔记】Github源码分析项目上线啦!!!下面是笔记的Github地址:** 374 | 375 | https://github.com/yuanmabiji/Java-SourceCode-Blogs 376 | 377 | **原创不易,帮忙Star一下呗**! 378 | 379 | -------------------------------------------------------------------------------- /SpringBoot/README.md: -------------------------------------------------------------------------------- 1 | 【**源码笔记**】专注于Java后端系列框架的源码分析。若觉得源码分析文章不错,欢迎Star哦。 2 | 3 | 4 | 5 | ================**SpringBoot源码专题持续更新中...**==================== 6 | 7 | #### 目录 8 | 9 | 1. [如何搭建自己的SpringBoot源码调试环境? SpringBoot源码(一)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/1%20%E5%A6%82%E4%BD%95%E6%90%AD%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84SpringBoot%E6%BA%90%E7%A0%81%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83%EF%BC%9F%20%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%80%EF%BC%89.md) 10 | 2. [如何分析SpringBoot源码模块及结构? SpringBoot源码(二)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/2%20%E5%A6%82%E4%BD%95%E5%88%86%E6%9E%90SpringBoot%E6%BA%90%E7%A0%81%E6%A8%A1%E5%9D%97%E5%8F%8A%E7%BB%93%E6%9E%84%EF%BC%9F%20%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E4%BA%8C%EF%BC%89.md) 11 | 3. [助力SpringBoot自动配置的条件注解原理揭秘 SpringBoot源码(三)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/3%20%E5%8A%A9%E5%8A%9BSpringBoot%E8%87%AA%E5%8A%A8%E9%85%8D%E7%BD%AE%E7%9A%84%E6%9D%A1%E4%BB%B6%E6%B3%A8%E8%A7%A3%E5%8E%9F%E7%90%86%E6%8F%AD%E7%A7%98%20%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%89%EF%BC%89.md) 12 | 4. [SpringBoot是如何实现自动配置的? SpringBoot源码(四)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/4%20SpringBoot%E6%98%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E9%85%8D%E7%BD%AE%E7%9A%84%EF%BC%9F%20%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E5%9B%9B%EF%BC%89.md) 13 | 5. [SpringBoot的配置属性值是如何绑定的? SpringBoot源码(五)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/5%20SpringBoot%E7%9A%84%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%80%BC%E6%98%AF%E5%A6%82%E4%BD%95%E7%BB%91%E5%AE%9A%E7%9A%84%EF%BC%9F%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E4%BA%94%EF%BC%89.md) 14 | 6. [SpringBoot内置的各种Starter是怎样构建的? SpringBoot源码(六)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/6%20SpringBoot%E5%86%85%E7%BD%AE%E7%9A%84%E5%90%84%E7%A7%8DStarter%E6%98%AF%E6%80%8E%E6%A0%B7%E6%9E%84%E5%BB%BA%E7%9A%84%EF%BC%9F%20%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E5%85%AD%EF%BC%89.md) 15 | 7. [SpringBoot的启动流程是怎样的?SpringBoot源码(七)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/7%20SpringBoot%E7%9A%84%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84%EF%BC%9FSpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E4%B8%83%EF%BC%89.md) 16 | 8. [SpringApplication对象是如何构建的? SpringBoot源码(八)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/8%20SpringApplication%E5%AF%B9%E8%B1%A1%E6%98%AF%E5%A6%82%E4%BD%95%E6%9E%84%E5%BB%BA%E7%9A%84%EF%BC%9F%20SpringBoot%E6%BA%90%E7%A0%81%EF%BC%88%E5%85%AB%EF%BC%89.md) 17 | 9. [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) 18 | 10. [SpringBoot内置生命周期事件详解 SpringBoot源码(十)](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/SpringBoot/10%20SpringBoot%E5%86%85%E7%BD%AE%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E4%BA%8B%E4%BB%B6%E8%AF%A6%E8%A7%A3%20%20SpringBoot%E6%BA%90%E7%A0%81(%E5%8D%81).md) 19 | 11. 持续更新中... 20 | 21 | **阅读本源码专题文章结合SpringBoot项目(带中文注释)调试效果会更佳哦** 22 | 23 | * 本源码专题文章对应的SpringBoot项目(带中文注释):https://github.com/yuanmabiji/spring-boot-2.1.0.RELEASE 24 | 25 | 26 | * 更多源码分析文章请跳转至:https://github.com/yuanmabiji/Java-SourceCode-Blogs 27 | 28 | 29 | -------------------------------------------------------------------------------- /分析开源项目源码,我们该如何入手分析?(授人以渔).md: -------------------------------------------------------------------------------- 1 | ## 1 前言 2 | 本文接上篇文章[跟大家聊聊我们为什么要学习源码?学习源码对我们有用吗?](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/%E8%B7%9F%E5%A4%A7%E5%AE%B6%E8%81%8A%E8%81%8A%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%AD%A6%E4%B9%A0%E6%BA%90%E7%A0%81%EF%BC%9F%E5%AD%A6%E4%B9%A0%E6%BA%90%E7%A0%81%E5%AF%B9%E6%88%91%E4%BB%AC%E6%9C%89%E7%94%A8%E5%90%97%EF%BC%9F.md),那么本篇文章再继续跟小伙伴们聊聊源码这个话题。 3 | 4 | 在工作之余开始写SpringBoot源码分析专栏前,跟小伙伴们聊聊“**分析开源项目源码,我们该如何入手分析?**”这个话题,我们就随便扯皮,反正是跟小伙伴们一起学习交流,没必要太正式。 5 | >小伙伴们看完本文后,若有自己的源码阅读心得可以在下面进行评论或私聊我进行分享,**让我从小伙伴们身上GET多点源码阅读的一些技巧**,嘿嘿。 6 | 7 | ## 2 学习开源框架源码到底难不难? 8 | 那么,先跟小伙伴们聊聊学习开源框架源码的感受,请问你们认为学习开源框架源码到底难不难?这是一个开放的话题,可谓仁者见仁,智者见智。有一些开源大牛们会说,**So easy!**;有一些有源码阅读习惯且工作多年的小伙伴们会说,**还好。**;有一些刚开始学习源码的小伙伴们会说,**太难了!**。是的,不同工作经验不同技术层次的人的回答是不一样的。 9 | 10 | 那么刚开始学习开源项目源码难不难呢?应该对绝大部分小伙伴们来说应该是偏难的。为什么呢?**可能有以下四点原因**: 11 | 1. **一个能流行起来的成熟的开源项目必定功能齐全,可扩展,而功能齐全可扩展的开源项目必定很复杂,代码量大**。比如Spring5框架的源码行数达到了六七十万行,SpringBoot的源码行数达到了25万行左右,Dubbo和RocketMQ的源码行数达到了10万行。一个成熟的开源项目代码量这么多,可以想象其有多复杂。 12 | 2. **阅读源码时,我们有时候无法猜透源码作者当时编码时的想法**。因为在刚开始阅读源码的过程中,我们肯定会遇到很不懂的代码,不知道作者为何这么写,为何在这个位置写代码,这些都是很正常的,因为当初作者为啥这么写,可能是针对一些比较特殊的业务场景,或者为了某方面的性能等等,我们根本无法猜透。打个不太恰当的比喻,阅读源码猜测作者的心思就像当初遇到一个自己喜欢的姑娘,猜测她的心思一样,比如猜测她喜欢什么,她的兴趣爱好是什么。其实刚开始阅读源码也一样,有些地方我们一开始是无法猜透作者的心思的。 13 | 3. **有些开源框架可能集操作系统知识,数据结构,算法和设计模式于一身**。是的,优秀的框架必定是集成了很多设计模式于一身,目前为止笔者还没见过哪种流行的又没有应用设计模式的框架哈。比如很多框架运用了单例模式,工厂模式,责任链模式,装饰器模式和模板方法模式等,因为使用设计模式能让框架易于扩展。同时,不乏一些框架应用了一些操作系统层面的知识,这一块比较底层,相信很多学java的小伙伴没接触过。此外,开源框架某些地方会用到数据结构和算法,举个栗子,比如Dubbo默认有四种负载均衡策略,而每种策略又对应一种算法,其中又数RoundRobinLoadBalance负载均衡策略最复杂,一开始实现RoundRobinLoadBalance负载均衡的方式并不太完美或者说有bug,Dubbo也是重写过RoundRobinLoadBalance几次,**最终借鉴了Nginx的RoundRobinLoadBalance负载均衡算法**。 14 | >上篇文章《[跟大家聊聊我们为什么要学习源码?学习源码对我们有用吗?](https://github.com/yuanmabiji/Java-SourceCode-Blogs/blob/master/%E8%B7%9F%E5%A4%A7%E5%AE%B6%E8%81%8A%E8%81%8A%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%AD%A6%E4%B9%A0%E6%BA%90%E7%A0%81%EF%BC%9F%E5%AD%A6%E4%B9%A0%E6%BA%90%E7%A0%81%E5%AF%B9%E6%88%91%E4%BB%AC%E6%9C%89%E7%94%A8%E5%90%97%EF%BC%9F.md)》也说过优秀框架之间的思想都是互相借鉴的,这就是我们要学习源码的原因之一。 15 | 16 | 这里好像扯的有点远了,总之这里要说明的是阅读优秀框架是有一定难度的。 17 | 4. **有些开源框架注释太少也增加了阅读源码的难度**。说到开源项目注释,如果我们阅读老外写的的框架源码可能还好,一般都会有大量注释,比如Spring框架,可以说几乎每个方法都有注释,这个就给我们阅读源码起了很大的帮助。不过唯一不好的可能就是英文注释,阅读对英语有一定的要求。其实英文注释还好,遇到不懂的,百度翻一下就好了。其实比较头疼的就是一些国内优秀的开源框架,其注释可以说是很少的,这无疑大大增加了阅读的难度,甚至有些框架的文档也不齐全,那就更加GG了。 18 | 19 | ## 3 该如何入手去分析开源框架源码? 20 | 前面跟小伙伴们聊了阅读源码的难度,千万不要被吓慌了。不可否认,**刚开始**阅读某个开源项目的源码是有一定的难度。注意,前面的用词是**刚开始**,**刚开始**哈。也就是说如果我们坚持阅读源码的话,养成**阅读源码是陶冶情操**的习惯的话(网上看到的这句话,这里引用装装逼,嘿嘿),长期坚持下来再去阅读其他项目的源码,游刃有余不敢说,但肯定可以很快入手。 21 | 22 | 23 | **那么,我们该如何入手去分析开源框架源码呢?** 24 | 25 | 首先,结合前面所说的阅读源码之所以难的原因,我们就要有针对性的去克服解决。比如有空多学学设计模式,算法和英语。这些软实力确实对阅读源码有很大帮助。 26 | 27 | 其次,阅读源码的前提是什么?当然,阅读源码是要建立在会使用的基础上,就像若还不会走路就学骑单车一样,若连用都不会就去钻研源码可能会适得其反。 28 | 29 | 最后,我们阅读源码要注意一些技巧,现在根据自身经历总结一下相关思路和技巧,如下: 30 | 31 | 1. **开始阅读源码时,先对框架的模块及其关系有一个整体的认识**。我们要对框架项目的模块和目录要有一个全盘的了解,要知道每个模块是干嘛的,然后要了解模块与模块之间的关系。 32 | >举个栗子,比如Dubbo的模块分包核心的主要有以下八个,如下图,我们要知道最基础的的模块应该是dubbo-common公共逻辑模块,这个模块作为最基础的模块,主要是提供了通用模型和工具类;然后dubbo-remoting是远程通讯模块,依赖于dubbo-common模块,相当于Dubbo协议的实现;而dubbo-rpc则是远程调用模块,依赖于dubbo-remoting模块,抽象各种协议,以及动态代理;dubbo-cluster是集群模块,依赖于dubbo-rpc模块,将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错,路由等。 33 | ![](https://user-gold-cdn.xitu.io/2020/2/19/1705c89d8d5c405c?w=85&h=55&f=png&s=34 ) 34 | 35 | 2. **分析源码先从父类或父接口开始分析**。因为父类或者父接口往往代表了一类功能,这些基类或基类接口往往抽象了各个具体子类共有的属性和行为,一些比较基础的方法都在父类中实现,然后留个模板方法给子类去实现即可(模板方法的应用)。 36 | > 举个栗子,这里还是拿Dubbo的负载均衡来说吧,如下图,LoadBalance是各种负载均衡策略的超级接口,定义了 select 方法用来实现选择哪台机器;然后AbstractLoadBalance是一个抽象类,实现了LoadBalance接口,在覆盖了 select 方法后,其又增加了 calculateWarmupWeight 和 getWeight 权重相关的两个方法,因为这些方法都跟具体的负载均衡策略类有关,故在父类实现了。值得注意的是AbstractLoadBalance抽象类的 select 方法中里留了个给子类覆盖的 doSelect 方法,具体的负载均衡策略将在doSelect中实现。 37 | ![](https://user-gold-cdn.xitu.io/2020/2/19/1705cafb6c2fdfce?w=1071&h=261&f=png&s=19127) 38 | 39 | 3. **阅读源码前首先要找到启动类**。阅读分析源码时要先从启动类开始,因此找到框架启动的入口很重要。 40 | 41 | 4. **阅读源码时要分清主干和枝节代码**。找到启动入口后,然后就可以顺着启动入口一步一步调试来阅读源码了。不过在初次调试源码时值得注意的是一定要分清主次代码,即要先阅读主干代码,其他枝枝节节的代码没明白的可以放一边。切忌一开始就深入细节然后出不来了,这样就会造成只见冰山一角而看不到全貌的感觉。 42 | 5. **阅读源码前要分清主次模块**。即阅读分析源码不能漫无目的,全盘通读,我们要从我们平时有用到的模块开始分析。每个人的时间都很宝贵,我们要把时间花在刀刃上。比如SpringBoot增加的新特性中有**自动配置**,而自动配置特性又非常重要,因此可以挑选自动配置来进行源码分析。 43 | 6. **要充分利用源码项目的测试类**。之前也说过,一个框架之所以能流行,必定是经过大量测试的。因此如果我们像具体了解某个类和某个方法,我们可以充分利用这些测试类来辅助我们源码分析。 44 | 7. **要学会一些调试技巧**。这一点也很重要,比如在调试过程中如何查看调用关系等等,这里不多说,[如何高效学习和阅读源码](https://blog.csdn.net/w605283073/article/details/89290798)这篇文章中分享了大量调试的干活,小伙伴们可以瞅瞅。此外,还要学会有技巧的搜索源码,说到这里,下面举个栗子。 45 | > 举个Spring事件监听的栗子。比如我们现在要知道哪个监听器监听了ContextRefreshedEvent事件,此时我们可以通过idea全局搜索"**(ContextRefreshedEvent**"关键字,得到以下截图:从下图可以看到spring-webmvc模块的FrameworkServlet,spring-context模块的ScheduledAnnotationBeanPostProcessor,和spring-jms模块的JmsListenerEndpointRegistry等类订阅了ContextRefreshedEvent事件,那么在容器刷新的时候这几个类将会监听到ContextRefreshedEvent事件,执行一些初始化逻辑。 46 | ![](https://user-gold-cdn.xitu.io/2020/2/19/1705cca6903bc15f?w=1911&h=257&f=png&s=112268) 47 | 8. **肯定还有大量的阅读源码技巧,希望本文能起到抛砖引玉的作用,期待小伙伴们可以留言分享下,让笔者也收益一下。** 48 | 49 | ## 4 学源码,谈实践,论坚持 50 | 最后,我们学习源码不是为了学习而学习,最理想的效果我们要学以致用。比如把从源码中学习到的设计模式,接口设计方法,面向对象原则和相关算法等等都可以应用到我们手头的项目中,这才是我们学习源码的最终目的,也是源码学习的最理想的效果。可能这里有些小伙伴会说,我平时参与的项目都是业务类的项目,而不是开发基础框架,开发中间件,CRUD比较多,可能学习基础框架的源码对我们用处很少。其实不是的,只要你有参与项目,学习源码我们学习的是思想,我们就可以把源码框架设计中的思想应用到我们的项目中。 51 | 52 | 最后的最后,我们来谈谈**坚持**,这是最难能可贵的。很多大道理我们都懂,比如要坚持运动,坚持学习,坚持...,可是就是没能坚持下来,**包括我自己**,嘿嘿。**坚持**这东西太南了,不过还是应该给自己立个flag吧,把自己有用到的框架比如SpringBoot,Spring,Mybatis,Dubbo,SpringCloud等框架源码都阅读分析一遍,加油,小伙伴们共勉! 53 | 54 | 55 | >欢迎小伙伴们在评论区补充源码阅读技巧哦,让笔者GET多点技能,嘿嘿。 56 | 57 | **原创不易,帮忙点个赞呗。** 58 | 59 | 参考: 60 | 61 | http://dubbo.apache.org/zh-cn/docs/dev/design.html 62 | 63 | 64 | ---------------------------------------------------------------------------------------- 65 | **原创不易,帮忙Star一下呗**! 66 | 67 | -------------------------------------------------------------------------------- /跟大家聊聊我们为什么要学习源码?学习源码对我们有用吗?.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 1 前言 4 | 由于现在微服务很流行,越来越多企业采用了SpringCloud微服务架构,而SpringBoot则是快速构建微服务项目的利器。于是**源码笔记**以此为切入点,将SpringBoot作为我们源码分析的第一个开源项目,之后还会对更多开源项目进行源码分析。要进行源码分析,笔者结合自身经历来跟大家聊聊**我们为什么要学习源码**这个话题,大家一起探讨学习。 5 | 6 | 我们程序员在开发代码时每天都在使用别人写好的框架,无论你是在使用Spring生态的Spring核心,SpringMVC,SpringBoot和SpringCloud等框架,还是在使用阿里系的Dubbo,RocketMQ,Seata,Druid等中间件框架,亦或你是搞大数据的,在使用Apache组织的Zookeeper,Hadoop,Hive,Spark等大数据组件框架,这些开源框架都给我们的项目编码带来了极大的方便,促进了我们的开发效率。是的,这些都是开源大神们帮我们造好的轮子,我们直接使用即可,而且用起来很少出bug,因为这些框架一般都是经过考验的才能流行起来。 7 | 8 | 可能大部分人会认为,这些轮子都已经造好了,我们直接用即可。是的,这些开源框架对我们使用来说是透明的,它们就像一个个黑盒子。至于这些黑盒里面装的是什么东西,里面是怎样构造的,如果没有去探究这些黑盒的话,我们无从得知。但是,我们做开发的天天跟这些黑盒打交道,难道你就没有一种强烈的欲望想知道这些黑盒里面装的是什么东西,黑盒里面是怎样构造的么?难道你就不想知道我们天天使用的黑盒子里面的原理么? 9 | 10 | **那么今天就跟小伙伴们聊聊“我们为什么要学习开源框架源码?学习开源框架源码对我们有用吗?”这个话题。** 11 | ## 2 我们为什么要学习源码?花那么多时间去学习源码值得么? 12 | 可能有些同学会问:我们为什么要去学习源码?花那么多时间去学习源码值得么? 13 | 14 | 对于这个问题,应该很多小伙伴在没读源码前深有感触。当时应该就是这种观念,反正开源框架一些外面的大牛帮我们封装好了,自己开箱即用即可,管它里面黑盒机制是啥。应该很多小伙伴也是这种观念,导致自己在开发项目时若遇到bug时一筹莫展,百度了很多解决方案都无效,故而浪费了很多时间。于是才会决心钻研一些常用框架的源码。可见,**没必要学习源码的观念**是可能是最错误的观念吧,个人观点(仅供参考)。 15 | 16 | **我们学习源码无非有以下几个原因:** 17 | 18 | **1)开发项目需求需要** 19 | 20 | 一方面,通常我们在开发项目的时候,遇到Bug是再正常不过的事情。比如某个工作项目采用了Spring生态系列的框架比如SpringBoot,SpringCloud等,当出现问题时自己要会解决,如果我们不懂框架里面的黑盒机制,当出现问题我们肯定会一头雾水,不知如何着手解决开源框架出现的问题,此时我们肯定会去百度,但百度的文章质量参差不齐,最坏的结果可能就是我们根据百度的解决方案,一个一个去试了,但仍然没有解决问题。这就是对框架黑盒机制不熟悉的原因导致的。 21 | 22 | 另一方面,我们在开发项目的时候,有时候开源框架不能完全符合我们的业务需求,此时我们需要对开源框架进行扩展甚至是改造,比如我们正在使用dubbo框架,若dubbo自带负载均衡策略不能满足我们的业务需求,此时我们是不是得要对dubbo的负载均衡策略进行替换或扩展。幸好,dubbo提供了SPI接口给我们即插即用,此时我们不用研究dubbo里面的黑盒也可以做到替换现有的负载均衡策略。那假如有一天,我们要对某个开源项目进行改造呢?此时是不是也需要我们知道开源框架的黑盒机制,若不懂框架黑盒原理,此时我们是无法下手的。 23 | 24 | 由于项目开发需要的以上原因,所以我们平时有空时就要多学习源码,多探究里面的黑盒机制,**磨刀不误砍柴工**。 25 | 26 | 这个就是典型的项目开发需求驱动我们不得不去研究开源框架的源码机制的原因吧。 27 | 28 | **2)对技术饱含热爱,不断深入学习黑盒机制** 29 | 30 | 是的,除了项目需求开发需要去研究源码外,应该很多小伙伴都是出于对技术的追求去研究开源框架源码。他们为了不断提高自己的编码能力,去不断学习外面大牛们的优秀作品。是的,大家都知道,**闭门造车可以说是造不出名车的**,此时,我们必须去学习大牛们优秀的开源作品,学习他们是怎么面向对象编程的,学习他们是如何熟练运用设计模式的,学习他们是怎样设计接口的等等,真的有太多需要我们学习了。如果我们走的是技术路线,对技术还有更高的追求,学习源码可以说是我们绕不过去的路。因此,行动起来吧,源码搞起来,我们只有不断学习源码,以后再阅读其他项目的源码时才会游刃有余。 31 | 32 | **3)有些人学习源码,可能是为了面试** 33 | 34 | 当然,有些人学习源码,可能是为了面试。因为现在很多面试若面试官稍微问难一点的问题都会涉及源码,往往很多人就是对源码不熟悉而挂掉。因为一部分小伙伴往往就是为了面试才去学习源码,但是往往这种为了面试才去学习源码的方式效果没有前面的项目驱动方式和技术追求方式的效果那么好(个人观点,不喜勿喷)。因为,有些同学为了面试,通常都是短期突击源码,为了某个面试问题而背诵一些答案,因为死记硬背的居多,所以这种方式往往深入不了源码的精髓。当然,不管出于什么目的学习源码都理应鼓励,因为毕竟去学习源码了肯定就会有提高,就踏出了与众不同的一步。 35 | 36 | ## 3 学习源码,我们能得到什么? 37 | 38 | 是的,既然我们花费了那么多时间去学习源码,去研究源码?我们的收益有多少,我们究竟能得到多少回到回报呢?可以先肯定的回答,当然有很大的回报。前面也说过,学习开源框架源码能帮助我们解决项目遇到的bug,扩展我们的项目需求;通过学习源码,学习开源大牛们是如何运用设计模式的,然后运用到我们开发的项目中,使我们的项目的模块更易于扩展;通过学习源码,我们能编码更高效。为什么呢?如果我们对某个开源项目源码很熟悉,那么我们就可以对这个项目的源码信手拈来应用到自己项目中,不是么?总之学习源码好处多多,这也是成为大牛们的必经之路吧,只有学习和借鉴别人优秀的作品,自己才能造出更优秀的作品。说到这点,不得不说下RocketMQ,RocketMQ是阿里的一款优秀的开源中间件,RocketMQ之所以性能高吞吐,抗得住阿里双十一的考验,其正是很多方面借鉴了Kafka的设计;此外,记得Dubbo的一个缓冲类也是借鉴了Netty的ByteBuf类。 39 | 40 | 是吧,要造出一些优秀的作品,必须去学习借鉴别人的优秀作品。 41 | 42 | ## 4 要想不被淘汰,必须学习源码 43 | 44 | 最后要说的是,要想不被淘汰,我们必须学习源码。 45 | 46 | 现在程序员越来越多,可以说竞争尤其激烈。如果我们想不被淘汰,则我们时刻要保持竞争力,那么必然要时刻学习,终生学习,**生而有涯而学无涯**。因为CRUD大家都会,要提高自己的技术能力,此时学习开源项目的源码就尤为关键了。**如果我们不想成为CRUD工程师,还有更高的技术追求,那么我们就要有更高的要求**。当然,学习开源项目源码的目的是借鉴大牛们的优秀作品,最终要学以致用。另外,个人观点:**学习源码是提高自己编码能力的最好方式**。 47 | >**如果我们走的是技术路线而非管理路线,若还有更高的技术追求,那么学习源码是我们绕不过去的砍。 48 | 真的,学习源码实在是太重要了。** 49 | 50 | 只要我们还有更高的技术追求,或许你想成为技术大牛,但是学习源码是成为大牛的必经之路。不是每个人都能成为大牛,但不学源码就肯定成为不了大牛,因为学习源码是提高自己的最好方式,不论何时,学习源码都不会晚,共勉! 51 | >**Are u ready?** 52 | 53 | ---------------------------------------------------------------------------------------- 54 | **原创不易,帮忙Star一下呗**! 55 | --------------------------------------------------------------------------------