001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.server.adapter; 018 019import javax.servlet.ServletContext; 020import javax.servlet.ServletContextEvent; 021import javax.servlet.ServletContextListener; 022import javax.servlet.ServletException; 023import javax.servlet.ServletRegistration; 024 025import org.springframework.context.ApplicationContext; 026import org.springframework.context.ConfigurableApplicationContext; 027import org.springframework.context.annotation.AnnotationConfigApplicationContext; 028import org.springframework.http.server.reactive.HttpHandler; 029import org.springframework.http.server.reactive.ServletHttpHandlerAdapter; 030import org.springframework.util.Assert; 031import org.springframework.web.WebApplicationInitializer; 032 033/** 034 * Base class for a {@link org.springframework.web.WebApplicationInitializer} 035 * that installs a Spring Reactive Web Application on a Servlet container. 036 * 037 * <p>Spring configuration is loaded and given to 038 * {@link WebHttpHandlerBuilder#applicationContext WebHttpHandlerBuilder} 039 * which scans the context looking for specific beans and creates a reactive 040 * {@link HttpHandler}. The resulting handler is installed as a Servlet through 041 * the {@link ServletHttpHandlerAdapter}. 042 * 043 * @author Rossen Stoyanchev 044 * @since 5.0.2 045 */ 046public abstract class AbstractReactiveWebInitializer implements WebApplicationInitializer { 047 048 /** 049 * The default servlet name to use. See {@link #getServletName}. 050 */ 051 public static final String DEFAULT_SERVLET_NAME = "http-handler-adapter"; 052 053 054 @Override 055 public void onStartup(ServletContext servletContext) throws ServletException { 056 String servletName = getServletName(); 057 Assert.hasLength(servletName, "getServletName() must not return null or empty"); 058 059 ApplicationContext applicationContext = createApplicationContext(); 060 Assert.notNull(applicationContext, "createApplicationContext() must not return null"); 061 062 refreshApplicationContext(applicationContext); 063 registerCloseListener(servletContext, applicationContext); 064 065 HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(applicationContext).build(); 066 ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler); 067 068 ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, servlet); 069 if (registration == null) { 070 throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " + 071 "Check if there is another servlet registered under the same name."); 072 } 073 074 registration.setLoadOnStartup(1); 075 registration.addMapping(getServletMapping()); 076 registration.setAsyncSupported(true); 077 } 078 079 /** 080 * Return the name to use to register the {@link ServletHttpHandlerAdapter}. 081 * <p>By default this is {@link #DEFAULT_SERVLET_NAME}. 082 */ 083 protected String getServletName() { 084 return DEFAULT_SERVLET_NAME; 085 } 086 087 /** 088 * Return the Spring configuration that contains application beans including 089 * the ones detected by {@link WebHttpHandlerBuilder#applicationContext}. 090 */ 091 protected ApplicationContext createApplicationContext() { 092 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); 093 Class<?>[] configClasses = getConfigClasses(); 094 Assert.notEmpty(configClasses, "No Spring configuration provided through getConfigClasses()"); 095 context.register(configClasses); 096 return context; 097 } 098 099 /** 100 * Specify {@link org.springframework.context.annotation.Configuration @Configuration} 101 * and/or {@link org.springframework.stereotype.Component @Component} 102 * classes that make up the application configuration. The config classes 103 * are given to {@linkplain #createApplicationContext()}. 104 */ 105 protected abstract Class<?>[] getConfigClasses(); 106 107 /** 108 * Refresh the given application context, if necessary. 109 */ 110 protected void refreshApplicationContext(ApplicationContext context) { 111 if (context instanceof ConfigurableApplicationContext) { 112 ConfigurableApplicationContext cac = (ConfigurableApplicationContext) context; 113 if (!cac.isActive()) { 114 cac.refresh(); 115 } 116 } 117 } 118 119 /** 120 * Register a {@link ServletContextListener} that closes the given 121 * application context when the servlet context is destroyed. 122 * @param servletContext the servlet context to listen to 123 * @param applicationContext the application context that is to be 124 * closed when {@code servletContext} is destroyed 125 */ 126 protected void registerCloseListener(ServletContext servletContext, ApplicationContext applicationContext) { 127 if (applicationContext instanceof ConfigurableApplicationContext) { 128 ConfigurableApplicationContext cac = (ConfigurableApplicationContext) applicationContext; 129 ServletContextDestroyedListener listener = new ServletContextDestroyedListener(cac); 130 servletContext.addListener(listener); 131 } 132 } 133 134 /** 135 * Return the Servlet mapping to use. Only the default Servlet mapping '/' 136 * and path-based Servlet mappings such as '/api/*' are supported. 137 * <p>By default this is set to '/'. 138 */ 139 protected String getServletMapping() { 140 return "/"; 141 } 142 143 144 private static class ServletContextDestroyedListener implements ServletContextListener { 145 146 private final ConfigurableApplicationContext applicationContext; 147 148 public ServletContextDestroyedListener(ConfigurableApplicationContext applicationContext) { 149 this.applicationContext = applicationContext; 150 } 151 152 @Override 153 public void contextInitialized(ServletContextEvent sce) { 154 } 155 156 @Override 157 public void contextDestroyed(ServletContextEvent sce) { 158 this.applicationContext.close(); 159 } 160 } 161 162}