001/* 002 * Copyright 2012-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 * http://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.boot.web.servlet.context; 018 019import java.util.Collection; 020import java.util.Collections; 021import java.util.EventListener; 022import java.util.HashMap; 023import java.util.LinkedHashSet; 024import java.util.Map; 025import java.util.Set; 026 027import javax.servlet.Filter; 028import javax.servlet.Servlet; 029import javax.servlet.ServletConfig; 030import javax.servlet.ServletContext; 031import javax.servlet.ServletException; 032 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035 036import org.springframework.beans.BeansException; 037import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 038import org.springframework.beans.factory.config.Scope; 039import org.springframework.beans.factory.support.DefaultListableBeanFactory; 040import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext; 041import org.springframework.boot.web.server.WebServer; 042import org.springframework.boot.web.servlet.FilterRegistrationBean; 043import org.springframework.boot.web.servlet.ServletContextInitializer; 044import org.springframework.boot.web.servlet.ServletContextInitializerBeans; 045import org.springframework.boot.web.servlet.ServletRegistrationBean; 046import org.springframework.boot.web.servlet.server.ServletWebServerFactory; 047import org.springframework.context.ApplicationContext; 048import org.springframework.context.ApplicationContextException; 049import org.springframework.core.io.Resource; 050import org.springframework.util.StringUtils; 051import org.springframework.web.context.ContextLoader; 052import org.springframework.web.context.ContextLoaderListener; 053import org.springframework.web.context.ServletContextAware; 054import org.springframework.web.context.WebApplicationContext; 055import org.springframework.web.context.support.GenericWebApplicationContext; 056import org.springframework.web.context.support.ServletContextAwareProcessor; 057import org.springframework.web.context.support.ServletContextResource; 058import org.springframework.web.context.support.ServletContextScope; 059import org.springframework.web.context.support.WebApplicationContextUtils; 060 061/** 062 * A {@link WebApplicationContext} that can be used to bootstrap itself from a contained 063 * {@link ServletWebServerFactory} bean. 064 * <p> 065 * This context will create, initialize and run an {@link WebServer} by searching for a 066 * single {@link ServletWebServerFactory} bean within the {@link ApplicationContext} 067 * itself. The {@link ServletWebServerFactory} is free to use standard Spring concepts 068 * (such as dependency injection, lifecycle callbacks and property placeholder variables). 069 * <p> 070 * In addition, any {@link Servlet} or {@link Filter} beans defined in the context will be 071 * automatically registered with the web server. In the case of a single Servlet bean, the 072 * '/' mapping will be used. If multiple Servlet beans are found then the lowercase bean 073 * name will be used as a mapping prefix. Any Servlet named 'dispatcherServlet' will 074 * always be mapped to '/'. Filter beans will be mapped to all URLs ('/*'). 075 * <p> 076 * For more advanced configuration, the context can instead define beans that implement 077 * the {@link ServletContextInitializer} interface (most often 078 * {@link ServletRegistrationBean}s and/or {@link FilterRegistrationBean}s). To prevent 079 * double registration, the use of {@link ServletContextInitializer} beans will disable 080 * automatic Servlet and Filter bean registration. 081 * <p> 082 * Although this context can be used directly, most developers should consider using the 083 * {@link AnnotationConfigServletWebServerApplicationContext} or 084 * {@link XmlServletWebServerApplicationContext} variants. 085 * 086 * @author Phillip Webb 087 * @author Dave Syer 088 * @see AnnotationConfigServletWebServerApplicationContext 089 * @see XmlServletWebServerApplicationContext 090 * @see ServletWebServerFactory 091 */ 092public class ServletWebServerApplicationContext extends GenericWebApplicationContext 093 implements ConfigurableWebServerApplicationContext { 094 095 private static final Log logger = LogFactory 096 .getLog(ServletWebServerApplicationContext.class); 097 098 /** 099 * Constant value for the DispatcherServlet bean name. A Servlet bean with this name 100 * is deemed to be the "main" servlet and is automatically given a mapping of "/" by 101 * default. To change the default behavior you can use a 102 * {@link ServletRegistrationBean} or a different bean name. 103 */ 104 public static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet"; 105 106 private volatile WebServer webServer; 107 108 private ServletConfig servletConfig; 109 110 private String serverNamespace; 111 112 /** 113 * Create a new {@link ServletWebServerApplicationContext}. 114 */ 115 public ServletWebServerApplicationContext() { 116 } 117 118 /** 119 * Create a new {@link ServletWebServerApplicationContext} with the given 120 * {@code DefaultListableBeanFactory}. 121 * @param beanFactory the DefaultListableBeanFactory instance to use for this context 122 */ 123 public ServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) { 124 super(beanFactory); 125 } 126 127 /** 128 * Register ServletContextAwareProcessor. 129 * @see ServletContextAwareProcessor 130 */ 131 @Override 132 protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { 133 beanFactory.addBeanPostProcessor( 134 new WebApplicationContextServletContextAwareProcessor(this)); 135 beanFactory.ignoreDependencyInterface(ServletContextAware.class); 136 registerWebApplicationScopes(); 137 } 138 139 @Override 140 public final void refresh() throws BeansException, IllegalStateException { 141 try { 142 super.refresh(); 143 } 144 catch (RuntimeException ex) { 145 stopAndReleaseWebServer(); 146 throw ex; 147 } 148 } 149 150 @Override 151 protected void onRefresh() { 152 super.onRefresh(); 153 try { 154 createWebServer(); 155 } 156 catch (Throwable ex) { 157 throw new ApplicationContextException("Unable to start web server", ex); 158 } 159 } 160 161 @Override 162 protected void finishRefresh() { 163 super.finishRefresh(); 164 WebServer webServer = startWebServer(); 165 if (webServer != null) { 166 publishEvent(new ServletWebServerInitializedEvent(webServer, this)); 167 } 168 } 169 170 @Override 171 protected void onClose() { 172 super.onClose(); 173 stopAndReleaseWebServer(); 174 } 175 176 private void createWebServer() { 177 WebServer webServer = this.webServer; 178 ServletContext servletContext = getServletContext(); 179 if (webServer == null && servletContext == null) { 180 ServletWebServerFactory factory = getWebServerFactory(); 181 this.webServer = factory.getWebServer(getSelfInitializer()); 182 } 183 else if (servletContext != null) { 184 try { 185 getSelfInitializer().onStartup(servletContext); 186 } 187 catch (ServletException ex) { 188 throw new ApplicationContextException("Cannot initialize servlet context", 189 ex); 190 } 191 } 192 initPropertySources(); 193 } 194 195 /** 196 * Returns the {@link ServletWebServerFactory} that should be used to create the 197 * embedded {@link WebServer}. By default this method searches for a suitable bean in 198 * the context itself. 199 * @return a {@link ServletWebServerFactory} (never {@code null}) 200 */ 201 protected ServletWebServerFactory getWebServerFactory() { 202 // Use bean names so that we don't consider the hierarchy 203 String[] beanNames = getBeanFactory() 204 .getBeanNamesForType(ServletWebServerFactory.class); 205 if (beanNames.length == 0) { 206 throw new ApplicationContextException( 207 "Unable to start ServletWebServerApplicationContext due to missing " 208 + "ServletWebServerFactory bean."); 209 } 210 if (beanNames.length > 1) { 211 throw new ApplicationContextException( 212 "Unable to start ServletWebServerApplicationContext due to multiple " 213 + "ServletWebServerFactory beans : " 214 + StringUtils.arrayToCommaDelimitedString(beanNames)); 215 } 216 return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class); 217 } 218 219 /** 220 * Returns the {@link ServletContextInitializer} that will be used to complete the 221 * setup of this {@link WebApplicationContext}. 222 * @return the self initializer 223 * @see #prepareWebApplicationContext(ServletContext) 224 */ 225 private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { 226 return this::selfInitialize; 227 } 228 229 private void selfInitialize(ServletContext servletContext) throws ServletException { 230 prepareWebApplicationContext(servletContext); 231 registerApplicationScope(servletContext); 232 WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), 233 servletContext); 234 for (ServletContextInitializer beans : getServletContextInitializerBeans()) { 235 beans.onStartup(servletContext); 236 } 237 } 238 239 private void registerApplicationScope(ServletContext servletContext) { 240 ServletContextScope appScope = new ServletContextScope(servletContext); 241 getBeanFactory().registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope); 242 // Register as ServletContext attribute, for ContextCleanupListener to detect it. 243 servletContext.setAttribute(ServletContextScope.class.getName(), appScope); 244 } 245 246 private void registerWebApplicationScopes() { 247 ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes( 248 getBeanFactory()); 249 WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory()); 250 existingScopes.restore(); 251 } 252 253 /** 254 * Returns {@link ServletContextInitializer}s that should be used with the embedded 255 * web server. By default this method will first attempt to find 256 * {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain 257 * {@link EventListener} beans. 258 * @return the servlet initializer beans 259 */ 260 protected Collection<ServletContextInitializer> getServletContextInitializerBeans() { 261 return new ServletContextInitializerBeans(getBeanFactory()); 262 } 263 264 /** 265 * Prepare the {@link WebApplicationContext} with the given fully loaded 266 * {@link ServletContext}. This method is usually called from 267 * {@link ServletContextInitializer#onStartup(ServletContext)} and is similar to the 268 * functionality usually provided by a {@link ContextLoaderListener}. 269 * @param servletContext the operational servlet context 270 */ 271 protected void prepareWebApplicationContext(ServletContext servletContext) { 272 Object rootContext = servletContext.getAttribute( 273 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); 274 if (rootContext != null) { 275 if (rootContext == this) { 276 throw new IllegalStateException( 277 "Cannot initialize context because there is already a root application context present - " 278 + "check whether you have multiple ServletContextInitializers!"); 279 } 280 return; 281 } 282 Log logger = LogFactory.getLog(ContextLoader.class); 283 servletContext.log("Initializing Spring embedded WebApplicationContext"); 284 try { 285 servletContext.setAttribute( 286 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this); 287 if (logger.isDebugEnabled()) { 288 logger.debug( 289 "Published root WebApplicationContext as ServletContext attribute with name [" 290 + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 291 + "]"); 292 } 293 setServletContext(servletContext); 294 if (logger.isInfoEnabled()) { 295 long elapsedTime = System.currentTimeMillis() - getStartupDate(); 296 logger.info("Root WebApplicationContext: initialization completed in " 297 + elapsedTime + " ms"); 298 } 299 } 300 catch (RuntimeException | Error ex) { 301 logger.error("Context initialization failed", ex); 302 servletContext.setAttribute( 303 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); 304 throw ex; 305 } 306 } 307 308 private WebServer startWebServer() { 309 WebServer webServer = this.webServer; 310 if (webServer != null) { 311 webServer.start(); 312 } 313 return webServer; 314 } 315 316 private void stopAndReleaseWebServer() { 317 WebServer webServer = this.webServer; 318 if (webServer != null) { 319 try { 320 webServer.stop(); 321 this.webServer = null; 322 } 323 catch (Exception ex) { 324 throw new IllegalStateException(ex); 325 } 326 } 327 } 328 329 @Override 330 protected Resource getResourceByPath(String path) { 331 if (getServletContext() == null) { 332 return new ClassPathContextResource(path, getClassLoader()); 333 } 334 return new ServletContextResource(getServletContext(), path); 335 } 336 337 @Override 338 public String getServerNamespace() { 339 return this.serverNamespace; 340 } 341 342 @Override 343 public void setServerNamespace(String serverNamespace) { 344 this.serverNamespace = serverNamespace; 345 } 346 347 @Override 348 public void setServletConfig(ServletConfig servletConfig) { 349 this.servletConfig = servletConfig; 350 } 351 352 @Override 353 public ServletConfig getServletConfig() { 354 return this.servletConfig; 355 } 356 357 /** 358 * Returns the {@link WebServer} that was created by the context or {@code null} if 359 * the server has not yet been created. 360 * @return the embedded web server 361 */ 362 @Override 363 public WebServer getWebServer() { 364 return this.webServer; 365 } 366 367 /** 368 * Utility class to store and restore any user defined scopes. This allow scopes to be 369 * registered in an ApplicationContextInitializer in the same way as they would in a 370 * classic non-embedded web application context. 371 */ 372 public static class ExistingWebApplicationScopes { 373 374 private static final Set<String> SCOPES; 375 376 static { 377 Set<String> scopes = new LinkedHashSet<>(); 378 scopes.add(WebApplicationContext.SCOPE_REQUEST); 379 scopes.add(WebApplicationContext.SCOPE_SESSION); 380 SCOPES = Collections.unmodifiableSet(scopes); 381 } 382 383 private final ConfigurableListableBeanFactory beanFactory; 384 385 private final Map<String, Scope> scopes = new HashMap<>(); 386 387 public ExistingWebApplicationScopes(ConfigurableListableBeanFactory beanFactory) { 388 this.beanFactory = beanFactory; 389 for (String scopeName : SCOPES) { 390 Scope scope = beanFactory.getRegisteredScope(scopeName); 391 if (scope != null) { 392 this.scopes.put(scopeName, scope); 393 } 394 } 395 } 396 397 public void restore() { 398 this.scopes.forEach((key, value) -> { 399 if (logger.isInfoEnabled()) { 400 logger.info("Restoring user defined scope " + key); 401 } 402 this.beanFactory.registerScope(key, value); 403 }); 404 } 405 406 } 407 408}