/*
 * Copyright 2012-2020 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.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.web.servlet.context;

import java.util.Collection;
import java.util.Collections;
import java.util.EventListener;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.ServletContextInitializerBeans;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.core.io.Resource;
import org.springframework.core.metrics.StartupStep;
import org.springframework.util.StringUtils;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.context.support.ServletContextAwareProcessor;
import org.springframework.web.context.support.ServletContextResource;
import org.springframework.web.context.support.ServletContextScope;
import org.springframework.web.context.support.WebApplicationContextUtils;

A WebApplicationContext that can be used to bootstrap itself from a contained ServletWebServerFactory bean.

This context will create, initialize and run an WebServer by searching for a single ServletWebServerFactory bean within the ApplicationContext itself. The ServletWebServerFactory is free to use standard Spring concepts (such as dependency injection, lifecycle callbacks and property placeholder variables).

In addition, any Servlet or Filter beans defined in the context will be automatically registered with the web server. In the case of a single Servlet bean, the '/' mapping will be used. If multiple Servlet beans are found then the lowercase bean name will be used as a mapping prefix. Any Servlet named 'dispatcherServlet' will always be mapped to '/'. Filter beans will be mapped to all URLs ('/*').

For more advanced configuration, the context can instead define beans that implement the ServletContextInitializer interface (most often ServletRegistrationBeans and/or FilterRegistrationBeans). To prevent double registration, the use of ServletContextInitializer beans will disable automatic Servlet and Filter bean registration.

Although this context can be used directly, most developers should consider using the AnnotationConfigServletWebServerApplicationContext or XmlServletWebServerApplicationContext variants.

Author:Phillip Webb, Dave Syer, Scott Frederick
See Also:
Since:2.0.0
/** * A {@link WebApplicationContext} that can be used to bootstrap itself from a contained * {@link ServletWebServerFactory} bean. * <p> * This context will create, initialize and run an {@link WebServer} by searching for a * single {@link ServletWebServerFactory} bean within the {@link ApplicationContext} * itself. The {@link ServletWebServerFactory} is free to use standard Spring concepts * (such as dependency injection, lifecycle callbacks and property placeholder variables). * <p> * In addition, any {@link Servlet} or {@link Filter} beans defined in the context will be * automatically registered with the web server. In the case of a single Servlet bean, the * '/' mapping will be used. If multiple Servlet beans are found then the lowercase bean * name will be used as a mapping prefix. Any Servlet named 'dispatcherServlet' will * always be mapped to '/'. Filter beans will be mapped to all URLs ('/*'). * <p> * For more advanced configuration, the context can instead define beans that implement * the {@link ServletContextInitializer} interface (most often * {@link ServletRegistrationBean}s and/or {@link FilterRegistrationBean}s). To prevent * double registration, the use of {@link ServletContextInitializer} beans will disable * automatic Servlet and Filter bean registration. * <p> * Although this context can be used directly, most developers should consider using the * {@link AnnotationConfigServletWebServerApplicationContext} or * {@link XmlServletWebServerApplicationContext} variants. * * @author Phillip Webb * @author Dave Syer * @author Scott Frederick * @since 2.0.0 * @see AnnotationConfigServletWebServerApplicationContext * @see XmlServletWebServerApplicationContext * @see ServletWebServerFactory */
public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext { private static final Log logger = LogFactory.getLog(ServletWebServerApplicationContext.class);
Constant value for the DispatcherServlet bean name. A Servlet bean with this name is deemed to be the "main" servlet and is automatically given a mapping of "/" by default. To change the default behavior you can use a ServletRegistrationBean or a different bean name.
/** * Constant value for the DispatcherServlet bean name. A Servlet bean with this name * is deemed to be the "main" servlet and is automatically given a mapping of "/" by * default. To change the default behavior you can use a * {@link ServletRegistrationBean} or a different bean name. */
public static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet"; private volatile WebServer webServer; private ServletConfig servletConfig; private String serverNamespace; /** * Create a new {@link ServletWebServerApplicationContext}. */ public ServletWebServerApplicationContext() { }
Create a new ServletWebServerApplicationContext with the given DefaultListableBeanFactory.
Params:
  • beanFactory – the DefaultListableBeanFactory instance to use for this context
/** * Create a new {@link ServletWebServerApplicationContext} with the given * {@code DefaultListableBeanFactory}. * @param beanFactory the DefaultListableBeanFactory instance to use for this context */
public ServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) { super(beanFactory); }
Register ServletContextAwareProcessor.
See Also:
  • ServletContextAwareProcessor
