SpringBoot ServletContext初始化深度源码剖析(58)

SpringBoot ServletContext初始化深度源码剖析

一、ServletContext基础概念与作用

1.1 ServletContext的定义与核心功能

ServletContext是Java EE规范中定义的重要接口,它代表了整个Web应用的上下文环境,为Web应用提供了全局共享的资源与信息。在Servlet规范中,每个Web应用在运行时都会有且仅有一个ServletContext实例,该实例由Servlet容器创建并管理。

从功能层面看,ServletContext主要承担以下职责:

  • 共享数据存储:可以通过setAttribute(String name, Object object)getAttribute(String name)方法在整个Web应用范围内存储和获取共享数据,类似于一个全局的键值对仓库。
  • 资源访问:提供getResourceAsStream(String path)等方法,允许Web应用访问部署目录下的资源文件,如读取配置文件、静态资源等。
  • Servlet容器信息获取:通过getServerInfo()获取Servlet容器的版本信息,getMajorVersion()getMinorVersion()获取Servlet规范的主、次版本号等。
  • 监听器注册:ServletContext是Servlet规范中事件监听器(如ServletContextListener)的注册场所,通过注册监听器可以在Web应用启动、停止等关键节点执行自定义逻辑。

1.2 ServletContext在SpringBoot中的地位

在SpringBoot应用中,ServletContext是连接Web应用与Servlet容器的桥梁,SpringBoot对ServletContext的管理和初始化进行了深度整合与优化。SpringBoot基于ServletContext构建了Web应用上下文环境,ServletContext的正确初始化是SpringBoot Web应用能够正常运行的基础。

SpringBoot通过ServletContext实现以下功能:

  • Web应用配置管理:ServletContext的初始化参数、属性等信息会影响SpringBoot应用的配置与行为。
  • Servlet、Filter、Listener注册:SpringBoot借助ServletContext将DispatcherServlet、自定义Servlet、Filter以及Listener等组件注册到Servlet容器中。
  • 资源加载与路径解析:利用ServletContext提供的资源访问方法,SpringBoot实现了静态资源的加载、模板文件的查找等功能 。

1.3 ServletContext与Spring上下文的关系

在SpringBoot应用中,ServletContext与Spring应用上下文(ApplicationContext)紧密关联但职责不同。ServletContext是Servlet规范的产物,主要服务于Web应用的底层运行环境;而Spring应用上下文则是Spring框架的核心,负责管理Bean的生命周期、依赖注入等。

两者的联系体现在:

  • 上下文关联:SpringBoot会将Spring应用上下文与ServletContext进行绑定,通过WebApplicationContextUtils.getWebApplicationContext(ServletContext sc)方法可以从ServletContext中获取对应的Spring应用上下文实例。
  • 配置传递:ServletContext的初始化参数可以作为Spring应用上下文配置的一部分,影响Spring Bean的创建与初始化。
  • 事件交互:ServletContext的生命周期事件(如启动、停止)会触发Spring中对应的事件机制,使得开发者可以在Spring层面监听并处理ServletContext的变化 。

二、SpringBoot应用启动与ServletContext初始化触发

2.1 SpringBoot应用启动入口

SpringBoot应用的启动入口是SpringApplication.run(Class<?> primarySource, String... args)方法,该方法是整个应用初始化流程的起点。在这个方法中,SpringBoot会进行一系列的初始化操作,包括创建Spring应用上下文、加载配置信息等,其中就包含ServletContext的初始化准备工作。

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}

上述代码展示了run方法的调用链,最终会实例化SpringApplication对象并调用其run方法。在SpringApplication的构造函数中,会根据应用类型(Web应用或非Web应用)进行不同的初始化操作,对于Web应用,会初始化与Servlet相关的配置。

2.2 应用上下文创建与ServletContext关联

SpringApplication.run方法执行过程中,会创建应用上下文。对于Servlet Web应用,SpringBoot会创建ServletWebServerApplicationContext,该类继承自GenericWebApplicationContext,是SpringBoot中Servlet Web应用上下文的核心实现类。

public class ServletWebServerApplicationContext extends GenericWebApplicationContext
        implements ConfigurableWebServerApplicationContext {

    @Override
    protected void onRefresh() {
        super.onRefresh();
        try {
            createWebServer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start web server", ex);
        }
    }

    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
            ServletWebServerFactory factory = getWebServerFactory();
            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();
    }
}

ServletWebServerApplicationContextonRefresh方法中,会调用createWebServer方法,该方法是ServletContext初始化的关键步骤。如果当前没有WebServer实例且ServletContext也未初始化,会通过ServletWebServerFactory创建WebServer,并在创建过程中初始化ServletContext。

2.3 ServletWebServerFactory的作用

ServletWebServerFactory是SpringBoot中用于创建WebServer的工厂接口,它的实现类(如TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactory)会根据不同的Servlet容器类型创建对应的WebServer实例。

TomcatServletWebServerFactory为例:

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
        implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {

    @Override
    public TomcatServletWebServer getWebServer(ServletContextInitializer... initializers) {
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
        return getTomcatWebServer(tomcat);
    }
}

getWebServer方法中,会创建Tomcat实例,配置连接器(Connector)等组件,并调用prepareContext方法初始化ServletContext相关配置,将ServletContextInitializer传递给ServletContext,用于后续的初始化工作 。

三、ServletContextInitializer的作用与实现

3.1 ServletContextInitializer接口定义

ServletContextInitializer是SpringBoot中用于初始化ServletContext的核心接口,它定义了onStartup(ServletContext servletContext)方法,所有实现该接口的类都会在ServletContext初始化过程中被调用,以完成自定义的初始化逻辑。

public interface ServletContextInitializer {
    void onStartup(ServletContext servletContext) throws ServletException;
}

通过实现ServletContextInitializer接口,开发者可以在ServletContext初始化时:

  • 注册Servlet、Filter、Listener等组件。
  • 设置ServletContext的初始化参数。
  • 执行其他与ServletContext相关的初始化操作。

3.2 SpringBoot内置的ServletContextInitializer实现

