diff --git a/spring-boot-devtools/pom.xml b/spring-boot-devtools/pom.xml
index b5641034ab..e5dc9709ec 100644
--- a/spring-boot-devtools/pom.xml
+++ b/spring-boot-devtools/pom.xml
@@ -30,6 +30,16 @@
spring-boot-autoconfigure
+
+ javax.servlet
+ javax.servlet-api
+ true
+
+
+ org.apache.logging.log4j
+ log4j-core
+ true
+
org.springframework
spring-web
@@ -45,11 +55,6 @@
spring-security-web
true
-
- javax.servlet
- javax.servlet-api
- true
-
org.springframework.boot
diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/log4j2/Log4J2RestartListener.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/log4j2/Log4J2RestartListener.java
new file mode 100644
index 0000000000..bb62e51f5b
--- /dev/null
+++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/log4j2/Log4J2RestartListener.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.devtools.log4j2;
+
+import java.lang.reflect.Field;
+import java.util.Collection;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.util.Cancellable;
+import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
+import org.apache.logging.log4j.spi.LoggerContextFactory;
+
+import org.springframework.boot.devtools.restart.RestartListener;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * {@link RestartListener} that prepares Log4J2 for an application restart.
+ *
+ * @author Andy Wilkinson
+ */
+public class Log4J2RestartListener implements RestartListener {
+
+ @Override
+ public void beforeRestart() {
+ if (ClassUtils.isPresent("org.apache.logging.log4j.LogManager",
+ getClass().getClassLoader())) {
+ prepareLog4J2ForRestart();
+ }
+ }
+
+ private void prepareLog4J2ForRestart() {
+ LoggerContextFactory factory = LogManager.getFactory();
+ Field field = ReflectionUtils.findField(factory.getClass(),
+ "shutdownCallbackRegistry");
+ ReflectionUtils.makeAccessible(field);
+ ShutdownCallbackRegistry shutdownCallbackRegistry = (ShutdownCallbackRegistry) ReflectionUtils
+ .getField(field, factory);
+ Field hooksField = ReflectionUtils.findField(shutdownCallbackRegistry.getClass(),
+ "hooks");
+ ReflectionUtils.makeAccessible(hooksField);
+ @SuppressWarnings("unchecked")
+ Collection state = (Collection) ReflectionUtils
+ .getField(hooksField, shutdownCallbackRegistry);
+ state.clear();
+ }
+
+}
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 0b8e718bea..db31b1d846 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
@@ -16,12 +16,15 @@
package org.springframework.boot.devtools.restart;
+import java.util.List;
+
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
+import org.springframework.core.io.support.SpringFactoriesLoader;
/**
* {@link ApplicationListener} to initialize the {@link Restarter}.
@@ -56,7 +59,11 @@ public class RestartApplicationListener
String[] args = event.getArgs();
DefaultRestartInitializer initializer = new DefaultRestartInitializer();
boolean restartOnInitialize = !AgentReloader.isActive();
- Restarter.initialize(args, false, initializer, restartOnInitialize);
+ List restartListeners = SpringFactoriesLoader
+ .loadFactories(RestartListener.class, getClass().getClassLoader());
+ Restarter.initialize(args, false, initializer, restartOnInitialize,
+ restartListeners
+ .toArray(new RestartListener[restartListeners.size()]));
}
else {
Restarter.disable();
diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartListener.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartListener.java
new file mode 100644
index 0000000000..fe30e6be2b
--- /dev/null
+++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartListener.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.devtools.restart;
+
+/**
+ * Listener that is notified of application restarts.
+ *
+ * @author Andy Wilkinson
+ */
+public interface RestartListener {
+
+ /**
+ * Called before an application restart.
+ */
+ void beforeRestart();
+
+}
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 aec0340451..c08175c796 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
@@ -108,6 +108,8 @@ public class Restarter {
private final BlockingDeque leakSafeThreads = new LinkedBlockingDeque();
+ private final RestartListener[] listeners;
+
private boolean finished = false;
private Lock stopLock = new ReentrantLock();
@@ -118,10 +120,11 @@ public class Restarter {
* @param args the application arguments
* @param forceReferenceCleanup if soft/weak reference cleanup should be forced
* @param initializer the restart initializer
+ * @param listeners listeners to be notified of restarts
* @see #initialize(String[])
*/
protected Restarter(Thread thread, String[] args, boolean forceReferenceCleanup,
- RestartInitializer initializer) {
+ RestartInitializer initializer, RestartListener... listeners) {
Assert.notNull(thread, "Thread must not be null");
Assert.notNull(args, "Args must not be null");
Assert.notNull(initializer, "Initializer must not be null");
@@ -134,6 +137,7 @@ public class Restarter {
this.args = args;
this.exceptionHandler = thread.getUncaughtExceptionHandler();
this.leakSafeThreads.add(new LeakSafeThread());
+ this.listeners = listeners;
}
private String getMainClassName(Thread thread) {
@@ -246,6 +250,7 @@ public class Restarter {
@Override
public Void call() throws Exception {
+ Restarter.this.beforeRestart();
Restarter.this.stop();
Restarter.this.start(failureHandler);
return null;
@@ -324,6 +329,12 @@ public class Restarter {
System.runFinalization();
}
+ private void beforeRestart() {
+ for (RestartListener listener : this.listeners) {
+ listener.beforeRestart();
+ }
+ }
+
@SuppressWarnings("rawtypes")
private void triggerShutdownHooks() throws Exception {
Class> hooksClass = Class.forName("java.lang.ApplicationShutdownHooks");
@@ -512,13 +523,15 @@ public class Restarter {
* @param initializer the restart initializer
* @param restartOnInitialize if the restarter should be restarted immediately when
* the {@link RestartInitializer} returns non {@code null} results
+ * @param listeners listeners to be notified of restarts
*/
public static void initialize(String[] args, boolean forceReferenceCleanup,
- RestartInitializer initializer, boolean restartOnInitialize) {
+ RestartInitializer initializer, boolean restartOnInitialize,
+ RestartListener... listeners) {
if (instance == null) {
synchronized (Restarter.class) {
instance = new Restarter(Thread.currentThread(), args,
- forceReferenceCleanup, initializer);
+ forceReferenceCleanup, initializer, listeners);
}
instance.initialize(restartOnInitialize);
}
diff --git a/spring-boot-devtools/src/main/resources/META-INF/spring.factories b/spring-boot-devtools/src/main/resources/META-INF/spring.factories
index c5923c0b13..fc66a3dcdb 100644
--- a/spring-boot-devtools/src/main/resources/META-INF/spring.factories
+++ b/spring-boot-devtools/src/main/resources/META-INF/spring.factories
@@ -15,3 +15,7 @@ org.springframework.boot.devtools.autoconfigure.RemoteDevToolsAutoConfiguration
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.devtools.env.DevToolsHomePropertiesPostProcessor,\
org.springframework.boot.devtools.env.DevToolsPropertyDefaultsPostProcessor
+
+# Restart Listeners
+org.springframework.boot.devtools.restart.RestartListener=\
+org.springframework.boot.devtools.log4j2.Log4J2RestartListener
diff --git a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestarterTests.java b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestarterTests.java
index aa033a40f6..f05342ec26 100644
--- a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestarterTests.java
+++ b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestarterTests.java
@@ -96,6 +96,7 @@ public class RestarterTests {
String output = this.out.toString();
assertThat(StringUtils.countOccurrencesOf(output, "Tick 0"), greaterThan(1));
assertThat(StringUtils.countOccurrencesOf(output, "Tick 1"), greaterThan(1));
+ assertThat(TestRestartListener.restarts, greaterThan(1));
}
@Test
@@ -214,7 +215,8 @@ public class RestarterTests {
}
public static void main(String... args) {
- Restarter.initialize(args, false, new MockRestartInitializer());
+ Restarter.initialize(args, false, new MockRestartInitializer(), true,
+ new TestRestartListener());
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
SampleApplication.class);
context.registerShutdownHook();
@@ -276,4 +278,15 @@ public class RestarterTests {
}
+ private static class TestRestartListener implements RestartListener {
+
+ private static int restarts;
+
+ @Override
+ public void beforeRestart() {
+ restarts++;
+ }
+
+ }
+
}