Skip to content

Commit 61e11cd

Browse files
committed
Ensure that TCCL is clean when DataSource is accessed
Previously, when using Tomcat, its web app class loader was the thread context class loader when H2ConsoleAutoConfiguration triggered initialization of Hikari's pool. This was the case because it's done in the bean method of a ServletRegistrationBean. Such Servlet-related beans are intentionally created with Tomcat's web app classloader as the TCCL. This arrangement results in the pool's threads using Tomcat's web app class loader as their TCCL which is not desirable. One consequence of this was that Tomcat could log a warning at shutdown about the thread being left running when it will, in fact, be stopped as part of the context being closed. This commit updates H2ConsoleAutoConfiguration to set the TCCL to its own ClassLoader while the DataSource information is being logged. Closes gh-32382
1 parent ab26050 commit 61e11cd

File tree

2 files changed

+34
-7
lines changed

2 files changed

+34
-7
lines changed

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 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.
@@ -67,11 +67,22 @@ public ServletRegistrationBean<WebServlet> h2Console(H2ConsoleProperties propert
6767
ServletRegistrationBean<WebServlet> registration = new ServletRegistrationBean<>(new WebServlet(), urlMapping);
6868
configureH2ConsoleSettings(registration, properties.getSettings());
6969
if (logger.isInfoEnabled()) {
70-
logDataSources(dataSource, path);
70+
withThreadContextClassLoader(getClass().getClassLoader(), () -> logDataSources(dataSource, path));
7171
}
7272
return registration;
7373
}
7474

75+
private void withThreadContextClassLoader(ClassLoader classLoader, Runnable action) {
76+
ClassLoader previous = Thread.currentThread().getContextClassLoader();
77+
try {
78+
Thread.currentThread().setContextClassLoader(classLoader);
79+
action.run();
80+
}
81+
finally {
82+
Thread.currentThread().setContextClassLoader(previous);
83+
}
84+
}
85+
7586
private void logDataSources(ObjectProvider<DataSource> dataSource, String path) {
7687
List<String> urls = dataSource.orderedStream().map((available) -> {
7788
try (Connection connection = available.getConnection()) {

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

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 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.
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.boot.autoconfigure.h2;
1818

19+
import java.net.URL;
20+
import java.net.URLClassLoader;
1921
import java.sql.Connection;
2022
import java.sql.DatabaseMetaData;
2123
import java.sql.SQLException;
@@ -24,6 +26,8 @@
2426

2527
import org.junit.jupiter.api.Test;
2628
import org.junit.jupiter.api.extension.ExtendWith;
29+
import org.mockito.invocation.InvocationOnMock;
30+
import org.mockito.stubbing.Answer;
2731

2832
import org.springframework.beans.factory.BeanCreationException;
2933
import org.springframework.boot.autoconfigure.AutoConfigurations;
@@ -137,7 +141,8 @@ void noDataSourceIsLoggedWhenNoneAvailable(CapturedOutput output) {
137141
@Test
138142
@ExtendWith(OutputCaptureExtension.class)
139143
void allDataSourceUrlsAreLoggedWhenMultipleAvailable(CapturedOutput output) {
140-
this.contextRunner
144+
ClassLoader webAppClassLoader = new URLClassLoader(new URL[0]);
145+
this.contextRunner.withClassLoader(webAppClassLoader)
141146
.withUserConfiguration(FailingDataSourceConfiguration.class, MultiDataSourceConfiguration.class)
142147
.withPropertyValues("spring.h2.console.enabled=true").run((context) -> assertThat(output).contains(
143148
"H2 console available at '/h2-console'. Databases available at 'someJdbcUrl', 'anotherJdbcUrl'"));
@@ -179,9 +184,20 @@ DataSource someDataSource() throws SQLException {
179184

180185
private DataSource mockDataSource(String url) throws SQLException {
181186
DataSource dataSource = mock(DataSource.class);
182-
given(dataSource.getConnection()).willReturn(mock(Connection.class));
183-
given(dataSource.getConnection().getMetaData()).willReturn(mock(DatabaseMetaData.class));
184-
given(dataSource.getConnection().getMetaData().getURL()).willReturn(url);
187+
given(dataSource.getConnection()).will(new Answer<Connection>() {
188+
189+
@Override
190+
public Connection answer(InvocationOnMock invocation) throws Throwable {
191+
assertThat(Thread.currentThread().getContextClassLoader()).isEqualTo(getClass().getClassLoader());
192+
Connection connection = mock(Connection.class);
193+
DatabaseMetaData metadata = mock(DatabaseMetaData.class);
194+
given(connection.getMetaData()).willReturn(metadata);
195+
given(metadata.getURL()).willReturn(url);
196+
return connection;
197+
}
198+
199+
});
200+
185201
return dataSource;
186202
}
187203

0 commit comments

Comments
 (0)