运行 Spring Webflux 应用程序 WAR

Running Spring Webflux Application as a WAR

我有一个 Spring 反应 sample application that was modified from one of the examples that was provided in the the Spring Webflux documentation。此应用程序的 master 分支以传统方式使用 Spring Boot,带有嵌入式应用程序服务器 (Netty)。它工作正常。

Liberty branch, I am trying to build the application as a WAR and deploy to Websphere Liberty Profile. Aside from changes to the build process, the most significant code change is having my Application.java (source here) 中扩展 AbstractAnnotationConfigDispatcherHandlerInitializer,根据 Webflux 文档:

For Servlet containers especially with WAR deployment you can use the AbstractAnnotationConfigDispatcherHandlerInitializer which as a WebApplicationInitializer and is auto-detected by Servlet containers. It takes care of registering the ServletHttpHandlerAdapter as shown above. You will need to implement one abstract method in order to point to your Spring configuration.

但是,当我这样做时,我的 resources/endpoints 中的 none 被映射,我在 Application.java 中声明的 bean 中的 none 被注册。这是我得到的完整输出,但在尝试访问上下文根时抛出异常:

13:29:48.848 [Default Executor-thread-6] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning
13:29:48.855 [Default Executor-thread-6] INFO org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4ba0f9a4: startup date [Fri Oct 13 13:29:48 CDT 2017]; root of context hierarchy
13:29:48.857 [Default Executor-thread-6] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Bean factory for org.springframework.context.annotation.AnnotationConfigApplicationContext@4ba0f9a4: org.springframework.beans.factory.support.DefaultListableBeanFactory@41daf3ea: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory]; root of factory hierarchy
13:29:48.907 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
13:29:48.907 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
13:29:48.939 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor' to allow for resolving potential circular references
13:29:48.943 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
13:29:49.371 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
13:29:49.371 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
13:29:49.372 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor' to allow for resolving potential circular references
13:29:49.425 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
13:29:49.426 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalRequiredAnnotationProcessor'
13:29:49.426 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'org.springframework.context.annotation.internalRequiredAnnotationProcessor'
13:29:49.428 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'org.springframework.context.annotation.internalRequiredAnnotationProcessor' to allow for resolving potential circular references
13:29:49.432 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'org.springframework.context.annotation.internalRequiredAnnotationProcessor'
13:29:49.433 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
13:29:49.433 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
13:29:49.441 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor' to allow for resolving potential circular references
13:29:49.450 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
13:29:49.454 [Default Executor-thread-6] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Unable to locate MessageSource with name 'messageSource': using default [org.springframework.context.support.DelegatingMessageSource@5c88ddc5]
13:29:49.458 [Default Executor-thread-6] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Unable to locate ApplicationEventMulticaster with name 'applicationEventMulticaster': using default [org.springframework.context.event.SimpleApplicationEventMulticaster@6a00d295]
13:29:49.461 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@41daf3ea: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory]; root of factory hierarchy
13:29:49.462 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
13:29:49.462 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
13:29:49.462 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.context.annotation.internalRequiredAnnotationProcessor'
13:29:49.463 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
13:29:49.463 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
13:29:49.463 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'org.springframework.context.event.internalEventListenerProcessor'
13:29:49.477 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'org.springframework.context.event.internalEventListenerProcessor' to allow for resolving potential circular references
13:29:49.479 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'org.springframework.context.event.internalEventListenerProcessor'
13:29:49.480 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
13:29:49.480 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'org.springframework.context.event.internalEventListenerFactory'
13:29:49.481 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'org.springframework.context.event.internalEventListenerFactory' to allow for resolving potential circular references
13:29:49.483 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'org.springframework.context.event.internalEventListenerFactory'
13:29:49.484 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
13:29:49.514 [Default Executor-thread-6] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Unable to locate LifecycleProcessor with name 'lifecycleProcessor': using default [org.springframework.context.support.DefaultLifecycleProcessor@6ffc157d]
13:29:49.515 [Default Executor-thread-6] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'lifecycleProcessor'
13:29:49.520 [Default Executor-thread-6] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Could not find key 'spring.liveBeansView.mbeanDomain' in any property source
[AUDIT   ] CWWKZ0001I: Application spring-reactive-playground started in 3.480 seconds.
[AUDIT   ] CWWKF0012I: The server installed the following features: [servlet-3.1, websocket-1.1].
[AUDIT   ] CWWKF0011I: The server LibertyProjectServer is ready to run a smarter planet.
13:30:05.943 [Default Executor-thread-14] DEBUG reactor.util.Loggers$LoggerFactory - Using Slf4j logging framework
13:30:05.994 [Default Executor-thread-14] DEBUG org.springframework.web.reactive.DispatcherHandler - Processing GET request for [http://localhost:9080/]
13:30:06.041 [Default Executor-thread-14] ERROR org.springframework.web.server.adapter.HttpWebHandlerAdapter - Failed to handle request
org.springframework.web.server.ResponseStatusException: Response status 404 with reason "No matching handler"
        at org.springframework.web.reactive.DispatcherHandler.<clinit>(DispatcherHandler.java:74)
        at org.springframework.web.reactive.support.AbstractDispatcherHandlerInitializer.createDispatcherHandler(AbstractDispatcherHandlerInitializer.java:145)
        at org.springframework.web.reactive.support.AbstractDispatcherHandlerInitializer.registerDispatcherHandler(AbstractDispatcherHandlerInitializer.java:90)
        at org.springframework.web.reactive.support.AbstractDispatcherHandlerInitializer.onStartup(AbstractDispatcherHandlerInitializer.java:63)
        at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:172)
        at com.ibm.ws.webcontainer.webapp.WebApp.initializeServletContainerInitializers(WebApp.java:2539)
        at com.ibm.ws.webcontainer.webapp.WebApp.initialize(WebApp.java:1055)
        at com.ibm.ws.webcontainer.webapp.WebApp.initialize(WebApp.java:6595)
        at com.ibm.ws.webcontainer.osgi.DynamicVirtualHost.startWebApp(DynamicVirtualHost.java:468)
        at com.ibm.ws.webcontainer.osgi.DynamicVirtualHost.startWebApplication(DynamicVirtualHost.java:463)
        at com.ibm.ws.webcontainer.osgi.WebContainer.startWebApplication(WebContainer.java:1120)
        at com.ibm.ws.webcontainer.osgi.WebContainer.startModule(WebContainer.java:925)
        at com.ibm.ws.app.manager.module.internal.ModuleHandlerBase.deployModule(ModuleHandlerBase.java:100)
        at com.ibm.ws.app.manager.module.internal.DeployedModuleInfoImpl.installModule(DeployedModuleInfoImpl.java:50)
        at com.ibm.ws.app.manager.module.internal.DeployedAppInfoBase.deployModules(DeployedAppInfoBase.java:420)
        at com.ibm.ws.app.manager.module.internal.DeployedAppInfoBase.deployApp(DeployedAppInfoBase.java:406)
        at com.ibm.ws.app.manager.war.internal.WARApplicationHandlerImpl.install(WARApplicationHandlerImpl.java:66)
        at com.ibm.ws.app.manager.internal.statemachine.StartAction.execute(StartAction.java:141)
        at com.ibm.ws.app.manager.internal.statemachine.ApplicationStateMachineImpl.enterState(ApplicationStateMachineImpl.java:1259)
        at com.ibm.ws.app.manager.internal.statemachine.ApplicationStateMachineImpl.run(ApplicationStateMachineImpl.java:874)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:748)

