Skip to content

Commit b340c85

Browse files
committedDec 3, 2024·
Prevent H2 console from causing early DataSource initialization
Update `H2ConsoleAutoConfiguration` so that DataSource connection logging occurs outside of the `ServletRegistrationBean`. Fixes gh-43337
1 parent 5a8e3d5 commit b340c85

File tree

2 files changed

+90
-35
lines changed

2 files changed

+90
-35
lines changed
 

‎spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/h2/H2ConsoleAutoConfiguration.java

+56-34
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -46,6 +46,7 @@
4646
* @author Andy Wilkinson
4747
* @author Marten Deinum
4848
* @author Stephane Nicoll
49+
* @author Phillip Webb
4950
* @since 1.3.0
5051
*/
5152
@AutoConfiguration(after = DataSourceAutoConfiguration.class)
@@ -57,46 +58,25 @@ public class H2ConsoleAutoConfiguration {
5758

5859
private static final Log logger = LogFactory.getLog(H2ConsoleAutoConfiguration.class);
5960

61+
private final H2ConsoleProperties properties;
62+
63+
H2ConsoleAutoConfiguration(H2ConsoleProperties properties) {
64+
this.properties = properties;
65+
}
66+
6067
@Bean
61-
public ServletRegistrationBean<JakartaWebServlet> h2Console(H2ConsoleProperties properties,
62-
ObjectProvider<DataSource> dataSource) {
63-
String path = properties.getPath();
68+
public ServletRegistrationBean<JakartaWebServlet> h2Console() {
69+
String path = this.properties.getPath();
6470
String urlMapping = path + (path.endsWith("/") ? "*" : "/*");
6571
ServletRegistrationBean<JakartaWebServlet> registration = new ServletRegistrationBean<>(new JakartaWebServlet(),
6672
urlMapping);
67-
configureH2ConsoleSettings(registration, properties.getSettings());
68-
if (logger.isInfoEnabled()) {
69-
withThreadContextClassLoader(getClass().getClassLoader(), () -> logDataSources(dataSource, path));
70-
}
73+
configureH2ConsoleSettings(registration, this.properties.getSettings());
7174
return registration;
7275
}
7376

74-
private void withThreadContextClassLoader(ClassLoader classLoader, Runnable action) {
75-
ClassLoader previous = Thread.currentThread().getContextClassLoader();
76-
try {
77-
Thread.currentThread().setContextClassLoader(classLoader);
78-
action.run();
79-
}
80-
finally {
81-
Thread.currentThread().setContextClassLoader(previous);
82-
}
83-
}
84-
85-
private void logDataSources(ObjectProvider<DataSource> dataSource, String path) {
86-
List<String> urls = dataSource.orderedStream().map(this::getConnectionUrl).filter(Objects::nonNull).toList();
87-
if (!urls.isEmpty()) {
88-
logger.info(LogMessage.format("H2 console available at '%s'. %s available at %s", path,
89-
(urls.size() > 1) ? "Databases" : "Database", String.join(", ", urls)));
90-
}
91-
}
92-
93-
private String getConnectionUrl(DataSource dataSource) {
94-
try (Connection connection = dataSource.getConnection()) {
95-
return "'" + connection.getMetaData().getURL() + "'";
96-
}
97-
catch (Exception ex) {
98-
return null;
99-
}
77+
@Bean
78+
H2ConsoleLogger h2ConsoleLogger(ObjectProvider<DataSource> dataSource) {
79+
return new H2ConsoleLogger(dataSource, this.properties.getPath());
10080
}
10181

10282
private void configureH2ConsoleSettings(ServletRegistrationBean<JakartaWebServlet> registration,
@@ -112,4 +92,46 @@ private void configureH2ConsoleSettings(ServletRegistrationBean<JakartaWebServle
11292
}
11393
}
11494

95+
static class H2ConsoleLogger {
96+
97+
H2ConsoleLogger(ObjectProvider<DataSource> dataSources, String path) {
98+
if (logger.isInfoEnabled()) {
99+
ClassLoader classLoader = getClass().getClassLoader();
100+
withThreadContextClassLoader(classLoader, () -> log(getConnectionUrls(dataSources), path));
101+
}
102+
}
103+
104+
private void withThreadContextClassLoader(ClassLoader classLoader, Runnable action) {
105+
ClassLoader previous = Thread.currentThread().getContextClassLoader();
106+
try {
107+
Thread.currentThread().setContextClassLoader(classLoader);
108+
action.run();
109+
}
110+
finally {
111+
Thread.currentThread().setContextClassLoader(previous);
112+
}
113+
}
114+
115+
private List<String> getConnectionUrls(ObjectProvider<DataSource> dataSources) {
116+
return dataSources.orderedStream().map(this::getConnectionUrl).filter(Objects::nonNull).toList();
117+
}
118+
119+
private String getConnectionUrl(DataSource dataSource) {
120+
try (Connection connection = dataSource.getConnection()) {
121+
return "'" + connection.getMetaData().getURL() + "'";
122+
}
123+
catch (Exception ex) {
124+
return null;
125+
}
126+
}
127+
128+
private void log(List<String> urls, String path) {
129+
if (!urls.isEmpty()) {
130+
logger.info(LogMessage.format("H2 console available at '%s'. %s available at %s", path,
131+
(urls.size() > 1) ? "Databases" : "Database", String.join(", ", urls)));
132+
}
133+
}
134+
135+
}
136+
115137
}

‎spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/h2/H2ConsoleAutoConfigurationTests.java

+34-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,15 +30,20 @@
3030
import org.springframework.beans.factory.BeanCreationException;
3131
import org.springframework.boot.autoconfigure.AutoConfigurations;
3232
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
33+
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
3334
import org.springframework.boot.context.properties.ConfigurationPropertiesBindException;
3435
import org.springframework.boot.context.properties.bind.BindException;
3536
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
3637
import org.springframework.boot.test.system.CapturedOutput;
3738
import org.springframework.boot.test.system.OutputCaptureExtension;
3839
import org.springframework.boot.web.servlet.ServletRegistrationBean;
40+
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
41+
import org.springframework.context.ConfigurableApplicationContext;
3942
import org.springframework.context.annotation.Bean;
4043
import org.springframework.context.annotation.Configuration;
4144
import org.springframework.core.annotation.Order;
45+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
46+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
4247

4348
import static org.assertj.core.api.Assertions.assertThat;
4449
import static org.mockito.BDDMockito.given;
@@ -51,6 +56,7 @@
5156
* @author Marten Deinum
5257
* @author Stephane Nicoll
5358
* @author Shraddha Yeole
59+
* @author Phillip Webb
5460
*/
5561
class H2ConsoleAutoConfigurationTests {
5662

@@ -163,6 +169,22 @@ void h2ConsoleShouldNotFailIfDatabaseConnectionFails() {
163169
.run((context) -> assertThat(context.isRunning()).isTrue());
164170
}
165171

172+
@Test
173+
@ExtendWith(OutputCaptureExtension.class)
174+
void dataSourceIsNotInitializedEarly(CapturedOutput output) {
175+
new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
176+
.withConfiguration(AutoConfigurations.of(H2ConsoleAutoConfiguration.class,
177+
ServletWebServerFactoryAutoConfiguration.class))
178+
.withUserConfiguration(EarlyInitializationConfiguration.class)
179+
.withPropertyValues("spring.h2.console.enabled=true", "server.port=0")
180+
.run((context) -> {
181+
try (Connection connection = context.getBean(DataSource.class).getConnection()) {
182+
assertThat(output).contains("H2 console available at '/h2-console'. Database available at '"
183+
+ connection.getMetaData().getURL() + "'");
184+
}
185+
});
186+
}
187+
166188
@Configuration(proxyBeanMethods = false)
167189
static class FailingDataSourceConfiguration {
168190

@@ -206,4 +228,15 @@ private DataSource mockDataSource(String url) throws SQLException {
206228

207229
}
208230

231+
@Configuration(proxyBeanMethods = false)
232+
static class EarlyInitializationConfiguration {
233+
234+
@Bean
235+
DataSource dataSource(ConfigurableApplicationContext applicationContext) {
236+
assertThat(applicationContext.getBeanFactory().isConfigurationFrozen()).isTrue();
237+
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
238+
}
239+
240+
}
241+
209242
}

0 commit comments

Comments
 (0)
Please sign in to comment.