SpringBoot提供了多个内置的ServletContextInitializer实现类,这些类在ServletContext初始化过程中发挥着重要作用:

  • ServletWebServerApplicationContext.SelfInitializer:该类是ServletWebServerApplicationContext的内部类,实现了ServletContextInitializer接口,主要负责将Spring应用上下文与ServletContext进行关联,并注册Spring相关的Servlet、Filter等组件。
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);
    }
}

selfInitialize方法中,会先准备Web应用上下文,注册应用作用域,然后将Spring环境相关的Bean注册到ServletContext中,最后调用其他ServletContextInitializeronStartup方法。

  • DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration:该类通过DispatcherServletRegistrationBeanDispatcherServlet注册到ServletContext中,是Spring MVC核心组件DispatcherServlet初始化的关键步骤。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {

    @Bean
    @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServletRegistrationBean dispatcherServletRegistration(
            DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties,
            ObjectProvider<MultipartConfigElement> multipartConfig) {
        DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(
                dispatcherServlet, webMvcProperties.getServlet().getPath());
        registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
        registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        multipartConfig.ifAvailable(registration::setMultipartConfig);
        return registration;
    }
}

DispatcherServletRegistrationBean继承自ServletRegistrationBean,在onStartup方法中会将DispatcherServlet注册到ServletContext。

3.3 自定义ServletContextInitializer的使用

开发者可以通过自定义实现ServletContextInitializer接口,在ServletContext初始化时执行特定的逻辑。例如,自定义一个用于注册自定义Servlet的ServletContextInitializer

public class CustomServletContextInitializer implements ServletContextInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        ServletRegistration.Dynamic registration = servletContext.addServlet("customServlet", CustomServlet.class);
        registration.addMapping("/custom");
        registration.setLoadOnStartup(1);
    }
}

然后在SpringBoot应用中,通过配置将自定义的ServletContextInitializer注册到应用中,可以在SpringApplication的构造函数中添加:

public class MySpringBootApplication {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MySpringBootApplication.class);
        application.addInitializers(new CustomServletContextInitializer());
        application.run(args);
    }
}

这样在ServletContext初始化时,自定义的逻辑就会被执行 。

四、ServletContext初始化参数处理

4.1 初始化参数的定义与作用

ServletContext的初始化参数是在Web应用部署描述符(如web.xml,在SpringBoot中也可通过配置指定)中定义的键值对信息,这些参数可以在整个Web应用范围内共享,用于配置Web应用的一些全局属性。例如,在传统的web.xml中可以这样定义初始化参数:

<context-param>
    <param-name>app.version</param-name>
    <param-value>1.0</param-value>
</context-param>

在SpringBoot中,虽然不再依赖web.xml,但仍然支持通过多种方式设置ServletContext的初始化参数,这些参数可以被Spring应用上下文、Servlet、Filter等组件读取并使用,用于控制应用的行为。

4.2 SpringBoot中初始化参数的设置方式

  • 通过配置文件设置:在application.propertiesapplication.yml中,可以使用server.servlet.context-parameters前缀来设置ServletContext的初始化参数。例如:
server.servlet.context-parameters.app.version=1.0
server.servlet.context-parameters.env=prod

SpringBoot会在ServletContext初始化过程中将这些配置转换为对应的初始化参数。

  • 通过代码设置:可以通过实现ServletContextInitializer接口,在onStartup方法中手动设置ServletContext的初始化参数。
public class CustomServletContextInitializer implements ServletContextInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        servletContext.setInitParameter("app.version", "1.0");
        servletContext.setInitParameter("env", "prod");
    }
}
  • 通过嵌入式Servlet容器配置:以Tomcat为例,可以通过TomcatServletWebServerFactory的相关方法设置初始化参数。
@Configuration
public class TomcatConfig {

    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.addContextCustomizers(context -> {
            context.addParameter("app.version", "1.0");
            context.addParameter("env", "prod");
        });
        return factory;
    }
}

4.3 初始化参数的读取与使用

在SpringBoot应用中,读取ServletContext初始化参数主要有以下几种方式:

  • 在Servlet中读取:在自定义Servlet中,可以通过getServletContext().getInitParameter("paramName")方法获取初始化参数。
public class CustomServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String appVersion = getServletContext().getInitParameter("app.version");
        response.getWriter().println("App Version: " + appVersion);
    }
}
  • 在Spring Bean中读取:可以通过注入ServletContext实例,然后获取初始化参数。
import javax.servlet.ServletContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class AppConfig {

    private final ServletContext servletContext;

    @Autowired
    public AppConfig(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    public String getAppVersion() {
        return servletContext.getInitParameter("app.version");
    }
}
  • 在Spring配置类中读取:在配置类中,可以通过实现ServletContextAware接口获取ServletContext实例,进而读取参数。
import javax.servlet.ServletContext;
import org.springframework.context.ServletContextAware;
import org.springframework.stereotype.Component;

@Component
public class AppConfig implements ServletContextAware {

    private ServletContext servletContext;

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    public String getAppVersion() {
        return servletContext.getInitParameter("app.version");
    }
}

五、Servlet、Filter与Listener注册流程

5.1 Servlet注册过程

在SpringBoot中,Servlet的注册主要通过ServletRegistrationBean及其子类来完成,DispatcherServlet的注册就是一个典型例子。ServletRegistrationBean实现了ServletContextInitializer接口,在其onStartup方法中会将Servlet注册到ServletContext。

public class ServletRegistrationBean<T extends Servlet> implements ServletContextInitializer, Ordered {

    private final T servlet;
    private final String[] urlMappings;

    public ServletRegistrationBean(T servlet, String... urlMappings) {
        this.servlet = servlet;
        this.urlMappings = urlMappings;
    }

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        String name = getServletName();
        Dynamic registration = servletContext.addServlet(name, this.servlet);
        if (registration == null) {
            throw new IllegalStateException("Failed to register servlet with name '" + name + "'. Check if there is already a servlet registered under the same name.");
        }
        configure(registration);
        for (String urlMapping : this.urlMappings) {
            registration.addMapping(urlMapping);
        }
    }

    protected void configure(Dynamic registration) {
        registration.setLoadOnStartup(getLoadOnStartup());
        registration.setAsyncSupported(isAsyncSupported());
        registration.setInitParameters(getInitParameters());
    }
}

onStartup方法中,首先会通过addServlet方法将Servlet添加到ServletContext,然后配置Servlet的相关属性(如加载顺序、是否支持异步等),最后设置Servlet的URL映射。