/** * Register ServletContextAwareProcessor. * @see ServletContextAwareProcessor */
@Override protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { beanFactory.addBeanPostProcessor(new WebApplicationContextServletContextAwareProcessor(this)); beanFactory.ignoreDependencyInterface(ServletContextAware.class); registerWebApplicationScopes(); } @Override public final void refresh() throws BeansException, IllegalStateException { try { super.refresh(); } catch (RuntimeException ex) { WebServer webServer = this.webServer; if (webServer != null) { webServer.stop(); } throw ex; } } @Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } } @Override protected void doClose() { if (isActive()) { AvailabilityChangeEvent.publish(this, ReadinessState.REFUSING_TRAFFIC); } super.doClose(); } private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create"); ServletWebServerFactory factory = getWebServerFactory(); createWebServer.tag("factory", factory.getClass().toString()); this.webServer = factory.getWebServer(getSelfInitializer()); createWebServer.end(); getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer)); getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer)); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
Returns the ServletWebServerFactory that should be used to create the embedded WebServer. By default this method searches for a suitable bean in the context itself.
Returns:a ServletWebServerFactory (never null)
/** * Returns the {@link ServletWebServerFactory} that should be used to create the * embedded {@link WebServer}. By default this method searches for a suitable bean in * the context itself. * @return a {@link ServletWebServerFactory} (never {@code null}) */
protected ServletWebServerFactory getWebServerFactory() { // Use bean names so that we don't consider the hierarchy String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class); if (beanNames.length == 0) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing " + "ServletWebServerFactory bean."); } if (beanNames.length > 1) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple " + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames)); } return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class); }
Returns the ServletContextInitializer that will be used to complete the setup of this WebApplicationContext.
See Also:
  • prepareWebApplicationContext(ServletContext)
Returns:the self initializer
/** * Returns the {@link ServletContextInitializer} that will be used to complete the * setup of this {@link WebApplicationContext}. * @return the self initializer * @see #prepareWebApplicationContext(ServletContext) */
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return this::selfInitialize; } private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } } private void registerApplicationScope(ServletContext servletContext) { ServletContextScope appScope = new ServletContextScope(servletContext); getBeanFactory().registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope); // Register as ServletContext attribute, for ContextCleanupListener to detect it. servletContext.setAttribute(ServletContextScope.class.getName(), appScope); } private void registerWebApplicationScopes() { ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(getBeanFactory()); WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory()); existingScopes.restore(); }
Returns ServletContextInitializers that should be used with the embedded web server. By default this method will first attempt to find ServletContextInitializer, Servlet, Filter and certain EventListener beans.
Returns:the servlet initializer beans
/** * Returns {@link ServletContextInitializer}s that should be used with the embedded * web server. By default this method will first attempt to find * {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain * {@link EventListener} beans. * @return the servlet initializer beans */
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() { return new ServletContextInitializerBeans(getBeanFactory()); }
Prepare the WebApplicationContext with the given fully loaded ServletContext. This method is usually called from ServletContextInitializer.onStartup(ServletContext) and is similar to the functionality usually provided by a ContextLoaderListener.
Params:
  • servletContext – the operational servlet context
/** * Prepare the {@link WebApplicationContext} with the given fully loaded * {@link ServletContext}. This method is usually called from * {@link ServletContextInitializer#onStartup(ServletContext)} and is similar to the * functionality usually provided by a {@link ContextLoaderListener}. * @param servletContext the operational servlet context */
protected void prepareWebApplicationContext(ServletContext servletContext) { Object rootContext = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); if (rootContext != null) { if (rootContext == this) { throw new IllegalStateException( "Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ServletContextInitializers!"); } return; } servletContext.log("Initializing Spring embedded WebApplicationContext"); try { servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this); if (logger.isDebugEnabled()) { logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); } setServletContext(servletContext); if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - getStartupDate(); logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); } } catch (RuntimeException | Error ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } } @Override protected Resource getResourceByPath(String path) { if (getServletContext() == null) { return new ClassPathContextResource(path, getClassLoader()); } return new ServletContextResource(getServletContext(), path); } @Override public String getServerNamespace() { return this.serverNamespace; } @Override public void setServerNamespace(String serverNamespace) { this.serverNamespace = serverNamespace; } @Override public void setServletConfig(ServletConfig servletConfig) { this.servletConfig = servletConfig; } @Override public ServletConfig getServletConfig() { return this.servletConfig; }
Returns the WebServer that was created by the context or null if the server has not yet been created.
Returns:the embedded web server
/** * Returns the {@link WebServer} that was created by the context or {@code null} if * the server has not yet been created. * @return the embedded web server */
@Override public WebServer getWebServer() { return this.webServer; }
Utility class to store and restore any user defined scopes. This allow scopes to be registered in an ApplicationContextInitializer in the same way as they would in a classic non-embedded web application context.
/** * Utility class to store and restore any user defined scopes. This allow scopes to be * registered in an ApplicationContextInitializer in the same way as they would in a * classic non-embedded web application context. */
public static class ExistingWebApplicationScopes { private static final Set<String> SCOPES; static { Set<String> scopes = new LinkedHashSet<>(); scopes.add(WebApplicationContext.SCOPE_REQUEST); scopes.add(WebApplicationContext.SCOPE_SESSION); SCOPES = Collections.unmodifiableSet(scopes); } private final ConfigurableListableBeanFactory beanFactory; private final Map<String, Scope> scopes = new HashMap<>(); public ExistingWebApplicationScopes(ConfigurableListableBeanFactory beanFactory) { this.beanFactory = beanFactory; for (String scopeName : SCOPES) { Scope scope = beanFactory.getRegisteredScope(scopeName); if (scope != null) { this.scopes.put(scopeName, scope); } } } public void restore() { this.scopes.forEach((key, value) -> { if (logger.isInfoEnabled()) { logger.info("Restoring user defined scope " + key); } this.beanFactory.registerScope(key, value); }); } } }