From 5326640f796e924d3baeda65d28c83b0d2d98593 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 26 Sep 2024 18:31:02 +0200 Subject: [PATCH] Initialize application context with initializer-given ServletContext Closes gh-22319 --- .../AbstractContextLoaderInitializer.java | 4 +- .../web/context/ContextLoader.java | 26 +++++----- .../web/context/ContextLoaderListener.java | 47 ++++++++++++++++--- 3 files changed, 55 insertions(+), 22 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/context/AbstractContextLoaderInitializer.java b/spring-web/src/main/java/org/springframework/web/context/AbstractContextLoaderInitializer.java index 01fae7c0aea..065acbadb26 100644 --- a/spring-web/src/main/java/org/springframework/web/context/AbstractContextLoaderInitializer.java +++ b/spring-web/src/main/java/org/springframework/web/context/AbstractContextLoaderInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,7 +58,7 @@ public abstract class AbstractContextLoaderInitializer implements WebApplication protected void registerContextLoaderListener(ServletContext servletContext) { WebApplicationContext rootAppContext = createRootApplicationContext(); if (rootAppContext != null) { - ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); + ContextLoaderListener listener = new ContextLoaderListener(rootAppContext, servletContext); listener.setContextInitializers(getRootApplicationContextInitializers()); servletContext.addListener(listener); } diff --git a/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java b/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java index d2f5fd841f0..83c9297bd69 100644 --- a/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java +++ b/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -152,7 +152,7 @@ public class ContextLoader { * The root WebApplicationContext instance that this loader manages. */ @Nullable - private WebApplicationContext context; + private WebApplicationContext rootContext; /** Actual ApplicationContextInitializer instances to apply to the context. */ private final List> contextInitializers = @@ -205,12 +205,12 @@ public class ContextLoader { * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and subclasses are * free to call the {@link #closeWebApplicationContext} method on container shutdown * to close the application context. - * @param context the application context to manage + * @param rootContext the application context to manage * @see #initWebApplicationContext(ServletContext) * @see #closeWebApplicationContext(ServletContext) */ - public ContextLoader(WebApplicationContext context) { - this.context = context; + public ContextLoader(WebApplicationContext rootContext) { + this.rootContext = rootContext; } @@ -259,10 +259,10 @@ public class ContextLoader { try { // Store context in local instance variable, to guarantee that // it is available on ServletContext shutdown. - if (this.context == null) { - this.context = createWebApplicationContext(servletContext); + if (this.rootContext == null) { + this.rootContext = createWebApplicationContext(servletContext); } - if (this.context instanceof ConfigurableWebApplicationContext cwac && !cwac.isActive()) { + if (this.rootContext instanceof ConfigurableWebApplicationContext cwac && !cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { @@ -273,14 +273,14 @@ public class ContextLoader { } configureAndRefreshWebApplicationContext(cwac, servletContext); } - servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); + servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.rootContext); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { - currentContext = this.context; + currentContext = this.rootContext; } else if (ccl != null) { - currentContextPerThread.put(ccl, this.context); + currentContextPerThread.put(ccl, this.rootContext); } if (logger.isInfoEnabled()) { @@ -288,7 +288,7 @@ public class ContextLoader { logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms"); } - return this.context; + return this.rootContext; } catch (RuntimeException | Error ex) { logger.error("Context initialization failed", ex); @@ -506,7 +506,7 @@ public class ContextLoader { public void closeWebApplicationContext(ServletContext servletContext) { servletContext.log("Closing Spring root WebApplicationContext"); try { - if (this.context instanceof ConfigurableWebApplicationContext cwac) { + if (this.rootContext instanceof ConfigurableWebApplicationContext cwac) { cwac.close(); } } diff --git a/spring-web/src/main/java/org/springframework/web/context/ContextLoaderListener.java b/spring-web/src/main/java/org/springframework/web/context/ContextLoaderListener.java index 8a3aa2e79c7..e065e415261 100644 --- a/spring-web/src/main/java/org/springframework/web/context/ContextLoaderListener.java +++ b/spring-web/src/main/java/org/springframework/web/context/ContextLoaderListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,12 @@ package org.springframework.web.context; +import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContextEvent; import jakarta.servlet.ServletContextListener; +import org.springframework.lang.Nullable; + /** * Bootstrap listener to start up and shut down Spring's root {@link WebApplicationContext}. * Simply delegates to {@link ContextLoader} as well as to {@link ContextCleanupListener}. @@ -36,6 +39,10 @@ import jakarta.servlet.ServletContextListener; */ public class ContextLoaderListener extends ContextLoader implements ServletContextListener { + @Nullable + private ServletContext servletContext; + + /** * Create a new {@code ContextLoaderListener} that will create a web application * context based on the "contextClass" and "contextConfigLocation" servlet @@ -56,6 +63,19 @@ public class ContextLoaderListener extends ContextLoader implements ServletConte public ContextLoaderListener() { } + /** + * Create a new {@code ContextLoaderListener} with the given application context, + * initializing it with the {@link ServletContextEvent}-provided + * {@link ServletContext} reference which is spec-restricted in terms of capabilities. + *

It is generally preferable to initialize the application context with a + * {@link org.springframework.web.WebApplicationInitializer#onStartup}-given reference + * which is usually fully capable. + * @see #ContextLoaderListener(WebApplicationContext, ServletContext) + */ + public ContextLoaderListener(WebApplicationContext rootContext) { + super(rootContext); + } + /** * Create a new {@code ContextLoaderListener} with the given application context. This * constructor is useful in Servlet initializers where instance-based registration of @@ -85,12 +105,15 @@ public class ContextLoaderListener extends ContextLoader implements ServletConte * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring * application context will be closed when the {@link #contextDestroyed} lifecycle * method is invoked on this listener. - * @param context the application context to manage + * @param rootContext the application context to manage + * @param servletContext the ServletContext to initialize with + * @since 6.2 * @see #contextInitialized(ServletContextEvent) * @see #contextDestroyed(ServletContextEvent) */ - public ContextLoaderListener(WebApplicationContext context) { - super(context); + public ContextLoaderListener(WebApplicationContext rootContext, ServletContext servletContext) { + super(rootContext); + this.servletContext = servletContext; } @@ -99,7 +122,8 @@ public class ContextLoaderListener extends ContextLoader implements ServletConte */ @Override public void contextInitialized(ServletContextEvent event) { - initWebApplicationContext(event.getServletContext()); + ServletContext scToUse = getServletContextToUse(event); + initWebApplicationContext(scToUse); } @@ -108,8 +132,17 @@ public class ContextLoaderListener extends ContextLoader implements ServletConte */ @Override public void contextDestroyed(ServletContextEvent event) { - closeWebApplicationContext(event.getServletContext()); - ContextCleanupListener.cleanupAttributes(event.getServletContext()); + ServletContext scToUse = getServletContextToUse(event); + closeWebApplicationContext(scToUse); + ContextCleanupListener.cleanupAttributes(scToUse); + } + + /** + * Preferably use a fully-capable local ServletContext instead of + * the spec-restricted ServletContextEvent-provided reference. + */ + private ServletContext getServletContextToUse(ServletContextEvent event) { + return (this.servletContext != null ? this.servletContext : event.getServletContext()); } }