5.2 Filter注册过程

Filter的注册与Servlet类似,主要通过FilterRegistrationBean来完成,该类同样实现了ServletContextInitializer接口。

public class FilterRegistrationBean<T extends Filter> extends RegistrationBean<FilterRegistrationBean<T>>
        implements ServletContextInitializer, Ordered {

    private final T filter;
    private List<String> urlPatterns;

    public FilterRegistrationBean(T filter) {
        this.filter = filter;
        this.urlPatterns = new ArrayList<>();
    }

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        String name = getFilterName();
        Dynamic registration = servletContext.addFilter(name, this.filter);
        if (registration == null) {
            throw new IllegalStateException("Failed to register

Filter的注册与Servlet类似,主要通过FilterRegistrationBean来完成,该类同样实现了ServletContextInitializer接口。

public class FilterRegistrationBean<T extends Filter> extends RegistrationBean<FilterRegistrationBean<T>>
        implements ServletContextInitializer, Ordered {

    private final T filter;
    private List<String> urlPatterns;

    public FilterRegistrationBean(T filter) {
        this.filter = filter;
        this.urlPatterns = new ArrayList<>();
    }

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        String name = getFilterName();
        Dynamic registration = servletContext.addFilter(name, this.filter);
        if (registration == null) {
            throw new IllegalStateException("Failed to register filter with name '" + name + "'. Check if there is already a filter registered under the same name.");
        }
        configure(registration);
        if (this.urlPatterns.isEmpty()) {
            this.urlPatterns = DEFAULT_URL_MAPPINGS;
        }
        registration.addMappingForUrlPatterns(getDispatcherTypes(), isMatchAfter(),
                this.urlPatterns.toArray(new String[0]));
    }

    protected void configure(Dynamic registration) {
        registration.setAsyncSupported(isAsyncSupported());
        registration.setInitParameters(getInitParameters());
    }
}

onStartup方法中,首先会通过addFilter方法将Filter添加到ServletContext,然后配置Filter的相关属性(如是否支持异步等),最后设置Filter的URL映射和Dispatcher类型。

5.3 Listener注册过程

Listener的注册通过ServletListenerRegistrationBean完成,该类同样实现了ServletContextInitializer接口。

public class ServletListenerRegistrationBean<T extends EventListener>
        implements ServletContextInitializer, Ordered {

    private final T listener;

    public ServletListenerRegistrationBean(T listener) {
        this.listener = listener;
    }

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        if (this.listener instanceof ServletContextListener) {
            servletContext.addListener((ServletContextListener) this.listener);
        }
        else if (this.listener instanceof ServletRequestListener) {
            servletContext.addListener((ServletRequestListener) this.listener);
        }
        else if (this.listener instanceof HttpSessionListener) {
            servletContext.addListener((HttpSessionListener) this.listener);
        }
        else if (this.listener instanceof ServletContextAttributeListener) {
            servletContext.addListener((ServletContextAttributeListener) this.listener);
        }
        else if (this.listener instanceof ServletRequestAttributeListener) {
            servletContext.addListener((ServletRequestAttributeListener) this.listener);
        }
        else if (this.listener instanceof HttpSessionAttributeListener) {
            servletContext.addListener((HttpSessionAttributeListener) this.listener);
        }
        else {
            // 对于其他类型的监听器,使用反射注册
            try {
                Method method = servletContext.getClass().getMethod("addListener", this.listener.getClass());
                method.invoke(servletContext, this.listener);
            }
            catch (Exception ex) {
                throw new IllegalStateException("Failed to register listener [" + this.listener + "]", ex);
            }
        }
    }
}

onStartup方法中,会根据监听器的类型,调用ServletContext相应的addListener方法进行注册。对于标准的Servlet监听器接口,直接调用对应的方法;对于其他类型的监听器,使用反射进行注册。

5.4 注册顺序与优先级

在SpringBoot中,Servlet、Filter和Listener的注册顺序由它们实现的Ordered接口或@Order注解决定。数值越小,优先级越高,越早被注册。

ServletRegistrationBeanFilterRegistrationBeanServletListenerRegistrationBean都实现了Ordered接口,默认优先级如下:

  • ServletRegistrationBean:默认优先级为Ordered.LOWEST_PRECEDENCE(Integer.MAX_VALUE)
  • FilterRegistrationBean:默认优先级为Ordered.LOWEST_PRECEDENCE - 100
  • ServletListenerRegistrationBean:默认没有实现Ordered接口,注册顺序取决于Spring容器中Bean的创建顺序

可以通过setOrder方法或@Order注解自定义注册顺序:

@Bean
public FilterRegistrationBean<CustomFilter> customFilterRegistration() {
    FilterRegistrationBean<CustomFilter> registration = new FilterRegistrationBean<>();
    registration.setFilter(new CustomFilter());
    registration.addUrlPatterns("/*");
    registration.setOrder(1); // 设置优先级
    return registration;
}

5.5 动态注册示例

下面是一个完整的示例,展示如何在SpringBoot中动态注册Servlet、Filter和Listener:

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.io.IOException;
import java.util.EnumSet;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class ServletRegistrationExample {

    public static void main(String[] args) {
        SpringApplication.run(ServletRegistrationExample.class, args);
    }

    // 注册自定义Servlet
    @Bean
    public ServletRegistrationBean<CustomServlet> customServletRegistration() {
        ServletRegistrationBean<CustomServlet> registration = new ServletRegistrationBean<>();
        registration.setServlet(new CustomServlet());
        registration.addUrlPatterns("/custom");
        registration.setLoadOnStartup(1);
        return registration;
    }

    // 注册自定义Filter
    @Bean
    public FilterRegistrationBean<CustomFilter> customFilterRegistration() {
        FilterRegistrationBean<CustomFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new CustomFilter());
        registration.addUrlPatterns("/*");
        registration.setOrder(1);
        return registration;
    }

    // 注册自定义Listener
    @Bean
    public ServletListenerRegistrationBean<CustomSessionListener> customSessionListenerRegistration() {
        ServletListenerRegistrationBean<CustomSessionListener> registration = new ServletListenerRegistrationBean<>();
        registration.setListener(new CustomSessionListener());
        return registration;
    }

    // 自定义Servlet
    public static class CustomServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.getWriter().println("Hello from CustomServlet!");
        }
    }

    // 自定义Filter
    public static class CustomFilter implements Filter {
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            System.out.println("Before request processing");
            chain.doFilter(request, response);
            System.out.println("After request processing");
        }
    }

    // 自定义Listener
    public static class CustomSessionListener implements HttpSessionListener {
        @Override
        public void sessionCreated(HttpSessionEvent se) {
            System.out.println("Session created: " + se.getSession().getId());
        }

        @Override
        public void sessionDestroyed(HttpSessionEvent se) {
            System.out.println("Session destroyed: " + se.getSession().getId());
        }
    }
}

