Running Spring Webflux Application as a WAR

懵懂的女人 提交于 2021-02-07 15:13:04

问题


I have a Spring reactive sample application that was modified from one of the examples that was provided in the the Spring Webflux documentation. The master branch of this application uses Spring Boot in the traditional manner, with an embedded application server (Netty). It is working fine.

In the 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) extend AbstractAnnotationConfigDispatcherHandlerInitializer, as per Webflux documentation:

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.

However, when I do this, none of my resources/endpoints get mapped and none of my beans that I declare in Application.java are registered. This is the full output that I get, with the exception being thrown when trying to access the context root:

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)

I have also tried deploying to Tomcat 9 and I get the same problem. I have previously successfully deployed traditional Spring MVC applications as a WAR by extending SpringBootServletInitializer instead of AbstractAnnotationConfigDispatcherHandlerInitializer. What is the equivalent process for Spring Webflux applications? What am I missing in my project code?


回答1:


Spring 5 brings some variants of WebApplicationInitializer for webflux based applications.

Before Spring 5.0.2(Spring Boot 2.0.0.M7 aligned with this version), there is a bug in AbstractAnnotationConfigDispatcherHandlerInitializer, and in 5.0.2, this class is marked as @Deprecated, there is a new AbstractReactiveWebInitializer introduced. But this class seems also buggy, I have to override the createApplicationContext() to make it work. See the comments in my sample AppInitializer for more details.

Check my workable war sample which was tested successfully on tomcat.




回答2:


I took @Eugene answer and added some changes:

@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");
    }

}

And for ReactiveWebServerFactory i extend an existing EmbededTomcatFactory (i think it easier than create own implementation):

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

}



回答3:


Using AbstractReactiveWebInitializer ( see @Hantsy answer) well suits for non-SpringBoot applications, as it creates correspondent context.

To start SpringBoot WebFlux application in servlet container, I use the following way:

  1. Disable auto-configuration for embedded servlet containers:
@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. Create custom 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. Create custom ReactiveWebServerFactory as 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;
            }
        };
    }

}

Now, you may package you SpringBoot WebFlux application as war and launch in servlet container.



来源:https://stackoverflow.com/questions/46736435/running-spring-webflux-application-as-a-war

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!