我也尝试部署到 Tomcat 9,但我遇到了同样的问题。我之前通过扩展 SpringBootServletInitializer 而不是 AbstractAnnotationConfigDispatcherHandlerInitializer 成功地将传统 Spring MVC 应用程序部署为 WAR。 Spring Webflux 应用程序的等效过程是什么?我的项目代码中缺少什么?

Spring 5 为基于 webflux 的应用程序带来了 WebApplicationInitializer 的一些变体。

在Spring5.0.2之前(SpringBoot 2.0.0.M7与这个版本对齐),AbstractAnnotationConfigDispatcherHandlerInitializer有一个bug,在5.0.2,这个class被标记为@Deprecated,有一个新的AbstractReactiveWebInitializer引入。但是这个 class 似乎也有问题,我必须重写 createApplicationContext() 才能让它工作。 有关更多详细信息,请参阅我的示例 AppInitializer 中的评论。

检查在 tomcat.

上测试成功的 my workable war sample

使用AbstractReactiveWebInitializer(参见@Hantsy 回答)非常适合非 SpringBoot 应用程序,因为它会创建相应的上下文。

要在servlet容器中启动SpringBoot WebFlux应用程序,我使用以下方式:

  1. 禁用嵌入式 servlet 容器的自动配置:
@EnableWebFlux
@SpringBootApplication
@EnableAutoConfiguration(exclude={ReactiveWebServerFactoryAutoConfiguration.class})
public class MyWebfluxApplication {