六、嵌入式Servlet容器与ServletContext交互

6.1 嵌入式Servlet容器概述

SpringBoot支持三种主要的嵌入式Servlet容器:Tomcat、Jetty和Undertow。这些容器无需单独安装,而是作为依赖项直接嵌入到应用中,使得SpringBoot应用可以像普通Java应用一样直接运行。

嵌入式Servlet容器的主要优势:

  • 简化部署:无需单独安装和配置Servlet容器
  • 提高开发效率:可以快速启动和调试应用
  • 统一环境:开发、测试和生产环境保持一致
  • 便于微服务架构:每个微服务可以独立运行在自己的容器中

6.2 Tomcat容器集成与ServletContext交互

在SpringBoot中,Tomcat是默认的嵌入式Servlet容器。当引入spring-boot-starter-web依赖时,会自动包含Tomcat相关依赖。

Tomcat与ServletContext的交互主要通过TomcatServletWebServerFactory类实现:

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
        implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {

    @Override
    public WebServer getWebServer(ServletContextInitializer... initializers) {
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        
        // 配置连接器
        Connector connector = new Connector(this.protocol);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        
        // 配置引擎和主机
        tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        
        // 准备上下文
        prepareContext(tomcat.getHost(), initializers);
        
        return getTomcatWebServer(tomcat);
    }

    protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
        TomcatEmbeddedContext context = new TomcatEmbeddedContext();
        context.setName(getContextPath());
        context.setDisplayName(getDisplayName());
        context.setPath(getContextPath());
        context.setDocBase(getDocumentRoot() != null ? getDocumentRoot().getAbsolutePath() : null);
        context.addLifecycleListener(new FixContextListener());
        context.setParentClassLoader((this.resourceLoader != null) ?
                this.resourceLoader.getClassLoader() : getClass().getClassLoader());
        
        // 添加上下文配置
        ContextConfig contextConfig = new ContextConfig();
        context.addLifecycleListener(contextConfig);
        contextConfig.setContext(context);
        
        // 添加初始化器
        TomcatStarter starter = new TomcatStarter(initializers);
        if (context instanceof TomcatEmbeddedContext) {
            ((TomcatEmbeddedContext) context).setStarter(starter);
            ((TomcatEmbeddedContext) context).addServletContainerInitializer(starter, Collections.emptySet());
        }
        
        // 将上下文添加到主机
        host.addChild(context);
    }
}

prepareContext方法中,会创建Tomcat的Context对象,并将SpringBoot的ServletContextInitializer包装成Tomcat的ServletContainerInitializer,以便在容器启动时调用这些初始化器来配置ServletContext。

6.3 Jetty容器集成与ServletContext交互

Jetty是另一种常用的嵌入式Servlet容器,可以通过引入spring-boot-starter-jetty依赖来使用。

Jetty与ServletContext的交互主要通过JettyServletWebServerFactory类实现:

public class JettyServletWebServerFactory extends AbstractServletWebServerFactory
        implements ConfigurableJettyWebServerFactory, ResourceLoaderAware {

    @Override
    public WebServer getWebServer(ServletContextInitializer... initializers) {
        Server server = new Server();
        ServerConnector connector = new ServerConnector(server);
        if (getPort() >= 0) {
            connector.setPort(getPort());
        }
        server.addConnector(connector);
        
        // 配置线程池
        configureThreadPool(server);
        
        // 创建WebAppContext
        WebAppContext context = new WebAppContext();
        context.setContextPath(getContextPath());
        context.setDisplayName(getDisplayName());
        if (getDocumentRoot() != null) {
            context.setResourceBase(getDocumentRoot().getAbsolutePath());
        }
        
        // 配置上下文
        configureWebAppContext(context, initializers);
        
        // 添加上下文到服务器
        server.setHandler(context);
        
        // 添加生命周期监听器
        addServerCustomizers(new AddServerLifecycleListenerCustomizer(
                new JettyServerLifecycleListener(this)));
        
        return new JettyWebServer(server, getPort() >= 0);
    }

    protected void configureWebAppContext(WebAppContext context,
            ServletContextInitializer... initializers) {
        // 设置上下文参数
        for (Map.Entry<String, String> entry : getInitParameters().entrySet()) {
            context.setInitParameter(entry.getKey(), entry.getValue());
        }
        
        // 设置错误页面
        configureErrorPages(context);
        
        // 添加ServletContextInitializer处理器
        context.addBean(new ServletContextInitializerConfiguration(initializers), true);
        
        // 配置类加载器
        if (this.resourceLoader != null) {
            context.setClassLoader(this.resourceLoader.getClassLoader());
        }
    }
}

configureWebAppContext方法中,会创建Jetty的WebAppContext对象,并将ServletContextInitializer包装成Jetty的EventListener,以便在容器启动时初始化ServletContext。

6.4 Undertow容器集成与ServletContext交互

Undertow是Red Hat开发的轻量级Servlet容器,可以通过引入spring-boot-starter-undertow依赖来使用。

Undertow与ServletContext的交互主要通过UndertowServletWebServerFactory类实现:

