001/* 002 * Copyright 2002-2019 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.util.ArrayList; 021import java.util.Collections; 022import java.util.Enumeration; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.LinkedList; 026import java.util.List; 027import java.util.Locale; 028import java.util.Map; 029import java.util.Properties; 030import java.util.Set; 031import javax.servlet.ServletContext; 032import javax.servlet.ServletException; 033import javax.servlet.http.HttpServletRequest; 034import javax.servlet.http.HttpServletResponse; 035 036import org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038 039import org.springframework.beans.factory.BeanFactoryUtils; 040import org.springframework.beans.factory.BeanInitializationException; 041import org.springframework.beans.factory.NoSuchBeanDefinitionException; 042import org.springframework.context.ApplicationContext; 043import org.springframework.context.ConfigurableApplicationContext; 044import org.springframework.context.i18n.LocaleContext; 045import org.springframework.core.annotation.AnnotationAwareOrderComparator; 046import org.springframework.core.io.ClassPathResource; 047import org.springframework.core.io.support.PropertiesLoaderUtils; 048import org.springframework.http.server.ServletServerHttpRequest; 049import org.springframework.ui.context.ThemeSource; 050import org.springframework.util.ClassUtils; 051import org.springframework.util.StringUtils; 052import org.springframework.web.context.WebApplicationContext; 053import org.springframework.web.context.request.ServletWebRequest; 054import org.springframework.web.context.request.async.WebAsyncManager; 055import org.springframework.web.context.request.async.WebAsyncUtils; 056import org.springframework.web.multipart.MultipartException; 057import org.springframework.web.multipart.MultipartHttpServletRequest; 058import org.springframework.web.multipart.MultipartResolver; 059import org.springframework.web.util.NestedServletException; 060import org.springframework.web.util.WebUtils; 061 062/** 063 * Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers 064 * or HTTP-based remote service exporters. Dispatches to registered handlers for processing 065 * a web request, providing convenient mapping and exception handling facilities. 066 * 067 * <p>This servlet is very flexible: It can be used with just about any workflow, with the 068 * installation of the appropriate adapter classes. It offers the following functionality 069 * that distinguishes it from other request-driven web MVC frameworks: 070 * 071 * <ul> 072 * <li>It is based around a JavaBeans configuration mechanism. 073 * 074 * <li>It can use any {@link HandlerMapping} implementation - pre-built or provided as part 075 * of an application - to control the routing of requests to handler objects. Default is 076 * {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping} and 077 * {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}. 078 * HandlerMapping objects can be defined as beans in the servlet's application context, 079 * implementing the HandlerMapping interface, overriding the default HandlerMapping if 080 * present. HandlerMappings can be given any bean name (they are tested by type). 081 * 082 * <li>It can use any {@link HandlerAdapter}; this allows for using any handler interface. 083 * Default adapters are {@link org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter}, 084 * {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter}, for Spring's 085 * {@link org.springframework.web.HttpRequestHandler} and 086 * {@link org.springframework.web.servlet.mvc.Controller} interfaces, respectively. A default 087 * {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter} 088 * will be registered as well. HandlerAdapter objects can be added as beans in the 089 * application context, overriding the default HandlerAdapters. Like HandlerMappings, 090 * HandlerAdapters can be given any bean name (they are tested by type). 091 * 092 * <li>The dispatcher's exception resolution strategy can be specified via a 093 * {@link HandlerExceptionResolver}, for example mapping certain exceptions to error pages. 094 * Default are 095 * {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver}, 096 * {@link org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver}, and 097 * {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver}. 098 * These HandlerExceptionResolvers can be overridden through the application context. 099 * HandlerExceptionResolver can be given any bean name (they are tested by type). 100 * 101 * <li>Its view resolution strategy can be specified via a {@link ViewResolver} 102 * implementation, resolving symbolic view names into View objects. Default is 103 * {@link org.springframework.web.servlet.view.InternalResourceViewResolver}. 104 * ViewResolver objects can be added as beans in the application context, overriding the 105 * default ViewResolver. ViewResolvers can be given any bean name (they are tested by type). 106 * 107 * <li>If a {@link View} or view name is not supplied by the user, then the configured 108 * {@link RequestToViewNameTranslator} will translate the current request into a view name. 109 * The corresponding bean name is "viewNameTranslator"; the default is 110 * {@link org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator}. 111 * 112 * <li>The dispatcher's strategy for resolving multipart requests is determined by a 113 * {@link org.springframework.web.multipart.MultipartResolver} implementation. 114 * Implementations for Apache Commons FileUpload and Servlet 3 are included; the typical 115 * choice is {@link org.springframework.web.multipart.commons.CommonsMultipartResolver}. 116 * The MultipartResolver bean name is "multipartResolver"; default is none. 117 * 118 * <li>Its locale resolution strategy is determined by a {@link LocaleResolver}. 119 * Out-of-the-box implementations work via HTTP accept header, cookie, or session. 120 * The LocaleResolver bean name is "localeResolver"; default is 121 * {@link org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver}. 122 * 123 * <li>Its theme resolution strategy is determined by a {@link ThemeResolver}. 124 * Implementations for a fixed theme and for cookie and session storage are included. 125 * The ThemeResolver bean name is "themeResolver"; default is 126 * {@link org.springframework.web.servlet.theme.FixedThemeResolver}. 127 * </ul> 128 * 129 * <p><b>NOTE: The {@code @RequestMapping} annotation will only be processed if a 130 * corresponding {@code HandlerMapping} (for type-level annotations) and/or 131 * {@code HandlerAdapter} (for method-level annotations) is present in the dispatcher.</b> 132 * This is the case by default. However, if you are defining custom {@code HandlerMappings} 133 * or {@code HandlerAdapters}, then you need to make sure that a corresponding custom 134 * {@code DefaultAnnotationHandlerMapping} and/or {@code AnnotationMethodHandlerAdapter} 135 * is defined as well - provided that you intend to use {@code @RequestMapping}. 136 * 137 * <p><b>A web application can define any number of DispatcherServlets.</b> 138 * Each servlet will operate in its own namespace, loading its own application context 139 * with mappings, handlers, etc. Only the root application context as loaded by 140 * {@link org.springframework.web.context.ContextLoaderListener}, if any, will be shared. 141 * 142 * <p>As of Spring 3.1, {@code DispatcherServlet} may now be injected with a web 143 * application context, rather than creating its own internally. This is useful in Servlet 144 * 3.0+ environments, which support programmatic registration of servlet instances. 145 * See the {@link #DispatcherServlet(WebApplicationContext)} javadoc for details. 146 * 147 * @author Rod Johnson 148 * @author Juergen Hoeller 149 * @author Rob Harrop 150 * @author Chris Beams 151 * @author Rossen Stoyanchev 152 * @see org.springframework.web.HttpRequestHandler 153 * @see org.springframework.web.servlet.mvc.Controller 154 * @see org.springframework.web.context.ContextLoaderListener 155 */ 156@SuppressWarnings("serial") 157public class DispatcherServlet extends FrameworkServlet { 158 159 /** Well-known name for the MultipartResolver object in the bean factory for this namespace. */ 160 public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver"; 161 162 /** Well-known name for the LocaleResolver object in the bean factory for this namespace. */ 163 public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver"; 164 165 /** Well-known name for the ThemeResolver object in the bean factory for this namespace. */ 166 public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver"; 167 168 /** 169 * Well-known name for the HandlerMapping object in the bean factory for this namespace. 170 * Only used when "detectAllHandlerMappings" is turned off. 171 * @see #setDetectAllHandlerMappings 172 */ 173 public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping"; 174 175 /** 176 * Well-known name for the HandlerAdapter object in the bean factory for this namespace. 177 * Only used when "detectAllHandlerAdapters" is turned off. 178 * @see #setDetectAllHandlerAdapters 179 */ 180 public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter"; 181 182 /** 183 * Well-known name for the HandlerExceptionResolver object in the bean factory for this namespace. 184 * Only used when "detectAllHandlerExceptionResolvers" is turned off. 185 * @see #setDetectAllHandlerExceptionResolvers 186 */ 187 public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver"; 188 189 /** 190 * Well-known name for the RequestToViewNameTranslator object in the bean factory for this namespace. 191 */ 192 public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator"; 193 194 /** 195 * Well-known name for the ViewResolver object in the bean factory for this namespace. 196 * Only used when "detectAllViewResolvers" is turned off. 197 * @see #setDetectAllViewResolvers 198 */ 199 public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver"; 200 201 /** 202 * Well-known name for the FlashMapManager object in the bean factory for this namespace. 203 */ 204 public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager"; 205 206 /** 207 * Request attribute to hold the current web application context. 208 * Otherwise only the global web app context is obtainable by tags etc. 209 * @see org.springframework.web.servlet.support.RequestContextUtils#findWebApplicationContext 210 */ 211 public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT"; 212 213 /** 214 * Request attribute to hold the current LocaleResolver, retrievable by views. 215 * @see org.springframework.web.servlet.support.RequestContextUtils#getLocaleResolver 216 */ 217 public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER"; 218 219 /** 220 * Request attribute to hold the current ThemeResolver, retrievable by views. 221 * @see org.springframework.web.servlet.support.RequestContextUtils#getThemeResolver 222 */ 223 public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER"; 224 225 /** 226 * Request attribute to hold the current ThemeSource, retrievable by views. 227 * @see org.springframework.web.servlet.support.RequestContextUtils#getThemeSource 228 */ 229 public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE"; 230 231 /** 232 * Name of request attribute that holds a read-only {@code Map<String,?>} 233 * with "input" flash attributes saved by a previous request, if any. 234 * @see org.springframework.web.servlet.support.RequestContextUtils#getInputFlashMap(HttpServletRequest) 235 */ 236 public static final String INPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".INPUT_FLASH_MAP"; 237 238 /** 239 * Name of request attribute that holds the "output" {@link FlashMap} with 240 * attributes to save for a subsequent request. 241 * @see org.springframework.web.servlet.support.RequestContextUtils#getOutputFlashMap(HttpServletRequest) 242 */ 243 public static final String OUTPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".OUTPUT_FLASH_MAP"; 244 245 /** 246 * Name of request attribute that holds the {@link FlashMapManager}. 247 * @see org.springframework.web.servlet.support.RequestContextUtils#getFlashMapManager(HttpServletRequest) 248 */ 249 public static final String FLASH_MAP_MANAGER_ATTRIBUTE = DispatcherServlet.class.getName() + ".FLASH_MAP_MANAGER"; 250 251 /** 252 * Name of request attribute that exposes an Exception resolved with an 253 * {@link HandlerExceptionResolver} but where no view was rendered 254 * (e.g. setting the status code). 255 */ 256 public static final String EXCEPTION_ATTRIBUTE = DispatcherServlet.class.getName() + ".EXCEPTION"; 257 258 /** Log category to use when no mapped handler is found for a request. */ 259 public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound"; 260 261 /** 262 * Name of the class path resource (relative to the DispatcherServlet class) 263 * that defines DispatcherServlet's default strategy names. 264 */ 265 private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties"; 266 267 /** 268 * Common prefix that DispatcherServlet's default strategy attributes start with. 269 */ 270 private static final String DEFAULT_STRATEGIES_PREFIX = "org.springframework.web.servlet"; 271 272 /** Additional logger to use when no mapped handler is found for a request. */ 273 protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY); 274 275 private static final Properties defaultStrategies; 276 277 static { 278 // Load default strategy implementations from properties file. 279 // This is currently strictly internal and not meant to be customized 280 // by application developers. 281 try { 282 ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); 283 defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); 284 } 285 catch (IOException ex) { 286 throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); 287 } 288 } 289 290 /** Detect all HandlerMappings or just expect "handlerMapping" bean? */ 291 private boolean detectAllHandlerMappings = true; 292 293 /** Detect all HandlerAdapters or just expect "handlerAdapter" bean? */ 294 private boolean detectAllHandlerAdapters = true; 295 296 /** Detect all HandlerExceptionResolvers or just expect "handlerExceptionResolver" bean? */ 297 private boolean detectAllHandlerExceptionResolvers = true; 298 299 /** Detect all ViewResolvers or just expect "viewResolver" bean? */ 300 private boolean detectAllViewResolvers = true; 301 302 /** Throw a NoHandlerFoundException if no Handler was found to process this request? **/ 303 private boolean throwExceptionIfNoHandlerFound = false; 304 305 /** Perform cleanup of request attributes after include request? */ 306 private boolean cleanupAfterInclude = true; 307 308 /** MultipartResolver used by this servlet */ 309 private MultipartResolver multipartResolver; 310 311 /** LocaleResolver used by this servlet */ 312 private LocaleResolver localeResolver; 313 314 /** ThemeResolver used by this servlet */ 315 private ThemeResolver themeResolver; 316 317 /** List of HandlerMappings used by this servlet */ 318 private List<HandlerMapping> handlerMappings; 319 320 /** List of HandlerAdapters used by this servlet */ 321 private List<HandlerAdapter> handlerAdapters; 322 323 /** List of HandlerExceptionResolvers used by this servlet */ 324 private List<HandlerExceptionResolver> handlerExceptionResolvers; 325 326 /** RequestToViewNameTranslator used by this servlet */ 327 private RequestToViewNameTranslator viewNameTranslator; 328 329 /** FlashMapManager used by this servlet */ 330 private FlashMapManager flashMapManager; 331 332 /** List of ViewResolvers used by this servlet */ 333 private List<ViewResolver> viewResolvers; 334 335 336 /** 337 * Create a new {@code DispatcherServlet} that will create its own internal web 338 * application context based on defaults and values provided through servlet 339 * init-params. Typically used in Servlet 2.5 or earlier environments, where the only 340 * option for servlet registration is through {@code web.xml} which requires the use 341 * of a no-arg constructor. 342 * <p>Calling {@link #setContextConfigLocation} (init-param 'contextConfigLocation') 343 * will dictate which XML files will be loaded by the 344 * {@linkplain #DEFAULT_CONTEXT_CLASS default XmlWebApplicationContext} 345 * <p>Calling {@link #setContextClass} (init-param 'contextClass') overrides the 346 * default {@code XmlWebApplicationContext} and allows for specifying an alternative class, 347 * such as {@code AnnotationConfigWebApplicationContext}. 348 * <p>Calling {@link #setContextInitializerClasses} (init-param 'contextInitializerClasses') 349 * indicates which {@code ApplicationContextInitializer} classes should be used to 350 * further configure the internal application context prior to refresh(). 351 * @see #DispatcherServlet(WebApplicationContext) 352 */ 353 public DispatcherServlet() { 354 super(); 355 setDispatchOptionsRequest(true); 356 } 357 358 /** 359 * Create a new {@code DispatcherServlet} with the given web application context. This 360 * constructor is useful in Servlet 3.0+ environments where instance-based registration 361 * of servlets is possible through the {@link ServletContext#addServlet} API. 362 * <p>Using this constructor indicates that the following properties / init-params 363 * will be ignored: 364 * <ul> 365 * <li>{@link #setContextClass(Class)} / 'contextClass'</li> 366 * <li>{@link #setContextConfigLocation(String)} / 'contextConfigLocation'</li> 367 * <li>{@link #setContextAttribute(String)} / 'contextAttribute'</li> 368 * <li>{@link #setNamespace(String)} / 'namespace'</li> 369 * </ul> 370 * <p>The given web application context may or may not yet be {@linkplain 371 * ConfigurableApplicationContext#refresh() refreshed}. If it has <strong>not</strong> 372 * already been refreshed (the recommended approach), then the following will occur: 373 * <ul> 374 * <li>If the given context does not already have a {@linkplain 375 * ConfigurableApplicationContext#setParent parent}, the root application context 376 * will be set as the parent.</li> 377 * <li>If the given context has not already been assigned an {@linkplain 378 * ConfigurableApplicationContext#setId id}, one will be assigned to it</li> 379 * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to 380 * the application context</li> 381 * <li>{@link #postProcessWebApplicationContext} will be called</li> 382 * <li>Any {@code ApplicationContextInitializer}s specified through the 383 * "contextInitializerClasses" init-param or through the {@link 384 * #setContextInitializers} property will be applied.</li> 385 * <li>{@link ConfigurableApplicationContext#refresh refresh()} will be called if the 386 * context implements {@link ConfigurableApplicationContext}</li> 387 * </ul> 388 * If the context has already been refreshed, none of the above will occur, under the 389 * assumption that the user has performed these actions (or not) per their specific 390 * needs. 391 * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples. 392 * @param webApplicationContext the context to use 393 * @see #initWebApplicationContext 394 * @see #configureAndRefreshWebApplicationContext 395 * @see org.springframework.web.WebApplicationInitializer 396 */ 397 public DispatcherServlet(WebApplicationContext webApplicationContext) { 398 super(webApplicationContext); 399 setDispatchOptionsRequest(true); 400 } 401 402 403 /** 404 * Set whether to detect all HandlerMapping beans in this servlet's context. Otherwise, 405 * just a single bean with name "handlerMapping" will be expected. 406 * <p>Default is "true". Turn this off if you want this servlet to use a single 407 * HandlerMapping, despite multiple HandlerMapping beans being defined in the context. 408 */ 409 public void setDetectAllHandlerMappings(boolean detectAllHandlerMappings) { 410 this.detectAllHandlerMappings = detectAllHandlerMappings; 411 } 412 413 /** 414 * Set whether to detect all HandlerAdapter beans in this servlet's context. Otherwise, 415 * just a single bean with name "handlerAdapter" will be expected. 416 * <p>Default is "true". Turn this off if you want this servlet to use a single 417 * HandlerAdapter, despite multiple HandlerAdapter beans being defined in the context. 418 */ 419 public void setDetectAllHandlerAdapters(boolean detectAllHandlerAdapters) { 420 this.detectAllHandlerAdapters = detectAllHandlerAdapters; 421 } 422 423 /** 424 * Set whether to detect all HandlerExceptionResolver beans in this servlet's context. Otherwise, 425 * just a single bean with name "handlerExceptionResolver" will be expected. 426 * <p>Default is "true". Turn this off if you want this servlet to use a single 427 * HandlerExceptionResolver, despite multiple HandlerExceptionResolver beans being defined in the context. 428 */ 429 public void setDetectAllHandlerExceptionResolvers(boolean detectAllHandlerExceptionResolvers) { 430 this.detectAllHandlerExceptionResolvers = detectAllHandlerExceptionResolvers; 431 } 432 433 /** 434 * Set whether to detect all ViewResolver beans in this servlet's context. Otherwise, 435 * just a single bean with name "viewResolver" will be expected. 436 * <p>Default is "true". Turn this off if you want this servlet to use a single 437 * ViewResolver, despite multiple ViewResolver beans being defined in the context. 438 */ 439 public void setDetectAllViewResolvers(boolean detectAllViewResolvers) { 440 this.detectAllViewResolvers = detectAllViewResolvers; 441 } 442 443 /** 444 * Set whether to throw a NoHandlerFoundException when no Handler was found for this request. 445 * This exception can then be caught with a HandlerExceptionResolver or an 446 * {@code @ExceptionHandler} controller method. 447 * <p>Note that if {@link org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler} 448 * is used, then requests will always be forwarded to the default servlet and a 449 * NoHandlerFoundException would never be thrown in that case. 450 * <p>Default is "false", meaning the DispatcherServlet sends a NOT_FOUND error through the 451 * Servlet response. 452 * @since 4.0 453 */ 454 public void setThrowExceptionIfNoHandlerFound(boolean throwExceptionIfNoHandlerFound) { 455 this.throwExceptionIfNoHandlerFound = throwExceptionIfNoHandlerFound; 456 } 457 458 /** 459 * Set whether to perform cleanup of request attributes after an include request, that is, 460 * whether to reset the original state of all request attributes after the DispatcherServlet 461 * has processed within an include request. Otherwise, just the DispatcherServlet's own 462 * request attributes will be reset, but not model attributes for JSPs or special attributes 463 * set by views (for example, JSTL's). 464 * <p>Default is "true", which is strongly recommended. Views should not rely on request attributes 465 * having been set by (dynamic) includes. This allows JSP views rendered by an included controller 466 * to use any model attributes, even with the same names as in the main JSP, without causing side 467 * effects. Only turn this off for special needs, for example to deliberately allow main JSPs to 468 * access attributes from JSP views rendered by an included controller. 469 */ 470 public void setCleanupAfterInclude(boolean cleanupAfterInclude) { 471 this.cleanupAfterInclude = cleanupAfterInclude; 472 } 473 474 475 /** 476 * This implementation calls {@link #initStrategies}. 477 */ 478 @Override 479 protected void onRefresh(ApplicationContext context) { 480 initStrategies(context); 481 } 482 483 /** 484 * Initialize the strategy objects that this servlet uses. 485 * <p>May be overridden in subclasses in order to initialize further strategy objects. 486 */ 487 protected void initStrategies(ApplicationContext context) { 488 initMultipartResolver(context); 489 initLocaleResolver(context); 490 initThemeResolver(context); 491 initHandlerMappings(context); 492 initHandlerAdapters(context); 493 initHandlerExceptionResolvers(context); 494 initRequestToViewNameTranslator(context); 495 initViewResolvers(context); 496 initFlashMapManager(context); 497 } 498 499 /** 500 * Initialize the MultipartResolver used by this class. 501 * <p>If no bean is defined with the given name in the BeanFactory for this namespace, 502 * no multipart handling is provided. 503 */ 504 private void initMultipartResolver(ApplicationContext context) { 505 try { 506 this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); 507 if (logger.isDebugEnabled()) { 508 logger.debug("Using MultipartResolver [" + this.multipartResolver + "]"); 509 } 510 } 511 catch (NoSuchBeanDefinitionException ex) { 512 // Default is no multipart resolver. 513 this.multipartResolver = null; 514 if (logger.isDebugEnabled()) { 515 logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME + 516 "': no multipart request handling provided"); 517 } 518 } 519 } 520 521 /** 522 * Initialize the LocaleResolver used by this class. 523 * <p>If no bean is defined with the given name in the BeanFactory for this namespace, 524 * we default to AcceptHeaderLocaleResolver. 525 */ 526 private void initLocaleResolver(ApplicationContext context) { 527 try { 528 this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class); 529 if (logger.isDebugEnabled()) { 530 logger.debug("Using LocaleResolver [" + this.localeResolver + "]"); 531 } 532 } 533 catch (NoSuchBeanDefinitionException ex) { 534 // We need to use the default. 535 this.localeResolver = getDefaultStrategy(context, LocaleResolver.class); 536 if (logger.isDebugEnabled()) { 537 logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME + 538 "': using default [" + this.localeResolver + "]"); 539 } 540 } 541 } 542 543 /** 544 * Initialize the ThemeResolver used by this class. 545 * <p>If no bean is defined with the given name in the BeanFactory for this namespace, 546 * we default to a FixedThemeResolver. 547 */ 548 private void initThemeResolver(ApplicationContext context) { 549 try { 550 this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class); 551 if (logger.isDebugEnabled()) { 552 logger.debug("Using ThemeResolver [" + this.themeResolver + "]"); 553 } 554 } 555 catch (NoSuchBeanDefinitionException ex) { 556 // We need to use the default. 557 this.themeResolver = getDefaultStrategy(context, ThemeResolver.class); 558 if (logger.isDebugEnabled()) { 559 logger.debug("Unable to locate ThemeResolver with name '" + THEME_RESOLVER_BEAN_NAME + 560 "': using default [" + this.themeResolver + "]"); 561 } 562 } 563 } 564 565 /** 566 * Initialize the HandlerMappings used by this class. 567 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace, 568 * we default to BeanNameUrlHandlerMapping. 569 */ 570 private void initHandlerMappings(ApplicationContext context) { 571 this.handlerMappings = null; 572 573 if (this.detectAllHandlerMappings) { 574 // Find all HandlerMappings in the ApplicationContext, including ancestor contexts. 575 Map<String, HandlerMapping> matchingBeans = 576 BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); 577 if (!matchingBeans.isEmpty()) { 578 this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values()); 579 // We keep HandlerMappings in sorted order. 580 AnnotationAwareOrderComparator.sort(this.handlerMappings); 581 } 582 } 583 else { 584 try { 585 HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); 586 this.handlerMappings = Collections.singletonList(hm); 587 } 588 catch (NoSuchBeanDefinitionException ex) { 589 // Ignore, we'll add a default HandlerMapping later. 590 } 591 } 592 593 // Ensure we have at least one HandlerMapping, by registering 594 // a default HandlerMapping if no other mappings are found. 595 if (this.handlerMappings == null) { 596 this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); 597 if (logger.isDebugEnabled()) { 598 logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default"); 599 } 600 } 601 } 602 603 /** 604 * Initialize the HandlerAdapters used by this class. 605 * <p>If no HandlerAdapter beans are defined in the BeanFactory for this namespace, 606 * we default to SimpleControllerHandlerAdapter. 607 */ 608 private void initHandlerAdapters(ApplicationContext context) { 609 this.handlerAdapters = null; 610 611 if (this.detectAllHandlerAdapters) { 612 // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts. 613 Map<String, HandlerAdapter> matchingBeans = 614 BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); 615 if (!matchingBeans.isEmpty()) { 616 this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values()); 617 // We keep HandlerAdapters in sorted order. 618 AnnotationAwareOrderComparator.sort(this.handlerAdapters); 619 } 620 } 621 else { 622 try { 623 HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class); 624 this.handlerAdapters = Collections.singletonList(ha); 625 } 626 catch (NoSuchBeanDefinitionException ex) { 627 // Ignore, we'll add a default HandlerAdapter later. 628 } 629 } 630 631 // Ensure we have at least some HandlerAdapters, by registering 632 // default HandlerAdapters if no other adapters are found. 633 if (this.handlerAdapters == null) { 634 this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class); 635 if (logger.isDebugEnabled()) { 636 logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default"); 637 } 638 } 639 } 640 641 /** 642 * Initialize the HandlerExceptionResolver used by this class. 643 * <p>If no bean is defined with the given name in the BeanFactory for this namespace, 644 * we default to no exception resolver. 645 */ 646 private void initHandlerExceptionResolvers(ApplicationContext context) { 647 this.handlerExceptionResolvers = null; 648 649 if (this.detectAllHandlerExceptionResolvers) { 650 // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts. 651 Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils 652 .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false); 653 if (!matchingBeans.isEmpty()) { 654 this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values()); 655 // We keep HandlerExceptionResolvers in sorted order. 656 AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers); 657 } 658 } 659 else { 660 try { 661 HandlerExceptionResolver her = 662 context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class); 663 this.handlerExceptionResolvers = Collections.singletonList(her); 664 } 665 catch (NoSuchBeanDefinitionException ex) { 666 // Ignore, no HandlerExceptionResolver is fine too. 667 } 668 } 669 670 // Ensure we have at least some HandlerExceptionResolvers, by registering 671 // default HandlerExceptionResolvers if no other resolvers are found. 672 if (this.handlerExceptionResolvers == null) { 673 this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class); 674 if (logger.isDebugEnabled()) { 675 logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default"); 676 } 677 } 678 } 679 680 /** 681 * Initialize the RequestToViewNameTranslator used by this servlet instance. 682 * <p>If no implementation is configured then we default to DefaultRequestToViewNameTranslator. 683 */ 684 private void initRequestToViewNameTranslator(ApplicationContext context) { 685 try { 686 this.viewNameTranslator = 687 context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class); 688 if (logger.isDebugEnabled()) { 689 logger.debug("Using RequestToViewNameTranslator [" + this.viewNameTranslator + "]"); 690 } 691 } 692 catch (NoSuchBeanDefinitionException ex) { 693 // We need to use the default. 694 this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class); 695 if (logger.isDebugEnabled()) { 696 logger.debug("Unable to locate RequestToViewNameTranslator with name '" + 697 REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME + "': using default [" + this.viewNameTranslator + 698 "]"); 699 } 700 } 701 } 702 703 /** 704 * Initialize the ViewResolvers used by this class. 705 * <p>If no ViewResolver beans are defined in the BeanFactory for this 706 * namespace, we default to InternalResourceViewResolver. 707 */ 708 private void initViewResolvers(ApplicationContext context) { 709 this.viewResolvers = null; 710 711 if (this.detectAllViewResolvers) { 712 // Find all ViewResolvers in the ApplicationContext, including ancestor contexts. 713 Map<String, ViewResolver> matchingBeans = 714 BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false); 715 if (!matchingBeans.isEmpty()) { 716 this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values()); 717 // We keep ViewResolvers in sorted order. 718 AnnotationAwareOrderComparator.sort(this.viewResolvers); 719 } 720 } 721 else { 722 try { 723 ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class); 724 this.viewResolvers = Collections.singletonList(vr); 725 } 726 catch (NoSuchBeanDefinitionException ex) { 727 // Ignore, we'll add a default ViewResolver later. 728 } 729 } 730 731 // Ensure we have at least one ViewResolver, by registering 732 // a default ViewResolver if no other resolvers are found. 733 if (this.viewResolvers == null) { 734 this.viewResolvers = getDefaultStrategies(context, ViewResolver.class); 735 if (logger.isDebugEnabled()) { 736 logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default"); 737 } 738 } 739 } 740 741 /** 742 * Initialize the {@link FlashMapManager} used by this servlet instance. 743 * <p>If no implementation is configured then we default to 744 * {@code org.springframework.web.servlet.support.DefaultFlashMapManager}. 745 */ 746 private void initFlashMapManager(ApplicationContext context) { 747 try { 748 this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class); 749 if (logger.isDebugEnabled()) { 750 logger.debug("Using FlashMapManager [" + this.flashMapManager + "]"); 751 } 752 } 753 catch (NoSuchBeanDefinitionException ex) { 754 // We need to use the default. 755 this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class); 756 if (logger.isDebugEnabled()) { 757 logger.debug("Unable to locate FlashMapManager with name '" + 758 FLASH_MAP_MANAGER_BEAN_NAME + "': using default [" + this.flashMapManager + "]"); 759 } 760 } 761 } 762 763 /** 764 * Return this servlet's ThemeSource, if any; else return {@code null}. 765 * <p>Default is to return the WebApplicationContext as ThemeSource, 766 * provided that it implements the ThemeSource interface. 767 * @return the ThemeSource, if any 768 * @see #getWebApplicationContext() 769 */ 770 public final ThemeSource getThemeSource() { 771 if (getWebApplicationContext() instanceof ThemeSource) { 772 return (ThemeSource) getWebApplicationContext(); 773 } 774 else { 775 return null; 776 } 777 } 778 779 /** 780 * Obtain this servlet's MultipartResolver, if any. 781 * @return the MultipartResolver used by this servlet, or {@code null} if none 782 * (indicating that no multipart support is available) 783 */ 784 public final MultipartResolver getMultipartResolver() { 785 return this.multipartResolver; 786 } 787 788 /** 789 * Return the default strategy object for the given strategy interface. 790 * <p>The default implementation delegates to {@link #getDefaultStrategies}, 791 * expecting a single object in the list. 792 * @param context the current WebApplicationContext 793 * @param strategyInterface the strategy interface 794 * @return the corresponding strategy object 795 * @see #getDefaultStrategies 796 */ 797 protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) { 798 List<T> strategies = getDefaultStrategies(context, strategyInterface); 799 if (strategies.size() != 1) { 800 throw new BeanInitializationException( 801 "DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]"); 802 } 803 return strategies.get(0); 804 } 805 806 /** 807 * Create a List of default strategy objects for the given strategy interface. 808 * <p>The default implementation uses the "DispatcherServlet.properties" file (in the same 809 * package as the DispatcherServlet class) to determine the class names. It instantiates 810 * the strategy objects through the context's BeanFactory. 811 * @param context the current WebApplicationContext 812 * @param strategyInterface the strategy interface 813 * @return the List of corresponding strategy objects 814 */ 815 @SuppressWarnings("unchecked") 816 protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { 817 String key = strategyInterface.getName(); 818 String value = defaultStrategies.getProperty(key); 819 if (value != null) { 820 String[] classNames = StringUtils.commaDelimitedListToStringArray(value); 821 List<T> strategies = new ArrayList<T>(classNames.length); 822 for (String className : classNames) { 823 try { 824 Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader()); 825 Object strategy = createDefaultStrategy(context, clazz); 826 strategies.add((T) strategy); 827 } 828 catch (ClassNotFoundException ex) { 829 throw new BeanInitializationException( 830 "Could not find DispatcherServlet's default strategy class [" + className + 831 "] for interface [" + key + "]", ex); 832 } 833 catch (LinkageError err) { 834 throw new BeanInitializationException( 835 "Error loading DispatcherServlet's default strategy class [" + className + 836 "] for interface [" + key + "]: problem with class file or dependent class", err); 837 } 838 } 839 return strategies; 840 } 841 else { 842 return new LinkedList<T>(); 843 } 844 } 845 846 /** 847 * Create a default strategy. 848 * <p>The default implementation uses 849 * {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean}. 850 * @param context the current WebApplicationContext 851 * @param clazz the strategy implementation class to instantiate 852 * @return the fully configured strategy instance 853 * @see org.springframework.context.ApplicationContext#getAutowireCapableBeanFactory() 854 * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean 855 */ 856 protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) { 857 return context.getAutowireCapableBeanFactory().createBean(clazz); 858 } 859 860 861 /** 862 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch} 863 * for the actual dispatching. 864 */ 865 @Override 866 protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { 867 if (logger.isDebugEnabled()) { 868 String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : ""; 869 logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed + 870 " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]"); 871 } 872 873 // Keep a snapshot of the request attributes in case of an include, 874 // to be able to restore the original attributes after the include. 875 Map<String, Object> attributesSnapshot = null; 876 if (WebUtils.isIncludeRequest(request)) { 877 attributesSnapshot = new HashMap<String, Object>(); 878 Enumeration<?> attrNames = request.getAttributeNames(); 879 while (attrNames.hasMoreElements()) { 880 String attrName = (String) attrNames.nextElement(); 881 if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { 882 attributesSnapshot.put(attrName, request.getAttribute(attrName)); 883 } 884 } 885 } 886 887 // Make framework objects available to handlers and view objects. 888 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); 889 request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); 890 request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); 891 request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); 892 893 FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); 894 if (inputFlashMap != null) { 895 request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); 896 } 897 request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); 898 request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); 899 900 try { 901 doDispatch(request, response); 902 } 903 finally { 904 if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { 905 // Restore the original attribute snapshot, in case of an include. 906 if (attributesSnapshot != null) { 907 restoreAttributesAfterInclude(request, attributesSnapshot); 908 } 909 } 910 } 911 } 912 913 /** 914 * Process the actual dispatching to the handler. 915 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order. 916 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters 917 * to find the first that supports the handler class. 918 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers 919 * themselves to decide which methods are acceptable. 920 * @param request current HTTP request 921 * @param response current HTTP response 922 * @throws Exception in case of any kind of processing failure 923 */ 924 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { 925 HttpServletRequest processedRequest = request; 926 HandlerExecutionChain mappedHandler = null; 927 boolean multipartRequestParsed = false; 928 929 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 930 931 try { 932 ModelAndView mv = null; 933 Exception dispatchException = null; 934 935 try { 936 processedRequest = checkMultipart(request); 937 multipartRequestParsed = (processedRequest != request); 938 939 // Determine handler for the current request. 940 mappedHandler = getHandler(processedRequest); 941 if (mappedHandler == null || mappedHandler.getHandler() == null) { 942 noHandlerFound(processedRequest, response); 943 return; 944 } 945 946 // Determine handler adapter for the current request. 947 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 948 949 // Process last-modified header, if supported by the handler. 950 String method = request.getMethod(); 951 boolean isGet = "GET".equals(method); 952 if (isGet || "HEAD".equals(method)) { 953 long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); 954 if (logger.isDebugEnabled()) { 955 logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); 956 } 957 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { 958 return; 959 } 960 } 961 962 if (!mappedHandler.applyPreHandle(processedRequest, response)) { 963 return; 964 } 965 966 // Actually invoke the handler. 967 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 968 969 if (asyncManager.isConcurrentHandlingStarted()) { 970 return; 971 } 972 973 applyDefaultViewName(processedRequest, mv); 974 mappedHandler.applyPostHandle(processedRequest, response, mv); 975 } 976 catch (Exception ex) { 977 dispatchException = ex; 978 } 979 catch (Throwable err) { 980 // As of 4.3, we're processing Errors thrown from handler methods as well, 981 // making them available for @ExceptionHandler methods and other scenarios. 982 dispatchException = new NestedServletException("Handler dispatch failed", err); 983 } 984 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 985 } 986 catch (Exception ex) { 987 triggerAfterCompletion(processedRequest, response, mappedHandler, ex); 988 } 989 catch (Throwable err) { 990 triggerAfterCompletion(processedRequest, response, mappedHandler, 991 new NestedServletException("Handler processing failed", err)); 992 } 993 finally { 994 if (asyncManager.isConcurrentHandlingStarted()) { 995 // Instead of postHandle and afterCompletion 996 if (mappedHandler != null) { 997 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); 998 } 999 } 1000 else { 1001 // Clean up any resources used by a multipart request. 1002 if (multipartRequestParsed) { 1003 cleanupMultipart(processedRequest); 1004 } 1005 } 1006 } 1007 } 1008 1009 /** 1010 * Do we need view name translation? 1011 */ 1012 private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception { 1013 if (mv != null && !mv.hasView()) { 1014 mv.setViewName(getDefaultViewName(request)); 1015 } 1016 } 1017 1018 /** 1019 * Handle the result of handler selection and handler invocation, which is 1020 * either a ModelAndView or an Exception to be resolved to a ModelAndView. 1021 */ 1022 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, 1023 HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { 1024 1025 boolean errorView = false; 1026 1027 if (exception != null) { 1028 if (exception instanceof ModelAndViewDefiningException) { 1029 logger.debug("ModelAndViewDefiningException encountered", exception); 1030 mv = ((ModelAndViewDefiningException) exception).getModelAndView(); 1031 } 1032 else { 1033 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); 1034 mv = processHandlerException(request, response, handler, exception); 1035 errorView = (mv != null); 1036 } 1037 } 1038 1039 // Did the handler return a view to render? 1040 if (mv != null && !mv.wasCleared()) { 1041 render(mv, request, response); 1042 if (errorView) { 1043 WebUtils.clearErrorRequestAttributes(request); 1044 } 1045 } 1046 else { 1047 if (logger.isDebugEnabled()) { 1048 logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + 1049 "': assuming HandlerAdapter completed request handling"); 1050 } 1051 } 1052 1053 if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { 1054 // Concurrent handling started during a forward 1055 return; 1056 } 1057 1058 if (mappedHandler != null) { 1059 mappedHandler.triggerAfterCompletion(request, response, null); 1060 } 1061 } 1062 1063 /** 1064 * Build a LocaleContext for the given request, exposing the request's primary locale as current locale. 1065 * <p>The default implementation uses the dispatcher's LocaleResolver to obtain the current locale, 1066 * which might change during a request. 1067 * @param request current HTTP request 1068 * @return the corresponding LocaleContext 1069 */ 1070 @Override 1071 protected LocaleContext buildLocaleContext(final HttpServletRequest request) { 1072 if (this.localeResolver instanceof LocaleContextResolver) { 1073 return ((LocaleContextResolver) this.localeResolver).resolveLocaleContext(request); 1074 } 1075 else { 1076 return new LocaleContext() { 1077 @Override 1078 public Locale getLocale() { 1079 return localeResolver.resolveLocale(request); 1080 } 1081 }; 1082 } 1083 } 1084 1085 /** 1086 * Convert the request into a multipart request, and make multipart resolver available. 1087 * <p>If no multipart resolver is set, simply use the existing request. 1088 * @param request current HTTP request 1089 * @return the processed request (multipart wrapper if necessary) 1090 * @see MultipartResolver#resolveMultipart 1091 */ 1092 protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { 1093 if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { 1094 if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { 1095 logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " + 1096 "this typically results from an additional MultipartFilter in web.xml"); 1097 } 1098 else if (hasMultipartException(request)) { 1099 logger.debug("Multipart resolution previously failed for current request - " + 1100 "skipping re-resolution for undisturbed error rendering"); 1101 } 1102 else { 1103 try { 1104 return this.multipartResolver.resolveMultipart(request); 1105 } 1106 catch (MultipartException ex) { 1107 if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) { 1108 logger.debug("Multipart resolution failed for error dispatch", ex); 1109 // Keep processing error dispatch with regular request handle below 1110 } 1111 else { 1112 throw ex; 1113 } 1114 } 1115 } 1116 } 1117 // If not returned before: return original request. 1118 return request; 1119 } 1120 1121 /** 1122 * Check "javax.servlet.error.exception" attribute for a multipart exception. 1123 */ 1124 private boolean hasMultipartException(HttpServletRequest request) { 1125 Throwable error = (Throwable) request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE); 1126 while (error != null) { 1127 if (error instanceof MultipartException) { 1128 return true; 1129 } 1130 error = error.getCause(); 1131 } 1132 return false; 1133 } 1134 1135 /** 1136 * Clean up any resources used by the given multipart request (if any). 1137 * @param request current HTTP request 1138 * @see MultipartResolver#cleanupMultipart 1139 */ 1140 protected void cleanupMultipart(HttpServletRequest request) { 1141 MultipartHttpServletRequest multipartRequest = 1142 WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class); 1143 if (multipartRequest != null) { 1144 this.multipartResolver.cleanupMultipart(multipartRequest); 1145 } 1146 } 1147 1148 /** 1149 * Return the HandlerExecutionChain for this request. 1150 * <p>Tries all handler mappings in order. 1151 * @param request current HTTP request 1152 * @return the HandlerExecutionChain, or {@code null} if no handler could be found 1153 */ 1154 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 1155 for (HandlerMapping hm : this.handlerMappings) { 1156 if (logger.isTraceEnabled()) { 1157 logger.trace( 1158 "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); 1159 } 1160 HandlerExecutionChain handler = hm.getHandler(request); 1161 if (handler != null) { 1162 return handler; 1163 } 1164 } 1165 return null; 1166 } 1167 1168 /** 1169 * No handler found -> set appropriate HTTP response status. 1170 * @param request current HTTP request 1171 * @param response current HTTP response 1172 * @throws Exception if preparing the response failed 1173 */ 1174 protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception { 1175 if (pageNotFoundLogger.isWarnEnabled()) { 1176 pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) + 1177 "] in DispatcherServlet with name '" + getServletName() + "'"); 1178 } 1179 if (this.throwExceptionIfNoHandlerFound) { 1180 throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request), 1181 new ServletServerHttpRequest(request).getHeaders()); 1182 } 1183 else { 1184 response.sendError(HttpServletResponse.SC_NOT_FOUND); 1185 } 1186 } 1187 1188 /** 1189 * Return the HandlerAdapter for this handler object. 1190 * @param handler the handler object to find an adapter for 1191 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error. 1192 */ 1193 protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { 1194 for (HandlerAdapter ha : this.handlerAdapters) { 1195 if (logger.isTraceEnabled()) { 1196 logger.trace("Testing handler adapter [" + ha + "]"); 1197 } 1198 if (ha.supports(handler)) { 1199 return ha; 1200 } 1201 } 1202 throw new ServletException("No adapter for handler [" + handler + 1203 "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); 1204 } 1205 1206 /** 1207 * Determine an error ModelAndView via the registered HandlerExceptionResolvers. 1208 * @param request current HTTP request 1209 * @param response current HTTP response 1210 * @param handler the executed handler, or {@code null} if none chosen at the time of the exception 1211 * (for example, if multipart resolution failed) 1212 * @param ex the exception that got thrown during handler execution 1213 * @return a corresponding ModelAndView to forward to 1214 * @throws Exception if no error ModelAndView found 1215 */ 1216 protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, 1217 Object handler, Exception ex) throws Exception { 1218 1219 // Check registered HandlerExceptionResolvers... 1220 ModelAndView exMv = null; 1221 for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) { 1222 exMv = handlerExceptionResolver.resolveException(request, response, handler, ex); 1223 if (exMv != null) { 1224 break; 1225 } 1226 } 1227 if (exMv != null) { 1228 if (exMv.isEmpty()) { 1229 request.setAttribute(EXCEPTION_ATTRIBUTE, ex); 1230 return null; 1231 } 1232 // We might still need view name translation for a plain error model... 1233 if (!exMv.hasView()) { 1234 exMv.setViewName(getDefaultViewName(request)); 1235 } 1236 if (logger.isDebugEnabled()) { 1237 logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex); 1238 } 1239 WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); 1240 return exMv; 1241 } 1242 1243 throw ex; 1244 } 1245 1246 /** 1247 * Render the given ModelAndView. 1248 * <p>This is the last stage in handling a request. It may involve resolving the view by name. 1249 * @param mv the ModelAndView to render 1250 * @param request current HTTP servlet request 1251 * @param response current HTTP servlet response 1252 * @throws ServletException if view is missing or cannot be resolved 1253 * @throws Exception if there's a problem rendering the view 1254 */ 1255 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { 1256 // Determine locale for request and apply it to the response. 1257 Locale locale = this.localeResolver.resolveLocale(request); 1258 response.setLocale(locale); 1259 1260 View view; 1261 if (mv.isReference()) { 1262 // We need to resolve the view name. 1263 view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); 1264 if (view == null) { 1265 throw new ServletException("Could not resolve view with name '" + mv.getViewName() + 1266 "' in servlet with name '" + getServletName() + "'"); 1267 } 1268 } 1269 else { 1270 // No need to lookup: the ModelAndView object contains the actual View object. 1271 view = mv.getView(); 1272 if (view == null) { 1273 throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + 1274 "View object in servlet with name '" + getServletName() + "'"); 1275 } 1276 } 1277 1278 // Delegate to the View object for rendering. 1279 if (logger.isDebugEnabled()) { 1280 logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'"); 1281 } 1282 try { 1283 if (mv.getStatus() != null) { 1284 response.setStatus(mv.getStatus().value()); 1285 } 1286 view.render(mv.getModelInternal(), request, response); 1287 } 1288 catch (Exception ex) { 1289 if (logger.isDebugEnabled()) { 1290 logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + 1291 getServletName() + "'", ex); 1292 } 1293 throw ex; 1294 } 1295 } 1296 1297 /** 1298 * Translate the supplied request into a default view name. 1299 * @param request current HTTP servlet request 1300 * @return the view name (or {@code null} if no default found) 1301 * @throws Exception if view name translation failed 1302 */ 1303 protected String getDefaultViewName(HttpServletRequest request) throws Exception { 1304 return this.viewNameTranslator.getViewName(request); 1305 } 1306 1307 /** 1308 * Resolve the given view name into a View object (to be rendered). 1309 * <p>The default implementations asks all ViewResolvers of this dispatcher. 1310 * Can be overridden for custom resolution strategies, potentially based on 1311 * specific model attributes or request parameters. 1312 * @param viewName the name of the view to resolve 1313 * @param model the model to be passed to the view 1314 * @param locale the current locale 1315 * @param request current HTTP servlet request 1316 * @return the View object, or {@code null} if none found 1317 * @throws Exception if the view cannot be resolved 1318 * (typically in case of problems creating an actual View object) 1319 * @see ViewResolver#resolveViewName 1320 */ 1321 protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale, 1322 HttpServletRequest request) throws Exception { 1323 1324 for (ViewResolver viewResolver : this.viewResolvers) { 1325 View view = viewResolver.resolveViewName(viewName, locale); 1326 if (view != null) { 1327 return view; 1328 } 1329 } 1330 return null; 1331 } 1332 1333 private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, 1334 HandlerExecutionChain mappedHandler, Exception ex) throws Exception { 1335 1336 if (mappedHandler != null) { 1337 mappedHandler.triggerAfterCompletion(request, response, ex); 1338 } 1339 throw ex; 1340 } 1341 1342 /** 1343 * Restore the request attributes after an include. 1344 * @param request current HTTP request 1345 * @param attributesSnapshot the snapshot of the request attributes before the include 1346 */ 1347 @SuppressWarnings("unchecked") 1348 private void restoreAttributesAfterInclude(HttpServletRequest request, Map<?, ?> attributesSnapshot) { 1349 // Need to copy into separate Collection here, to avoid side effects 1350 // on the Enumeration when removing attributes. 1351 Set<String> attrsToCheck = new HashSet<String>(); 1352 Enumeration<?> attrNames = request.getAttributeNames(); 1353 while (attrNames.hasMoreElements()) { 1354 String attrName = (String) attrNames.nextElement(); 1355 if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { 1356 attrsToCheck.add(attrName); 1357 } 1358 } 1359 1360 // Add attributes that may have been removed 1361 attrsToCheck.addAll((Set<String>) attributesSnapshot.keySet()); 1362 1363 // Iterate over the attributes to check, restoring the original value 1364 // or removing the attribute, respectively, if appropriate. 1365 for (String attrName : attrsToCheck) { 1366 Object attrValue = attributesSnapshot.get(attrName); 1367 if (attrValue == null) { 1368 request.removeAttribute(attrName); 1369 } 1370 else if (attrValue != request.getAttribute(attrName)) { 1371 request.setAttribute(attrName, attrValue); 1372 } 1373 } 1374 } 1375 1376 private static String getRequestUri(HttpServletRequest request) { 1377 String uri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE); 1378 if (uri == null) { 1379 uri = request.getRequestURI(); 1380 } 1381 return uri; 1382 } 1383 1384}