Fix detection of reactive web environments in DevTools

DevToolsPropertyDefaultsPostProcessor looked for
ConfigurableReactiveWebEnvironment
in org.springframework.boot.web.reactive.context by name, but the class
moved to org.springframework.boot.web.context.reactive in 4.0. As a
result, reactive web applications were never identified as web
applications and the hint about setting logging.level.web to DEBUG was
not logged for them.

ConfigurableReactiveWebEnvironment is part of spring-boot, so it is now
referenced directly rather than by name. The servlet environment check
is unchanged as spring-web is an optional dependency.

See gh-51708

Signed-off-by: ohchanKyu <okc0202@naver.com>
This commit is contained in:
ohchanKyu
2026-09-14 11:09:40 +02:00
committed by Stéphane Nicoll
parent 96f5c74b03
commit f5b563934a
2 changed files with 94 additions and 9 deletions
@@ -28,6 +28,7 @@ import org.springframework.boot.devtools.logger.DevToolsLogFactory;
import org.springframework.boot.devtools.restart.Restarter;
import org.springframework.boot.devtools.settings.DevToolsSettings;
import org.springframework.boot.devtools.system.DevToolsEnablementDeducer;
import org.springframework.boot.web.context.reactive.ConfigurableReactiveWebEnvironment;
import org.springframework.core.NativeDetector;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@@ -55,9 +56,7 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro
private static final String WEB_LOGGING = "logging.level.web";
private static final String[] WEB_ENVIRONMENT_CLASSES = {
"org.springframework.web.context.ConfigurableWebEnvironment",
"org.springframework.boot.web.reactive.context.ConfigurableReactiveWebEnvironment" };
private static final String SERVLET_WEB_ENVIRONMENT_CLASS = "org.springframework.web.context.ConfigurableWebEnvironment";
private static final Map<String, Object> PROPERTIES;
@@ -112,13 +111,12 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro
}
private boolean isWebApplication(Environment environment) {
for (String candidate : WEB_ENVIRONMENT_CLASSES) {
Class<?> environmentClass = resolveClassName(candidate, environment.getClass().getClassLoader());
if (environmentClass != null && environmentClass.isInstance(environment)) {
return true;
}
if (environment instanceof ConfigurableReactiveWebEnvironment) {
return true;
}
return false;
Class<?> servletWebEnvironmentClass = resolveClassName(SERVLET_WEB_ENVIRONMENT_CLASS,
environment.getClass().getClassLoader());
return servletWebEnvironmentClass != null && servletWebEnvironmentClass.isInstance(environment);
}
private @Nullable Class<?> resolveClassName(String candidate, ClassLoader classLoader) {
@@ -0,0 +1,87 @@
/*
* Copyright 2012-present 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
*
* https://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.env;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.logging.DeferredLog;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.context.reactive.StandardReactiveWebEnvironment;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.context.support.StandardServletEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DevToolsPropertyDefaultsPostProcessor}.
*
* @author ohchanKyu
*/
@ExtendWith(OutputCaptureExtension.class)
class DevToolsPropertyDefaultsPostProcessorTests {
private static final String WEB_LOGGING_HINT = "For additional web related logging consider setting the "
+ "'logging.level.web' property to 'DEBUG'";
@Test
void postProcessWhenServletWebEnvironmentLogsWebLoggingHint(CapturedOutput output) throws Exception {
postProcess(new StandardServletEnvironment());
assertThat(output).contains(WEB_LOGGING_HINT);
}
@Test
void postProcessWhenReactiveWebEnvironmentLogsWebLoggingHint(CapturedOutput output) throws Exception {
postProcess(new StandardReactiveWebEnvironment());
assertThat(output).contains(WEB_LOGGING_HINT);
}
@Test
void postProcessWhenNonWebEnvironmentDoesNotLogWebLoggingHint(CapturedOutput output) throws Exception {
postProcess(new StandardEnvironment());
assertThat(output).doesNotContain(WEB_LOGGING_HINT);
}
@Test
void postProcessWhenWebLoggingIsConfiguredDoesNotLogWebLoggingHint(CapturedOutput output) throws Exception {
StandardReactiveWebEnvironment environment = new StandardReactiveWebEnvironment();
TestPropertyValues.of("logging.level.web=DEBUG").applyTo(environment);
postProcess(environment);
assertThat(output).doesNotContain(WEB_LOGGING_HINT);
}
private void postProcess(ConfigurableEnvironment environment) throws Exception {
DevToolsPropertyDefaultsPostProcessor postProcessor = new DevToolsPropertyDefaultsPostProcessor();
// Run in a new thread so that DevTools is not disabled by the test runner
Thread thread = new Thread(() -> postProcessor.postProcessEnvironment(environment, new SpringApplication()));
thread.start();
thread.join();
replayDeferredLog();
}
private void replayDeferredLog() {
Object logger = ReflectionTestUtils.getField(DevToolsPropertyDefaultsPostProcessor.class, "logger");
assertThat(logger).isInstanceOf(DeferredLog.class);
((DeferredLog) logger).switchTo(DevToolsPropertyDefaultsPostProcessor.class);
}
}