我有一个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应用程序的等效过程是什么?我的项目代码中缺少什么?
答案 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应用程序,我使用以下方式:
@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);
//}
}
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);
}
}
@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;
}
};
}
}