将Spring Webflux应用程序作为WAR运行

时间:2017-10-13 18:58:19

标签: java spring spring-mvc war spring-webflux

我有一个Spring sample application,它是根据Spring Webflux documentation中提供的一个示例进行修改的。此应用程序的master分支以传统方式使用Spring Boot,具有嵌入式应用程序服务器(Netty)。它工作正常。

Liberty branch中,我尝试将应用程序构建为WAR并部署到Websphere Liberty Profile。除了对构建过程的更改之外,最重要的代码更改是使用Application.java(源here)扩展AbstractAnnotationConfigDispatcherHandlerInitializer,根据Webflux文档:

  

对于特别是使用WAR部署的Servlet容器,您可以使用AbstractAnnotationConfigDispatcherHandlerInitializer作为WebApplicationInitializer,并由Servlet容器自动检测。它负责注册ServletHttpHandlerAdapter,如上所示。您需要实现一个抽象方法才能指向Spring配置。

但是,当我这样做时,我的资源/端点都没有被映射,我在Application.java中声明的所有bean都没有被注册。这是我得到的完整输出,在尝试访问上下文根时抛出异常:

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应用程序的等效过程是什么?我的项目代码中缺少什么?

3 个答案:

答案 0 :(得分:2)

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

在Spring 5.0.2之前(Spring Boot 2.0.0.M7与此版本对齐),AbstractAnnotationConfigDispatcherHandlerInitializer中存在错误,而在5.0.2中,此类标记为@Deprecated,引入了新的AbstractReactiveWebInitializer。但是这个类似乎也有错误,我必须覆盖createApplicationContext()才能使它工作。 有关详情,请参阅我的示例AppInitializer中的评论。

检查已在tomcat上成功测试的my workable war sample

答案 1 :(得分:0)

使用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容器中启动。

答案 2 :(得分:0)

我接受@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;
            }
        };
    }

}