Merge branch '3.3.x'

Closes gh-43359
This commit is contained in:
Phillip Webb 2024-12-02 19:05:21 -08:00
commit 91778e9f96
2 changed files with 90 additions and 35 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2022 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -46,6 +46,7 @@ import org.springframework.core.log.LogMessage;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Marten Deinum * @author Marten Deinum
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb
* @since 1.3.0 * @since 1.3.0
*/ */
@AutoConfiguration(after = DataSourceAutoConfiguration.class) @AutoConfiguration(after = DataSourceAutoConfiguration.class)
@ -57,46 +58,25 @@ public class H2ConsoleAutoConfiguration {
private static final Log logger = LogFactory.getLog(H2ConsoleAutoConfiguration.class); private static final Log logger = LogFactory.getLog(H2ConsoleAutoConfiguration.class);
private final H2ConsoleProperties properties;
H2ConsoleAutoConfiguration(H2ConsoleProperties properties) {
this.properties = properties;
}
@Bean @Bean
public ServletRegistrationBean<JakartaWebServlet> h2Console(H2ConsoleProperties properties, public ServletRegistrationBean<JakartaWebServlet> h2Console() {
ObjectProvider<DataSource> dataSource) { String path = this.properties.getPath();
String path = properties.getPath();
String urlMapping = path + (path.endsWith("/") ? "*" : "/*"); String urlMapping = path + (path.endsWith("/") ? "*" : "/*");
ServletRegistrationBean<JakartaWebServlet> registration = new ServletRegistrationBean<>(new JakartaWebServlet(), ServletRegistrationBean<JakartaWebServlet> registration = new ServletRegistrationBean<>(new JakartaWebServlet(),
urlMapping); urlMapping);
configureH2ConsoleSettings(registration, properties.getSettings()); configureH2ConsoleSettings(registration, this.properties.getSettings());
if (logger.isInfoEnabled()) {
withThreadContextClassLoader(getClass().getClassLoader(), () -> logDataSources(dataSource, path));
}
return registration; return registration;
} }
private void withThreadContextClassLoader(ClassLoader classLoader, Runnable action) { @Bean
ClassLoader previous = Thread.currentThread().getContextClassLoader(); H2ConsoleLogger h2ConsoleLogger(ObjectProvider<DataSource> dataSource) {
try { return new H2ConsoleLogger(dataSource, this.properties.getPath());
Thread.currentThread().setContextClassLoader(classLoader);
action.run();
}
finally {
Thread.currentThread().setContextClassLoader(previous);
}
}
private void logDataSources(ObjectProvider<DataSource> dataSource, String path) {
List<String> urls = dataSource.orderedStream().map(this::getConnectionUrl).filter(Objects::nonNull).toList();
if (!urls.isEmpty()) {
logger.info(LogMessage.format("H2 console available at '%s'. %s available at %s", path,
(urls.size() > 1) ? "Databases" : "Database", String.join(", ", urls)));
}
}
private String getConnectionUrl(DataSource dataSource) {
try (Connection connection = dataSource.getConnection()) {
return "'" + connection.getMetaData().getURL() + "'";
}
catch (Exception ex) {
return null;
}
} }
private void configureH2ConsoleSettings(ServletRegistrationBean<JakartaWebServlet> registration, private void configureH2ConsoleSettings(ServletRegistrationBean<JakartaWebServlet> registration,
@ -112,4 +92,46 @@ public class H2ConsoleAutoConfiguration {
} }
} }
static class H2ConsoleLogger {
H2ConsoleLogger(ObjectProvider<DataSource> dataSources, String path) {
if (logger.isInfoEnabled()) {
ClassLoader classLoader = getClass().getClassLoader();
withThreadContextClassLoader(classLoader, () -> log(getConnectionUrls(dataSources), path));
}
}
private void withThreadContextClassLoader(ClassLoader classLoader, Runnable action) {
ClassLoader previous = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(classLoader);
action.run();
}
finally {
Thread.currentThread().setContextClassLoader(previous);
}
}
private List<String> getConnectionUrls(ObjectProvider<DataSource> dataSources) {
return dataSources.orderedStream().map(this::getConnectionUrl).filter(Objects::nonNull).toList();
}
private String getConnectionUrl(DataSource dataSource) {
try (Connection connection = dataSource.getConnection()) {
return "'" + connection.getMetaData().getURL() + "'";
}
catch (Exception ex) {
return null;
}
}
private void log(List<String> urls, String path) {
if (!urls.isEmpty()) {
logger.info(LogMessage.format("H2 console available at '%s'. %s available at %s", path,
(urls.size() > 1) ? "Databases" : "Database", String.join(", ", urls)));
}
}
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -30,15 +30,20 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindException; import org.springframework.boot.context.properties.ConfigurationPropertiesBindException;
import org.springframework.boot.context.properties.bind.BindException; import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
@ -51,6 +56,7 @@ import static org.mockito.Mockito.mock;
* @author Marten Deinum * @author Marten Deinum
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Shraddha Yeole * @author Shraddha Yeole
* @author Phillip Webb
*/ */
class H2ConsoleAutoConfigurationTests { class H2ConsoleAutoConfigurationTests {
@ -163,6 +169,22 @@ class H2ConsoleAutoConfigurationTests {
.run((context) -> assertThat(context.isRunning()).isTrue()); .run((context) -> assertThat(context.isRunning()).isTrue());
} }
@Test
@ExtendWith(OutputCaptureExtension.class)
void dataSourceIsNotInitializedEarly(CapturedOutput output) {
new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(H2ConsoleAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(EarlyInitializationConfiguration.class)
.withPropertyValues("spring.h2.console.enabled=true", "server.port=0")
.run((context) -> {
try (Connection connection = context.getBean(DataSource.class).getConnection()) {
assertThat(output).contains("H2 console available at '/h2-console'. Database available at '"
+ connection.getMetaData().getURL() + "'");
}
});
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class FailingDataSourceConfiguration { static class FailingDataSourceConfiguration {
@ -206,4 +228,15 @@ class H2ConsoleAutoConfigurationTests {
} }
@Configuration(proxyBeanMethods = false)
static class EarlyInitializationConfiguration {
@Bean
DataSource dataSource(ConfigurableApplicationContext applicationContext) {
assertThat(applicationContext.getBeanFactory().isConfigurationFrozen()).isTrue();
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
}
}
} }