ServletContextInitializer

ServletContextInitializer 是 Spring Boot 提供的一个接口,允许你在内嵌 Servlet 容器(如 Tomcat)启动时,自定义注册 Servlet、Filter、Listener 或修改 ServletContext 属性。

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

这个接口会在内嵌容器启动时自动被调用(等价于 web.xml 的内容)。

它和Bean的生命周期的关系?

它不是 Bean 的生命周期钩子,但它是由 Spring Boot 的 ServletWebServerApplicationContext 在 Web 容器启动前主动调用的。

大概在 Spring 完成 ApplicationContext.refresh() 时:

// 调用所有 ServletContextInitializer 的 onStartup()
this.selfInitialize(servletContext);

Lab

注册一个原始 Servlet(不使用 @WebServlet)

@Component
public class MyServletInitializer implements ServletContextInitializer {
    @Override
    public void onStartup(ServletContext servletContext) {
        ServletRegistration.Dynamic servlet = servletContext.addServlet("myServlet", new MyCustomServlet());
        servlet.addMapping("/custom");
        servlet.setLoadOnStartup(1);
    }
}

等效于 web.xml 的配置:

<servlet>
  <servlet-name>myServlet</servlet-name>
  <servlet-class>com.example.MyCustomServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>myServlet</servlet-name>
  <url-pattern>/custom</url-pattern>
</servlet-mapping>

注册一个 Filter

@Component
public class MyFilterInitializer implements ServletContextInitializer {
    @Override
    public void onStartup(ServletContext servletContext) {
        FilterRegistration.Dynamic filter = servletContext.addFilter("myFilter", new MyFilter());
        filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
    }
}

注册一个 Listener

@Component
public class MyListenerInitializer implements ServletContextInitializer {
    @Override
    public void onStartup(ServletContext servletContext) {
        servletContext.addListener(MySessionListener.class);
    }
}

Spring Boot 自动适配的变种接口

Spring Boot 自动支持以下扩展:

接口 / 注解 说明
ServletContextInitializer 最底层的原始注册能力
@ServletComponentScan+@WebServlet/@WebFilter/@WebListener 注解式注册(Spring Boot 自动支持)
FilterRegistrationBean,ServletRegistrationBean Spring Boot 提供的注册工具类

FilterRegistrationBean

这本质上最终也会被转换为 ServletContextInitializer 调用。

@Bean
public FilterRegistrationBean<MyFilter> myFilter() {
    FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>();
    registration.setFilter(new MyFilter());
    registration.addUrlPatterns("/*");
    return registration;
}

常见高级用途

目标 实现方式
注册动态 SSE Servlet 用ServletContextInitializer注册 SSE Servlet
手动配置 ServletContext 的参数 servletContext.setInitParameter("xxx", "yyy")
与非 Spring 的旧 Servlet 框架桥接 注册第三方原生 Servlet(如 Swagger、Shiro)
替代 web.xml 全部 Servlet/Filter/Listener 配置转移到 Java

注意

springboot内嵌的tomcat容器,是无法在启动之后动态add一个servlet的

    private ServletRegistration.Dynamic addServlet(String servletName, String servletClass, Servlet servlet,
            Map<String,String> initParams) throws IllegalStateException {

        if (servletName == null || servletName.isEmpty()) {
            throw new IllegalArgumentException(sm.getString("applicationContext.invalidServletName", servletName));
        }

        // 这里会检查容器状态,只有容器启动之前能够注册,启动之后注册会报错 IllegalStateException
        checkState("applicationContext.addServlet.ise");

        Wrapper wrapper = (Wrapper) context.findChild(servletName);

        // Assume a 'complete' ServletRegistration is one that has a class and
        // a name
        if (wrapper == null) {
            wrapper = context.createWrapper();
            wrapper.setName(servletName);
            context.addChild(wrapper);
        } else {
            if (wrapper.getName() != null && wrapper.getServletClass() != null) {
                if (wrapper.isOverridable()) {
                    wrapper.setOverridable(false);
                } else {
                    return null;
                }
            }
        }

        ServletSecurity annotation = null;
        if (servlet == null) {
            wrapper.setServletClass(servletClass);
            Class<?> clazz = Introspection.loadClass(context, servletClass);
            if (clazz != null) {
                annotation = clazz.getAnnotation(ServletSecurity.class);
            }
        } else {
            wrapper.setServletClass(servlet.getClass().getName());
            wrapper.setServlet(servlet);
            if (context.wasCreatedDynamicServlet(servlet)) {
                annotation = servlet.getClass().getAnnotation(ServletSecurity.class);
            }
        }

        if (initParams != null) {
            for (Map.Entry<String,String> initParam : initParams.entrySet()) {
                wrapper.addInitParameter(initParam.getKey(), initParam.getValue());
            }
        }

        ServletRegistration.Dynamic registration = new ApplicationServletRegistration(wrapper, context);
        if (annotation != null) {
            registration.setServletSecurity(new ServletSecurityElement(annotation));
        }
        return registration;
    }

标题:ServletContextInitializer
作者:Gakkiyomi2019
地址:https://gakkiyomi.blog/articles/2025/07/15/1752548212181.html

    评论
    23 评论
    javaNewCat 2025-07-16 16:22

    看不懂,大佬

    Gakkiyomi 2025-07-16 14:44
    回复» @mitu2

    孩子,这并不是tomcat

    mitu2 2025-07-16 14:16

    666666666 羡慕大佬手撕tomact

    tu-dog 2025-07-16 08:46

    大佬

    z3ph 2025-07-16 08:40

    大佬

    moyupi 2025-07-16 08:39

    大佬

    LIYA 2025-07-15 19:16

    终于有技术佬了吗

    EM0 2025-07-15 18:38

    ZDream03 2025-07-15 15:00

    又是逃课大g老师讲堂的一天

    Gakkiyomi 2025-07-15 14:27
    回复» @stillwarter

    前端叉出去

    Gakkiyomi 2025-07-15 14:27
    回复» @deeplovebug

    这不是源码啊,这是应用代码啊,源码我配吗?

    stillwarter 2025-07-15 14:08

    又是逃课大g老师讲堂的一天

    deeplovebug 2025-07-15 14:00

    又是大g老师带我读源码的一天

    caffish 2025-07-15 13:37

    佬儿

    XZZ233 2025-07-15 13:27

    不愧是g大佬

    tbw 2025-07-15 13:03

    G佬 tql

    star-wu 2025-07-15 12:12

    牛!

    ZZZ1234 2025-07-15 11:40

    不明觉立

    zainyy 2025-07-15 11:37

    佬!

    Saintbutcher 2025-07-15 11:18

    虽然看不懂 但肯定很厉害

    jingjie 2025-07-15 11:17

    厉害 G老师

    RenleyAlden 2025-07-15 11:13

    不明觉厉!🐂🐸

    xiong 2025-07-15 11:10

    看不懂,大佬

avatar

取消