public class UndertowServletWebServerFactory extends AbstractServletWebServerFactory
        implements ConfigurableUndertowWebServerFactory, ResourceLoaderAware {

    @Override
    public WebServer getWebServer(ServletContextInitializer... initializers) {
        Undertow.Builder builder = Undertow.builder();
        
        // 配置连接器
        configureConnectors(builder);
        
        // 创建Servlet处理容器
        DeploymentInfo deploymentInfo = createDeploymentInfo(initializers);
        
        // 创建Servlet部署管理器
        DeploymentManager manager = Servlets.newContainer().addDeployment(deploymentInfo);
        manager.deploy();
        
        // 获取Servlet上下文
        ServletContext servletContext = manager.getDeployment().getServletContext();
        
        // 配置Servlet上下文
        try {
            for (ServletContextInitializer initializer : initializers) {
                initializer.onStartup(servletContext);
            }
        }
        catch (ServletException ex) {
            throw new WebServerException("Unable to start embedded Undertow server", ex);
        }
        
        // 添加Servlet部署到服务器
        builder.addServletContainer(new UndertowServletContainer(manager));
        
        // 创建并返回Undertow服务器
        return new UndertowWebServer(builder, getPort() >= 0, this.shutdown);
    }

    private DeploymentInfo createDeploymentInfo(ServletContextInitializer... initializers) {
        DeploymentInfo deploymentInfo = Servlets.deployment()
                .setClassLoader(UndertowServletWebServerFactory.class.getClassLoader())
                .setContextPath(getContextPath())
                .setDisplayName(getDisplayName())
                .setDeploymentName("spring-boot")
                .setServletStackTraces(ServletStackTraces.NONE);
        
        // 添加初始化参数
        for (Map.Entry<String, String> entry : getInitParameters().entrySet()) {
            deploymentInfo.addInitParameter(entry.getKey(), entry.getValue());
        }
        
        // 添加错误页面
        for (ErrorPage errorPage : getErrorPages()) {
            if (errorPage.getStatusCode() != null) {
                deploymentInfo.addErrorPage(Servlets.errorPage(errorPage.getPath())
                        .addStatusCodes(errorPage.getStatusCode()));
            }
            else if (errorPage.getExceptionName() != null) {
                deploymentInfo.addErrorPage(Servlets.errorPage(errorPage.getPath())
                        .addException(errorPage.getExceptionName()));
            }
        }
        
        // 添加ServletContextInitializer处理器
        deploymentInfo.addServletContainerInitializer(
                new ServletContextInitializerAdapter(initializers), Collections.emptySet());
        
        return deploymentInfo;
    }
}

createDeploymentInfo方法中,会创建Undertow的DeploymentInfo对象,并将ServletContextInitializer包装成Undertow的ServletContainerInitializer,以便在容器启动时初始化ServletContext。

6.5 容器选择与切换

SpringBoot默认使用Tomcat作为嵌入式Servlet容器,但可以通过排除默认容器并添加所需容器的依赖来切换。

例如,从Tomcat切换到Jetty:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

切换到Undertow:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

不同容器在性能、内存占用和特性支持上略有差异,开发者可以根据应用需求选择合适的容器。

七、ServletContext与Spring应用上下文集成

7.1 集成架构概述

在SpringBoot应用中,ServletContext与Spring应用上下文(ApplicationContext)是紧密集成的。ServletContext作为Servlet容器提供的全局上下文,负责管理Servlet、Filter等Web组件;而Spring应用上下文则负责管理Bean的生命周期和依赖注入。两者通过特定的机制相互协作,共同构成了SpringBoot Web应用的运行环境。

集成的核心目标是:

  • 将Spring应用上下文与ServletContext关联,使Servlet组件能够访问Spring管理的Bean
  • 让Spring能够感知ServletContext的生命周期事件
  • 实现Web请求与Spring MVC控制器的无缝对接

7.2 WebApplicationContext的创建与注册

在SpringBoot中,Web应用上下文的创建由ServletWebServerApplicationContext类负责。这个类继承自GenericWebApplicationContext,并实现了ConfigurableWebServerApplicationContext接口。

public class ServletWebServerApplicationContext extends GenericWebApplicationContext
        implements ConfigurableWebServerApplicationContext {

    @Override
    protected void onRefresh() {
        super.onRefresh();
        try {
            createWebServer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start web server", ex);
        }
    }

    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
            // 获取ServletWebServerFactory并创建WebServer
            ServletWebServerFactory factory = getWebServerFactory();
            this.webServer = factory.getWebServer(getSelfInitializer());
        }
        else if (servletContext != null) {
            try {
                // 如果已有ServletContext,初始化它
                getSelfInitializer().onStartup(servletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context", ex);
            }
        }
        initPropertySources();
    }

    private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
        return this::selfInitialize;
    }

    private void selfInitialize(ServletContext servletContext) throws ServletException {
        // 准备WebApplicationContext
        prepareWebApplicationContext(servletContext);
        
        // 注册应用作用域
        registerApplicationScope(servletContext);
        
        // 注册环境相关的Bean
        WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
        
        // 调用所有ServletContextInitializer的onStartup方法
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            beans.onStartup(servletContext);
        }
    }
}

selfInitialize方法中,会调用prepareWebApplicationContext方法将WebApplicationContext与ServletContext关联起来:

protected void prepareWebApplicationContext(ServletContext servletContext) {
    // 设置ServletContext
    setServletContext(servletContext);
    
    // 如果上下文尚未刷新,设置ServletContextAwareProcessor
    if (!isActive()) {
        getBeanFactory().addBeanPostProcessor(new ServletContextAwareProcessor(servletContext));
        getBeanFactory().ignoreDependencyInterface(ServletContextAware.class);
    }
    
    // 将WebApplicationContext存储到ServletContext中
    WebApplicationContextUtils.registerWebApplicationContext(servletContext, this);
}

7.3 上下文属性共享

SpringBoot通过ServletContextWebApplicationContext实现属性共享:

  1. 从ServletContext获取属性
    Spring的ServletContextAware接口允许Bean获取ServletContext引用,从而访问其属性。

    import javax.servlet.ServletContext;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.ServletContextAware;
    
    @Component
    public class MyServletContextAwareBean implements ServletContextAware {
    
        private ServletContext servletContext;
    
        @Override
        public void setServletContext(ServletContext servletContext) {
            this.servletContext = servletContext;
        }
    
        public String getContextAttribute(String name) {
            return (String) servletContext.getAttribute(name);
        }
    }
    
  2. 向ServletContext设置属性
    可以通过实现ServletContextInitializer接口,在初始化时向ServletContext设置属性。

    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import org.springframework.boot.web.servlet.ServletContextInitializer;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MyServletContextInitializer implements ServletContextInitializer {
    
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
            servletContext.setAttribute("app.version", "1.0.0");
            servletContext.setAttribute("env", "development");
        }
    }
    
  3. 通过Environment访问ServletContext参数
    Spring的Environment对象可以访问ServletContext的初始化参数。

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.env.Environment;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MyEnvironmentBean {
    
        @Autowired
        private Environment environment;
    
        public String getContextParam(String name) {
            return environment.getProperty("servletContextInitParams." + name);
        }
    }
    

