繁体   English   中英

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

[英]Running Spring Webflux Application as a WAR

我有一个 Spring 反应式示例应用程序,它是从Spring Webflux 文档中提供的示例之一修改而来的。 此应用程序的master分支以传统方式使用 Spring Boot,带有嵌入式应用程序服务器(Netty)。 它工作正常。

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

对于 Servlet 容器,尤其是 WAR 部署,您可以使用 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 应用程序的等效流程是什么? 我的项目代码中缺少什么?

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

在 Spring 5.0.2 之前(Spring Boot 2.0.0.M7 与此版本一致), AbstractAnnotationConfigDispatcherHandlerInitializer存在一个 bug,在 5.0.2 中,这个类被标记为@Deprecated ,引入了一个新的AbstractReactiveWebInitializer 但是这个类似乎也有问题,我必须覆盖createApplicationContext()才能使其工作。 有关更多详细信息,请参阅我的示例AppInitializer 中的注释。

检查在 tomcat 上成功测试的可行战争样本

使用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;
            }
        };
    }

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM