diff --git a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/TomcatEmbeddedContext.java b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/TomcatEmbeddedContext.java index fecdb9698ed..de6ce645195 100644 --- a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/TomcatEmbeddedContext.java +++ b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/TomcatEmbeddedContext.java @@ -34,6 +34,7 @@ import org.jspecify.annotations.Nullable; import org.springframework.boot.web.server.MimeMappings; import org.springframework.boot.web.server.WebServerException; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** @@ -46,7 +47,7 @@ import org.springframework.util.ClassUtils; */ public class TomcatEmbeddedContext extends StandardContext { - private @Nullable TomcatStarter starter; + private DeferredStartupExceptions deferredStartupExceptions = DeferredStartupExceptions.NONE; private @Nullable MimeMappings mimeMappings; @@ -116,12 +117,17 @@ public class TomcatEmbeddedContext extends StandardContext { } } - public void setStarter(@Nullable TomcatStarter starter) { - this.starter = starter; + /** + * Set the a strategy used to capture and rethrow deferred startup exceptions. + * @param deferredStartupExceptions the strategy to use + */ + public void setDeferredStartupExceptions(DeferredStartupExceptions deferredStartupExceptions) { + Assert.notNull(deferredStartupExceptions, "'deferredStartupExceptions' must not be null"); + this.deferredStartupExceptions = deferredStartupExceptions; } - @Nullable TomcatStarter getStarter() { - return this.starter; + DeferredStartupExceptions getDeferredStartupExceptions() { + return this.deferredStartupExceptions; } public void setMimeMappings(MimeMappings mimeMappings) { @@ -146,4 +152,24 @@ public class TomcatEmbeddedContext extends StandardContext { return (this.mimeMappings != null) ? this.mimeMappings.get(extension) : null; } + /** + * Strategy interface that can be used to rethrow deferred startup exceptions. + */ + @FunctionalInterface + public interface DeferredStartupExceptions { + + /** + * {@link DeferredStartupExceptions} that does nothing. + */ + DeferredStartupExceptions NONE = () -> { + }; + + /** + * Rethrow deferred startup exceptions if there are any. + * @throws Exception the deferred startup exception + */ + void rethrow() throws Exception; + + } + } diff --git a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/TomcatWebServer.java b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/TomcatWebServer.java index 8810428441a..5368372d1fc 100644 --- a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/TomcatWebServer.java +++ b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/TomcatWebServer.java @@ -200,13 +200,7 @@ public class TomcatWebServer implements WebServer { Container[] children = this.tomcat.getHost().findChildren(); for (Container container : children) { if (container instanceof TomcatEmbeddedContext embeddedContext) { - TomcatStarter tomcatStarter = embeddedContext.getStarter(); - if (tomcatStarter != null) { - Exception exception = tomcatStarter.getStartUpException(); - if (exception != null) { - throw exception; - } - } + embeddedContext.getDeferredStartupExceptions().rethrow(); } if (!LifecycleState.STARTED.equals(container.getState())) { throw new IllegalStateException(container + " failed to start"); diff --git a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/TomcatStarter.java b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/servlet/DeferredServletContainerInitializers.java similarity index 77% rename from module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/TomcatStarter.java rename to module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/servlet/DeferredServletContainerInitializers.java index 0a9de4154f0..d1e0dce5e65 100644 --- a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/TomcatStarter.java +++ b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/servlet/DeferredServletContainerInitializers.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.tomcat; +package org.springframework.boot.tomcat.servlet; import java.util.Set; @@ -25,6 +25,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; +import org.springframework.boot.tomcat.TomcatEmbeddedContext; import org.springframework.boot.web.servlet.ServletContextInitializer; /** @@ -33,17 +34,17 @@ import org.springframework.boot.web.servlet.ServletContextInitializer; * * @author Phillip Webb * @author Andy Wilkinson - * @since 4.0.0 */ -public class TomcatStarter implements ServletContainerInitializer { +class DeferredServletContainerInitializers + implements ServletContainerInitializer, TomcatEmbeddedContext.DeferredStartupExceptions { - private static final Log logger = LogFactory.getLog(TomcatStarter.class); + private static final Log logger = LogFactory.getLog(DeferredServletContainerInitializers.class); private final Iterable initializers; private volatile @Nullable Exception startUpException; - public TomcatStarter(Iterable initializers) { + DeferredServletContainerInitializers(Iterable initializers) { this.initializers = initializers; } @@ -65,8 +66,11 @@ public class TomcatStarter implements ServletContainerInitializer { } } - @Nullable Exception getStartUpException() { - return this.startUpException; + @Override + public void rethrow() throws Exception { + if (this.startUpException != null) { + throw this.startUpException; + } } } diff --git a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/servlet/TomcatServletWebServerFactory.java b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/servlet/TomcatServletWebServerFactory.java index dd5eb0c663b..e62635bd984 100644 --- a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/servlet/TomcatServletWebServerFactory.java +++ b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/servlet/TomcatServletWebServerFactory.java @@ -70,7 +70,6 @@ import org.springframework.boot.tomcat.DisableReferenceClearingContextCustomizer import org.springframework.boot.tomcat.TomcatContextCustomizer; import org.springframework.boot.tomcat.TomcatEmbeddedContext; import org.springframework.boot.tomcat.TomcatEmbeddedWebappClassLoader; -import org.springframework.boot.tomcat.TomcatStarter; import org.springframework.boot.tomcat.TomcatWebServer; import org.springframework.boot.tomcat.TomcatWebServerFactory; import org.springframework.boot.web.error.ErrorPage; @@ -289,12 +288,13 @@ public class TomcatServletWebServerFactory extends TomcatWebServerFactory * @param initializers initializers to apply */ protected void configureContext(Context context, Iterable initializers) { - TomcatStarter starter = new TomcatStarter(initializers); + DeferredServletContainerInitializers deferredInitializers = new DeferredServletContainerInitializers( + initializers); if (context instanceof TomcatEmbeddedContext embeddedContext) { - embeddedContext.setStarter(starter); + embeddedContext.setDeferredStartupExceptions(deferredInitializers); embeddedContext.setFailCtxIfServletStartFails(true); } - context.addServletContainerInitializer(starter, NO_CLASSES); + context.addServletContainerInitializer(deferredInitializers, NO_CLASSES); for (LifecycleListener lifecycleListener : this.getContextLifecycleListeners()) { context.addLifecycleListener(lifecycleListener); }