From 8b6cdbb9770158a874aef4eaea5ea16d4b6e0022 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 13 May 2020 17:46:37 +0100 Subject: [PATCH] Deregister JDBC drivers when deployed war's ServletContext is destroyed Closes gh-21221 --- .../support/SpringBootServletInitializer.java | 36 +++++++++++++++++++ .../SpringBootServletInitializerTests.java | 32 +++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/support/SpringBootServletInitializer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/support/SpringBootServletInitializer.java index f443ec5f2c82..980e1a1434b4 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/support/SpringBootServletInitializer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/support/SpringBootServletInitializer.java @@ -16,6 +16,9 @@ package org.springframework.boot.web.servlet.support; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; import java.util.Collections; import javax.servlet.Filter; @@ -98,7 +101,18 @@ public void contextInitialized(ServletContextEvent event) { // no-op because the application context is already initialized } + @Override + public void contextDestroyed(ServletContextEvent event) { + try { + super.contextDestroyed(event); + } + finally { + deregisterJdbcDrivers(event.getServletContext()); + } + } + }); + } else { this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not " @@ -106,6 +120,28 @@ public void contextInitialized(ServletContextEvent event) { } } + /** + * Deregisters the JDBC drivers that were registered by the application represented by + * the given {@code servletContext}. The default implementation + * {@link DriverManager#deregisterDriver(Driver) deregisters} every {@link Driver} + * that was loaded by the {@link ServletContext#getClassLoader web application's class + * loader}. + * @param servletContext the web application's servlet context + * @since 2.3.0 + */ + protected void deregisterJdbcDrivers(ServletContext servletContext) { + for (Driver driver : Collections.list(DriverManager.getDrivers())) { + if (driver.getClass().getClassLoader() == servletContext.getClassLoader()) { + try { + DriverManager.deregisterDriver(driver); + } + catch (SQLException ex) { + // Continue + } + } + } + } + protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) { SpringApplicationBuilder builder = createSpringApplicationBuilder(); builder.main(getClass()); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/support/SpringBootServletInitializerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/support/SpringBootServletInitializerTests.java index 790914bc28ac..514490070f79 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/support/SpringBootServletInitializerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/support/SpringBootServletInitializerTests.java @@ -17,12 +17,18 @@ package org.springframework.boot.web.servlet.support; import java.util.Collections; +import java.util.Vector; +import java.util.concurrent.atomic.AtomicBoolean; import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.springframework.boot.SpringApplication; import org.springframework.boot.builder.SpringApplicationBuilder; @@ -46,6 +52,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Tests for {@link SpringBootServletInitializer}. @@ -142,6 +149,31 @@ void servletContextPropertySourceIsAvailablePriorToRefresh() { } } + @Test + void whenServletContextIsDestroyedThenJdbcDriversAreDeregistered() throws ServletException { + ServletContext servletContext = mock(ServletContext.class); + given(servletContext.getInitParameterNames()).willReturn(new Vector().elements()); + given(servletContext.getAttributeNames()).willReturn(new Vector().elements()); + AtomicBoolean driversDeregistered = new AtomicBoolean(); + new SpringBootServletInitializer() { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { + return builder.sources(Config.class); + } + + @Override + protected void deregisterJdbcDrivers(ServletContext servletContext) { + driversDeregistered.set(true); + } + + }.onStartup(servletContext); + ArgumentCaptor captor = ArgumentCaptor.forClass(ServletContextListener.class); + verify(servletContext).addListener(captor.capture()); + captor.getValue().contextDestroyed(new ServletContextEvent(servletContext)); + assertThat(driversDeregistered).isTrue(); + } + static class PropertySourceVerifyingSpringBootServletInitializer extends SpringBootServletInitializer { @Override