FrameworkServlet类源代码分析
前言
FrameworkServlet是一个抽象类,继承自HttpServletBean并实现了ApplicationContextAware接口。它提供了一些方法和属性来配置和管理Web应用程序的上下文。
在这个类中,有一些重要的属性和方法,包括:
- contextClass:用于设置上下文的类。默认情况下,使用DEFAULT_CONTEXT_CLASS属性指定的类。
- contextId:用于设置上下文的ID。
- namespace:命名空间,默认为servlet名称加上"-servlet"后缀。
- contextConfigLocation:上下文的配置文件位置。
- contextInitializers:用于初始化上下文的ApplicationContextInitializer列表。
- publishContext:是否在servlet上下文中发布应用程序上下文。
- publishEvents:是否发布应用程序上下文事件。
- threadContextInheritable:是否使用可继承的线程上下文。
- dispatchOptionsRequest:是否分派OPTIONS请求。
- dispatchTraceRequest:是否分派TRACE请求。
- enableLoggingRequestDetails:是否启用记录请求详细信息。
- webApplicationContext:Web应用程序上下文。
- refreshEventReceived:是否接收到刷新事件。
- onRefreshMonitor:刷新事件监听器。
此外,还有一些用于初始化和处理请求的方法,如initServletBean()、service()和doService()等。
总体而言,FrameworkServlet是一个基于Spring Framework的通用Servlet,用于处理Web请求并管理Web应用程序的上下文。通过配置不同的属性,可以自定义和控制Servlet的行为。
源码分析
类用途分析
/**
* Spring框架的web框架的基本servlet。提供了与Spring应用程序上下文的集成,以JavaBean为基础的整体解决方案。
*
* <p>这个类提供以下功能:
* <ul>
* <li>管理每个servlet的{@link org.springframework.web.context.WebApplicationContext WebApplicationContext}实例。servlet的配置由servlet命名空间中的bean确定。
* <li>在请求处理期间发布事件,无论请求是否成功处理。
* </ul>
*
* <p>子类必须实现{@link #doService}来处理请求。因为这个类扩展了{@link HttpServletBean}而不是直接扩展HttpServlet,所以bean属性会自动映射到它上面。子类可以重写{@link #initFrameworkServlet()}进行自定义初始化。
*
* <p>在servlet init-param级别检测到"contextClass"参数,如果没有找到则回退到默认的上下文类{@link org.springframework.web.context.support.XmlWebApplicationContext XmlWebApplicationContext}。注意,默认情况下,自定义上下文类需要实现{@link org.springframework.web.context.ConfigurableWebApplicationContext ConfigurableWebApplicationContext} SPI。
*
* <p>接受一个可选的"contextInitializerClasses" servlet init-param,指定一个或多个{@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer}类。托管的web应用程序上下文将委托给这些初始化器,允许进行额外的编程配置,例如添加属性源或在{@linkplain org.springframework.context.ConfigurableApplicationContext#getEnvironment()上下文的环境}中激活配置文件。另请参阅{@link org.springframework.web.context.ContextLoader},它为"root" web应用程序上下文支持具有相同语义的"contextInitializerClasses" context-param。
*
* <p>将"contextConfigLocation" servlet init-param传递给上下文实例,将其解析为可能的多个文件路径,可以由任意数量的逗号和空格分隔,例如"test-servlet.xml, myServlet.xml"。如果没有明确指定,则上下文实现应该根据servlet的命名空间构建一个默认位置。
*
* <p>注意:如果存在多个配置位置,则后面加载的文件中的bean定义将覆盖先前加载的文件中的bean定义,至少在使用Spring的默认ApplicationContext实现时是这样的。这可以利用额外的XML文件有意覆盖某些bean定义。
*
* <p>默认命名空间是"'servlet-name'-servlet",例如对于servlet-name为"test"的servlet,命名空间将解析为"test-servlet"(使用XmlWebApplicationContext时,默认位置为"/WEB-INF/test-servlet.xml")。也可以通过"namespace" servlet init-param来显式设置命名空间。
*
* <p>从Spring 3.1开始,{@code FrameworkServlet}现在可以注入一个web应用程序上下文,而不是在内部创建自己的上下文。这在支持程序化注册servlet实例的Servlet 3.0+环境中很有用。有关详细信息,请参阅{@link #FrameworkServlet(WebApplicationContext)}的Javadoc。
*/
@SuppressWarnings("serial")
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
属性部分源码分析
/**
* WebApplicationContext命名空间的后缀。如果在上下文中给此类servlet命名为"test",则servlet使用的命名空间将解析为"test-servlet"。
*/
public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";
/**
* FrameworkServlet的默认上下文类。
* @see org.springframework.web.context.support.XmlWebApplicationContext
*/
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
/**
* 用于WebApplicationContext的ServletContext属性的前缀。完成的名称是servlet名称。
*/
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
/**
* 在单个init-param String值中,任何数量的这些字符都被视为多个值之间的分隔符。
*/
private static final String INIT_PARAM_DELIMITERS = ",; \t\n";
/** 用于查找WebApplicationContext的ServletContext属性。 */
@Nullable
private String contextAttribute;
/** 要创建的WebApplicationContext实现类。 */
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
/** 要分配的WebApplicationContext id。 */
@Nullable
private String contextId;
/** 此servlet的命名空间。 */
@Nullable
private String namespace;
/** 显式的上下文配置位置。 */
@Nullable
private String contextConfigLocation;
/** 应用于上下文的实际ApplicationContextInitializer实例。 */
private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers =
new ArrayList<>();
/** 通过init-param设置的逗号分隔的ApplicationContextInitializer类名。 */
@Nullable
private String contextInitializerClasses;
/** 是否将上下文发布为ServletContext属性。 */
private boolean publishContext = true;
/** 是否在每个请求结束时发布ServletRequestHandledEvent。 */
private boolean publishEvents = true;
/** 是否将LocaleContext和RequestAttributes公开为可继承的子线程。 */
private boolean threadContextInheritable = false;
/** 是否将HTTP OPTIONS请求分派给{@link #doService}。 */
private boolean dispatchOptionsRequest = false;
/** 是否将HTTP TRACE请求分派给{@link #doService}。 */
private boolean dispatchTraceRequest = false;
/** 是否记录潜在敏感信息(DEBUG级别的请求参数 + TRACE级别的头部)。 */
private boolean enableLoggingRequestDetails = false;
/** 此servlet的WebApplicationContext。 */
@Nullable
private WebApplicationContext webApplicationContext;
/** 是否通过{@link #setApplicationContext}注入了WebApplicationContext。 */
private boolean webApplicationContextInjected = false;
/** 用于检测是否已调用onRefresh的标志。 */
private volatile boolean refreshEventReceived = false;
/**标志位refreshEventReceived
用于检测onRefresh
方法是否已经被调用过。这个变量被定义为volatile
,表示它的值在多线程环境下是可见的,即不会被线程本地缓存。 */
private volatile boolean refreshEventReceived = false;
/**监视器对象onRefreshMonitor
用于实现对onRefresh
方法的同步执行。在多线程环境下,多个线程可能同时调用onRefresh
方法,使用这个监视器对象可以确保只有一个线程可以执行onRefresh
方法,其他线程需要等待。 */
private final Object onRefreshMonitor = new Object();
构造函数分析
/**
* 创建一个新的{@code FrameworkServlet},它将基于servlet init-params提供的默认值和值创建自己的内部web应用程序上下文。通常在Servlet 2.5或更早版本的环境中使用,其中servlet注册的唯一选项是通过{@code web.xml},这要求使用无参构造函数。
* <p>调用{@link #setContextConfigLocation}(init-param 'contextConfigLocation')将决定要由{@linkplain #DEFAULT_CONTEXT_CLASS默认XmlWebApplicationContext}加载的XML文件。
* <p>调用{@link #setContextClass}(init-param 'contextClass')会覆盖默认的{@code XmlWebApplicationContext},并允许指定替代类,例如{@code AnnotationConfigWebApplicationContext}。
* <p>调用{@link #setContextInitializerClasses}(init-param 'contextInitializerClasses')指示在refresh()之前应使用哪些{@link ApplicationContextInitializer}类进一步配置内部应用程序上下文。
* @see #FrameworkServlet(WebApplicationContext)
*/
public FrameworkServlet() {
}
/**
* 使用给定的web应用程序上下文创建一个新的{@code FrameworkServlet}。在Servlet 3.0+环境中,通过{@link ServletContext#addServlet} API可以进行基于实例的servlet注册。
* <p>使用此构造函数表示以下属性/ init-params 将被忽略:
* <ul>
* <li>{@link #setContextClass(Class)} / 'contextClass'</li>
* <li>{@link #setContextConfigLocation(String)} / 'contextConfigLocation'</li>
* <li>{@link #setContextAttribute(String)} / 'contextAttribute'</li>
* <li>{@link #setNamespace(String)} / 'namespace'</li>
* </ul>
* <p>给定的web应用程序上下文可能已经{@linkplain ConfigurableApplicationContext#refresh() 刷新},也可能还没有。如果(a)它是{@link ConfigurableWebApplicationContext}的实现并且(b)尚未刷新(推荐的方法),则会发生以下情况:
* <ul>
* <li>如果给定的上下文还没有{@linkplain ConfigurableApplicationContext#setParent parent},则将根应用程序上下文设置为父上下文。</li>
* <li>如果给定的上下文尚未分配{@linkplain ConfigurableApplicationContext#setId id},则将分配一个id。</li>
* <li>{@code ServletContext}和{@code ServletConfig}对象将委托给应用程序上下文。</li>
* <li>将调用{@link #postProcessWebApplicationContext}。</li>
* <li>将应用任何通过"contextInitializerClasses" init-param或通过{@link #setContextInitializers}属性指定的{@link ApplicationContextInitializer ApplicationContextInitializers}。</li>
* <li>将调用{@link ConfigurableApplicationContext#refresh refresh()}。</li>
* </ul>
* 如果上下文已经刷新或不实现{@code ConfigurableWebApplicationContext},则不会发生上述任何情况,因为假设用户根据自己的特定需求执行了这些操作(或未执行)。
* <p>有关用法示例,请参见{@link org.springframework.web.WebApplicationInitializer}。
* @param webApplicationContext 要使用的上下文
* @see #initWebApplicationContext
* @see #configureAndRefreshWebApplicationContext
* @see org.springframework.web.WebApplicationInitializer
*/
public FrameworkServlet(WebApplicationContext webApplicationContext) {
this.webApplicationContext = webApplicationContext;
}
属性的获取及设置方法分析
/**
* 设置应该用于检索此servlet应该使用的{@link WebApplicationContext}的ServletContext属性的名称。
*/
public void setContextAttribute(@Nullable String contextAttribute) {
this.contextAttribute = contextAttribute;
}
/**
* 返回应该用于检索此servlet应该使用的{@link WebApplicationContext}的ServletContext属性的名称。
*/
@Nullable
public String getContextAttribute() {
return this.contextAttribute;
}
/**
* 设置自定义的上下文类。此类必须是{@link org.springframework.web.context.WebApplicationContext}类型。
* <p>当使用默认的FrameworkServlet实现时,上下文类还必须实现{@link org.springframework.web.context.ConfigurableWebApplicationContext}接口。
* @see #createWebApplicationContext
*/
public void setContextClass(Class<?> contextClass) {
this.contextClass = contextClass;
}
/**
* 返回自定义的上下文类。
*/
public Class<?> getContextClass() {
return this.contextClass;
}
/**
* 指定自定义的WebApplicationContext id,用于底层BeanFactory的序列化id。
*/
public void setContextId(@Nullable String contextId) {
this.contextId = contextId;
}
/**
* 返回自定义的WebApplicationContext id,如果有的话。
*/
@Nullable
public String getContextId() {
return this.contextId;
}
/**
* 设置此servlet的自定义命名空间,用于构建默认的上下文配置位置。
*/
public void setNamespace(String namespace) {
this.namespace = namespace;
}
/**
* 返回此servlet的命名空间,如果没有设置自定义命名空间,则返回默认方案:例如,对于名为"test"的servlet,返回"test-servlet"。
*/
public String getNamespace() {
return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
}
/**
* 显式设置上下文配置位置,而不是依赖于从命名空间构建的默认位置。此位置字符串可以由多个位置组成,由任意数量的逗号和空格分隔。
*/
public void setContextConfigLocation(@Nullable String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}
/**
* 返回显式的上下文配置位置,如果有的话。
*/
@Nullable
public String getContextConfigLocation() {
return this.contextConfigLocation;
}
/**
* 设置用于初始化该{@code FrameworkServlet}使用的应用程序上下文的{@link ApplicationContextInitializer}实例。
* @see #configureAndRefreshWebApplicationContext
* @see #applyInitializers
*/
@SuppressWarnings("unchecked")
public void setContextInitializers(@Nullable ApplicationContextInitializer<?>... initializers) {
if (initializers != null) {
for (ApplicationContextInitializer<?> initializer : initializers) {
this.contextInitializers.add((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer);
}
}
}
/**
* 设置完全限定的{@link ApplicationContextInitializer}类名集合,根据可选的"contextInitializerClasses" servlet init-param。
* @see #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext)
* @see #applyInitializers(ConfigurableApplicationContext)
*/
public void setContextInitializerClasses(String contextInitializerClasses) {
this.contextInitializerClasses = contextInitializerClasses;
}
/**
* 设置是否将此servlet的上下文发布为ServletContext属性,对Web容器中的所有对象都可用。默认为"true"。
* <p>这在测试期间特别方便,尽管是否让其他应用程序对象以这种方式访问上下文是一个有争议的问题。
*/
public void setPublishContext(boolean publishContext) {
this.publishContext = publishContext;
}
/**
* 设置此servlet是否应在每个请求结束时发布一个ServletRequestHandledEvent。默认为"true";
* 可以关闭以获得轻微的性能提升,前提是没有ApplicationListeners依赖于此类事件。
* @see org.springframework.web.context.support.ServletRequestHandledEvent
*/
public void setPublishEvents(boolean publishEvents) {
this.publishEvents = publishEvents;
}
/**
* 设置是否将LocaleContext和RequestAttributes作为可继承的,
* 供子线程使用(使用{@link java.lang.InheritableThreadLocal})。
* <p>默认为"false",以避免在生成的后台线程上产生副作用。
* 将此设置为"true"以启用继承,用于在请求处理期间生成的自定义子线程,
* 仅用于此请求(即,在其初始任务之后结束,而不再重用线程)。
* <p><b>警告:</b>如果访问配置为可能按需添加新线程的线程池(例如JDK的{@link java.util.concurrent.ThreadPoolExecutor}),
* 则不要为子线程使用继承,因为这将使继承上下文暴露给此类池化线程。
*/
public void setThreadContextInheritable(boolean threadContextInheritable) {
this.threadContextInheritable = threadContextInheritable;
}
/**
* 设置此servlet是否将HTTP OPTIONS请求分派到{@link #doService}方法。
* <p>{@code FrameworkServlet}中默认为"false",应用{@link javax.servlet.http.HttpServlet}的默认行为(即对OPTIONS请求的响应枚举所有标准HTTP请求方法)。
* 但是请注意,从4.3开始,{@code DispatcherServlet}默认将此属性设置为"true",因为其对OPTIONS的内置支持。
* <p>如果您希望OPTIONS请求通过常规调度链路传递,就像其他HTTP请求一样,请打开此标志。这通常意味着您的控制器将接收这些请求;请确保这些端点实际上能够处理OPTIONS请求。
* <p>请注意,如果您的控制器恰好未设置'Allow'头(作为OPTIONS响应所需),无论如何都会应用HttpServlet的默认OPTIONS处理。
*/
public void setDispatchOptionsRequest(boolean dispatchOptionsRequest) {
this.dispatchOptionsRequest = dispatchOptionsRequest;
}
/**
* 设置是否将HTTP TRACE请求发送到doService方法。
* <p>默认值为“false”,应用{@link javax.servlet.http.HttpServlet}的默认行为(即反射接收到的消息给客户端)。
* <p>如果您希望TRACE请求通过常规调度链路传递,就像其他HTTP请求一样,请打开此标志。这通常意味着您的控制器将接收这些请求;请确保这些端点实际上能够处理TRACE请求。
* <p>请注意,如果您的控制器不生成内容类型为'message/http'的响应(TRACE响应所需的内容类型),那么无论如何都会应用HttpServlet的默认TRACE处理。
*/
public void setDispatchTraceRequest(boolean dispatchTraceRequest) {
this.dispatchTraceRequest = dispatchTraceRequest;
}
/**
* 是否以DEBUG级别记录请求参数,并在TRACE级别记录头信息。两者均可能包含敏感信息。
* <p>默认值为{@code false},因此不显示请求详细信息。
* @param enable 是否启用
* @since 5.1
*/
public void setEnableLoggingRequestDetails(boolean enable) {
this.enableLoggingRequestDetails = enable;
}
/**
* 是否允许在DEBUG和TRACE级别记录可能包含敏感请求详细信息。
* @since 5.1
*/
public boolean isEnableLoggingRequestDetails() {
return this.enableLoggingRequestDetails;
}
/**
* 通过Spring的{@link ApplicationContextAware}调用,注入当前的应用程序上下文。此方法允许在现有的{@link WebApplicationContext}中注册FrameworkServlet作为Spring bean,而不是{@link #findWebApplicationContext()找到}一个已经{@link org.springframework.web.context.ContextLoaderListener引导}的上下文。
* <p>主要用于支持嵌入式servlet容器中的使用。
* @since 4.0
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
this.webApplicationContext = (WebApplicationContext) applicationContext;
this.webApplicationContextInjected = true;
}
}
/**
* {@link HttpServletBean}的重写方法,在设置了任何bean属性后调用。创建此servlet的WebApplicationContext。
*/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}