/*
* Copyright 2012-2019 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.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.util.StringUtils;
import org.springframework.web.context.ContextLoader;
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;
/**
* 一个 {@link WebApplicationContext},可用于从包含的
* {@link ServletWebServerFactory} bean。
* <p>
* 此上下文将创建、初始化和运行 {@link WebServer}
* {@link ApplicationContext} 中的单个 {@link ServletWebServerFactory} bean
* 本身。 {@link ServletWebServerFactory} 可以免费使用标准的 Spring 概念
* (例如依赖注入、生命周期回调和属性占位符变量)。
* <p>
* 此外,上下文中定义的任何 {@link Servlet} 或 {@link Filter} bean 都将被
* 自动注册到网络服务器。在单个 Servlet bean 的情况下,
* '/' 映射将被使用。如果找到多个 Servlet bean,则使用小写 bean
* 名称将用作映射前缀。任何名为“dispatcherServlet”的 Servlet 将
* 总是映射到'/'。过滤器 bean 将映射到所有 URL ('/*')。
* <p>
* 对于更高级的配置,上下文可以改为定义实现
* {@link ServletContextInitializer} 接口(最常见的是
* {@link ServletRegistrationBean}s 和/或 {@link FilterRegistrationBean}s)。阻止
* 双重注册,使用{@link ServletContextInitializer} bean 会禁用
* 自动 Servlet 和过滤器 bean 注册。
* <p>
* 虽然这个上下文可以直接使用,但是大多数开发者应该考虑使用
* {@link AnnotationConfigServletWebServerApplicationContext} 或
* {@link XmlServletWebServerApplicationContext} 变体。
*
* @author Phillip Webb
* @author Dave Syer
* @see AnnotationConfigServletWebServerApplicationContext
* @see XmlServletWebServerApplicationContext
* @see ServletWebServerFactory
* @since 2.0.0
*/
public class ServletWebServerApplicationContext extends GenericWebApplicationContext
implements ConfigurableWebServerApplicationContext {
private static final Log logger = LogFactory.getLog(ServletWebServerApplicationContext.class);
/**
* DispatcherServlet bean 名称的常量值。具有此名称的 Servlet bean
* 被认为是“主”servlet,并被自动赋予“/”的映射
* 默认。要更改默认行为,您可以使用
* {@link ServletRegistrationBean} 或不同的 bean 名称。
*/
public static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet";
private volatile WebServer webServer;
private ServletConfig servletConfig;
private String serverNamespace;
/**
* 创建一个新的 {@link ServletWebServerApplicationContext}。
*/
public ServletWebServerApplicationContext() {
}
/**
* 使用给定的创建一个新的 {@link ServletWebServerApplicationContext}
* {@code DefaultListableBeanFactory}。
*
* @param beanFactory the DefaultListableBeanFactory instance to use for this context
*/
public ServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {
super(beanFactory);
}
/**
* 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 {
//调用父类的refresh方法
super.refresh();
} catch (RuntimeException ex) {
stopAndReleaseWebServer();
throw ex;
}
}
@Override
protected void onRefresh() {
//调用父类的onRefresh方法
super.onRefresh();
try {
//创建webServer嵌入web服务器
createWebServer();
} catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
@Override
protected void finishRefresh() {
super.finishRefresh();
WebServer webServer = startWebServer();
if (webServer != null) {
publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
@Override
protected void onClose() {
super.onClose();
stopAndReleaseWebServer();
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
//获取WebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
//获取web服务器
this.webServer = factory.getWebServer(getSelfInitializer());
} else if (servletContext != null) {
try {
//
getSelfInitializer().onStartup(servletContext);
} catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
/**
* R返回应该用于创建的 {@link ServletWebServerFactory}
* 嵌入式 {@link WebServer}。默认情况下,此方法在
* 上下文本身。
*
* @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);
}
/**
* 返回 {@link ServletContextInitializer} 将用于完成
* 设置此 {@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);
//注册servletContext这个bean的作用域是application全
registerApplicationScope(servletContext);
//注册servlet相关的bean
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
//执行所有的ServletContextInitializer
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();
}
/**
* 返回 {@link ServletContextInitializer} 应该与嵌入式
* 网络服务器。默认情况下,此方法将首先尝试查找
* {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain
* {@link EventListener} beans.
*
* @return the servlet initializer beans
*/
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}
/**
* 使用给定的完全加载准备 {@link WebApplicationContext}
* {@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;
}
Log logger = LogFactory.getLog(ContextLoader.class);
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;
}
}
private WebServer startWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.start();
}
return webServer;
}
private void stopAndReleaseWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
try {
webServer.stop();
this.webServer = null;
} catch (Exception ex) {
throw new IllegalStateException(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;
}
/**
* 返回由上下文创建的 {@link WebServer} 或 {@code null},如果
* 服务器尚未创建。
*
* @return the embedded web server
*/
@Override
public WebServer getWebServer() {
return this.webServer;
}
/**
* 用于存储和恢复任何用户定义范围的实用程序类。这允许范围是
* 以与在 ApplicationContextInitializer 中相同的方式在 ApplicationContextInitializer 中注册
* 经典的非嵌入式 Web 应用程序上下文。
*/
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);
});
}
}
}