From 34fb909b411fb832eaaf8bb266f3f6e569e20c1e Mon Sep 17 00:00:00 2001 From: Marius Bogoevici Date: Mon, 7 Nov 2016 13:43:32 -0500 Subject: [PATCH 1/2] Support multiple root contexts in `Restarter` Update devtools restarter to support multiple application contexts. Fixes gh-7335 Closes gh-7336 --- .../restart/RestartApplicationListener.java | 2 +- .../boot/devtools/restart/Restarter.java | 20 ++++++++++++++----- .../RestartApplicationListenerTests.java | 10 ++++++---- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java index 8cd7d81229a..2966ac1456e 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java @@ -52,7 +52,7 @@ public class RestartApplicationListener || event instanceof ApplicationFailedEvent) { Restarter.getInstance().finish(); if (event instanceof ApplicationFailedEvent) { - Restarter.getInstance().prepare(null); + Restarter.getInstance().remove(((ApplicationFailedEvent) event).getApplicationContext()); } } } diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java index 1c94bc55061..35b4b937c4c 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.BlockingDeque; import java.util.concurrent.Callable; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadFactory; import java.util.concurrent.locks.Lock; @@ -51,6 +52,7 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.ReflectionUtils; /** @@ -116,7 +118,7 @@ public class Restarter { private boolean finished = false; - private volatile ConfigurableApplicationContext rootContext; + private final List rootContexts = new CopyOnWriteArrayList(); /** * Internal constructor to create a new {@link Restarter} instance. @@ -314,9 +316,11 @@ public class Restarter { this.logger.debug("Stopping application"); this.stopLock.lock(); try { - if (this.rootContext != null) { - this.rootContext.close(); - this.rootContext = null; + if (!CollectionUtils.isEmpty(this.rootContexts)) { + for (ConfigurableApplicationContext rootContext : this.rootContexts) { + rootContext.close(); + } + this.rootContexts.clear(); } cleanupCaches(); if (this.forceReferenceCleanup) { @@ -418,7 +422,13 @@ public class Restarter { if (applicationContext != null && applicationContext.getParent() != null) { return; } - this.rootContext = applicationContext; + this.rootContexts.add(applicationContext); + } + + void remove(ConfigurableApplicationContext applicationContext) { + if (applicationContext != null) { + this.rootContexts.remove(applicationContext); + } } private LeakSafeThread getLeakSafeThread() { diff --git a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java index 31acad0f0e2..34848adbc28 100644 --- a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java +++ b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java @@ -16,6 +16,8 @@ package org.springframework.boot.devtools.restart; +import java.util.List; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -64,8 +66,8 @@ public class RestartApplicationListenerTests { assertThat(ReflectionTestUtils.getField(Restarter.getInstance(), "args")) .isEqualTo(ARGS); assertThat(Restarter.getInstance().isFinished()).isTrue(); - assertThat(ReflectionTestUtils.getField(Restarter.getInstance(), "rootContext")) - .isNotNull(); + assertThat((List) ReflectionTestUtils.getField(Restarter.getInstance(), + "rootContexts")).isNotEmpty(); } @Test @@ -74,8 +76,8 @@ public class RestartApplicationListenerTests { assertThat(ReflectionTestUtils.getField(Restarter.getInstance(), "args")) .isEqualTo(ARGS); assertThat(Restarter.getInstance().isFinished()).isTrue(); - assertThat(ReflectionTestUtils.getField(Restarter.getInstance(), "rootContext")) - .isNull(); + assertThat((List) ReflectionTestUtils.getField(Restarter.getInstance(), + "rootContexts")).isEmpty(); } @Test From dfd327d68fca364b3267db63da8a504a4c0e65a6 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 16 Nov 2016 15:17:23 -0800 Subject: [PATCH 2/2] Polish multiple root contexts in `Restarter` See gh-7335 See gh-7336 --- .../restart/RestartApplicationListener.java | 17 ++++++++++++----- .../boot/devtools/restart/Restarter.java | 9 +++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java index 2966ac1456e..817d1733598 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java @@ -45,15 +45,14 @@ public class RestartApplicationListener onApplicationStartingEvent((ApplicationStartingEvent) event); } if (event instanceof ApplicationPreparedEvent) { - Restarter.getInstance() - .prepare(((ApplicationPreparedEvent) event).getApplicationContext()); + onApplicationPreparedEvent((ApplicationPreparedEvent) event); } if (event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent) { Restarter.getInstance().finish(); - if (event instanceof ApplicationFailedEvent) { - Restarter.getInstance().remove(((ApplicationFailedEvent) event).getApplicationContext()); - } + } + if (event instanceof ApplicationFailedEvent) { + onApplicationFailedEvent((ApplicationFailedEvent) event); } } @@ -72,6 +71,14 @@ public class RestartApplicationListener } } + private void onApplicationPreparedEvent(ApplicationPreparedEvent event) { + Restarter.getInstance().prepare(event.getApplicationContext()); + } + + private void onApplicationFailedEvent(ApplicationFailedEvent event) { + Restarter.getInstance().remove(event.getApplicationContext()); + } + @Override public int getOrder() { return this.order; diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java index 35b4b937c4c..681026c0325 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java @@ -52,7 +52,6 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; import org.springframework.util.ReflectionUtils; /** @@ -316,11 +315,9 @@ public class Restarter { this.logger.debug("Stopping application"); this.stopLock.lock(); try { - if (!CollectionUtils.isEmpty(this.rootContexts)) { - for (ConfigurableApplicationContext rootContext : this.rootContexts) { - rootContext.close(); - } - this.rootContexts.clear(); + for (ConfigurableApplicationContext context : this.rootContexts) { + context.close(); + this.rootContexts.remove(context); } cleanupCaches(); if (this.forceReferenceCleanup) {