7.4 生命周期事件交互

ServletContext的生命周期事件(如初始化、销毁)与Spring应用上下文的生命周期事件相互关联:

  1. ServletContext初始化触发Spring应用上下文刷新
    当ServletContext初始化时,会调用ServletWebServerApplicationContextonRefresh方法,触发Spring应用上下文的刷新过程。

  2. ServletContext销毁触发Spring应用上下文关闭
    当ServletContext被销毁时,会触发Spring应用上下文的关闭事件,销毁所有管理的Bean。

    public class ServletWebServerApplicationContext extends GenericWebApplicationContext
            implements ConfigurableWebServerApplicationContext {
    
        @Override
        protected void doClose() {
            // 标记上下文正在关闭
            if (this.active.get() && this.closed.compareAndSet(false, true)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Closing " + this);
                }
                
                // 关闭内嵌的WebServer
                stopAndReleaseWebServer();
                
                // 调用父类的关闭方法
                super.doClose();
                
                // 清除ServletContext引用
                if (this.servletContext instanceof Closeable) {
                    try {
                        ((Closeable) this.servletContext).close();
                    }
                    catch (IOException ex) {
                        logger.warn("Could not close ServletContext", ex);
                    }
                }
            }
        }
    }
    
  3. 自定义生命周期监听器
    开发者可以通过实现ServletContextListener接口监听ServletContext的生命周期事件,并在事件发生时执行自定义逻辑。

    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    import javax.servlet.annotation.WebListener;
    import org.springframework.stereotype.Component;
    
    @Component
    @WebListener
    public class MyServletContextListener implements ServletContextListener {
    
        @Override
        public void contextInitialized(ServletContextEvent sce) {
            System.out.println("ServletContext initialized");
            // 可以在这里执行初始化操作
        }
    
        @Override
        public void contextDestroyed(ServletContextEvent sce) {
            System.out.println("ServletContext destroyed");
            // 可以在这里执行资源清理操作
        }
    }
    

7.5 在Servlet中访问Spring Bean

在Servlet中访问Spring管理的Bean是常见需求,SpringBoot提供了多种方式实现这一功能:

  1. 通过ServletContext获取WebApplicationContext

    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.springframework.web.context.WebApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    
    @WebServlet("/myServlet")
    public class MyServlet extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // 从ServletContext获取WebApplicationContext
            WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
            
            // 获取Spring管理的Bean
            MyService myService = context.getBean(MyService.class);
            
            // 使用Bean
            String result = myService.doSomething();
            resp.getWriter().println(result);
        }
    }
    
  2. 通过Spring的WebApplicationContextUtils工具类

    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.springframework.web.context.WebApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    
    @WebServlet("/myServlet")
    public class MyServlet extends HttpServlet {
    
        private MyService myService;
    
        @Override
        public void init() throws ServletException {
            super.init();
            // 在Servlet初始化时获取Spring Bean
            WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
            myService = context.getBean(MyService.class);
        }
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // 使用已注入的Bean
            String result = myService.doSomething();
            resp.getWriter().println(result);
        }
    }
    
  3. 使用Spring的@WebServlet和@Autowired注解
    需要通过ServletComponentScan注解启用Servlet组件扫描:

    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    @WebServlet("/myServlet")
    public class MyServlet extends HttpServlet {
    
        @Autowired
        private MyService myService;
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            String result = myService.doSomething();
            resp.getWriter().println(result);
        }
    }
    

    同时,在主应用类上添加@ServletComponentScan注解:

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.web.servlet.ServletComponentScan;
    
    @SpringBootApplication
    @ServletComponentScan
    public class MyApplication {
        public static void main(String[] args) {
            SpringApplication.run(MyApplication.class, args);
        }
    }
    

八、ServletContext初始化参数配置与使用

8.1 初始化参数概述

ServletContext初始化参数是Web应用的全局配置参数,在ServletContext创建时初始化,并在整个应用生命周期内可用。这些参数存储在ServletContext中,可以被应用内的所有Servlet、Filter和其他组件访问。

初始化参数与应用配置属性(如application.properties中的属性)的区别:

  • 初始化参数属于Servlet规范的一部分,由Servlet容器管理
  • 应用配置属性是SpringBoot特有的配置机制
  • 初始化参数通常用于配置与Servlet容器相关的特性
  • 应用配置属性用于更广泛的应用配置

8.2 在SpringBoot中配置初始化参数

SpringBoot提供了多种方式配置ServletContext初始化参数:

  1. 通过application.properties/application.yml配置

    server.servlet.context-parameters.myParam1=value1
    server.servlet.context-parameters.myParam2=value2
    

    或在application.yml中:

    server:
      servlet:
        context-parameters:
          myParam1: value1
          myParam2: value2
    
  2. 通过ServletContextInitializer手动配置

    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import org.springframework.boot.web.servlet.ServletContextInitializer;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MyServletContextInitializer implements ServletContextInitializer {
    
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
            servletContext.setInitParameter("myParam1", "value1");
            servletContext.setInitParameter("myParam2", "value2");
        }
    }
    
  3. 通过嵌入式Servlet容器配置
    以Tomcat为例:

    import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
    import org.springframework.boot.web.server.WebServerFactoryCustomizer;
    import org.springframework.stereotype.Component;
    
    @Component
    public class TomcatCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
    
        @Override
        public void customize(TomcatServletWebServerFactory factory) {
            factory.addContextCustomizers(context -> {
                context.addParameter("myParam1", "value1");
                context.addParameter("myParam2", "value2");
            });
        }
    }
    

8.3 读取和使用初始化参数

