【问题标题】:How to use TomcatEmbeddedServletContainerFactory with a AbstractAnnotationConfigDispatcherServletInitializer如何使用带有 AbstractAnnotationConfigDispatcherServletInitializer 的 TomcatEmbeddedServletContainerFactory
【发布时间】:2026-02-09 11:10:01
【问题描述】:

我从 Spring Boot 项目中找到了“TomcatEmbeddedServletContainerFactory”,它可以创建一个 Tomcat 嵌入式实例。 我已经有一组“传统的”Spring(基于 Java)配置类以及用于设置根应用程序上下文和控制器上下文的“AbstractAnnotationConfigDispatcherServletInitializer”实现

public class IntegrationTestDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected String[] getServletMappings() {
    return new String[]{"/"};
}

@Override
protected Class<?>[] getRootConfigClasses() {
    return new Class<?>[]{ServiceModuleConfiguration.class};
}

@Override
protected Class<?>[] getServletConfigClasses() {
    return new Class<?>[]{WebModuleConfiguration.class };
}
}

现在我尝试用

创建一个Tomcat实例
TomcatEmbeddedServletContainerFactory tomcatFactory = new TomcatEmbeddedServletContainerFactory("/", 8080);
tomcatFactory.getEmbeddedServletContainer(???)

getEmbeddedServletContainer() 需要一个来自 Spring 引导的 ServletContextInitializer,现在我坚持将它桥接到我的配置类。

Spring Boot 的 tomcat 工厂是否支持这一点?如果是,有什么例子吗?

【问题讨论】:

    标签: tomcat spring-boot


    【解决方案1】:

    EmbeddedServletContainerFactory 确实与 EmbeddedWebApplicationContext 配对(不是您从 AbstractAnnotationConfigDispatcherServletInitializer 获得的 AnnotationConfigWebApplicationContext)。如果您想构建一个 WAR 文件,那么鉴于初始化程序的简单性质,您最好的选择是改用 SpringApplicationServletInitializer,例如

    public class IntegrationTestDispatcherServletInitializer extends SpringBootServletInitializer {
    
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
            return application.sources(ServiceModuleConfiguration.class).child(WebModuleConfiguration.class);
        }
    
    }
    

    如果您不想要 WAR 文件(如 cmets 中的建议,当我考虑更多时),只需创建一个 SpringApplication,如果它认为您正在尝试构建一个 webapp(或使用上面代码中的构建器)。

    【讨论】:

    • 我没有得到 SpringServletContainerInitializerkicking 来初始化 Web 应用程序。我正在使用带有EmbeddedServletContainerFactory 的bean 定义的AnnotationConfigEmbeddedWebApplicationContext。容器本身启动,但我想为了上面的IntegrationTestDispatcherServletInitializerSpringServletContainerInitializer必须运行?
    • 是的,抱歉,我对您想要做什么感到困惑。如果您不想构建和部署war 文件,那么ServletContextInitializer 将无济于事,如果您想要嵌入式容器,您可能不需要war 文件。为什么不直接使用SpringApplication 或构建器?
    • 好的,将对此进行调查。我的目的是从嵌入式 tomcat 运行一个简单的集成测试,而无需构建 WAR 和部署它。
    • SpringApplicationBuilder builder = new SpringApplicationBuilder(WebModuleConfiguration.class,ServiceModuleConfiguration.class); builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); builder.application().run(); 这是启动上下文,但看起来这并没有给我一个有效的 DispatcherServlet 设置。但现在的问题是,如果我必须像使用 AbstractDispatcherServletInitializer 那样手动注册它,我需要使用控制器 bean 访问应用程序上下文吗?我不知道如何从构建器/应用程序中获得?
    • 对,我明白了。要让DispatcherServlet 拥有您的上下文,您需要创建该类型的@Bean 并将其放入您的应用程序上下文中。不应该是大事吗?注:但是,要保留原始应用程序的应用程序上下文层次结构,您需要使用SpringApplicationBuilder(或等效项),如上面的示例中所示。 OTOH 也许您的原始应用程序不需要这种层次结构(大多数应用程序不需要)。
    【解决方案2】:

    通过更多的调试和来自 Dave 的更多输入,正确的方法是显式获取子构建器实例并调用它的 run() 方法。这将正确启动上下文。

        SpringApplicationBuilder builder = new SpringApplicationBuilder();
        builder.sources(ServiceModuleConfiguration.class);
        childBuilder = builder.child(WebModuleConfiguration.class, WebConfiguration.class, TomcatContainerFactoryConfigur‌​ation.class);
        childBuilder.run();
    

    WebConfiguration 包含 DispatcherServlet bean。

        @Bean
        public ServletRegistrationBean servletRegistrationBean() {
            return new ServletRegistrationBean(new DispatcherServlet((WebApplicationContext) applicationContext), "/*");
        }
    

    TomcatContainerFactoryConfigur‌​ation 包含 TomcatEmbeddedServletContainerFactory bean

        @Bean
        public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
            return new TomcatEmbeddedServletContainerFactory("", 8080);
        }
    

    【讨论】: