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.context; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.List; 022import java.util.Map; 023import java.util.Properties; 024import java.util.concurrent.ConcurrentHashMap; 025 026import javax.servlet.ServletContext; 027 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030 031import org.springframework.beans.BeanUtils; 032import org.springframework.context.ApplicationContext; 033import org.springframework.context.ApplicationContextException; 034import org.springframework.context.ApplicationContextInitializer; 035import org.springframework.context.ConfigurableApplicationContext; 036import org.springframework.core.GenericTypeResolver; 037import org.springframework.core.annotation.AnnotationAwareOrderComparator; 038import org.springframework.core.env.ConfigurableEnvironment; 039import org.springframework.core.io.ClassPathResource; 040import org.springframework.core.io.support.PropertiesLoaderUtils; 041import org.springframework.lang.Nullable; 042import org.springframework.util.ClassUtils; 043import org.springframework.util.ObjectUtils; 044import org.springframework.util.StringUtils; 045 046/** 047 * Performs the actual initialization work for the root application context. 048 * Called by {@link ContextLoaderListener}. 049 * 050 * <p>Looks for a {@link #CONTEXT_CLASS_PARAM "contextClass"} parameter at the 051 * {@code web.xml} context-param level to specify the context class type, falling 052 * back to {@link org.springframework.web.context.support.XmlWebApplicationContext} 053 * if not found. With the default ContextLoader implementation, any context class 054 * specified needs to implement the {@link ConfigurableWebApplicationContext} interface. 055 * 056 * <p>Processes a {@link #CONFIG_LOCATION_PARAM "contextConfigLocation"} context-param 057 * and passes its value to the context instance, parsing it into potentially multiple 058 * file paths which can be separated by any number of commas and spaces, e.g. 059 * "WEB-INF/applicationContext1.xml, WEB-INF/applicationContext2.xml". 060 * Ant-style path patterns are supported as well, e.g. 061 * "WEB-INF/*Context.xml,WEB-INF/spring*.xml" or "WEB-INF/**/*Context.xml". 062 * If not explicitly specified, the context implementation is supposed to use a 063 * default location (with XmlWebApplicationContext: "/WEB-INF/applicationContext.xml"). 064 * 065 * <p>Note: In case of multiple config locations, later bean definitions will 066 * override ones defined in previously loaded files, at least when using one of 067 * Spring's default ApplicationContext implementations. This can be leveraged 068 * to deliberately override certain bean definitions via an extra XML file. 069 * 070 * <p>Above and beyond loading the root application context, this class can optionally 071 * load or obtain and hook up a shared parent context to the root application context. 072 * See the {@link #loadParentContext(ServletContext)} method for more information. 073 * 074 * <p>As of Spring 3.1, {@code ContextLoader} supports injecting the root web 075 * application context via the {@link #ContextLoader(WebApplicationContext)} 076 * constructor, allowing for programmatic configuration in Servlet 3.0+ environments. 077 * See {@link org.springframework.web.WebApplicationInitializer} for usage examples. 078 * 079 * @author Juergen Hoeller 080 * @author Colin Sampaleanu 081 * @author Sam Brannen 082 * @since 17.02.2003 083 * @see ContextLoaderListener 084 * @see ConfigurableWebApplicationContext 085 * @see org.springframework.web.context.support.XmlWebApplicationContext 086 */ 087public class ContextLoader { 088 089 /** 090 * Config param for the root WebApplicationContext id, 091 * to be used as serialization id for the underlying BeanFactory: {@value}. 092 */ 093 public static final String CONTEXT_ID_PARAM = "contextId"; 094 095 /** 096 * Name of servlet context parameter (i.e., {@value}) that can specify the 097 * config location for the root context, falling back to the implementation's 098 * default otherwise. 099 * @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION 100 */ 101 public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation"; 102 103 /** 104 * Config param for the root WebApplicationContext implementation class to use: {@value}. 105 * @see #determineContextClass(ServletContext) 106 */ 107 public static final String CONTEXT_CLASS_PARAM = "contextClass"; 108 109 /** 110 * Config param for {@link ApplicationContextInitializer} classes to use 111 * for initializing the root web application context: {@value}. 112 * @see #customizeContext(ServletContext, ConfigurableWebApplicationContext) 113 */ 114 public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses"; 115 116 /** 117 * Config param for global {@link ApplicationContextInitializer} classes to use 118 * for initializing all web application contexts in the current application: {@value}. 119 * @see #customizeContext(ServletContext, ConfigurableWebApplicationContext) 120 */ 121 public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses"; 122 123 /** 124 * Any number of these characters are considered delimiters between 125 * multiple values in a single init-param String value. 126 */ 127 private static final String INIT_PARAM_DELIMITERS = ",; \t\n"; 128 129 /** 130 * Name of the class path resource (relative to the ContextLoader class) 131 * that defines ContextLoader's default strategy names. 132 */ 133 private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties"; 134 135 136 private static final Properties defaultStrategies; 137 138 static { 139 // Load default strategy implementations from properties file. 140 // This is currently strictly internal and not meant to be customized 141 // by application developers. 142 try { 143 ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class); 144 defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); 145 } 146 catch (IOException ex) { 147 throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage()); 148 } 149 } 150 151 152 /** 153 * Map from (thread context) ClassLoader to corresponding 'current' WebApplicationContext. 154 */ 155 private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread = 156 new ConcurrentHashMap<>(1); 157 158 /** 159 * The 'current' WebApplicationContext, if the ContextLoader class is 160 * deployed in the web app ClassLoader itself. 161 */ 162 @Nullable 163 private static volatile WebApplicationContext currentContext; 164 165 166 /** 167 * The root WebApplicationContext instance that this loader manages. 168 */ 169 @Nullable 170 private WebApplicationContext context; 171 172 /** Actual ApplicationContextInitializer instances to apply to the context. */ 173 private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers = 174 new ArrayList<>(); 175 176 177 /** 178 * Create a new {@code ContextLoader} that will create a web application context 179 * based on the "contextClass" and "contextConfigLocation" servlet context-params. 180 * See class-level documentation for details on default values for each. 181 * <p>This constructor is typically used when declaring the {@code 182 * ContextLoaderListener} subclass as a {@code <listener>} within {@code web.xml}, as 183 * a no-arg constructor is required. 184 * <p>The created application context will be registered into the ServletContext under 185 * the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} 186 * and subclasses are free to call the {@link #closeWebApplicationContext} method on 187 * container shutdown to close the application context. 188 * @see #ContextLoader(WebApplicationContext) 189 * @see #initWebApplicationContext(ServletContext) 190 * @see #closeWebApplicationContext(ServletContext) 191 */ 192 public ContextLoader() { 193 } 194 195 /** 196 * Create a new {@code ContextLoader} with the given application context. This 197 * constructor is useful in Servlet 3.0+ environments where instance-based 198 * registration of listeners is possible through the {@link ServletContext#addListener} 199 * API. 200 * <p>The context may or may not yet be {@linkplain 201 * ConfigurableApplicationContext#refresh() refreshed}. If it (a) is an implementation 202 * of {@link ConfigurableWebApplicationContext} and (b) has <strong>not</strong> 203 * already been refreshed (the recommended approach), then the following will occur: 204 * <ul> 205 * <li>If the given context has not already been assigned an {@linkplain 206 * ConfigurableApplicationContext#setId id}, one will be assigned to it</li> 207 * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to 208 * the application context</li> 209 * <li>{@link #customizeContext} will be called</li> 210 * <li>Any {@link ApplicationContextInitializer ApplicationContextInitializers} specified through the 211 * "contextInitializerClasses" init-param will be applied.</li> 212 * <li>{@link ConfigurableApplicationContext#refresh refresh()} will be called</li> 213 * </ul> 214 * If the context has already been refreshed or does not implement 215 * {@code ConfigurableWebApplicationContext}, none of the above will occur under the 216 * assumption that the user has performed these actions (or not) per his or her 217 * specific needs. 218 * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples. 219 * <p>In any case, the given application context will be registered into the 220 * ServletContext under the attribute name {@link 221 * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and subclasses are 222 * free to call the {@link #closeWebApplicationContext} method on container shutdown 223 * to close the application context. 224 * @param context the application context to manage 225 * @see #initWebApplicationContext(ServletContext) 226 * @see #closeWebApplicationContext(ServletContext) 227 */ 228 public ContextLoader(WebApplicationContext context) { 229 this.context = context; 230 } 231 232 233 /** 234 * Specify which {@link ApplicationContextInitializer} instances should be used 235 * to initialize the application context used by this {@code ContextLoader}. 236 * @since 4.2 237 * @see #configureAndRefreshWebApplicationContext 238 * @see #customizeContext 239 */ 240 @SuppressWarnings("unchecked") 241 public void setContextInitializers(@Nullable ApplicationContextInitializer<?>... initializers) { 242 if (initializers != null) { 243 for (ApplicationContextInitializer<?> initializer : initializers) { 244 this.contextInitializers.add((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer); 245 } 246 } 247 } 248 249 250 /** 251 * Initialize Spring's web application context for the given servlet context, 252 * using the application context provided at construction time, or creating a new one 253 * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and 254 * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params. 255 * @param servletContext current servlet context 256 * @return the new WebApplicationContext 257 * @see #ContextLoader(WebApplicationContext) 258 * @see #CONTEXT_CLASS_PARAM 259 * @see #CONFIG_LOCATION_PARAM 260 */ 261 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { 262 if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { 263 throw new IllegalStateException( 264 "Cannot initialize context because there is already a root application context present - " + 265 "check whether you have multiple ContextLoader* definitions in your web.xml!"); 266 } 267 268 servletContext.log("Initializing Spring root WebApplicationContext"); 269 Log logger = LogFactory.getLog(ContextLoader.class); 270 if (logger.isInfoEnabled()) { 271 logger.info("Root WebApplicationContext: initialization started"); 272 } 273 long startTime = System.currentTimeMillis(); 274 275 try { 276 // Store context in local instance variable, to guarantee that 277 // it is available on ServletContext shutdown. 278 if (this.context == null) { 279 this.context = createWebApplicationContext(servletContext); 280 } 281 if (this.context instanceof ConfigurableWebApplicationContext) { 282 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; 283 if (!cwac.isActive()) { 284 // The context has not yet been refreshed -> provide services such as 285 // setting the parent context, setting the application context id, etc 286 if (cwac.getParent() == null) { 287 // The context instance was injected without an explicit parent -> 288 // determine parent for root web application context, if any. 289 ApplicationContext parent = loadParentContext(servletContext); 290 cwac.setParent(parent); 291 } 292 configureAndRefreshWebApplicationContext(cwac, servletContext); 293 } 294 } 295 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); 296 297 ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 298 if (ccl == ContextLoader.class.getClassLoader()) { 299 currentContext = this.context; 300 } 301 else if (ccl != null) { 302 currentContextPerThread.put(ccl, this.context); 303 } 304 305 if (logger.isInfoEnabled()) { 306 long elapsedTime = System.currentTimeMillis() - startTime; 307 logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms"); 308 } 309 310 return this.context; 311 } 312 catch (RuntimeException | Error ex) { 313 logger.error("Context initialization failed", ex); 314 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); 315 throw ex; 316 } 317 } 318 319 /** 320 * Instantiate the root WebApplicationContext for this loader, either the 321 * default context class or a custom context class if specified. 322 * <p>This implementation expects custom contexts to implement the 323 * {@link ConfigurableWebApplicationContext} interface. 324 * Can be overridden in subclasses. 325 * <p>In addition, {@link #customizeContext} gets called prior to refreshing the 326 * context, allowing subclasses to perform custom modifications to the context. 327 * @param sc current servlet context 328 * @return the root WebApplicationContext 329 * @see ConfigurableWebApplicationContext 330 */ 331 protected WebApplicationContext createWebApplicationContext(ServletContext sc) { 332 Class<?> contextClass = determineContextClass(sc); 333 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { 334 throw new ApplicationContextException("Custom context class [" + contextClass.getName() + 335 "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); 336 } 337 return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); 338 } 339 340 /** 341 * Return the WebApplicationContext implementation class to use, either the 342 * default XmlWebApplicationContext or a custom context class if specified. 343 * @param servletContext current servlet context 344 * @return the WebApplicationContext implementation class to use 345 * @see #CONTEXT_CLASS_PARAM 346 * @see org.springframework.web.context.support.XmlWebApplicationContext 347 */ 348 protected Class<?> determineContextClass(ServletContext servletContext) { 349 String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); 350 if (contextClassName != null) { 351 try { 352 return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); 353 } 354 catch (ClassNotFoundException ex) { 355 throw new ApplicationContextException( 356 "Failed to load custom context class [" + contextClassName + "]", ex); 357 } 358 } 359 else { 360 contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); 361 try { 362 return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); 363 } 364 catch (ClassNotFoundException ex) { 365 throw new ApplicationContextException( 366 "Failed to load default context class [" + contextClassName + "]", ex); 367 } 368 } 369 } 370 371 protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { 372 if (ObjectUtils.identityToString(wac).equals(wac.getId())) { 373 // The application context id is still set to its original default value 374 // -> assign a more useful id based on available information 375 String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); 376 if (idParam != null) { 377 wac.setId(idParam); 378 } 379 else { 380 // Generate default id... 381 wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + 382 ObjectUtils.getDisplayString(sc.getContextPath())); 383 } 384 } 385 386 wac.setServletContext(sc); 387 String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); 388 if (configLocationParam != null) { 389 wac.setConfigLocation(configLocationParam); 390 } 391 392 // The wac environment's #initPropertySources will be called in any case when the context 393 // is refreshed; do it eagerly here to ensure servlet property sources are in place for 394 // use in any post-processing or initialization that occurs below prior to #refresh 395 ConfigurableEnvironment env = wac.getEnvironment(); 396 if (env instanceof ConfigurableWebEnvironment) { 397 ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); 398 } 399 400 customizeContext(sc, wac); 401 wac.refresh(); 402 } 403 404 /** 405 * Customize the {@link ConfigurableWebApplicationContext} created by this 406 * ContextLoader after config locations have been supplied to the context 407 * but before the context is <em>refreshed</em>. 408 * <p>The default implementation {@linkplain #determineContextInitializerClasses(ServletContext) 409 * determines} what (if any) context initializer classes have been specified through 410 * {@linkplain #CONTEXT_INITIALIZER_CLASSES_PARAM context init parameters} and 411 * {@linkplain ApplicationContextInitializer#initialize invokes each} with the 412 * given web application context. 413 * <p>Any {@code ApplicationContextInitializers} implementing 414 * {@link org.springframework.core.Ordered Ordered} or marked with @{@link 415 * org.springframework.core.annotation.Order Order} will be sorted appropriately. 416 * @param sc the current servlet context 417 * @param wac the newly created application context 418 * @see #CONTEXT_INITIALIZER_CLASSES_PARAM 419 * @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext) 420 */ 421 protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) { 422 List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = 423 determineContextInitializerClasses(sc); 424 425 for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) { 426 Class<?> initializerContextClass = 427 GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class); 428 if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) { 429 throw new ApplicationContextException(String.format( 430 "Could not apply context initializer [%s] since its generic parameter [%s] " + 431 "is not assignable from the type of application context used by this " + 432 "context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(), 433 wac.getClass().getName())); 434 } 435 this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass)); 436 } 437 438 AnnotationAwareOrderComparator.sort(this.contextInitializers); 439 for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) { 440 initializer.initialize(wac); 441 } 442 } 443 444 /** 445 * Return the {@link ApplicationContextInitializer} implementation classes to use 446 * if any have been specified by {@link #CONTEXT_INITIALIZER_CLASSES_PARAM}. 447 * @param servletContext current servlet context 448 * @see #CONTEXT_INITIALIZER_CLASSES_PARAM 449 */ 450 protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> 451 determineContextInitializerClasses(ServletContext servletContext) { 452 453 List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes = 454 new ArrayList<>(); 455 456 String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM); 457 if (globalClassNames != null) { 458 for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) { 459 classes.add(loadInitializerClass(className)); 460 } 461 } 462 463 String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM); 464 if (localClassNames != null) { 465 for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) { 466 classes.add(loadInitializerClass(className)); 467 } 468 } 469 470 return classes; 471 } 472 473 @SuppressWarnings("unchecked") 474 private Class<ApplicationContextInitializer<ConfigurableApplicationContext>> loadInitializerClass(String className) { 475 try { 476 Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader()); 477 if (!ApplicationContextInitializer.class.isAssignableFrom(clazz)) { 478 throw new ApplicationContextException( 479 "Initializer class does not implement ApplicationContextInitializer interface: " + clazz); 480 } 481 return (Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz; 482 } 483 catch (ClassNotFoundException ex) { 484 throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex); 485 } 486 } 487 488 /** 489 * Template method with default implementation (which may be overridden by a 490 * subclass), to load or obtain an ApplicationContext instance which will be 491 * used as the parent context of the root WebApplicationContext. If the 492 * return value from the method is null, no parent context is set. 493 * <p>The main reason to load a parent context here is to allow multiple root 494 * web application contexts to all be children of a shared EAR context, or 495 * alternately to also share the same parent context that is visible to 496 * EJBs. For pure web applications, there is usually no need to worry about 497 * having a parent context to the root web application context. 498 * <p>The default implementation simply returns {@code null}, as of 5.0. 499 * @param servletContext current servlet context 500 * @return the parent application context, or {@code null} if none 501 */ 502 @Nullable 503 protected ApplicationContext loadParentContext(ServletContext servletContext) { 504 return null; 505 } 506 507 /** 508 * Close Spring's web application context for the given servlet context. 509 * <p>If overriding {@link #loadParentContext(ServletContext)}, you may have 510 * to override this method as well. 511 * @param servletContext the ServletContext that the WebApplicationContext runs in 512 */ 513 public void closeWebApplicationContext(ServletContext servletContext) { 514 servletContext.log("Closing Spring root WebApplicationContext"); 515 try { 516 if (this.context instanceof ConfigurableWebApplicationContext) { 517 ((ConfigurableWebApplicationContext) this.context).close(); 518 } 519 } 520 finally { 521 ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 522 if (ccl == ContextLoader.class.getClassLoader()) { 523 currentContext = null; 524 } 525 else if (ccl != null) { 526 currentContextPerThread.remove(ccl); 527 } 528 servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); 529 } 530 } 531 532 533 /** 534 * Obtain the Spring root web application context for the current thread 535 * (i.e. for the current thread's context ClassLoader, which needs to be 536 * the web application's ClassLoader). 537 * @return the current root web application context, or {@code null} 538 * if none found 539 * @see org.springframework.web.context.support.SpringBeanAutowiringSupport 540 */ 541 @Nullable 542 public static WebApplicationContext getCurrentWebApplicationContext() { 543 ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 544 if (ccl != null) { 545 WebApplicationContext ccpt = currentContextPerThread.get(ccl); 546 if (ccpt != null) { 547 return ccpt; 548 } 549 } 550 return currentContext; 551 } 552 553}