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