在SpringBoot应用中,可以通过多种方式读取ServletContext初始化参数:

  1. 直接通过ServletContext读取

    import javax.servlet.ServletContext;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MyComponent {
    
        private final ServletContext servletContext;
    
        @Autowired
        public MyComponent(ServletContext servletContext) {
            this.servletContext = servletContext;
        }
    
        public String getInitParameter(String name) {
            return servletContext.getInitParameter(name);
        }
    }
    
  2. 在Servlet或Filter中读取

    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import java.io.IOException;
    
    public class MyFilter implements Filter {
    
        private String myParam;
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            // 从FilterConfig获取ServletContext
            myParam = filterConfig.getServletContext().getInitParameter("myParam1");
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            // 使用参数
            System.out.println("MyParam: " + myParam);
            chain.doFilter(request, response);
        }
    
        @Override
        public void destroy() {
            // 清理资源
        }
    }
    
  3. 通过Environment读取
    Spring的Environment对象可以访问ServletContext初始化参数:

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.env.Environment;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MyEnvComponent {
    
        @Autowired
        private Environment environment;
    
        public String getInitParameter(String name) {
            return environment.getProperty("servletContextInitParams." + name);
        }
    }
    

8.4 初始化参数与配置属性的优先级

当ServletContext初始化参数与application.properties中的配置属性同名时,优先级如下:

  1. ServletContext初始化参数:通过server.servlet.context-parameters配置的参数
  2. 应用配置属性:application.properties或application.yml中配置的属性
  3. 默认配置:SpringBoot的默认配置值

可以通过以下示例验证优先级:

# application.properties
my.property=from-application-properties

# 通过ServletContextInitializer设置
servletContext.setInitParameter("my.property", "from-servlet-context");
import javax.servlet.ServletContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
public class PriorityChecker {

    @Autowired
    private Environment environment;

    @Autowired
    private ServletContext servletContext;

    public void checkPriorities() {
        // 从Environment获取,将返回from-servlet-context
        String envValue = environment.getProperty("my.property");
        
        // 直接从ServletContext获取,也返回from-servlet-context
        String contextValue = servletContext.getInitParameter("my.property");
        
        System.out.println("Environment value: " + envValue);
        System.out.println("ServletContext value: " + contextValue);
    }
}

8.5 初始化参数的应用场景

ServletContext初始化参数适用于以下场景:

  1. Web应用全局配置:如应用版本号、环境标识等

    server.servlet.context-parameters.app.version=1.0.0
    server.servlet.context-parameters.environment=production
    
  2. 第三方库配置:某些第三方库需要通过ServletContext初始化参数进行配置

    @Component
    public class MyServletContextInitializer implements ServletContextInitializer {
    
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
            // 配置第三方库
            servletContext.setInitParameter("thirdPartyLib.config", "value");
        }
    }
    
  3. Servlet和Filter配置:为自定义Servlet或Filter提供配置参数

    public class MyServlet extends HttpServlet {
    
        @Override
        public void init() throws ServletException {
            String configValue = getServletContext().getInitParameter("myServletConfig");
            // 使用配置值初始化Servlet
        }
    }
    
  4. 资源路径配置:配置静态资源或模板文件的路径

    server.servlet.context-parameters.resources.path=/static/resources
    

九、调试ServletContext初始化问题

9.1 常见初始化问题

在ServletContext初始化过程中,可能会遇到以下常见问题:

  1. ServletContext未正确初始化

    • 症状:应用启动后无法访问Servlet或Filter
    • 可能原因:Servlet容器未正确启动、ServletContextInitializer未被调用
  2. 初始化参数未生效

    • 症状:通过配置文件或代码设置的初始化参数无法获取
    • 可能原因:配置位置错误、参数名称拼写错误、初始化顺序问题
  3. Servlet、Filter或Listener未注册

    • 症状:自定义Servlet、Filter或Listener未按预期工作
    • 可能原因:未正确实现ServletContextInitializer、Bean未被Spring管理、注册顺序错误
  4. 上下文属性共享失败

    • 症状:在Servlet中无法访问Spring管理的Bean
    • 可能原因:WebApplicationContext未正确注册到ServletContext、Servlet未正确获取上下文
  5. 嵌入式Servlet容器启动失败

    • 症状:应用启动时抛出异常,无法创建WebServer
    • 可能原因:端口被占用、Servlet容器配置错误、依赖冲突

9.2 启用调试日志

通过配置日志级别可以获取更详细的初始化过程信息:

# application.properties
logging.level.org.springframework=DEBUG
logging.level.org.springframework.boot.web=TRACE
logging.level.org.apache.catalina=DEBUG  # 针对Tomcat容器
logging.level.org.eclipse.jetty=DEBUG   # 针对Jetty容器
logging.level.io.undertow=DEBUG         # 针对Undertow容器

关键日志类别:

  • org.springframework.boot.web.servlet.context:ServletContext初始化相关日志
  • org.springframework.boot.web.embedded:嵌入式Servlet容器相关日志
  • org.springframework.web:Spring Web模块相关日志

9.3 使用断点调试

在关键代码位置设置断点可以帮助追踪初始化流程:

  1. ServletWebServerApplicationContext.onRefresh():Web应用上下文刷新,触发WebServer创建
  2. ServletWebServerFactory.getWebServer():创建嵌入式Servlet容器
  3. ServletContextInitializer.onStartup():ServletContext初始化器被调用
  4. ServletRegistrationBean.onStartup():Servlet注册过程
  5. FilterRegistrationBean.onStartup():Filter注册过程
  6. ServletListenerRegistrationBean.onStartup():Listener注册过程

9.4 检查自动配置报告

SpringBoot的自动配置报告可以帮助分析哪些自动配置被应用,哪些被排除:

# application.properties
debug=true

启动应用后,查看控制台输出的自动配置报告,重点关注:

  • ServletWebServerFactoryAutoConfiguration:嵌入式Servlet容器自动配置
  • DispatcherServletAutoConfiguration:DispatcherServlet自动配置
  • WebMvcAutoConfiguration:Spring MVC自动配置

9.5 使用Actuator端点

SpringBoot Actuator提供了多个端点帮助诊断应用问题:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

常用端点:

  • /actuator/beans:查看所有Spring管理的Bean
  • /actuator/mappings:查看所有请求映射
  • /actuator/env:查看应用环境和配置属性
  • /actuator/health:查看应用健康状态