    //this method actionally will not be executed 
    //public static void main(String[] args) {
    //    SpringApplication.run(MyWebfluxApplication.class, args);
    //}
}
  1. 创建自定义 WebApplicationInitializer:
public class ReactiveWebInitializer implements WebApplicationInitializer {

    private ConfigurableApplicationContext springContext;

    @Override
    public void onStartup(final ServletContext servletContext) throws ServletException {
        servletContext.addListener(this);

        SpringApplication app = new SpringApplication(MyWebfluxApplication.class);
        app.addInitializers((appCtx)->{
            // this initializer stores servlet context in spring context
            appCtx
                .getBeanFactory()
                .registerSingleton("storedServletContext",servletContext);
        });
        this.springContext = app.run("--debug");
    }

    public void contextDestroyed(ServletContextEvent sce) {

        springContext.stop();
        springContext
                .getBeansOfType(ExecutorConfigurationSupport.class)
                .values()
                .forEach(ExecutorConfigurationSupport::destroy);
    }
}
  1. 创建自定义 ReactiveWebServerFactory 作为 bean :
@Component
public class ServletContextReactiveWebServerFactory implements ReactiveWebServerFactory {

    @Autowired
    private ServletContext storedServletContext;

    private static final String DEFAULT_SERVLET_NAME = "http-handler-adapter";

    @Override
    public WebServer getWebServer(HttpHandler httpHandler) {
        // create and register special servlet 
        ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler);
        ServletRegistration.Dynamic registration = storedServletContext.addServlet(DEFAULT_SERVLET_NAME, servlet);
        if (registration == null) {
            throw new IllegalStateException("Failed to register servlet with name '" + DEFAULT_SERVLET_NAME + "'. " +
                    "Check if there is another servlet registered under the same name.");
        }

        registration.setLoadOnStartup(1);
        registration.addMapping("/");
        registration.setAsyncSupported(true);

        //we cannot control external server/tomcat, so the webserver does nothing
        return new WebServer(){
            public void start() throws WebServerException {}
            public void stop() throws WebServerException {}
            public int getPort() {
                return 8080;
            }
        };
    }

}

现在,您可以将 SpringBoot WebFlux 应用程序打包为 war 并在 servlet 容器中启动。

我接受了@Eugene 的回答并添加了一些更改:

@SpringBootApplication //No need to exclude some autoconfiguration classes
public class TestReactProjectApplication implements WebApplicationInitializer {
    //This application will start from tomcat and IDE
    public static void main(String[] args) {
        SpringApplication.run(TestReactProjectApplication.class, args);
    }

   @Override
    public void onStartup(ServletContext ctx) throws ServletException {
        SpringApplication app = new 
SpringApplication(TestReactProjectApplication.class);
        app.addInitializers((appCtx)->{
            // this initializer stores servlet context in spring context
           appCtx
            .getBeanFactory()
            .registerSingleton("storedServletContext",ctx);
            appCtx
           .getBeanFactory()
            .registerSingleton("reactiveWebServerFactory",new 
           MyReactiveWebServerFactory(ctx)); //ReactiveWebServerFactoryAutoConfiguration will not autoconfigure if class ReactiveWebServerFactory exists
        });
        app.run("--debug");
    }

}

对于 ReactiveWebServerFactory,我扩展了现有的 EmbededTomcatFactory(我认为这比创建自己的实现更容易):

public class MyReactiveWebServerFactory extends TomcatReactiveWebServerFactory {

    private ServletContext storedServletContext;

    private static final String DEFAULT_SERVLET_NAME = "http-handler-adapter";

    public MyReactiveWebServerFactory(ServletContext storedServletContext) {
        this.storedServletContext = storedServletContext;
    }

    @Override
    public WebServer getWebServer(HttpHandler httpHandler) {
        // create and register special servlet 
        ServletHttpHandlerAdapter servlet = new 
ServletHttpHandlerAdapter(httpHandler);
        ServletRegistration.Dynamic registration = 
storedServletContext.addServlet(DEFAULT_SERVLET_NAME, servlet);
        if (registration == null) {
            throw new IllegalStateException("Failed to register servlet with name '" + DEFAULT_SERVLET_NAME + "'. " +
                    "Check if there is another servlet registered under the same name.");
        }

        registration.setLoadOnStartup(1);
        registration.addMapping("/");
        registration.setAsyncSupported(true);

       //we cannot control external server/tomcat, so the webserver does nothing
        return new WebServer(){
            public void start() throws WebServerException {}
            public void stop() throws WebServerException {}
            public int getPort() {
                return 8080;
            }
        };
    }

}