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.servlet; 018 019import java.io.IOException; 020import java.security.Principal; 021import java.util.ArrayList; 022import java.util.List; 023import java.util.concurrent.Callable; 024import javax.servlet.ServletContext; 025import javax.servlet.ServletException; 026import javax.servlet.http.HttpServletRequest; 027import javax.servlet.http.HttpServletResponse; 028import javax.servlet.http.HttpServletResponseWrapper; 029 030import org.springframework.beans.BeanUtils; 031import org.springframework.context.ApplicationContext; 032import org.springframework.context.ApplicationContextAware; 033import org.springframework.context.ApplicationContextException; 034import org.springframework.context.ApplicationContextInitializer; 035import org.springframework.context.ApplicationListener; 036import org.springframework.context.ConfigurableApplicationContext; 037import org.springframework.context.event.ContextRefreshedEvent; 038import org.springframework.context.event.SourceFilteringListener; 039import org.springframework.context.i18n.LocaleContext; 040import org.springframework.context.i18n.LocaleContextHolder; 041import org.springframework.context.i18n.SimpleLocaleContext; 042import org.springframework.core.GenericTypeResolver; 043import org.springframework.core.annotation.AnnotationAwareOrderComparator; 044import org.springframework.core.env.ConfigurableEnvironment; 045import org.springframework.http.HttpMethod; 046import org.springframework.util.ClassUtils; 047import org.springframework.util.ObjectUtils; 048import org.springframework.util.StringUtils; 049import org.springframework.web.context.ConfigurableWebApplicationContext; 050import org.springframework.web.context.ConfigurableWebEnvironment; 051import org.springframework.web.context.ContextLoader; 052import org.springframework.web.context.WebApplicationContext; 053import org.springframework.web.context.request.NativeWebRequest; 054import org.springframework.web.context.request.RequestAttributes; 055import org.springframework.web.context.request.RequestContextHolder; 056import org.springframework.web.context.request.ServletRequestAttributes; 057import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; 058import org.springframework.web.context.request.async.WebAsyncManager; 059import org.springframework.web.context.request.async.WebAsyncUtils; 060import org.springframework.web.context.support.ServletRequestHandledEvent; 061import org.springframework.web.context.support.WebApplicationContextUtils; 062import org.springframework.web.context.support.XmlWebApplicationContext; 063import org.springframework.web.cors.CorsUtils; 064import org.springframework.web.util.NestedServletException; 065import org.springframework.web.util.WebUtils; 066 067/** 068 * Base servlet for Spring's web framework. Provides integration with 069 * a Spring application context, in a JavaBean-based overall solution. 070 * 071 * <p>This class offers the following functionality: 072 * <ul> 073 * <li>Manages a {@link org.springframework.web.context.WebApplicationContext 074 * WebApplicationContext} instance per servlet. The servlet's configuration is determined 075 * by beans in the servlet's namespace. 076 * <li>Publishes events on request processing, whether or not a request is 077 * successfully handled. 078 * </ul> 079 * 080 * <p>Subclasses must implement {@link #doService} to handle requests. Because this extends 081 * {@link HttpServletBean} rather than HttpServlet directly, bean properties are 082 * automatically mapped onto it. Subclasses can override {@link #initFrameworkServlet()} 083 * for custom initialization. 084 * 085 * <p>Detects a "contextClass" parameter at the servlet init-param level, 086 * falling back to the default context class, 087 * {@link org.springframework.web.context.support.XmlWebApplicationContext 088 * XmlWebApplicationContext}, if not found. Note that, with the default 089 * {@code FrameworkServlet}, a custom context class needs to implement the 090 * {@link org.springframework.web.context.ConfigurableWebApplicationContext 091 * ConfigurableWebApplicationContext} SPI. 092 * 093 * <p>Accepts an optional "contextInitializerClasses" servlet init-param that 094 * specifies one or more {@link org.springframework.context.ApplicationContextInitializer 095 * ApplicationContextInitializer} classes. The managed web application context will be 096 * delegated to these initializers, allowing for additional programmatic configuration, 097 * e.g. adding property sources or activating profiles against the {@linkplain 098 * org.springframework.context.ConfigurableApplicationContext#getEnvironment() context's 099 * environment}. See also {@link org.springframework.web.context.ContextLoader} which 100 * supports a "contextInitializerClasses" context-param with identical semantics for 101 * the "root" web application context. 102 * 103 * <p>Passes a "contextConfigLocation" servlet init-param to the context instance, 104 * parsing it into potentially multiple file paths which can be separated by any 105 * number of commas and spaces, like "test-servlet.xml, myServlet.xml". 106 * If not explicitly specified, the context implementation is supposed to build a 107 * default location from the namespace of the servlet. 108 * 109 * <p>Note: In case of multiple config locations, later bean definitions will 110 * override ones defined in earlier loaded files, at least when using Spring's 111 * default ApplicationContext implementation. This can be leveraged to 112 * deliberately override certain bean definitions via an extra XML file. 113 * 114 * <p>The default namespace is "'servlet-name'-servlet", e.g. "test-servlet" for a 115 * servlet-name "test" (leading to a "/WEB-INF/test-servlet.xml" default location 116 * with XmlWebApplicationContext). The namespace can also be set explicitly via 117 * the "namespace" servlet init-param. 118 * 119 * <p>As of Spring 3.1, {@code FrameworkServlet} may now be injected with a web 120 * application context, rather than creating its own internally. This is useful in Servlet 121 * 3.0+ environments, which support programmatic registration of servlet instances. See 122 * {@link #FrameworkServlet(WebApplicationContext)} Javadoc for details. 123 * 124 * @author Rod Johnson 125 * @author Juergen Hoeller 126 * @author Sam Brannen 127 * @author Chris Beams 128 * @author Rossen Stoyanchev 129 * @author Phillip Webb 130 * @see #doService 131 * @see #setContextClass 132 * @see #setContextConfigLocation 133 * @see #setContextInitializerClasses 134 * @see #setNamespace 135 */ 136@SuppressWarnings("serial") 137public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware { 138 139 /** 140 * Suffix for WebApplicationContext namespaces. If a servlet of this class is 141 * given the name "test" in a context, the namespace used by the servlet will 142 * resolve to "test-servlet". 143 */ 144 public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet"; 145 146 /** 147 * Default context class for FrameworkServlet. 148 * @see org.springframework.web.context.support.XmlWebApplicationContext 149 */ 150 public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class; 151 152 /** 153 * Prefix for the ServletContext attribute for the WebApplicationContext. 154 * The completion is the servlet name. 155 */ 156 public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT."; 157 158 /** 159 * Any number of these characters are considered delimiters between 160 * multiple values in a single init-param String value. 161 */ 162 private static final String INIT_PARAM_DELIMITERS = ",; \t\n"; 163 164 165 /** Checking for Servlet 3.0+ HttpServletResponse.getStatus() */ 166 private static final boolean responseGetStatusAvailable = 167 ClassUtils.hasMethod(HttpServletResponse.class, "getStatus"); 168 169 170 /** ServletContext attribute to find the WebApplicationContext in */ 171 private String contextAttribute; 172 173 /** WebApplicationContext implementation class to create */ 174 private Class<?> contextClass = DEFAULT_CONTEXT_CLASS; 175 176 /** WebApplicationContext id to assign */ 177 private String contextId; 178 179 /** Namespace for this servlet */ 180 private String namespace; 181 182 /** Explicit context config location */ 183 private String contextConfigLocation; 184 185 /** Actual ApplicationContextInitializer instances to apply to the context */ 186 private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers = 187 new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>(); 188 189 /** Comma-delimited ApplicationContextInitializer class names set through init param */ 190 private String contextInitializerClasses; 191 192 /** Should we publish the context as a ServletContext attribute? */ 193 private boolean publishContext = true; 194 195 /** Should we publish a ServletRequestHandledEvent at the end of each request? */ 196 private boolean publishEvents = true; 197 198 /** Expose LocaleContext and RequestAttributes as inheritable for child threads? */ 199 private boolean threadContextInheritable = false; 200 201 /** Should we dispatch an HTTP OPTIONS request to {@link #doService}? */ 202 private boolean dispatchOptionsRequest = false; 203 204 /** Should we dispatch an HTTP TRACE request to {@link #doService}? */ 205 private boolean dispatchTraceRequest = false; 206 207 /** WebApplicationContext for this servlet */ 208 private WebApplicationContext webApplicationContext; 209 210 /** If the WebApplicationContext was injected via {@link #setApplicationContext} */ 211 private boolean webApplicationContextInjected = false; 212 213 /** Flag used to detect whether onRefresh has already been called */ 214 private boolean refreshEventReceived = false; 215 216 217 /** 218 * Create a new {@code FrameworkServlet} that will create its own internal web 219 * application context based on defaults and values provided through servlet 220 * init-params. Typically used in Servlet 2.5 or earlier environments, where the only 221 * option for servlet registration is through {@code web.xml} which requires the use 222 * of a no-arg constructor. 223 * <p>Calling {@link #setContextConfigLocation} (init-param 'contextConfigLocation') 224 * will dictate which XML files will be loaded by the 225 * {@linkplain #DEFAULT_CONTEXT_CLASS default XmlWebApplicationContext} 226 * <p>Calling {@link #setContextClass} (init-param 'contextClass') overrides the 227 * default {@code XmlWebApplicationContext} and allows for specifying an alternative class, 228 * such as {@code AnnotationConfigWebApplicationContext}. 229 * <p>Calling {@link #setContextInitializerClasses} (init-param 'contextInitializerClasses') 230 * indicates which {@link ApplicationContextInitializer} classes should be used to 231 * further configure the internal application context prior to refresh(). 232 * @see #FrameworkServlet(WebApplicationContext) 233 */ 234 public FrameworkServlet() { 235 } 236 237 /** 238 * Create a new {@code FrameworkServlet} with the given web application context. This 239 * constructor is useful in Servlet 3.0+ environments where instance-based registration 240 * of servlets is possible through the {@link ServletContext#addServlet} API. 241 * <p>Using this constructor indicates that the following properties / init-params 242 * will be ignored: 243 * <ul> 244 * <li>{@link #setContextClass(Class)} / 'contextClass'</li> 245 * <li>{@link #setContextConfigLocation(String)} / 'contextConfigLocation'</li> 246 * <li>{@link #setContextAttribute(String)} / 'contextAttribute'</li> 247 * <li>{@link #setNamespace(String)} / 'namespace'</li> 248 * </ul> 249 * <p>The given web application context may or may not yet be {@linkplain 250 * ConfigurableApplicationContext#refresh() refreshed}. If it (a) is an implementation 251 * of {@link ConfigurableWebApplicationContext} and (b) has <strong>not</strong> 252 * already been refreshed (the recommended approach), then the following will occur: 253 * <ul> 254 * <li>If the given context does not already have a {@linkplain 255 * ConfigurableApplicationContext#setParent parent}, the root application context 256 * will be set as the parent.</li> 257 * <li>If the given context has not already been assigned an {@linkplain 258 * ConfigurableApplicationContext#setId id}, one will be assigned to it</li> 259 * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to 260 * the application context</li> 261 * <li>{@link #postProcessWebApplicationContext} will be called</li> 262 * <li>Any {@link ApplicationContextInitializer}s specified through the 263 * "contextInitializerClasses" init-param or through the {@link 264 * #setContextInitializers} property will be applied.</li> 265 * <li>{@link ConfigurableApplicationContext#refresh refresh()} will be called</li> 266 * </ul> 267 * If the context has already been refreshed or does not implement 268 * {@code ConfigurableWebApplicationContext}, none of the above will occur under the 269 * assumption that the user has performed these actions (or not) per his or her 270 * specific needs. 271 * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples. 272 * @param webApplicationContext the context to use 273 * @see #initWebApplicationContext 274 * @see #configureAndRefreshWebApplicationContext 275 * @see org.springframework.web.WebApplicationInitializer 276 */ 277 public FrameworkServlet(WebApplicationContext webApplicationContext) { 278 this.webApplicationContext = webApplicationContext; 279 } 280 281 282 /** 283 * Set the name of the ServletContext attribute which should be used to retrieve the 284 * {@link WebApplicationContext} that this servlet is supposed to use. 285 */ 286 public void setContextAttribute(String contextAttribute) { 287 this.contextAttribute = contextAttribute; 288 } 289 290 /** 291 * Return the name of the ServletContext attribute which should be used to retrieve the 292 * {@link WebApplicationContext} that this servlet is supposed to use. 293 */ 294 public String getContextAttribute() { 295 return this.contextAttribute; 296 } 297 298 /** 299 * Set a custom context class. This class must be of type 300 * {@link org.springframework.web.context.WebApplicationContext}. 301 * <p>When using the default FrameworkServlet implementation, 302 * the context class must also implement the 303 * {@link org.springframework.web.context.ConfigurableWebApplicationContext} 304 * interface. 305 * @see #createWebApplicationContext 306 */ 307 public void setContextClass(Class<?> contextClass) { 308 this.contextClass = contextClass; 309 } 310 311 /** 312 * Return the custom context class. 313 */ 314 public Class<?> getContextClass() { 315 return this.contextClass; 316 } 317 318 /** 319 * Specify a custom WebApplicationContext id, 320 * to be used as serialization id for the underlying BeanFactory. 321 */ 322 public void setContextId(String contextId) { 323 this.contextId = contextId; 324 } 325 326 /** 327 * Return the custom WebApplicationContext id, if any. 328 */ 329 public String getContextId() { 330 return this.contextId; 331 } 332 333 /** 334 * Set a custom namespace for this servlet, 335 * to be used for building a default context config location. 336 */ 337 public void setNamespace(String namespace) { 338 this.namespace = namespace; 339 } 340 341 /** 342 * Return the namespace for this servlet, falling back to default scheme if 343 * no custom namespace was set: e.g. "test-servlet" for a servlet named "test". 344 */ 345 public String getNamespace() { 346 return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX); 347 } 348 349 /** 350 * Set the context config location explicitly, instead of relying on the default 351 * location built from the namespace. This location string can consist of 352 * multiple locations separated by any number of commas and spaces. 353 */ 354 public void setContextConfigLocation(String contextConfigLocation) { 355 this.contextConfigLocation = contextConfigLocation; 356 } 357 358 /** 359 * Return the explicit context config location, if any. 360 */ 361 public String getContextConfigLocation() { 362 return this.contextConfigLocation; 363 } 364 365 /** 366 * Specify which {@link ApplicationContextInitializer} instances should be used 367 * to initialize the application context used by this {@code FrameworkServlet}. 368 * @see #configureAndRefreshWebApplicationContext 369 * @see #applyInitializers 370 */ 371 @SuppressWarnings("unchecked") 372 public void setContextInitializers(ApplicationContextInitializer<?>... initializers) { 373 if (initializers != null) { 374 for (ApplicationContextInitializer<?> initializer : initializers) { 375 this.contextInitializers.add((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer); 376 } 377 } 378 } 379 380 /** 381 * Specify the set of fully-qualified {@link ApplicationContextInitializer} class 382 * names, per the optional "contextInitializerClasses" servlet init-param. 383 * @see #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext) 384 * @see #applyInitializers(ConfigurableApplicationContext) 385 */ 386 public void setContextInitializerClasses(String contextInitializerClasses) { 387 this.contextInitializerClasses = contextInitializerClasses; 388 } 389 390 /** 391 * Set whether to publish this servlet's context as a ServletContext attribute, 392 * available to all objects in the web container. Default is "true". 393 * <p>This is especially handy during testing, although it is debatable whether 394 * it's good practice to let other application objects access the context this way. 395 */ 396 public void setPublishContext(boolean publishContext) { 397 this.publishContext = publishContext; 398 } 399 400 /** 401 * Set whether this servlet should publish a ServletRequestHandledEvent at the end 402 * of each request. Default is "true"; can be turned off for a slight performance 403 * improvement, provided that no ApplicationListeners rely on such events. 404 * @see org.springframework.web.context.support.ServletRequestHandledEvent 405 */ 406 public void setPublishEvents(boolean publishEvents) { 407 this.publishEvents = publishEvents; 408 } 409 410 /** 411 * Set whether to expose the LocaleContext and RequestAttributes as inheritable 412 * for child threads (using an {@link java.lang.InheritableThreadLocal}). 413 * <p>Default is "false", to avoid side effects on spawned background threads. 414 * Switch this to "true" to enable inheritance for custom child threads which 415 * are spawned during request processing and only used for this request 416 * (that is, ending after their initial task, without reuse of the thread). 417 * <p><b>WARNING:</b> Do not use inheritance for child threads if you are 418 * accessing a thread pool which is configured to potentially add new threads 419 * on demand (e.g. a JDK {@link java.util.concurrent.ThreadPoolExecutor}), 420 * since this will expose the inherited context to such a pooled thread. 421 */ 422 public void setThreadContextInheritable(boolean threadContextInheritable) { 423 this.threadContextInheritable = threadContextInheritable; 424 } 425 426 /** 427 * Set whether this servlet should dispatch an HTTP OPTIONS request to 428 * the {@link #doService} method. 429 * <p>Default in the {@code FrameworkServlet} is "false", applying 430 * {@link javax.servlet.http.HttpServlet}'s default behavior (i.e.enumerating 431 * all standard HTTP request methods as a response to the OPTIONS request). 432 * Note however that as of 4.3 the {@code DispatcherServlet} sets this 433 * property to "true" by default due to its built-in support for OPTIONS. 434 * <p>Turn this flag on if you prefer OPTIONS requests to go through the 435 * regular dispatching chain, just like other HTTP requests. This usually 436 * means that your controllers will receive those requests; make sure 437 * that those endpoints are actually able to handle an OPTIONS request. 438 * <p>Note that HttpServlet's default OPTIONS processing will be applied 439 * in any case if your controllers happen to not set the 'Allow' header 440 * (as required for an OPTIONS response). 441 */ 442 public void setDispatchOptionsRequest(boolean dispatchOptionsRequest) { 443 this.dispatchOptionsRequest = dispatchOptionsRequest; 444 } 445 446 /** 447 * Set whether this servlet should dispatch an HTTP TRACE request to 448 * the {@link #doService} method. 449 * <p>Default is "false", applying {@link javax.servlet.http.HttpServlet}'s 450 * default behavior (i.e. reflecting the message received back to the client). 451 * <p>Turn this flag on if you prefer TRACE requests to go through the 452 * regular dispatching chain, just like other HTTP requests. This usually 453 * means that your controllers will receive those requests; make sure 454 * that those endpoints are actually able to handle a TRACE request. 455 * <p>Note that HttpServlet's default TRACE processing will be applied 456 * in any case if your controllers happen to not generate a response 457 * of content type 'message/http' (as required for a TRACE response). 458 */ 459 public void setDispatchTraceRequest(boolean dispatchTraceRequest) { 460 this.dispatchTraceRequest = dispatchTraceRequest; 461 } 462 463 /** 464 * Called by Spring via {@link ApplicationContextAware} to inject the current 465 * application context. This method allows FrameworkServlets to be registered as 466 * Spring beans inside an existing {@link WebApplicationContext} rather than 467 * {@link #findWebApplicationContext() finding} a 468 * {@link org.springframework.web.context.ContextLoaderListener bootstrapped} context. 469 * <p>Primarily added to support use in embedded servlet containers. 470 * @since 4.0 471 */ 472 @Override 473 public void setApplicationContext(ApplicationContext applicationContext) { 474 if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) { 475 this.webApplicationContext = (WebApplicationContext) applicationContext; 476 this.webApplicationContextInjected = true; 477 } 478 } 479 480 481 /** 482 * Overridden method of {@link HttpServletBean}, invoked after any bean properties 483 * have been set. Creates this servlet's WebApplicationContext. 484 */ 485 @Override 486 protected final void initServletBean() throws ServletException { 487 getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'"); 488 if (this.logger.isInfoEnabled()) { 489 this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started"); 490 } 491 long startTime = System.currentTimeMillis(); 492 493 try { 494 this.webApplicationContext = initWebApplicationContext(); 495 initFrameworkServlet(); 496 } 497 catch (ServletException ex) { 498 this.logger.error("Context initialization failed", ex); 499 throw ex; 500 } 501 catch (RuntimeException ex) { 502 this.logger.error("Context initialization failed", ex); 503 throw ex; 504 } 505 506 if (this.logger.isInfoEnabled()) { 507 long elapsedTime = System.currentTimeMillis() - startTime; 508 this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " + 509 elapsedTime + " ms"); 510 } 511 } 512 513 /** 514 * Initialize and publish the WebApplicationContext for this servlet. 515 * <p>Delegates to {@link #createWebApplicationContext} for actual creation 516 * of the context. Can be overridden in subclasses. 517 * @return the WebApplicationContext instance 518 * @see #FrameworkServlet(WebApplicationContext) 519 * @see #setContextClass 520 * @see #setContextConfigLocation 521 */ 522 protected WebApplicationContext initWebApplicationContext() { 523 WebApplicationContext rootContext = 524 WebApplicationContextUtils.getWebApplicationContext(getServletContext()); 525 WebApplicationContext wac = null; 526 527 if (this.webApplicationContext != null) { 528 // A context instance was injected at construction time -> use it 529 wac = this.webApplicationContext; 530 if (wac instanceof ConfigurableWebApplicationContext) { 531 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; 532 if (!cwac.isActive()) { 533 // The context has not yet been refreshed -> provide services such as 534 // setting the parent context, setting the application context id, etc 535 if (cwac.getParent() == null) { 536 // The context instance was injected without an explicit parent -> set 537 // the root application context (if any; may be null) as the parent 538 cwac.setParent(rootContext); 539 } 540 configureAndRefreshWebApplicationContext(cwac); 541 } 542 } 543 } 544 if (wac == null) { 545 // No context instance was injected at construction time -> see if one 546 // has been registered in the servlet context. If one exists, it is assumed 547 // that the parent context (if any) has already been set and that the 548 // user has performed any initialization such as setting the context id 549 wac = findWebApplicationContext(); 550 } 551 if (wac == null) { 552 // No context instance is defined for this servlet -> create a local one 553 wac = createWebApplicationContext(rootContext); 554 } 555 556 if (!this.refreshEventReceived) { 557 // Either the context is not a ConfigurableApplicationContext with refresh 558 // support or the context injected at construction time had already been 559 // refreshed -> trigger initial onRefresh manually here. 560 onRefresh(wac); 561 } 562 563 if (this.publishContext) { 564 // Publish the context as a servlet context attribute. 565 String attrName = getServletContextAttributeName(); 566 getServletContext().setAttribute(attrName, wac); 567 if (this.logger.isDebugEnabled()) { 568 this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + 569 "' as ServletContext attribute with name [" + attrName + "]"); 570 } 571 } 572 573 return wac; 574 } 575 576 /** 577 * Retrieve a {@code WebApplicationContext} from the {@code ServletContext} 578 * attribute with the {@link #setContextAttribute configured name}. The 579 * {@code WebApplicationContext} must have already been loaded and stored in the 580 * {@code ServletContext} before this servlet gets initialized (or invoked). 581 * <p>Subclasses may override this method to provide a different 582 * {@code WebApplicationContext} retrieval strategy. 583 * @return the WebApplicationContext for this servlet, or {@code null} if not found 584 * @see #getContextAttribute() 585 */ 586 protected WebApplicationContext findWebApplicationContext() { 587 String attrName = getContextAttribute(); 588 if (attrName == null) { 589 return null; 590 } 591 WebApplicationContext wac = 592 WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName); 593 if (wac == null) { 594 throw new IllegalStateException("No WebApplicationContext found: initializer not registered?"); 595 } 596 return wac; 597 } 598 599 /** 600 * Instantiate the WebApplicationContext for this servlet, either a default 601 * {@link org.springframework.web.context.support.XmlWebApplicationContext} 602 * or a {@link #setContextClass custom context class}, if set. 603 * <p>This implementation expects custom contexts to implement the 604 * {@link org.springframework.web.context.ConfigurableWebApplicationContext} 605 * interface. Can be overridden in subclasses. 606 * <p>Do not forget to register this servlet instance as application listener on the 607 * created context (for triggering its {@link #onRefresh callback}, and to call 608 * {@link org.springframework.context.ConfigurableApplicationContext#refresh()} 609 * before returning the context instance. 610 * @param parent the parent ApplicationContext to use, or {@code null} if none 611 * @return the WebApplicationContext for this servlet 612 * @see org.springframework.web.context.support.XmlWebApplicationContext 613 */ 614 protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { 615 Class<?> contextClass = getContextClass(); 616 if (this.logger.isDebugEnabled()) { 617 this.logger.debug("Servlet with name '" + getServletName() + 618 "' will try to create custom WebApplicationContext context of class '" + 619 contextClass.getName() + "'" + ", using parent context [" + parent + "]"); 620 } 621 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { 622 throw new ApplicationContextException( 623 "Fatal initialization error in servlet with name '" + getServletName() + 624 "': custom WebApplicationContext class [" + contextClass.getName() + 625 "] is not of type ConfigurableWebApplicationContext"); 626 } 627 ConfigurableWebApplicationContext wac = 628 (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); 629 630 wac.setEnvironment(getEnvironment()); 631 wac.setParent(parent); 632 wac.setConfigLocation(getContextConfigLocation()); 633 634 configureAndRefreshWebApplicationContext(wac); 635 636 return wac; 637 } 638 639 protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { 640 if (ObjectUtils.identityToString(wac).equals(wac.getId())) { 641 // The application context id is still set to its original default value 642 // -> assign a more useful id based on available information 643 if (this.contextId != null) { 644 wac.setId(this.contextId); 645 } 646 else { 647 // Generate default id... 648 wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + 649 ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName()); 650 } 651 } 652 653 wac.setServletContext(getServletContext()); 654 wac.setServletConfig(getServletConfig()); 655 wac.setNamespace(getNamespace()); 656 wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); 657 658 // The wac environment's #initPropertySources will be called in any case when the context 659 // is refreshed; do it eagerly here to ensure servlet property sources are in place for 660 // use in any post-processing or initialization that occurs below prior to #refresh 661 ConfigurableEnvironment env = wac.getEnvironment(); 662 if (env instanceof ConfigurableWebEnvironment) { 663 ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); 664 } 665 666 postProcessWebApplicationContext(wac); 667 applyInitializers(wac); 668 wac.refresh(); 669 } 670 671 /** 672 * Instantiate the WebApplicationContext for this servlet, either a default 673 * {@link org.springframework.web.context.support.XmlWebApplicationContext} 674 * or a {@link #setContextClass custom context class}, if set. 675 * Delegates to #createWebApplicationContext(ApplicationContext). 676 * @param parent the parent WebApplicationContext to use, or {@code null} if none 677 * @return the WebApplicationContext for this servlet 678 * @see org.springframework.web.context.support.XmlWebApplicationContext 679 * @see #createWebApplicationContext(ApplicationContext) 680 */ 681 protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) { 682 return createWebApplicationContext((ApplicationContext) parent); 683 } 684 685 /** 686 * Post-process the given WebApplicationContext before it is refreshed 687 * and activated as context for this servlet. 688 * <p>The default implementation is empty. {@code refresh()} will 689 * be called automatically after this method returns. 690 * <p>Note that this method is designed to allow subclasses to modify the application 691 * context, while {@link #initWebApplicationContext} is designed to allow 692 * end-users to modify the context through the use of 693 * {@link ApplicationContextInitializer}s. 694 * @param wac the configured WebApplicationContext (not refreshed yet) 695 * @see #createWebApplicationContext 696 * @see #initWebApplicationContext 697 * @see ConfigurableWebApplicationContext#refresh() 698 */ 699 protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext wac) { 700 } 701 702 /** 703 * Delegate the WebApplicationContext before it is refreshed to any 704 * {@link ApplicationContextInitializer} instances specified by the 705 * "contextInitializerClasses" servlet init-param. 706 * <p>See also {@link #postProcessWebApplicationContext}, which is designed to allow 707 * subclasses (as opposed to end-users) to modify the application context, and is 708 * called immediately before this method. 709 * @param wac the configured WebApplicationContext (not refreshed yet) 710 * @see #createWebApplicationContext 711 * @see #postProcessWebApplicationContext 712 * @see ConfigurableApplicationContext#refresh() 713 */ 714 protected void applyInitializers(ConfigurableApplicationContext wac) { 715 String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM); 716 if (globalClassNames != null) { 717 for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) { 718 this.contextInitializers.add(loadInitializer(className, wac)); 719 } 720 } 721 722 if (this.contextInitializerClasses != null) { 723 for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) { 724 this.contextInitializers.add(loadInitializer(className, wac)); 725 } 726 } 727 728 AnnotationAwareOrderComparator.sort(this.contextInitializers); 729 for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) { 730 initializer.initialize(wac); 731 } 732 } 733 734 @SuppressWarnings("unchecked") 735 private ApplicationContextInitializer<ConfigurableApplicationContext> loadInitializer( 736 String className, ConfigurableApplicationContext wac) { 737 try { 738 Class<?> initializerClass = ClassUtils.forName(className, wac.getClassLoader()); 739 Class<?> initializerContextClass = 740 GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class); 741 if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) { 742 throw new ApplicationContextException(String.format( 743 "Could not apply context initializer [%s] since its generic parameter [%s] " + 744 "is not assignable from the type of application context used by this " + 745 "framework servlet: [%s]", initializerClass.getName(), initializerContextClass.getName(), 746 wac.getClass().getName())); 747 } 748 return BeanUtils.instantiateClass(initializerClass, ApplicationContextInitializer.class); 749 } 750 catch (ClassNotFoundException ex) { 751 throw new ApplicationContextException(String.format("Could not load class [%s] specified " + 752 "via 'contextInitializerClasses' init-param", className), ex); 753 } 754 } 755 756 /** 757 * Return the ServletContext attribute name for this servlet's WebApplicationContext. 758 * <p>The default implementation returns 759 * {@code SERVLET_CONTEXT_PREFIX + servlet name}. 760 * @see #SERVLET_CONTEXT_PREFIX 761 * @see #getServletName 762 */ 763 public String getServletContextAttributeName() { 764 return SERVLET_CONTEXT_PREFIX + getServletName(); 765 } 766 767 /** 768 * Return this servlet's WebApplicationContext. 769 */ 770 public final WebApplicationContext getWebApplicationContext() { 771 return this.webApplicationContext; 772 } 773 774 775 /** 776 * This method will be invoked after any bean properties have been set and 777 * the WebApplicationContext has been loaded. The default implementation is empty; 778 * subclasses may override this method to perform any initialization they require. 779 * @throws ServletException in case of an initialization exception 780 */ 781 protected void initFrameworkServlet() throws ServletException { 782 } 783 784 /** 785 * Refresh this servlet's application context, as well as the 786 * dependent state of the servlet. 787 * @see #getWebApplicationContext() 788 * @see org.springframework.context.ConfigurableApplicationContext#refresh() 789 */ 790 public void refresh() { 791 WebApplicationContext wac = getWebApplicationContext(); 792 if (!(wac instanceof ConfigurableApplicationContext)) { 793 throw new IllegalStateException("WebApplicationContext does not support refresh: " + wac); 794 } 795 ((ConfigurableApplicationContext) wac).refresh(); 796 } 797 798 /** 799 * Callback that receives refresh events from this servlet's WebApplicationContext. 800 * <p>The default implementation calls {@link #onRefresh}, 801 * triggering a refresh of this servlet's context-dependent state. 802 * @param event the incoming ApplicationContext event 803 */ 804 public void onApplicationEvent(ContextRefreshedEvent event) { 805 this.refreshEventReceived = true; 806 onRefresh(event.getApplicationContext()); 807 } 808 809 /** 810 * Template method which can be overridden to add servlet-specific refresh work. 811 * Called after successful context refresh. 812 * <p>This implementation is empty. 813 * @param context the current WebApplicationContext 814 * @see #refresh() 815 */ 816 protected void onRefresh(ApplicationContext context) { 817 // For subclasses: do nothing by default. 818 } 819 820 /** 821 * Close the WebApplicationContext of this servlet. 822 * @see org.springframework.context.ConfigurableApplicationContext#close() 823 */ 824 @Override 825 public void destroy() { 826 getServletContext().log("Destroying Spring FrameworkServlet '" + getServletName() + "'"); 827 // Only call close() on WebApplicationContext if locally managed... 828 if (this.webApplicationContext instanceof ConfigurableApplicationContext && !this.webApplicationContextInjected) { 829 ((ConfigurableApplicationContext) this.webApplicationContext).close(); 830 } 831 } 832 833 834 /** 835 * Override the parent class implementation in order to intercept PATCH requests. 836 */ 837 @Override 838 protected void service(HttpServletRequest request, HttpServletResponse response) 839 throws ServletException, IOException { 840 841 HttpMethod httpMethod = HttpMethod.resolve(request.getMethod()); 842 if (httpMethod == HttpMethod.PATCH || httpMethod == null) { 843 processRequest(request, response); 844 } 845 else { 846 super.service(request, response); 847 } 848 } 849 850 /** 851 * Delegate GET requests to processRequest/doService. 852 * <p>Will also be invoked by HttpServlet's default implementation of {@code doHead}, 853 * with a {@code NoBodyResponse} that just captures the content length. 854 * @see #doService 855 * @see #doHead 856 */ 857 @Override 858 protected final void doGet(HttpServletRequest request, HttpServletResponse response) 859 throws ServletException, IOException { 860 861 processRequest(request, response); 862 } 863 864 /** 865 * Delegate POST requests to {@link #processRequest}. 866 * @see #doService 867 */ 868 @Override 869 protected final void doPost(HttpServletRequest request, HttpServletResponse response) 870 throws ServletException, IOException { 871 872 processRequest(request, response); 873 } 874 875 /** 876 * Delegate PUT requests to {@link #processRequest}. 877 * @see #doService 878 */ 879 @Override 880 protected final void doPut(HttpServletRequest request, HttpServletResponse response) 881 throws ServletException, IOException { 882 883 processRequest(request, response); 884 } 885 886 /** 887 * Delegate DELETE requests to {@link #processRequest}. 888 * @see #doService 889 */ 890 @Override 891 protected final void doDelete(HttpServletRequest request, HttpServletResponse response) 892 throws ServletException, IOException { 893 894 processRequest(request, response); 895 } 896 897 /** 898 * Delegate OPTIONS requests to {@link #processRequest}, if desired. 899 * <p>Applies HttpServlet's standard OPTIONS processing otherwise, 900 * and also if there is still no 'Allow' header set after dispatching. 901 * @see #doService 902 */ 903 @Override 904 protected void doOptions(HttpServletRequest request, HttpServletResponse response) 905 throws ServletException, IOException { 906 907 if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) { 908 processRequest(request, response); 909 if (response.containsHeader("Allow")) { 910 // Proper OPTIONS response coming from a handler - we're done. 911 return; 912 } 913 } 914 915 // Use response wrapper for Servlet 2.5 compatibility where 916 // the getHeader() method does not exist 917 super.doOptions(request, new HttpServletResponseWrapper(response) { 918 @Override 919 public void setHeader(String name, String value) { 920 if ("Allow".equals(name)) { 921 value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name(); 922 } 923 super.setHeader(name, value); 924 } 925 }); 926 } 927 928 /** 929 * Delegate TRACE requests to {@link #processRequest}, if desired. 930 * <p>Applies HttpServlet's standard TRACE processing otherwise. 931 * @see #doService 932 */ 933 @Override 934 protected void doTrace(HttpServletRequest request, HttpServletResponse response) 935 throws ServletException, IOException { 936 937 if (this.dispatchTraceRequest) { 938 processRequest(request, response); 939 if ("message/http".equals(response.getContentType())) { 940 // Proper TRACE response coming from a handler - we're done. 941 return; 942 } 943 } 944 super.doTrace(request, response); 945 } 946 947 /** 948 * Process this request, publishing an event regardless of the outcome. 949 * <p>The actual event handling is performed by the abstract 950 * {@link #doService} template method. 951 */ 952 protected final void processRequest(HttpServletRequest request, HttpServletResponse response) 953 throws ServletException, IOException { 954 955 long startTime = System.currentTimeMillis(); 956 Throwable failureCause = null; 957 958 LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); 959 LocaleContext localeContext = buildLocaleContext(request); 960 961 RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); 962 ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); 963 964 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 965 asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); 966 967 initContextHolders(request, localeContext, requestAttributes); 968 969 try { 970 doService(request, response); 971 } 972 catch (ServletException ex) { 973 failureCause = ex; 974 throw ex; 975 } 976 catch (IOException ex) { 977 failureCause = ex; 978 throw ex; 979 } 980 catch (Throwable ex) { 981 failureCause = ex; 982 throw new NestedServletException("Request processing failed", ex); 983 } 984 985 finally { 986 resetContextHolders(request, previousLocaleContext, previousAttributes); 987 if (requestAttributes != null) { 988 requestAttributes.requestCompleted(); 989 } 990 991 if (logger.isDebugEnabled()) { 992 if (failureCause != null) { 993 this.logger.debug("Could not complete request", failureCause); 994 } 995 else { 996 if (asyncManager.isConcurrentHandlingStarted()) { 997 logger.debug("Leaving response open for concurrent processing"); 998 } 999 else { 1000 this.logger.debug("Successfully completed request"); 1001 } 1002 } 1003 } 1004 1005 publishRequestHandledEvent(request, response, startTime, failureCause); 1006 } 1007 } 1008 1009 /** 1010 * Build a LocaleContext for the given request, exposing the request's 1011 * primary locale as current locale. 1012 * @param request current HTTP request 1013 * @return the corresponding LocaleContext, or {@code null} if none to bind 1014 * @see LocaleContextHolder#setLocaleContext 1015 */ 1016 protected LocaleContext buildLocaleContext(HttpServletRequest request) { 1017 return new SimpleLocaleContext(request.getLocale()); 1018 } 1019 1020 /** 1021 * Build ServletRequestAttributes for the given request (potentially also 1022 * holding a reference to the response), taking pre-bound attributes 1023 * (and their type) into consideration. 1024 * @param request current HTTP request 1025 * @param response current HTTP response 1026 * @param previousAttributes pre-bound RequestAttributes instance, if any 1027 * @return the ServletRequestAttributes to bind, or {@code null} to preserve 1028 * the previously bound instance (or not binding any, if none bound before) 1029 * @see RequestContextHolder#setRequestAttributes 1030 */ 1031 protected ServletRequestAttributes buildRequestAttributes( 1032 HttpServletRequest request, HttpServletResponse response, RequestAttributes previousAttributes) { 1033 1034 if (previousAttributes == null || previousAttributes instanceof ServletRequestAttributes) { 1035 return new ServletRequestAttributes(request, response); 1036 } 1037 else { 1038 return null; // preserve the pre-bound RequestAttributes instance 1039 } 1040 } 1041 1042 private void initContextHolders( 1043 HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) { 1044 1045 if (localeContext != null) { 1046 LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable); 1047 } 1048 if (requestAttributes != null) { 1049 RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable); 1050 } 1051 if (logger.isTraceEnabled()) { 1052 logger.trace("Bound request context to thread: " + request); 1053 } 1054 } 1055 1056 private void resetContextHolders(HttpServletRequest request, 1057 LocaleContext prevLocaleContext, RequestAttributes previousAttributes) { 1058 1059 LocaleContextHolder.setLocaleContext(prevLocaleContext, this.threadContextInheritable); 1060 RequestContextHolder.setRequestAttributes(previousAttributes, this.threadContextInheritable); 1061 if (logger.isTraceEnabled()) { 1062 logger.trace("Cleared thread-bound request context: " + request); 1063 } 1064 } 1065 1066 private void publishRequestHandledEvent( 1067 HttpServletRequest request, HttpServletResponse response, long startTime, Throwable failureCause) { 1068 1069 if (this.publishEvents) { 1070 // Whether or not we succeeded, publish an event. 1071 long processingTime = System.currentTimeMillis() - startTime; 1072 int statusCode = (responseGetStatusAvailable ? response.getStatus() : -1); 1073 this.webApplicationContext.publishEvent( 1074 new ServletRequestHandledEvent(this, 1075 request.getRequestURI(), request.getRemoteAddr(), 1076 request.getMethod(), getServletConfig().getServletName(), 1077 WebUtils.getSessionId(request), getUsernameForRequest(request), 1078 processingTime, failureCause, statusCode)); 1079 } 1080 } 1081 1082 /** 1083 * Determine the username for the given request. 1084 * <p>The default implementation takes the name of the UserPrincipal, if any. 1085 * Can be overridden in subclasses. 1086 * @param request current HTTP request 1087 * @return the username, or {@code null} if none found 1088 * @see javax.servlet.http.HttpServletRequest#getUserPrincipal() 1089 */ 1090 protected String getUsernameForRequest(HttpServletRequest request) { 1091 Principal userPrincipal = request.getUserPrincipal(); 1092 return (userPrincipal != null ? userPrincipal.getName() : null); 1093 } 1094 1095 1096 /** 1097 * Subclasses must implement this method to do the work of request handling, 1098 * receiving a centralized callback for GET, POST, PUT and DELETE. 1099 * <p>The contract is essentially the same as that for the commonly overridden 1100 * {@code doGet} or {@code doPost} methods of HttpServlet. 1101 * <p>This class intercepts calls to ensure that exception handling and 1102 * event publication takes place. 1103 * @param request current HTTP request 1104 * @param response current HTTP response 1105 * @throws Exception in case of any kind of processing failure 1106 * @see javax.servlet.http.HttpServlet#doGet 1107 * @see javax.servlet.http.HttpServlet#doPost 1108 */ 1109 protected abstract void doService(HttpServletRequest request, HttpServletResponse response) 1110 throws Exception; 1111 1112 1113 /** 1114 * ApplicationListener endpoint that receives events from this servlet's WebApplicationContext 1115 * only, delegating to {@code onApplicationEvent} on the FrameworkServlet instance. 1116 */ 1117 private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> { 1118 1119 @Override 1120 public void onApplicationEvent(ContextRefreshedEvent event) { 1121 FrameworkServlet.this.onApplicationEvent(event); 1122 } 1123 } 1124 1125 1126 /** 1127 * CallableProcessingInterceptor implementation that initializes and resets 1128 * FrameworkServlet's context holders, i.e. LocaleContextHolder and RequestContextHolder. 1129 */ 1130 private class RequestBindingInterceptor extends CallableProcessingInterceptorAdapter { 1131 1132 @Override 1133 public <T> void preProcess(NativeWebRequest webRequest, Callable<T> task) { 1134 HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); 1135 if (request != null) { 1136 HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class); 1137 initContextHolders(request, buildLocaleContext(request), 1138 buildRequestAttributes(request, response, null)); 1139 } 1140 } 1141 @Override 1142 public <T> void postProcess(NativeWebRequest webRequest, Callable<T> task, Object concurrentResult) { 1143 HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); 1144 if (request != null) { 1145 resetContextHolders(request, null, null); 1146 } 1147 } 1148 } 1149 1150}