diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/support/AbstractAnnotationConfigDispatcherHandlerInitializer.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/support/AbstractAnnotationConfigDispatcherHandlerInitializer.java new file mode 100644 index 0000000000..642339a514 --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/support/AbstractAnnotationConfigDispatcherHandlerInitializer.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.reactive.support; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.http.server.reactive.ServletHttpHandlerAdapter; +import org.springframework.util.ObjectUtils; +import org.springframework.web.reactive.DispatcherHandler; + +/** + * Base class for {@link org.springframework.web.WebApplicationInitializer} + * implementations that register a {@link DispatcherHandler} configured with annotated + * {@link org.springframework.context.annotation.Configuration @Configuration} classes in the + * servlet context, wrapping it in a {@link ServletHttpHandlerAdapter}. + * + *

Concrete implementations are required to implement {@link #getConfigClasses()} and + * {@link #getServletMapping()}. + * Further template and customization methods are provided by + * {@link AbstractDispatcherHandlerInitializer}. + * + * @author Arjen Poutsma + * @since 5.0 + */ +public abstract class AbstractAnnotationConfigDispatcherHandlerInitializer + extends AbstractDispatcherHandlerInitializer { + + /** + * {@inheritDoc} + *

This implementation creates an {@link AnnotationConfigApplicationContext}, + * providing it the annotated classes returned by {@link #getConfigClasses()}. + */ + @Override + protected ApplicationContext createApplicationContext() { + AnnotationConfigApplicationContext servletAppContext = new AnnotationConfigApplicationContext(); + Class[] configClasses = getConfigClasses(); + if (!ObjectUtils.isEmpty(configClasses)) { + servletAppContext.register(configClasses); + } + return servletAppContext; + } + + /** + * Specify {@link org.springframework.context.annotation.Configuration @Configuration} + * and/or {@link org.springframework.stereotype.Component @Component} classes to be + * provided to the {@linkplain #createApplicationContext() application context}. + * @return the configuration classes for the dispatcher servlet application context + */ + protected abstract Class[] getConfigClasses(); + +} diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/support/AbstractDispatcherHandlerInitializer.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/support/AbstractDispatcherHandlerInitializer.java new file mode 100644 index 0000000000..a2fccc73a8 --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/support/AbstractDispatcherHandlerInitializer.java @@ -0,0 +1,215 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.reactive.support; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.http.server.reactive.ServletHttpHandlerAdapter; +import org.springframework.util.Assert; +import org.springframework.web.WebApplicationInitializer; +import org.springframework.web.reactive.DispatcherHandler; +import org.springframework.web.server.WebHandler; +import org.springframework.web.server.adapter.HttpWebHandlerAdapter; + +/** + * Base class for {@link org.springframework.web.WebApplicationInitializer} + * implementations that register a {@link DispatcherHandler} in the servlet context, wrapping it in + * a {@link ServletHttpHandlerAdapter}. + * + *

Concrete implementations are required to implement + * {@link #createApplicationContext()}, which gets invoked from + * {@link #registerDispatcherHandler(ServletContext)}. Further customization can be achieved by + * overriding {@link #customizeRegistration(ServletRegistration.Dynamic)}. + * + * @author Arjen Poutsma + * @since 5.0 + */ +public abstract class AbstractDispatcherHandlerInitializer implements WebApplicationInitializer { + + /** + * The default servlet name. Can be customized by overriding {@link #getServletName}. + */ + public static final String DEFAULT_SERVLET_NAME = "dispatcher-handler"; + + /** + * The default servlet mapping. Can be customized by overriding {@link #getServletMapping()}. + */ + public static final String DEFAULT_SERVLET_MAPPING = "/"; + + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + registerDispatcherHandler(servletContext); + } + + /** + * Register a {@link DispatcherHandler} against the given servlet context. + *

This method will create a {@link DispatcherHandler}, initializing it with the application + * context returned from {@link #createApplicationContext()}. The created handler will be + * wrapped in a {@link ServletHttpHandlerAdapter} servlet with the name + * returned by {@link #getServletName()}, mapping it to the pattern + * returned from {@link #getServletMapping()}. + *

Further customization can be achieved by overriding {@link + * #customizeRegistration(ServletRegistration.Dynamic)} or + * {@link #createDispatcherHandler(ApplicationContext)}. + * @param servletContext the context to register the servlet against + */ + protected void registerDispatcherHandler(ServletContext servletContext) { + String servletName = getServletName(); + Assert.hasLength(servletName, "getServletName() must not return empty or null"); + + ApplicationContext applicationContext = createApplicationContext(); + Assert.notNull(applicationContext, + "createApplicationContext() did not return an application " + + "context for servlet [" + servletName + "]"); + refreshApplicationContext(applicationContext); + registerCloseListener(servletContext, applicationContext); + + WebHandler dispatcherHandler = createDispatcherHandler(applicationContext); + Assert.notNull(dispatcherHandler, + "createDispatcherHandler() did not return a WebHandler for servlet [" + + servletName + "]"); + + ServletHttpHandlerAdapter handlerAdapter = createHandlerAdapter(dispatcherHandler); + Assert.notNull(handlerAdapter, + "createHttpHandler() did not return a ServletHttpHandlerAdapter for servlet [" + + servletName + "]"); + + ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, handlerAdapter); + Assert.notNull(registration, + "Failed to register servlet with name '" + servletName + "'." + + "Check if there is another servlet registered under the same name."); + + registration.setLoadOnStartup(1); + registration.addMapping(getServletMapping()); + registration.setAsyncSupported(true); + + + customizeRegistration(registration); + } + + /** + * Return the name under which the {@link ServletHttpHandlerAdapter} will be registered. + * Defaults to {@link #DEFAULT_SERVLET_NAME}. + * @see #registerDispatcherHandler(ServletContext) + */ + protected String getServletName() { + return DEFAULT_SERVLET_NAME; + } + + /** + * Create an application context to be provided to the {@code DispatcherHandler}. + *

The returned context is delegated to Spring's + * {@link DispatcherHandler#DispatcherHandler(ApplicationContext)}. As such, + * it typically contains controllers, view resolvers, and other web-related beans. + * @see #registerDispatcherHandler(ServletContext) + */ + protected abstract ApplicationContext createApplicationContext(); + + /** + * Refresh the given application context, if necessary. + */ + protected void refreshApplicationContext(ApplicationContext applicationContext) { + if (applicationContext instanceof ConfigurableApplicationContext) { + ConfigurableApplicationContext cac = + (ConfigurableApplicationContext) applicationContext; + if (!cac.isActive()) { + cac.refresh(); + } + } + } + + /** + * Create a {@link DispatcherHandler} (or other kind of {@link WebHandler}-derived + * dispatcher) with the specified {@link ApplicationContext}. + */ + protected WebHandler createDispatcherHandler(ApplicationContext applicationContext) { + return new DispatcherHandler(applicationContext); + } + + /** + * Create a {@link ServletHttpHandlerAdapter}. + *

Default implementation returns a {@code ServletHttpHandlerAdapter} with the provided + * {@code webHandler}. + */ + protected ServletHttpHandlerAdapter createHandlerAdapter(WebHandler webHandler) { + HttpHandler httpHandler = new HttpWebHandlerAdapter(webHandler); + return new ServletHttpHandlerAdapter(httpHandler); + } + + /** + * Specify the servlet mapping for the {@code ServletHttpHandlerAdapter}. + *

Default implementation returns {@code /}. + * @see #registerDispatcherHandler(ServletContext) + */ + protected String getServletMapping() { + return DEFAULT_SERVLET_MAPPING; + } + + /** + * Optionally perform further registration customization once + * {@link #registerDispatcherHandler(ServletContext)} has completed. + * @param registration the {@code ServletHttpHandlerAdapter} registration to be customized + * @see #registerDispatcherHandler(ServletContext) + */ + protected void customizeRegistration(ServletRegistration.Dynamic registration) { + } + + /** + * Register a {@link ServletContextListener} that closes the given application context when + * the servlet context is destroyed. + * @param servletContext the servlet context to listen to + * @param applicationContext the application context that is to be closed when + * {@code servletContext} is destroyed + */ + protected void registerCloseListener(ServletContext servletContext, + ApplicationContext applicationContext) { + + if (applicationContext instanceof ConfigurableApplicationContext) { + ConfigurableApplicationContext context = + (ConfigurableApplicationContext) applicationContext; + ServletContextDestroyedListener listener = new ServletContextDestroyedListener(context); + servletContext.addListener(listener); + } + } + + private static class ServletContextDestroyedListener implements ServletContextListener { + + private final ConfigurableApplicationContext applicationContext; + + public ServletContextDestroyedListener(ConfigurableApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + public void contextInitialized(ServletContextEvent sce) { + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + this.applicationContext.close(); + } + } + +} diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/support/package-info.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/support/package-info.java new file mode 100644 index 0000000000..a8ee25fdfd --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/support/package-info.java @@ -0,0 +1,4 @@ +/** + * Support classes for Spring Web Reactive. + */ +package org.springframework.web.reactive.support; \ No newline at end of file diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/support/AbstractServletHttpHandlerAdapterInitializer.java b/spring-web/src/main/java/org/springframework/http/server/reactive/support/AbstractServletHttpHandlerAdapterInitializer.java new file mode 100644 index 0000000000..8a6f67924a --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/support/AbstractServletHttpHandlerAdapterInitializer.java @@ -0,0 +1,130 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.server.reactive.support; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; + +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.http.server.reactive.ServletHttpHandlerAdapter; +import org.springframework.util.Assert; +import org.springframework.web.WebApplicationInitializer; +import org.springframework.web.context.WebApplicationContext; + +/** + * Base class for {@link org.springframework.web.WebApplicationInitializer} + * implementations that register a {@link ServletHttpHandlerAdapter} in the servlet context. + * + *

Concrete implementations are required to implement + * {@link #createHttpHandler()}, as well as {@link #getServletMappings()}, + * both of which get invoked from {@link #registerHandlerAdapter(ServletContext)}. + * Further customization can be achieved by overriding + * {@link #customizeRegistration(ServletRegistration.Dynamic)}. + * + * @author Arjen Poutsma + * @since 5.0 + */ +public abstract class AbstractServletHttpHandlerAdapterInitializer + implements WebApplicationInitializer { + + /** + * The default servlet name. Can be customized by overriding {@link #getServletName}. + */ + public static final String DEFAULT_SERVLET_NAME = "http-handler-adapter"; + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + registerHandlerAdapter(servletContext); + } + + /** + * Register a {@link ServletHttpHandlerAdapter} against the given servlet context. + *

This method will create a {@code HttpHandler} using {@link #createHttpHandler()}, + * and use it to create a {@code ServletHttpHandlerAdapter} with the name returned by + * {@link #getServletName()}, and mapping it to the patterns + * returned from {@link #getServletMappings()}. + *

Further customization can be achieved by overriding {@link + * #customizeRegistration(ServletRegistration.Dynamic)} or + * {@link #createServlet(HttpHandler)}. + * @param servletContext the context to register the servlet against + */ + protected void registerHandlerAdapter(ServletContext servletContext) { + String servletName = getServletName(); + Assert.hasLength(servletName, "getServletName() must not return empty or null"); + + HttpHandler httpHandler = createHttpHandler(); + Assert.notNull(httpHandler, + "createHttpHandler() did not return a HttpHandler for servlet [" + + servletName + "]"); + + ServletHttpHandlerAdapter servlet = createServlet(httpHandler); + Assert.notNull(servlet, + "createHttpHandler() did not return a ServletHttpHandlerAdapter for servlet [" + + servletName + "]"); + + ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, servlet); + Assert.notNull(registration, + "Failed to register servlet with name '" + servletName + "'." + + "Check if there is another servlet registered under the same name."); + + registration.setLoadOnStartup(1); + registration.addMapping(getServletMappings()); + registration.setAsyncSupported(true); + + customizeRegistration(registration); + } + + /** + * Return the name under which the {@link ServletHttpHandlerAdapter} will be registered. + * Defaults to {@link #DEFAULT_SERVLET_NAME}. + * @see #registerHandlerAdapter(ServletContext) + */ + protected String getServletName() { + return DEFAULT_SERVLET_NAME; + } + + /** + * Create the {@link HttpHandler}. + */ + protected abstract HttpHandler createHttpHandler(); + + /** + * Create a {@link ServletHttpHandlerAdapter} with the specified {@link WebApplicationContext}. + *

Default implementation returns a {@code ServletHttpHandlerAdapter} with the provided + * {@code httpHandler}. + */ + protected ServletHttpHandlerAdapter createServlet(HttpHandler httpHandler) { + return new ServletHttpHandlerAdapter(httpHandler); + } + + /** + * Specify the servlet mapping(s) for the {@code ServletHttpHandlerAdapter} — + * for example {@code "/"}, {@code "/app"}, etc. + * @see #registerHandlerAdapter(ServletContext) + */ + protected abstract String[] getServletMappings(); + + /** + * Optionally perform further registration customization once + * {@link #registerHandlerAdapter(ServletContext)} has completed. + * @param registration the {@code DispatcherServlet} registration to be customized + * @see #registerHandlerAdapter(ServletContext) + */ + protected void customizeRegistration(ServletRegistration.Dynamic registration) { + } +} diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/support/package-info.java b/spring-web/src/main/java/org/springframework/http/server/reactive/support/package-info.java new file mode 100644 index 0000000000..2f8756faec --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/support/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Support classes for Spring's reactive HTTP server abstraction. + */ + +package org.springframework.http.server.reactive.support; \ No newline at end of file