例如,访问/actuator/beans可以确认ServletContextInitializer是否被正确注册为Bean。

9.6 问题排查示例

假设遇到ServletContext初始化参数未生效的问题,可以按以下步骤排查:

  1. 确认参数配置

    • 检查application.properties或application.yml中参数配置是否正确
    • 确认参数名称和格式符合规范(如server.servlet.context-parameters.xxx=yyy
  2. 检查初始化顺序

    • 确认ServletContextInitializer在正确的时机被调用
    • 使用调试日志或断点确认参数设置代码被执行
  3. 验证参数获取方式

    • 尝试多种方式获取参数(直接通过ServletContext、通过Environment)
    @Component
    public class ParamChecker {
    
        @Autowired
        private ServletContext servletContext;
    
        @Autowired
        private Environment environment;
    
        public void checkParams() {
            String directValue = servletContext.getInitParameter("myParam");
            String envValue = environment.getProperty("servletContextInitParams.myParam");
            
            System.out.println("Direct value: " + directValue);
            System.out.println("Env value: " + envValue);
        }
    }
    
  4. 检查嵌入式Servlet容器配置

    • 如果使用自定义嵌入式Servlet容器配置,确认没有覆盖参数设置
    • 检查容器版本是否与SpringBoot兼容

通过以上步骤,通常可以定位并解决ServletContext初始化过程中的常见问题。

十、ServletContext初始化性能优化

10.1 延迟初始化策略

对于某些不影响应用启动后立即响应的组件,可以采用延迟初始化策略:

  1. 延迟初始化Servlet

    @Bean
    public ServletRegistrationBean<MyServlet> myServletRegistration() {
        ServletRegistrationBean<MyServlet> registration = new ServletRegistrationBean<>(new MyServlet());
        registration.addUrlPatterns("/myServlet");
        registration.setLoadOnStartup(-1); // 设置为负数表示延迟初始化
        return registration;
    }
    
  2. 使用@Lazy注解

    @Component
    @Lazy
    public class ExpensiveComponent {
        // 初始化耗时的组件
    }
    
  3. SpringBoot全局延迟初始化

    spring.main.lazy-initialization=true
    

10.2 减少ServletContextInitializer数量

过多的ServletContextInitializer会增加初始化时间,可以通过以下方式优化:

  1. 合并初始化逻辑
    将多个ServletContextInitializer合并为一个:

    @Component
    public class CombinedServletContextInitializer implements ServletContextInitializer {
    
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
            // 合并多个初始化器的逻辑
            initServletContext(servletContext);
            initFilters(servletContext);
            initListeners(servletContext);
        }
    
        private void initServletContext(ServletContext servletContext) {
            // 初始化ServletContext参数
        }
    
        private void initFilters(ServletContext servletContext) {
            // 注册Filters
        }
    
        private void initListeners(ServletContext servletContext) {
            // 注册Listeners
        }
    }
    
  2. 条件性注册
    只在必要时注册ServletContextInitializer:

    @Component
    @ConditionalOnProperty(name = "myapp.enable.custom-initializer", havingValue = "true")
    public class CustomServletContextInitializer implements ServletContextInitializer {
        // ...
    }
    

10.3 优化嵌入式Servlet容器配置

针对嵌入式Servlet容器进行优化:

  1. 调整线程池配置

    @Bean
    public ConfigurableServletWebServerFactory webServerFactory() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.addConnectorCustomizers(connector -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractProtocol) {
                AbstractProtocol<?> protocol = (AbstractProtocol<?>) handler;
                protocol.setMaxThreads(200);
                protocol.setMinSpareThreads(10);
                protocol.setAcceptCount(100);
            }
        });
        return factory;
    }
    
  2. 禁用不必要的组件

    @Bean
    public ConfigurableServletWebServerFactory webServerFactory() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.setTomcatConnectorCustomizers(customizers -> {
            customizers.forEach(customizer -> {
                if (customizer instanceof TomcatConnectorCustomizer) {
                    ((TomcatConnectorCustomizer) customizer).customize(connector);
                }
            });
            // 禁用AJP连接器
            connector.setEnableLookups(false);
            connector.setURIEncoding("UTF-8");
        });
        return factory;
    }
    
  3. 使用更轻量级的容器
    如果应用对性能要求较高,可以考虑使用Undertow代替Tomcat:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>
    

10.4 缓存与预加载

  1. 静态资源缓存

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/static/**")
                    .addResourceLocations("classpath:/static/")
                    .setCachePeriod(3600); // 缓存1小时
        }
    }
    
  2. 预加载关键组件

    @Component
    public class StartupPreloader implements ApplicationListener<ContextRefreshedEvent> {
    
        @Autowired
        private ExpensiveService expensiveService;
    
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            // 在应用上下文刷新后预加载
            expensiveService.preload();
        }
    }
    
  3. 使用CDN加速静态资源

    <!-- 在HTML中使用CDN资源 -->
    <script src="https://cdn.example.com/jquery.min.js"></script>
    

10.5 避免阻塞操作

在ServletContext初始化过程中避免执行耗时操作:

  1. 异步初始化

    @Component
    public class AsyncInitializer implements CommandLineRunner {
    
        @Autowired
        private ApplicationContext applicationContext;
    
        @Override
        public void run(String... args) throws Exception {
            // 使用异步任务执行耗时初始化
            CompletableFuture.runAsync(() -> {
                ExpensiveService service = applicationContext.getBean(ExpensiveService.class);
                service.initialize();
            });
        }
    }
    
  2. 懒加载机制

    @Service
    public class LazyInitializedService {
    
        private ExpensiveResource resource;
    
        public synchronized ExpensiveResource getResource() {
            if (resource == null) {
                resource = loadExpensiveResource();
            }
            return resource;
        }
    
        private ExpensiveResource loadExpensiveResource() {
            // 耗时的资源加载操作
            return new ExpensiveResource();
        }
    }
    

10.6 监控与调优

  1. 使用Actuator监控性能

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  2. 分析启动性能

    spring.startup.info.enabled=true
    
  3. 使用性能分析工具

    • VisualVM
    • YourKit
    • Java Mission Control

通过对ServletContext初始化过程的性能优化,可以显著提高SpringBoot应用的启动速度和响应性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Android 小码蜂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值