diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/DefaultRestartInitializer.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/DefaultRestartInitializer.java index 62d139c94306..2f502534dd84 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/DefaultRestartInitializer.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/DefaultRestartInitializer.java @@ -43,14 +43,38 @@ public URL[] getInitialUrls(Thread thread) { } /** - * Returns if the thread is for a main invocation. By default checks the name of the - * thread and the context classloader. + * Returns if the thread is for a main invocation. By default {@link #isMain(Thread) + * checks the name of the thread} and {@link #isDevelopmentClassLoader(ClassLoader) + * the context classloader}. * @param thread the thread to check * @return {@code true} if the thread is a main invocation + * @see #isMainThread + * @see #isDevelopmentClassLoader(ClassLoader) */ protected boolean isMain(Thread thread) { - return thread.getName().equals("main") - && thread.getContextClassLoader().getClass().getName().contains("AppClassLoader"); + return isMainThread(thread) && isDevelopmentClassLoader(thread.getContextClassLoader()); + } + + /** + * Returns whether the given {@code thread} is considered to be the main thread. + * @param thread the thread to check + * @return {@code true} if it's the main thread, otherwise {@code false} + * @since 2.4.0 + */ + protected boolean isMainThread(Thread thread) { + return thread.getName().equals("main"); + } + + /** + * Returns whether the given {@code classLoader} is one that is typically used during + * development. + * @param classLoader the ClassLoader to check + * @return {@code true} if it's a ClassLoader typically used during development, + * otherwise {@code false} + * @since 2.4.0 + */ + protected boolean isDevelopmentClassLoader(ClassLoader classLoader) { + return classLoader.getClass().getName().contains("AppClassLoader"); } /** diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java index 87be6ec2d155..fe2f84107e61 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/RestartApplicationListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -64,7 +64,24 @@ private void onApplicationStartingEvent(ApplicationStartingEvent event) { // It's too early to use the Spring environment but we should still allow // users to disable restart using a System property. String enabled = System.getProperty(ENABLED_PROPERTY); - if (enabled == null || Boolean.parseBoolean(enabled)) { + RestartInitializer restartInitializer = null; + if (enabled == null) { + restartInitializer = new DefaultRestartInitializer(); + } + else if (Boolean.parseBoolean(enabled)) { + restartInitializer = new DefaultRestartInitializer() { + + @Override + protected boolean isDevelopmentClassLoader(ClassLoader classLoader) { + return true; + } + + }; + logger.info(LogMessage.format( + "Restart enabled irrespective of application packaging due to System property '%s' being set to true", + ENABLED_PROPERTY)); + } + if (restartInitializer != null) { String[] args = event.getArgs(); DefaultRestartInitializer initializer = new DefaultRestartInitializer(); boolean restartOnInitialize = !AgentReloader.isActive(); diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java index 923781c9a69d..a6c2f65053ee 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestartApplicationListenerTests.java @@ -88,6 +88,14 @@ void disableWithSystemProperty(CapturedOutput output) { assertThat(output).contains("Restart disabled due to System property"); } + @Test + void enableWithSystemProperty(CapturedOutput output) { + System.setProperty(ENABLED_PROPERTY, "true"); + testInitialize(false); + assertThat(Restarter.getInstance()).hasFieldOrPropertyWithValue("enabled", true); + assertThat(output).contains("Restart enabled irrespective of application packaging due to System property"); + } + private void testInitialize(boolean failed) { Restarter.clearInstance(); RestartApplicationListener listener = new RestartApplicationListener(); diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using-spring-boot.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using-spring-boot.adoc index d1b49e1fe1b1..f87e9db1b039 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using-spring-boot.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using-spring-boot.adoc @@ -542,7 +542,10 @@ To include devtools support, add the module dependency to your build, as shown i NOTE: Developer tools are automatically disabled when running a fully packaged application. If your application is launched from `java -jar` or if it is started from a special classloader, then it is considered a "`production application`". -If that does not apply to you (i.e. if you run your application from a container), consider excluding devtools or set the `-Dspring.devtools.restart.enabled=false` system property. +You can control this behavior by using the `spring.devtools.restart.enabled` system property. +To enable devtools, irrespective of the classloader used to launch your application, set the `-Dspring.devtools.restart.enabled=true` system property. +This must not be done in a production environment where running devtools is a security risk. +To disable devtools, exclude the dependency or set the `-Dspring.devtools.restart.enabled=false` system property. TIP: Flagging the dependency as optional in Maven or using the `developmentOnly` configuration in Gradle (as shown above) prevents devtools from being transitively applied to other modules that use your project.