SPR-5624 - A default HandlerExceptionResolver that resolves standard Spring exceptions

This commit is contained in:
Arjen Poutsma 2009-03-27 19:53:42 +00:00
parent f92b7f1011
commit 161c926054
9 changed files with 1094 additions and 780 deletions

View File

@ -22,7 +22,6 @@ import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -48,7 +47,6 @@ import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.ui.context.ThemeSource; import org.springframework.ui.context.ThemeSource;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.multipart.MultipartException; import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.multipart.MultipartResolver;
@ -57,88 +55,65 @@ import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils; import org.springframework.web.util.WebUtils;
/** /**
* Central dispatcher for HTTP request handlers/controllers, * Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers or HTTP-based remote service
* e.g. for web UI controllers or HTTP-based remote service exporters. * exporters. Dispatches to registered handlers for processing a web request, providing convenient mapping and exception
* Dispatches to registered handlers for processing a web request, * handling facilities.
* providing convenient mapping and exception handling facilities.
* *
* <p>This servlet is very flexible: It can be used with just about any workflow, * <p>This servlet is very flexible: It can be used with just about any workflow, with the installation of the
* with the installation of the appropriate adapter classes. It offers the * appropriate adapter classes. It offers the following functionality that distinguishes it from other request-driven
* following functionality that distinguishes it from other request-driven
* web MVC frameworks: * web MVC frameworks:
* *
* <ul> * <ul> <li>It is based around a JavaBeans configuration mechanism.
* <li>It is based around a JavaBeans configuration mechanism.
* *
* <li>It can use any {@link HandlerMapping} implementation - pre-built or provided * <li>It can use any {@link HandlerMapping} implementation - pre-built or provided as part of an application - to
* as part of an application - to control the routing of requests to handler objects. * control the routing of requests to handler objects. Default is {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}
* Default is {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping} and * and {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}. HandlerMapping objects
* {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}. * can be defined as beans in the servlet's application context, implementing the HandlerMapping interface, overriding
* HandlerMapping objects can be defined as beans in the servlet's application context, * the default HandlerMapping if present. HandlerMappings can be given any bean name (they are tested by type).
* implementing the HandlerMapping interface, overriding the default HandlerMapping if present.
* HandlerMappings can be given any bean name (they are tested by type).
* *
* <li>It can use any {@link HandlerAdapter}; this allows for using any handler interface. * <li>It can use any {@link HandlerAdapter}; this allows for using any handler interface. Default adapters are {@link
* Default adapters are {@link org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter}, * org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter}, {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter},
* {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter}, * for Spring's {@link org.springframework.web.HttpRequestHandler} and {@link org.springframework.web.servlet.mvc.Controller}
* for Spring's {@link org.springframework.web.HttpRequestHandler} and * interfaces, respectively. A default {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter}
* {@link org.springframework.web.servlet.mvc.Controller} interfaces, respectively. A default * will be registered as well. HandlerAdapter objects can be added as beans in the application context, overriding the
* {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter} * default HandlerAdapters. Like HandlerMappings, HandlerAdapters can be given any bean name (they are tested by type).
* will be registered as well. HandlerAdapter objects can be added as beans in the
* application context, overriding the default HandlerAdapters. Like HandlerMappings,
* HandlerAdapters can be given any bean name (they are tested by type).
* *
* <li>The dispatcher's exception resolution strategy can be specified via a * <li>The dispatcher's exception resolution strategy can be specified via a {@link HandlerExceptionResolver}, for
* {@link HandlerExceptionResolver}, for example mapping certain exceptions to * example mapping certain exceptions to error pages. Default is none. Additional HandlerExceptionResolvers can be added
* error pages. Default is none. Additional HandlerExceptionResolvers can be added * through the application context. HandlerExceptionResolver can be given any bean name (they are tested by type).
* through the application context. HandlerExceptionResolver can be given any
* bean name (they are tested by type).
* *
* <li>Its view resolution strategy can be specified via a {@link ViewResolver} * <li>Its view resolution strategy can be specified via a {@link ViewResolver} implementation, resolving symbolic view
* implementation, resolving symbolic view names into View objects. Default is * names into View objects. Default is {@link org.springframework.web.servlet.view.InternalResourceViewResolver}.
* {@link org.springframework.web.servlet.view.InternalResourceViewResolver}. * ViewResolver objects can be added as beans in the application context, overriding the default ViewResolver.
* ViewResolver objects can be added as beans in the application context, * ViewResolvers can be given any bean name (they are tested by type).
* overriding the default ViewResolver. ViewResolvers can be given any bean name
* (they are tested by type).
* *
* <li>If a {@link View} or view name is not supplied by the user, then the configured * <li>If a {@link View} or view name is not supplied by the user, then the configured {@link
* {@link RequestToViewNameTranslator} will translate the current request into a * RequestToViewNameTranslator} will translate the current request into a view name. The corresponding bean name is
* view name. The corresponding bean name is "viewNameTranslator"; the default is * "viewNameTranslator"; the default is {@link org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator}.
* {@link org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator}.
* *
* <li>The dispatcher's strategy for resolving multipart requests is determined by * <li>The dispatcher's strategy for resolving multipart requests is determined by a {@link
* a {@link org.springframework.web.multipart.MultipartResolver} implementation. * org.springframework.web.multipart.MultipartResolver} implementation. Implementations for Jakarta Commons FileUpload
* Implementations for Jakarta Commons FileUpload and Jason Hunter's COS are * and Jason Hunter's COS are included; the typical choise is {@link org.springframework.web.multipart.commons.CommonsMultipartResolver}.
* included; the typical choise is
* {@link org.springframework.web.multipart.commons.CommonsMultipartResolver}.
* The MultipartResolver bean name is "multipartResolver"; default is none. * The MultipartResolver bean name is "multipartResolver"; default is none.
* *
* <li>Its locale resolution strategy is determined by a {@link LocaleResolver}. * <li>Its locale resolution strategy is determined by a {@link LocaleResolver}. Out-of-the-box implementations work via
* Out-of-the-box implementations work via HTTP accept header, cookie, or session. * HTTP accept header, cookie, or session. The LocaleResolver bean name is "localeResolver"; default is {@link
* The LocaleResolver bean name is "localeResolver"; default is * org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver}.
* {@link org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver}.
* *
* <li>Its theme resolution strategy is determined by a {@link ThemeResolver}. * <li>Its theme resolution strategy is determined by a {@link ThemeResolver}. Implementations for a fixed theme and for
* Implementations for a fixed theme and for cookie and session storage are included. * cookie and session storage are included. The ThemeResolver bean name is "themeResolver"; default is {@link
* The ThemeResolver bean name is "themeResolver"; default is * org.springframework.web.servlet.theme.FixedThemeResolver}. </ul>
* {@link org.springframework.web.servlet.theme.FixedThemeResolver}.
* </ul>
* *
* <p><b>NOTE: The <code>@RequestMapping</code> annotation will only be processed * <p><b>NOTE: The <code>@RequestMapping</code> annotation will only be processed if a corresponding
* if a corresponding <code>HandlerMapping</code> (for type level annotations) * <code>HandlerMapping</code> (for type level annotations) and/or <code>HandlerAdapter</code> (for method level
* and/or <code>HandlerAdapter</code> (for method level annotations) * annotations) is present in the dispatcher.</b> This is the case by default. However, if you are defining custom
* is present in the dispatcher.</b> This is the case by default. * <code>HandlerMappings</code> or <code>HandlerAdapters</code>, then you need to make sure that a corresponding custom
* However, if you are defining custom <code>HandlerMappings</code> or * <code>DefaultAnnotationHandlerMapping</code> and/or <code>AnnotationMethodHandlerAdapter</code> is defined as well -
* <code>HandlerAdapters</code>, then you need to make sure that a * provided that you intend to use <code>@RequestMapping</code>.
* corresponding custom <code>DefaultAnnotationHandlerMapping</code>
* and/or <code>AnnotationMethodHandlerAdapter</code> is defined as well
* - provided that you intend to use <code>@RequestMapping</code>.
* *
* <p><b>A web application can define any number of DispatcherServlets.</b> * <p><b>A web application can define any number of DispatcherServlets.</b> Each servlet will operate in its own
* Each servlet will operate in its own namespace, loading its own application * namespace, loading its own application context with mappings, handlers, etc. Only the root application context as
* context with mappings, handlers, etc. Only the root application context * loaded by {@link org.springframework.web.context.ContextLoaderListener}, if any, will be shared.
* as loaded by {@link org.springframework.web.context.ContextLoaderListener},
* if any, will be shared.
* *
* @author Rod Johnson * @author Rod Johnson
* @author Juergen Hoeller * @author Juergen Hoeller
@ -149,102 +124,92 @@ import org.springframework.web.util.WebUtils;
*/ */
public class DispatcherServlet extends FrameworkServlet { public class DispatcherServlet extends FrameworkServlet {
/** /** Well-known name for the MultipartResolver object in the bean factory for this namespace. */
* Well-known name for the MultipartResolver object in the bean factory for this namespace.
*/
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver"; public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
/** /** Well-known name for the LocaleResolver object in the bean factory for this namespace. */
* Well-known name for the LocaleResolver object in the bean factory for this namespace.
*/
public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver"; public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
/** /** Well-known name for the ThemeResolver object in the bean factory for this namespace. */
* Well-known name for the ThemeResolver object in the bean factory for this namespace.
*/
public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver"; public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
/** /**
* Well-known name for the HandlerMapping object in the bean factory for this namespace. * Well-known name for the HandlerMapping object in the bean factory for this namespace. Only used when
* Only used when "detectAllHandlerMappings" is turned off. * "detectAllHandlerMappings" is turned off.
*
* @see #setDetectAllHandlerMappings * @see #setDetectAllHandlerMappings
*/ */
public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping"; public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
/** /**
* Well-known name for the HandlerAdapter object in the bean factory for this namespace. * Well-known name for the HandlerAdapter object in the bean factory for this namespace. Only used when
* Only used when "detectAllHandlerAdapters" is turned off. * "detectAllHandlerAdapters" is turned off.
*
* @see #setDetectAllHandlerAdapters * @see #setDetectAllHandlerAdapters
*/ */
public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter"; public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
/** /**
* Well-known name for the HandlerExceptionResolver object in the bean factory for this * Well-known name for the HandlerExceptionResolver object in the bean factory for this namespace. Only used when
* namespace. Only used when "detectAllHandlerExceptionResolvers" is turned off. * "detectAllHandlerExceptionResolvers" is turned off.
*
* @see #setDetectAllHandlerExceptionResolvers * @see #setDetectAllHandlerExceptionResolvers
*/ */
public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver"; public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
/** /** Well-known name for the RequestToViewNameTranslator object in the bean factory for this namespace. */
* Well-known name for the RequestToViewNameTranslator object in the bean factory for
* this namespace.
*/
public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator"; public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
/** /**
* Well-known name for the ViewResolver object in the bean factory for this namespace. * Well-known name for the ViewResolver object in the bean factory for this namespace. Only used when
* Only used when "detectAllViewResolvers" is turned off. * "detectAllViewResolvers" is turned off.
*
* @see #setDetectAllViewResolvers * @see #setDetectAllViewResolvers
*/ */
public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver"; public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
/** /** Request attribute to hold the currently chosen HandlerExecutionChain. Only used for internal optimizations. */
* Request attribute to hold the currently chosen HandlerExecutionChain.
* Only used for internal optimizations.
*/
public static final String HANDLER_EXECUTION_CHAIN_ATTRIBUTE = DispatcherServlet.class.getName() + ".HANDLER"; public static final String HANDLER_EXECUTION_CHAIN_ATTRIBUTE = DispatcherServlet.class.getName() + ".HANDLER";
/** /**
* Request attribute to hold the current web application context. * Request attribute to hold the current web application context. Otherwise only the global web app context is
* Otherwise only the global web app context is obtainable by tags etc. * obtainable by tags etc.
*
* @see org.springframework.web.servlet.support.RequestContextUtils#getWebApplicationContext * @see org.springframework.web.servlet.support.RequestContextUtils#getWebApplicationContext
*/ */
public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT"; public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT";
/** /**
* Request attribute to hold the current LocaleResolver, retrievable by views. * Request attribute to hold the current LocaleResolver, retrievable by views.
*
* @see org.springframework.web.servlet.support.RequestContextUtils#getLocaleResolver * @see org.springframework.web.servlet.support.RequestContextUtils#getLocaleResolver
*/ */
public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER"; public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER";
/** /**
* Request attribute to hold the current ThemeResolver, retrievable by views. * Request attribute to hold the current ThemeResolver, retrievable by views.
*
* @see org.springframework.web.servlet.support.RequestContextUtils#getThemeResolver * @see org.springframework.web.servlet.support.RequestContextUtils#getThemeResolver
*/ */
public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER"; public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER";
/** /**
* Request attribute to hold the current ThemeSource, retrievable by views. * Request attribute to hold the current ThemeSource, retrievable by views.
*
* @see org.springframework.web.servlet.support.RequestContextUtils#getThemeSource * @see org.springframework.web.servlet.support.RequestContextUtils#getThemeSource
*/ */
public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE"; public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";
/** Log category to use when no mapped handler is found for a request. */
/**
* Log category to use when no mapped handler is found for a request.
*/
public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound"; public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
/** /**
* Name of the class path resource (relative to the DispatcherServlet class) * Name of the class path resource (relative to the DispatcherServlet class) that defines DispatcherServlet's default
* that defines DispatcherServlet's default strategy names. * strategy names.
*/ */
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties"; private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
/** Additional logger to use when no mapped handler is found for a request. */
/**
* Additional logger to use when no mapped handler is found for a request.
*/
protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY); protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
private static final Properties defaultStrategies; private static final Properties defaultStrategies;
@ -262,7 +227,6 @@ public class DispatcherServlet extends FrameworkServlet {
} }
} }
/** Detect all HandlerMappings or just expect "handlerMapping" bean? */ /** Detect all HandlerMappings or just expect "handlerMapping" bean? */
private boolean detectAllHandlerMappings = true; private boolean detectAllHandlerMappings = true;
@ -278,7 +242,6 @@ public class DispatcherServlet extends FrameworkServlet {
/** Perform cleanup of request attributes after include request? */ /** Perform cleanup of request attributes after include request? */
private boolean cleanupAfterInclude = true; private boolean cleanupAfterInclude = true;
/** MultipartResolver used by this servlet */ /** MultipartResolver used by this servlet */
private MultipartResolver multipartResolver; private MultipartResolver multipartResolver;
@ -303,80 +266,65 @@ public class DispatcherServlet extends FrameworkServlet {
/** List of ViewResolvers used by this servlet */ /** List of ViewResolvers used by this servlet */
private List<ViewResolver> viewResolvers; private List<ViewResolver> viewResolvers;
/** /**
* Set whether to detect all HandlerMapping beans in this servlet's context. * Set whether to detect all HandlerMapping beans in this servlet's context. Else, just a single bean with name
* Else, just a single bean with name "handlerMapping" will be expected. * "handlerMapping" will be expected. <p>Default is "true". Turn this off if you want this servlet to use a single
* <p>Default is "true". Turn this off if you want this servlet to use a * HandlerMapping, despite multiple HandlerMapping beans being defined in the context.
* single HandlerMapping, despite multiple HandlerMapping beans being
* defined in the context.
*/ */
public void setDetectAllHandlerMappings(boolean detectAllHandlerMappings) { public void setDetectAllHandlerMappings(boolean detectAllHandlerMappings) {
this.detectAllHandlerMappings = detectAllHandlerMappings; this.detectAllHandlerMappings = detectAllHandlerMappings;
} }
/** /**
* Set whether to detect all HandlerAdapter beans in this servlet's context. * Set whether to detect all HandlerAdapter beans in this servlet's context. Else, just a single bean with name
* Else, just a single bean with name "handlerAdapter" will be expected. * "handlerAdapter" will be expected. <p>Default is "true". Turn this off if you want this servlet to use a single
* <p>Default is "true". Turn this off if you want this servlet to use a * HandlerAdapter, despite multiple HandlerAdapter beans being defined in the context.
* single HandlerAdapter, despite multiple HandlerAdapter beans being
* defined in the context.
*/ */
public void setDetectAllHandlerAdapters(boolean detectAllHandlerAdapters) { public void setDetectAllHandlerAdapters(boolean detectAllHandlerAdapters) {
this.detectAllHandlerAdapters = detectAllHandlerAdapters; this.detectAllHandlerAdapters = detectAllHandlerAdapters;
} }
/** /**
* Set whether to detect all HandlerExceptionResolver beans in this servlet's context. * Set whether to detect all HandlerExceptionResolver beans in this servlet's context. Else, just a single bean with
* Else, just a single bean with name "handlerExceptionResolver" will be expected. * name "handlerExceptionResolver" will be expected. <p>Default is "true". Turn this off if you want this servlet to
* <p>Default is "true". Turn this off if you want this servlet to use a * use a single HandlerExceptionResolver, despite multiple HandlerExceptionResolver beans being defined in the
* single HandlerExceptionResolver, despite multiple HandlerExceptionResolver * context.
* beans being defined in the context.
*/ */
public void setDetectAllHandlerExceptionResolvers(boolean detectAllHandlerExceptionResolvers) { public void setDetectAllHandlerExceptionResolvers(boolean detectAllHandlerExceptionResolvers) {
this.detectAllHandlerExceptionResolvers = detectAllHandlerExceptionResolvers; this.detectAllHandlerExceptionResolvers = detectAllHandlerExceptionResolvers;
} }
/** /**
* Set whether to detect all ViewResolver beans in this servlet's context. * Set whether to detect all ViewResolver beans in this servlet's context. Else, just a single bean with name
* Else, just a single bean with name "viewResolver" will be expected. * "viewResolver" will be expected. <p>Default is "true". Turn this off if you want this servlet to use a single
* <p>Default is "true". Turn this off if you want this servlet to use a * ViewResolver, despite multiple ViewResolver beans being defined in the context.
* single ViewResolver, despite multiple ViewResolver beans being
* defined in the context.
*/ */
public void setDetectAllViewResolvers(boolean detectAllViewResolvers) { public void setDetectAllViewResolvers(boolean detectAllViewResolvers) {
this.detectAllViewResolvers = detectAllViewResolvers; this.detectAllViewResolvers = detectAllViewResolvers;
} }
/** /**
* Set whether to perform cleanup of request attributes after an include request, * Set whether to perform cleanup of request attributes after an include request, that is, whether to reset the
* that is, whether to reset the original state of all request attributes after * original state of all request attributes after the DispatcherServlet has processed within an include request. Else,
* the DispatcherServlet has processed within an include request. Else, just the * just the DispatcherServlet's own request attributes will be reset, but not model attributes for JSPs or special
* DispatcherServlet's own request attributes will be reset, but not model * attributes set by views (for example, JSTL's). <p>Default is "true", which is strongly recommended. Views should not
* attributes for JSPs or special attributes set by views (for example, JSTL's). * rely on request attributes having been set by (dynamic) includes. This allows JSP views rendered by an included
* <p>Default is "true", which is strongly recommended. Views should not rely on * controller to use any model attributes, even with the same names as in the main JSP, without causing side effects.
* request attributes having been set by (dynamic) includes. This allows JSP views * Only turn this off for special needs, for example to deliberately allow main JSPs to access attributes from JSP
* rendered by an included controller to use any model attributes, even with the * views rendered by an included controller.
* same names as in the main JSP, without causing side effects. Only turn this
* off for special needs, for example to deliberately allow main JSPs to access
* attributes from JSP views rendered by an included controller.
*/ */
public void setCleanupAfterInclude(boolean cleanupAfterInclude) { public void setCleanupAfterInclude(boolean cleanupAfterInclude) {
this.cleanupAfterInclude = cleanupAfterInclude; this.cleanupAfterInclude = cleanupAfterInclude;
} }
/** This implementation calls {@link #initStrategies}. */
/**
* This implementation calls {@link #initStrategies}.
*/
@Override @Override
protected void onRefresh(ApplicationContext context) throws BeansException { protected void onRefresh(ApplicationContext context) throws BeansException {
initStrategies(context); initStrategies(context);
} }
/** /**
* Initialize the strategy objects that this servlet uses. * Initialize the strategy objects that this servlet uses. <p>May be overridden in subclasses in order to initialize
* <p>May be overridden in subclasses in order to initialize
* further strategy objects. * further strategy objects.
*/ */
protected void initStrategies(ApplicationContext context) { protected void initStrategies(ApplicationContext context) {
@ -391,8 +339,7 @@ public class DispatcherServlet extends FrameworkServlet {
} }
/** /**
* Initialize the MultipartResolver used by this class. * Initialize the MultipartResolver used by this class. <p>If no bean is defined with the given name in the BeanFactory
* <p>If no bean is defined with the given name in the BeanFactory
* for this namespace, no multipart handling is provided. * for this namespace, no multipart handling is provided.
*/ */
private void initMultipartResolver(ApplicationContext context) { private void initMultipartResolver(ApplicationContext context) {
@ -406,15 +353,14 @@ public class DispatcherServlet extends FrameworkServlet {
// Default is no multipart resolver. // Default is no multipart resolver.
this.multipartResolver = null; this.multipartResolver = null;
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME + logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
"': no multipart request handling provided"); "': no multipart request handling provided");
} }
} }
} }
/** /**
* Initialize the LocaleResolver used by this class. * Initialize the LocaleResolver used by this class. <p>If no bean is defined with the given name in the BeanFactory
* <p>If no bean is defined with the given name in the BeanFactory
* for this namespace, we default to AcceptHeaderLocaleResolver. * for this namespace, we default to AcceptHeaderLocaleResolver.
*/ */
private void initLocaleResolver(ApplicationContext context) { private void initLocaleResolver(ApplicationContext context) {
@ -435,9 +381,8 @@ public class DispatcherServlet extends FrameworkServlet {
} }
/** /**
* Initialize the ThemeResolver used by this class. * Initialize the ThemeResolver used by this class. <p>If no bean is defined with the given name in the BeanFactory for
* <p>If no bean is defined with the given name in the BeanFactory * this namespace, we default to a FixedThemeResolver.
* for this namespace, we default to a FixedThemeResolver.
*/ */
private void initThemeResolver(ApplicationContext context) { private void initThemeResolver(ApplicationContext context) {
try { try {
@ -450,24 +395,24 @@ public class DispatcherServlet extends FrameworkServlet {
// We need to use the default. // We need to use the default.
this.themeResolver = getDefaultStrategy(context, ThemeResolver.class); this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Unable to locate ThemeResolver with name '" + THEME_RESOLVER_BEAN_NAME + logger.debug(
"': using default [" + this.themeResolver + "]"); "Unable to locate ThemeResolver with name '" + THEME_RESOLVER_BEAN_NAME + "': using default [" +
this.themeResolver + "]");
} }
} }
} }
/** /**
* Initialize the HandlerMappings used by this class. * Initialize the HandlerMappings used by this class. <p>If no HandlerMapping beans are defined in the BeanFactory for
* <p>If no HandlerMapping beans are defined in the BeanFactory * this namespace, we default to BeanNameUrlHandlerMapping.
* for this namespace, we default to BeanNameUrlHandlerMapping.
*/ */
private void initHandlerMappings(ApplicationContext context) { private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null; this.handlerMappings = null;
if (this.detectAllHandlerMappings) { if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts. // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors( Map<String, HandlerMapping> matchingBeans =
context, HandlerMapping.class, true, false); BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) { if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values()); this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order. // We keep HandlerMappings in sorted order.
@ -495,17 +440,16 @@ public class DispatcherServlet extends FrameworkServlet {
} }
/** /**
* Initialize the HandlerAdapters used by this class. * Initialize the HandlerAdapters used by this class. <p>If no HandlerAdapter beans are defined in the BeanFactory for
* <p>If no HandlerAdapter beans are defined in the BeanFactory * this namespace, we default to SimpleControllerHandlerAdapter.
* for this namespace, we default to SimpleControllerHandlerAdapter.
*/ */
private void initHandlerAdapters(ApplicationContext context) { private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null; this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) { if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts. // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors( Map<String, HandlerAdapter> matchingBeans =
context, HandlerAdapter.class, true, false); BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) { if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values()); this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
// We keep HandlerAdapters in sorted order. // We keep HandlerAdapters in sorted order.
@ -533,17 +477,16 @@ public class DispatcherServlet extends FrameworkServlet {
} }
/** /**
* Initialize the HandlerExceptionResolver used by this class. * Initialize the HandlerExceptionResolver used by this class. <p>If no bean is defined with the given name in the
* <p>If no bean is defined with the given name in the BeanFactory * BeanFactory for this namespace, we default to no exception resolver.
* for this namespace, we default to no exception resolver.
*/ */
private void initHandlerExceptionResolvers(ApplicationContext context) { private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null; this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) { if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts. // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors( Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
context, HandlerExceptionResolver.class, true, false); .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) { if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values()); this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order. // We keep HandlerExceptionResolvers in sorted order.
@ -552,8 +495,8 @@ public class DispatcherServlet extends FrameworkServlet {
} }
else { else {
try { try {
HandlerExceptionResolver her = context.getBean( HandlerExceptionResolver her =
HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class); context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
this.handlerExceptionResolvers = Collections.singletonList(her); this.handlerExceptionResolvers = Collections.singletonList(her);
} }
catch (NoSuchBeanDefinitionException ex) { catch (NoSuchBeanDefinitionException ex) {
@ -561,8 +504,8 @@ public class DispatcherServlet extends FrameworkServlet {
} }
} }
// Just for consistency, check for default HandlerExceptionResolvers... // Ensure we have at least some HandlerExceptionResolvers, by registering
// There aren't any in usual scenarios. // default HandlerExceptionResolvers if no other resolvers are found.
if (this.handlerExceptionResolvers == null) { if (this.handlerExceptionResolvers == null) {
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class); this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
@ -572,13 +515,13 @@ public class DispatcherServlet extends FrameworkServlet {
} }
/** /**
* Initialize the RequestToViewNameTranslator used by this servlet instance. If no * Initialize the RequestToViewNameTranslator used by this servlet instance. If no implementation is configured then we
* implementation is configured then we default to DefaultRequestToViewNameTranslator. * default to DefaultRequestToViewNameTranslator.
*/ */
private void initRequestToViewNameTranslator(ApplicationContext context) { private void initRequestToViewNameTranslator(ApplicationContext context) {
try { try {
this.viewNameTranslator = context.getBean( this.viewNameTranslator =
REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class); context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Using RequestToViewNameTranslator [" + this.viewNameTranslator + "]"); logger.debug("Using RequestToViewNameTranslator [" + this.viewNameTranslator + "]");
} }
@ -588,24 +531,23 @@ public class DispatcherServlet extends FrameworkServlet {
this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class); this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Unable to locate RequestToViewNameTranslator with name '" + logger.debug("Unable to locate RequestToViewNameTranslator with name '" +
REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME + REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME + "': using default [" + this.viewNameTranslator +
"': using default [" + this.viewNameTranslator + "]"); "]");
} }
} }
} }
/** /**
* Initialize the ViewResolvers used by this class. * Initialize the ViewResolvers used by this class. <p>If no ViewResolver beans are defined in the BeanFactory for this
* <p>If no ViewResolver beans are defined in the BeanFactory * namespace, we default to InternalResourceViewResolver.
* for this namespace, we default to InternalResourceViewResolver.
*/ */
private void initViewResolvers(ApplicationContext context) { private void initViewResolvers(ApplicationContext context) {
this.viewResolvers = null; this.viewResolvers = null;
if (this.detectAllViewResolvers) { if (this.detectAllViewResolvers) {
// Find all ViewResolvers in the ApplicationContext, including ancestor contexts. // Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
Map<String, ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors( Map<String, ViewResolver> matchingBeans =
context, ViewResolver.class, true, false); BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
if (!matchingBeans.isEmpty()) { if (!matchingBeans.isEmpty()) {
this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values()); this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
// We keep ViewResolvers in sorted order. // We keep ViewResolvers in sorted order.
@ -633,9 +575,9 @@ public class DispatcherServlet extends FrameworkServlet {
} }
/** /**
* Return this servlet's ThemeSource, if any; else return <code>null</code>. * Return this servlet's ThemeSource, if any; else return <code>null</code>. <p>Default is to return the
* <p>Default is to return the WebApplicationContext as ThemeSource, * WebApplicationContext as ThemeSource, provided that it implements the ThemeSource interface.
* provided that it implements the ThemeSource interface. *
* @return the ThemeSource, if any * @return the ThemeSource, if any
* @see #getWebApplicationContext() * @see #getWebApplicationContext()
*/ */
@ -650,18 +592,18 @@ public class DispatcherServlet extends FrameworkServlet {
/** /**
* Obtain this servlet's MultipartResolver, if any. * Obtain this servlet's MultipartResolver, if any.
* @return the MultipartResolver used by this servlet, or <code>null</code> *
* if none (indicating that no multipart support is available) * @return the MultipartResolver used by this servlet, or <code>null</code> if none (indicating that no multipart
* support is available)
*/ */
public final MultipartResolver getMultipartResolver() { public final MultipartResolver getMultipartResolver() {
return this.multipartResolver; return this.multipartResolver;
} }
/** /**
* Return the default strategy object for the given strategy interface. * Return the default strategy object for the given strategy interface. <p>The default implementation delegates to
* <p>The default implementation delegates to {@link #getDefaultStrategies}, * {@link #getDefaultStrategies}, expecting a single object in the list.
* expecting a single object in the list. *
* @param context the current WebApplicationContext * @param context the current WebApplicationContext
* @param strategyInterface the strategy interface * @param strategyInterface the strategy interface
* @return the corresponding strategy object * @return the corresponding strategy object
@ -677,10 +619,10 @@ public class DispatcherServlet extends FrameworkServlet {
} }
/** /**
* Create a List of default strategy objects for the given strategy interface. * Create a List of default strategy objects for the given strategy interface. <p>The default implementation uses the
* <p>The default implementation uses the "DispatcherServlet.properties" file * "DispatcherServlet.properties" file (in the same package as the DispatcherServlet class) to determine the class
* (in the same package as the DispatcherServlet class) to determine the class names. * names. It instantiates the strategy objects through the context's BeanFactory.
* It instantiates the strategy objects through the context's BeanFactory. *
* @param context the current WebApplicationContext * @param context the current WebApplicationContext
* @param strategyInterface the strategy interface * @param strategyInterface the strategy interface
* @return the List of corresponding strategy objects * @return the List of corresponding strategy objects
@ -717,13 +659,12 @@ public class DispatcherServlet extends FrameworkServlet {
} }
/** /**
* Create a default strategy. * Create a default strategy. <p>The default implementation uses {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean}.
* <p>The default implementation uses *
* {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean}.
* @param context the current WebApplicationContext * @param context the current WebApplicationContext
* @param clazz the strategy implementation class to instantiate * @param clazz the strategy implementation class to instantiate
* @throws BeansException if initialization failed
* @return the fully configured strategy instance * @return the fully configured strategy instance
* @throws BeansException if initialization failed
* @see org.springframework.context.ApplicationContext#getAutowireCapableBeanFactory() * @see org.springframework.context.ApplicationContext#getAutowireCapableBeanFactory()
* @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean
*/ */
@ -731,17 +672,16 @@ public class DispatcherServlet extends FrameworkServlet {
return context.getAutowireCapableBeanFactory().createBean(clazz); return context.getAutowireCapableBeanFactory().createBean(clazz);
} }
/** /**
* Exposes the DispatcherServlet-specific request attributes and * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch} for the actual
* delegates to {@link #doDispatch} for the actual dispatching. * dispatching.
*/ */
@Override @Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
String requestUri = new UrlPathHelper().getRequestUri(request); String requestUri = new UrlPathHelper().getRequestUri(request);
logger.debug("DispatcherServlet with name '" + getServletName() + logger.debug("DispatcherServlet with name '" + getServletName() + "' processing " + request.getMethod() +
"' processing " + request.getMethod() + " request for [" + requestUri + "]"); " request for [" + requestUri + "]");
} }
// Keep a snapshot of the request attributes in case of an include, // Keep a snapshot of the request attributes in case of an include,
@ -777,12 +717,11 @@ public class DispatcherServlet extends FrameworkServlet {
} }
/** /**
* Process the actual dispatching to the handler. * Process the actual dispatching to the handler. <p>The handler will be obtained by applying the servlet's
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order. * HandlerMappings in order. The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters to
* The HandlerAdapter will be obtained by querying the servlet's installed * find the first that supports the handler class. <p>All HTTP methods are handled by this method. It's up to
* HandlerAdapters to find the first that supports the handler class. * HandlerAdapters or handlers themselves to decide which methods are acceptable.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or *
* handlers themselves to decide which methods are acceptable.
* @param request current HTTP request * @param request current HTTP request
* @param response current HTTP response * @param response current HTTP response
* @throws Exception in case of any kind of processing failure * @throws Exception in case of any kind of processing failure
@ -855,8 +794,8 @@ public class DispatcherServlet extends FrameworkServlet {
} }
else { else {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
getServletName() + "': assuming HandlerAdapter completed request handling"); "': assuming HandlerAdapter completed request handling");
} }
} }
@ -885,15 +824,16 @@ public class DispatcherServlet extends FrameworkServlet {
} }
/** /**
* Override HttpServlet's <code>getLastModified</code> method to evaluate * Override HttpServlet's <code>getLastModified</code> method to evaluate the Last-Modified value of the mapped
* the Last-Modified value of the mapped handler. * handler.
*/ */
@Override @Override
protected long getLastModified(HttpServletRequest request) { protected long getLastModified(HttpServletRequest request) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
String requestUri = new UrlPathHelper().getRequestUri(request); String requestUri = new UrlPathHelper().getRequestUri(request);
logger.debug("DispatcherServlet with name '" + getServletName() + logger.debug(
"' determining Last-Modified value for [" + requestUri + "]"); "DispatcherServlet with name '" + getServletName() + "' determining Last-Modified value for [" +
requestUri + "]");
} }
try { try {
HandlerExecutionChain mappedHandler = getHandler(request, true); HandlerExecutionChain mappedHandler = getHandler(request, true);
@ -918,12 +858,11 @@ public class DispatcherServlet extends FrameworkServlet {
} }
} }
/** /**
* Build a LocaleContext for the given request, exposing the request's * Build a LocaleContext for the given request, exposing the request's primary locale as current locale. <p>The default
* primary locale as current locale. * implementation uses the dispatcher's LocaleResolver to obtain the current locale, which might change during a
* <p>The default implementation uses the dispatcher's LocaleResolver * request.
* to obtain the current locale, which might change during a request. *
* @param request current HTTP request * @param request current HTTP request
* @return the corresponding LocaleContext * @return the corresponding LocaleContext
*/ */
@ -933,6 +872,7 @@ public class DispatcherServlet extends FrameworkServlet {
public Locale getLocale() { public Locale getLocale() {
return localeResolver.resolveLocale(request); return localeResolver.resolveLocale(request);
} }
@Override @Override
public String toString() { public String toString() {
return getLocale().toString(); return getLocale().toString();
@ -941,8 +881,9 @@ public class DispatcherServlet extends FrameworkServlet {
} }
/** /**
* Convert the request into a multipart request, and make multipart resolver available. * Convert the request into a multipart request, and make multipart resolver available. If no multipart resolver is
* If no multipart resolver is set, simply use the existing request. * set, simply use the existing request.
*
* @param request current HTTP request * @param request current HTTP request
* @return the processed request (multipart wrapper if necessary) * @return the processed request (multipart wrapper if necessary)
* @see MultipartResolver#resolveMultipart * @see MultipartResolver#resolveMultipart
@ -963,6 +904,7 @@ public class DispatcherServlet extends FrameworkServlet {
/** /**
* Clean up any resources used by the given multipart request (if any). * Clean up any resources used by the given multipart request (if any).
*
* @param request current HTTP request * @param request current HTTP request
* @see MultipartResolver#cleanupMultipart * @see MultipartResolver#cleanupMultipart
*/ */
@ -973,15 +915,14 @@ public class DispatcherServlet extends FrameworkServlet {
} }
/** /**
* Return the HandlerExecutionChain for this request. * Return the HandlerExecutionChain for this request. Try all handler mappings in order.
* Try all handler mappings in order. *
* @param request current HTTP request * @param request current HTTP request
* @param cache whether to cache the HandlerExecutionChain in a request attribute * @param cache whether to cache the HandlerExecutionChain in a request attribute
* @return the HandlerExceutionChain, or <code>null</code> if no handler could be found * @return the HandlerExceutionChain, or <code>null</code> if no handler could be found
*/ */
protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception { protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
HandlerExecutionChain handler = HandlerExecutionChain handler = (HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
(HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
if (handler != null) { if (handler != null) {
if (!cache) { if (!cache) {
request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE); request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
@ -1007,6 +948,7 @@ public class DispatcherServlet extends FrameworkServlet {
/** /**
* No handler found -> set appropriate HTTP response status. * No handler found -> set appropriate HTTP response status.
*
* @param request current HTTP request * @param request current HTTP request
* @param response current HTTP response * @param response current HTTP response
* @throws Exception if preparing the response failed * @throws Exception if preparing the response failed
@ -1014,17 +956,17 @@ public class DispatcherServlet extends FrameworkServlet {
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception { protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) { if (pageNotFoundLogger.isWarnEnabled()) {
String requestUri = new UrlPathHelper().getRequestUri(request); String requestUri = new UrlPathHelper().getRequestUri(request);
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + requestUri +
requestUri + "] in DispatcherServlet with name '" + getServletName() + "'"); "] in DispatcherServlet with name '" + getServletName() + "'");
} }
response.sendError(HttpServletResponse.SC_NOT_FOUND); response.sendError(HttpServletResponse.SC_NOT_FOUND);
} }
/** /**
* Return the HandlerAdapter for this handler object. * Return the HandlerAdapter for this handler object.
*
* @param handler the handler object to find an adapter for * @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
* This is a fatal error.
*/ */
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) { for (HandlerAdapter ha : this.handlerAdapters) {
@ -1041,17 +983,19 @@ public class DispatcherServlet extends FrameworkServlet {
/** /**
* Determine an error ModelAndView via the registered HandlerExceptionResolvers. * Determine an error ModelAndView via the registered HandlerExceptionResolvers.
*
* @param request current HTTP request * @param request current HTTP request
* @param response current HTTP response * @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of * @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* the exception (for example, if multipart resolution failed) * if multipart resolution failed)
* @param ex the exception that got thrown during handler execution * @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to * @return a corresponding ModelAndView to forward to
* @throws Exception if no error ModelAndView found * @throws Exception if no error ModelAndView found
*/ */
protected ModelAndView processHandlerException( protected ModelAndView processHandlerException(HttpServletRequest request,
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) HttpServletResponse response,
throws Exception { Object handler,
Exception ex) throws Exception {
// Check registerer HandlerExceptionResolvers... // Check registerer HandlerExceptionResolvers...
ModelAndView exMv = null; ModelAndView exMv = null;
@ -1066,35 +1010,26 @@ public class DispatcherServlet extends FrameworkServlet {
return null; return null;
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex); logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv,
ex);
} }
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv; return exMv;
} }
// Send default responses for well-known exceptions, if possible.
if (ex instanceof HttpRequestMethodNotSupportedException && !response.isCommitted()) {
String[] supportedMethods = ((HttpRequestMethodNotSupportedException) ex).getSupportedMethods();
if (supportedMethods != null) {
response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
}
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
return null;
}
throw ex; throw ex;
} }
/** /**
* Render the given ModelAndView. This is the last stage in handling a request. * Render the given ModelAndView. This is the last stage in handling a request. It may involve resolving the view by
* It may involve resolving the view by name. * name.
*
* @param mv the ModelAndView to render * @param mv the ModelAndView to render
* @param request current HTTP servlet request * @param request current HTTP servlet request
* @param response current HTTP servlet response * @param response current HTTP servlet response
* @throws Exception if there's a problem rendering the view * @throws Exception if there's a problem rendering the view
*/ */
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
throws Exception {
// Determine locale for request and apply it to the response. // Determine locale for request and apply it to the response.
Locale locale = this.localeResolver.resolveLocale(request); Locale locale = this.localeResolver.resolveLocale(request);
@ -1106,8 +1041,9 @@ public class DispatcherServlet extends FrameworkServlet {
// We need to resolve the view name. // We need to resolve the view name.
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) { if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() + throw new ServletException(
"' in servlet with name '" + getServletName() + "'"); "Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" +
getServletName() + "'");
} }
} }
else { else {
@ -1128,6 +1064,7 @@ public class DispatcherServlet extends FrameworkServlet {
/** /**
* Translate the supplied request into a default view name. * Translate the supplied request into a default view name.
*
* @param request current HTTP servlet request * @param request current HTTP servlet request
* @return the view name (or <code>null</code> if no default found) * @return the view name (or <code>null</code> if no default found)
* @throws Exception if view name translation failed * @throws Exception if view name translation failed
@ -1137,22 +1074,22 @@ public class DispatcherServlet extends FrameworkServlet {
} }
/** /**
* Resolve the given view name into a View object (to be rendered). * Resolve the given view name into a View object (to be rendered). <p>Default implementations asks all ViewResolvers
* <p>Default implementations asks all ViewResolvers of this dispatcher. * of this dispatcher. Can be overridden for custom resolution strategies, potentially based on specific model
* Can be overridden for custom resolution strategies, potentially based * attributes or request parameters.
* on specific model attributes or request parameters. *
* @param viewName the name of the view to resolve * @param viewName the name of the view to resolve
* @param model the model to be passed to the view * @param model the model to be passed to the view
* @param locale the current locale * @param locale the current locale
* @param request current HTTP servlet request * @param request current HTTP servlet request
* @return the View object, or <code>null</code> if none found * @return the View object, or <code>null</code> if none found
* @throws Exception if the view cannot be resolved * @throws Exception if the view cannot be resolved (typically in case of problems creating an actual View object)
* (typically in case of problems creating an actual View object)
* @see ViewResolver#resolveViewName * @see ViewResolver#resolveViewName
*/ */
protected View resolveViewName( protected View resolveViewName(String viewName,
String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request) Map<String, Object> model,
throws Exception { Locale locale,
HttpServletRequest request) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) { for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale); View view = viewResolver.resolveViewName(viewName, locale);
@ -1164,18 +1101,19 @@ public class DispatcherServlet extends FrameworkServlet {
} }
/** /**
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors. * Trigger afterCompletion callbacks on the mapped HandlerInterceptors. Will just invoke afterCompletion for all
* Will just invoke afterCompletion for all interceptors whose preHandle * interceptors whose preHandle invocation has successfully completed and returned true.
* invocation has successfully completed and returned true. *
* @param mappedHandler the mapped HandlerExecutionChain * @param mappedHandler the mapped HandlerExecutionChain
* @param interceptorIndex index of last interceptor that successfully completed * @param interceptorIndex index of last interceptor that successfully completed
* @param ex Exception thrown on handler execution, or <code>null</code> if none * @param ex Exception thrown on handler execution, or <code>null</code> if none
* @see HandlerInterceptor#afterCompletion * @see HandlerInterceptor#afterCompletion
*/ */
private void triggerAfterCompletion( private void triggerAfterCompletion(HandlerExecutionChain mappedHandler,
HandlerExecutionChain mappedHandler, int interceptorIndex, int interceptorIndex,
HttpServletRequest request, HttpServletResponse response, Exception ex) HttpServletRequest request,
throws Exception { HttpServletResponse response,
Exception ex) throws Exception {
// Apply afterCompletion methods of registered interceptors. // Apply afterCompletion methods of registered interceptors.
if (mappedHandler != null) { if (mappedHandler != null) {
@ -1196,9 +1134,9 @@ public class DispatcherServlet extends FrameworkServlet {
/** /**
* Restore the request attributes after an include. * Restore the request attributes after an include.
*
* @param request current HTTP request * @param request current HTTP request
* @param attributesSnapshot the snapshot of the request attributes * @param attributesSnapshot the snapshot of the request attributes before the include
* before the include
*/ */
private void restoreAttributesAfterInclude(HttpServletRequest request, Map attributesSnapshot) { private void restoreAttributesAfterInclude(HttpServletRequest request, Map attributesSnapshot) {
logger.debug("Restoring snapshot of request attributes after include"); logger.debug("Restoring snapshot of request attributes after include");

View File

@ -13,6 +13,8 @@ org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.m
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.handler.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

View File

@ -0,0 +1,190 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
/**
* Abstract base class for {@link HandlerExceptionResolver} implementations. <p>Provides a set of mapped handlers that
* the resolver should map to, and the {@link Ordered} implementation.
*
* @author Arjen Poutsma
* @since 3.0
*/
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private int order = Ordered.LOWEST_PRECEDENCE;
private Set mappedHandlers;
private Class[] mappedHandlerClasses;
private Log warnLogger;
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return this.order;
}
/**
* Specify the set of handlers that this exception resolver should apply to. The exception mappings and the default
* error view will only apply to the specified handlers. <p>If no handlers and handler classes are set, the exception
* mappings and the default error view will apply to all handlers. This means that a specified default error view will
* be used as fallback for all exceptions; any further HandlerExceptionResolvers in the chain will be ignored in this
* case.
*/
public void setMappedHandlers(Set mappedHandlers) {
this.mappedHandlers = mappedHandlers;
}
/**
* Specify the set of classes that this exception resolver should apply to. The exception mappings and the default
* error view will only apply to handlers of the specified type; the specified types may be interfaces and superclasses
* of handlers as well. <p>If no handlers and handler classes are set, the exception mappings and the default error
* view will apply to all handlers. This means that a specified default error view will be used as fallback for all
* exceptions; any further HandlerExceptionResolvers in the chain will be ignored in this case.
*/
public void setMappedHandlerClasses(Class[] mappedHandlerClasses) {
this.mappedHandlerClasses = mappedHandlerClasses;
}
/**
* Set the log category for warn logging. The name will be passed to the underlying logger implementation through
* Commons Logging, getting interpreted as log category according to the logger's configuration. <p>Default is no warn
* logging. Specify this setting to activate warn logging into a specific category. Alternatively, override the {@link
* #logException} method for custom logging.
*
* @see org.apache.commons.logging.LogFactory#getLog(String)
* @see org.apache.log4j.Logger#getLogger(String)
* @see java.util.logging.Logger#getLogger(String)
*/
public void setWarnLogCategory(String loggerName) {
this.warnLogger = LogFactory.getLog(loggerName);
}
/**
* Checks whether this resolver is supposed to apply (i.e. the handler matches in case of "mappedHandlers" having been
* specified), then delegates to the {@link #doResolveException} template method.
*/
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
if (shouldApplyTo(request, handler)) {
// Log exception, both at debug log level and at warn level, if desired.
if (logger.isDebugEnabled()) {
logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
}
logException(ex, request);
return doResolveException(request, response, handler, ex);
}
else {
return null;
}
}
/**
* Check whether this resolver is supposed to apply to the given handler. <p>The default implementation checks against
* the specified mapped handlers and handler classes, if any.
*
* @param request current HTTP request
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return whether this resolved should proceed with resolving the exception for the given request and handler
* @see #setMappedHandlers
* @see #setMappedHandlerClasses
*/
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
if (handler != null) {
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
return true;
}
if (this.mappedHandlerClasses != null) {
for (Class handlerClass : this.mappedHandlerClasses) {
if (handlerClass.isInstance(handler)) {
return true;
}
}
}
}
// Else only apply if there are no explicit handler mappings.
return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
}
/**
* Log the given exception at warn level, provided that warn logging has been activated through the {@link
* #setWarnLogCategory "warnLogCategory"} property. <p>Calls {@link #buildLogMessage} in order to determine the
* concrete message to log. Always passes the full exception to the logger.
*
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @see #setWarnLogCategory
* @see #buildLogMessage
* @see org.apache.commons.logging.Log#warn(Object, Throwable)
*/
protected void logException(Exception ex, HttpServletRequest request) {
if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) {
this.warnLogger.warn(buildLogMessage(ex, request), ex);
}
}
/**
* Build a log message for the given exception, occured during processing the given request.
*
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @return the log message to use
*/
protected String buildLogMessage(Exception ex, HttpServletRequest request) {
return "Handler execution resulted in exception";
}
/**
* Actually resolve the given exception that got thrown during on handler execution, returning a ModelAndView that
* represents a specific error page if appropriate. <p>May be overridden in subclasses, in order to apply specific
* exception checks. Note that this template method will be invoked <i>after</i> checking whether this resolved applies
* ("mappedHandlers" etc), so an implementation may simply proceed with its actual exception handling.
*
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to, or <code>null</code> for default processing
*/
protected abstract ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex);
}

View File

@ -0,0 +1,280 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.core.Ordered;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
/**
* Default implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver
* HandlerExceptionResolver} interface that resolves standard Spring exceptions. <p>Default implementations typically
* set the response status.
*
* @author Arjen Poutsma
* @see #handleNoSuchRequestHandlingMethod
* @see #handleHttpRequestMethodNotSupported
* @see #handleHttpMediaTypeNotSupported
* @see #handleMissingServletRequestParameter
* @see #handleTypeMismatch
* @see #handleHttpMessageNotReadable
* @see #handleHttpMessageNotWritable
* @since 3.0
*/
public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
/**
* Log category to use when no mapped handler is found for a request.
*
* @see #pageNotFoundLogger
*/
public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
/**
* Additional logger to use when no mapped handler is found for a request.
*
* @see #PAGE_NOT_FOUND_LOG_CATEGORY
*/
protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
/** Sets the {@linkplain #setOrder(int) order} to {@link #LOWEST_PRECEDENCE}. */
public DefaultHandlerExceptionResolver() {
setOrder(Ordered.LOWEST_PRECEDENCE);
}
@Override
protected ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
try {
if (ex instanceof NoSuchRequestHandlingMethodException) {
return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response,
handler);
}
else if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request,
response, handler);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response,
handler);
}
else if (ex instanceof MissingServletRequestParameterException) {
return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, request,
response, handler);
}
else if (ex instanceof TypeMismatchException) {
return handleTypeMismatch((TypeMismatchException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotReadableException) {
return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotWritableException) {
return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, request, response, handler);
}
}
catch (Exception handlerException) {
logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
}
return null;
}
/**
* Handle the case where no request handler method was found. <p>The default implementation logs a warning, sends an
* HTTP 404 error, and returns an empty {@code ModelAndView}. Alternatively, a fallback view could be chosen, or the
* NoSuchRequestHandlingMethodException could be rethrown as-is.
*
* @param ex the NoSuchRequestHandlingMethodException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
pageNotFoundLogger.warn(ex.getMessage());
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return new ModelAndView();
}
/**
* Handle the case where no request handler method was found for the particular HTTP request method. <p>The default
* implementation logs a warning, sends an HTTP 405 error, sets the "Allow" header, and returns an empty {@code
* ModelAndView}. Alternatively, a fallback view could be chosen, or the HttpRequestMethodNotSupportedException could
* be rethrown as-is.
*
* @param ex the HttpRequestMethodNotSupportedException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
pageNotFoundLogger.warn(ex.getMessage());
String[] supportedMethods = ex.getSupportedMethods();
if (supportedMethods != null) {
response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
}
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
return new ModelAndView();
}
/**
* Handle the case where no {@linkplain org.springframework.http.converter.HttpMessageConverter message converters}
* were found for the PUT or POSTed content. <p>The default implementation sends an HTTP 415 error, sets the "Allow"
* header, and returns an empty {@code ModelAndView}. Alternatively, a fallback view could be chosen, or the
* HttpMediaTypeNotSupportedException could be rethrown as-is.
*
* @param ex the HttpMediaTypeNotSupportedException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
List<MediaType> mediaTypes = ex.getSupportedMediaTypes();
if (mediaTypes != null) {
response.setHeader("Accept", MediaType.toString(mediaTypes));
}
return new ModelAndView();
}
/**
* Handle the case when a required parameter is missing. <p>The default implementation sends an HTTP 400 error, and
* returns an empty {@code ModelAndView}. Alternatively, a fallback view could be chosen, or the
* MissingServletRequestParameterException could be rethrown as-is.
*
* @param ex the MissingServletRequestParameterException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleMissingServletRequestParameter(MissingServletRequestParameterException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return new ModelAndView();
}
/**
* Handle the case when a {@link org.springframework.web.bind.WebDataBinder} conversion error occurs. <p>The default
* implementation sends an HTTP 400 error, and returns an empty {@code ModelAndView}. Alternatively, a fallback view
* could be chosen, or the TypeMismatchException could be rethrown as-is.
*
* @param ex the TypeMismatchException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleTypeMismatch(TypeMismatchException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return new ModelAndView();
}
/**
* Handle the case where a {@linkplain org.springframework.http.converter.HttpMessageConverter message converter} can
* not read from a HTTP request. <p>The default implementation sends an HTTP 400 error, and returns an empty {@code
* ModelAndView}. Alternatively, a fallback view could be chosen, or the HttpMediaTypeNotSupportedException could be
* rethrown as-is.
*
* @param ex the HttpMessageNotReadableException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return new ModelAndView();
}
/**
* Handle the case where a {@linkplain org.springframework.http.converter.HttpMessageConverter message converter} can
* not write to a HTTP request. <p>The default implementation sends an HTTP 500 error, and returns an empty {@code
* ModelAndView}. Alternatively, a fallback view could be chosen, or the HttpMediaTypeNotSupportedException could be
* rethrown as-is.
*
* @param ex the HttpMessageNotWritableException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleHttpMessageNotWritable(HttpMessageNotWritableException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return new ModelAndView();
}
}

View File

@ -18,51 +18,29 @@ package org.springframework.web.servlet.handler;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Properties; import java.util.Properties;
import java.util.Set;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.WebUtils; import org.springframework.web.util.WebUtils;
/** /**
* {@link org.springframework.web.servlet.HandlerExceptionResolver} implementation * {@link org.springframework.web.servlet.HandlerExceptionResolver} implementation that allows for mapping exception
* that allows for mapping exception class names to view names, either for a * class names to view names, either for a set of given handlers or for all handlers in the DispatcherServlet.
* set of given handlers or for all handlers in the DispatcherServlet.
* *
* <p>Error views are analogous to error page JSPs, but can be used with any * <p>Error views are analogous to error page JSPs, but can be used with any kind of exception including any checked
* kind of exception including any checked one, with fine-granular mappings for * one, with fine-granular mappings for specific handlers.
* specific handlers.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 22.11.2003 * @author Arjen Poutsma
* @see org.springframework.web.servlet.DispatcherServlet * @see org.springframework.web.servlet.DispatcherServlet
* @since 22.11.2003
*/ */
public class SimpleMappingExceptionResolver implements HandlerExceptionResolver, Ordered { public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionResolver {
/** /** The default name of the exception attribute: "exception". */
* The default name of the exception attribute: "exception".
*/
public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception"; public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private int order = Integer.MAX_VALUE; // default: same as non-Ordered
private Set mappedHandlers;
private Class[] mappedHandlerClasses;
private Log warnLogger;
private Properties exceptionMappings; private Properties exceptionMappings;
private String defaultErrorView; private String defaultErrorView;
@ -71,74 +49,18 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
private String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE; private String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE;
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return this.order;
}
/** /**
* Specify the set of handlers that this exception resolver should apply to. * Set the mappings between exception class names and error view names. The exception class name can be a substring,
* The exception mappings and the default error view will only apply * with no wildcard support at present. A value of "ServletException" would match
* to the specified handlers. * <code>javax.servlet.ServletException</code> and subclasses, for example. <p><b>NB:</b> Consider carefully how
* <p>If no handlers and handler classes are set, the exception mappings * specific the pattern is, and whether to include package information (which isn't mandatory). For example,
* and the default error view will apply to all handlers. This means that * "Exception" will match nearly anything, and will probably hide other rules. "java.lang.Exception" would be correct
* a specified default error view will be used as fallback for all exceptions; * if "Exception" was meant to define a rule for all checked exceptions. With more unusual exception names such as
* any further HandlerExceptionResolvers in the chain will be ignored in * "BaseBusinessException" there's no need to use a FQN. <p>Follows the same matching algorithm as
* this case. * RuleBasedTransactionAttribute and RollbackRuleAttribute.
*/ *
public void setMappedHandlers(Set mappedHandlers) { * @param mappings exception patterns (can also be fully qualified class names) as keys, and error view names as
this.mappedHandlers = mappedHandlers; * values
}
/**
* Specify the set of classes that this exception resolver should apply to.
* The exception mappings and the default error view will only apply
* to handlers of the specified type; the specified types may be interfaces
* and superclasses of handlers as well.
* <p>If no handlers and handler classes are set, the exception mappings
* and the default error view will apply to all handlers. This means that
* a specified default error view will be used as fallback for all exceptions;
* any further HandlerExceptionResolvers in the chain will be ignored in
* this case.
*/
public void setMappedHandlerClasses(Class[] mappedHandlerClasses) {
this.mappedHandlerClasses = mappedHandlerClasses;
}
/**
* Set the log category for warn logging. The name will be passed to the
* underlying logger implementation through Commons Logging, getting
* interpreted as log category according to the logger's configuration.
* <p>Default is no warn logging. Specify this setting to activate
* warn logging into a specific category. Alternatively, override
* the {@link #logException} method for custom logging.
* @see org.apache.commons.logging.LogFactory#getLog(String)
* @see org.apache.log4j.Logger#getLogger(String)
* @see java.util.logging.Logger#getLogger(String)
*/
public void setWarnLogCategory(String loggerName) {
this.warnLogger = LogFactory.getLog(loggerName);
}
/**
* Set the mappings between exception class names and error view names.
* The exception class name can be a substring, with no wildcard support
* at present. A value of "ServletException" would match
* <code>javax.servlet.ServletException</code> and subclasses, for example.
* <p><b>NB:</b> Consider carefully how specific the pattern is, and whether
* to include package information (which isn't mandatory). For example,
* "Exception" will match nearly anything, and will probably hide other rules.
* "java.lang.Exception" would be correct if "Exception" was meant to define
* a rule for all checked exceptions. With more unusual exception names such
* as "BaseBusinessException" there's no need to use a FQN.
* <p>Follows the same matching algorithm as RuleBasedTransactionAttribute
* and RollbackRuleAttribute.
* @param mappings exception patterns (can also be fully qualified class names)
* as keys, and error view names as values
* @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute * @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
* @see org.springframework.transaction.interceptor.RollbackRuleAttribute * @see org.springframework.transaction.interceptor.RollbackRuleAttribute
*/ */
@ -147,24 +69,20 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
} }
/** /**
* Set the name of the default error view. * Set the name of the default error view. This view will be returned if no specific mapping was found. <p>Default is
* This view will be returned if no specific mapping was found. * none.
* <p>Default is none.
*/ */
public void setDefaultErrorView(String defaultErrorView) { public void setDefaultErrorView(String defaultErrorView) {
this.defaultErrorView = defaultErrorView; this.defaultErrorView = defaultErrorView;
} }
/** /**
* Set the default HTTP status code that this exception resolver will apply * Set the default HTTP status code that this exception resolver will apply if it resolves an error view. <p>Note that
* if it resolves an error view. * this error code will only get applied in case of a top-level request. It will not be set for an include request,
* <p>Note that this error code will only get applied in case of a top-level * since the HTTP status cannot be modified from within an include. <p>If not specified, no status code will be
* request. It will not be set for an include request, since the HTTP status * applied, either leaving this to the controller or view, or keeping the servlet engine's default of 200 (OK).
* cannot be modified from within an include. *
* <p>If not specified, no status code will be applied, either leaving this to * @param defaultStatusCode HTTP status code value, for example 500 (SC_INTERNAL_SERVER_ERROR) or 404 (SC_NOT_FOUND)
* the controller or view, or keeping the servlet engine's default of 200 (OK).
* @param defaultStatusCode HTTP status code value, for example
* 500 (SC_INTERNAL_SERVER_ERROR) or 404 (SC_NOT_FOUND)
* @see javax.servlet.http.HttpServletResponse#SC_INTERNAL_SERVER_ERROR * @see javax.servlet.http.HttpServletResponse#SC_INTERNAL_SERVER_ERROR
* @see javax.servlet.http.HttpServletResponse#SC_NOT_FOUND * @see javax.servlet.http.HttpServletResponse#SC_NOT_FOUND
*/ */
@ -173,84 +91,33 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
} }
/** /**
* Set the name of the model attribute as which the exception should * Set the name of the model attribute as which the exception should be exposed. Default is "exception". <p>This can be
* be exposed. Default is "exception". * either set to a different attribute name or to <code>null</code> for not exposing an exception attribute at all.
* <p>This can be either set to a different attribute name or to *
* <code>null</code> for not exposing an exception attribute at all.
* @see #DEFAULT_EXCEPTION_ATTRIBUTE * @see #DEFAULT_EXCEPTION_ATTRIBUTE
*/ */
public void setExceptionAttribute(String exceptionAttribute) { public void setExceptionAttribute(String exceptionAttribute) {
this.exceptionAttribute = exceptionAttribute; this.exceptionAttribute = exceptionAttribute;
} }
/** /**
* Checks whether this resolver is supposed to apply (i.e. the handler * Actually resolve the given exception that got thrown during on handler execution, returning a ModelAndView that
* matches in case of "mappedHandlers" having been specified), then * represents a specific error page if appropriate. <p>May be overridden in subclasses, in order to apply specific
* delegates to the {@link #doResolveException} template method. * exception checks. Note that this template method will be invoked <i>after</i> checking whether this resolved applies
*/ * ("mappedHandlers" etc), so an implementation may simply proceed with its actual exception handling.
public ModelAndView resolveException( *
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
return doResolveException(request, response, handler, ex);
}
else {
return null;
}
}
/**
* Check whether this resolver is supposed to apply to the given handler.
* <p>The default implementation checks against the specified mapped handlers
* and handler classes, if any.
* @param request current HTTP request
* @param handler the executed handler, or <code>null</code> if none chosen at the
* time of the exception (for example, if multipart resolution failed)
* @return whether this resolved should proceed with resolving the exception
* for the given request and handler
* @see #setMappedHandlers
* @see #setMappedHandlerClasses
*/
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
if (handler != null) {
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
return true;
}
if (this.mappedHandlerClasses != null) {
for (Class handlerClass : this.mappedHandlerClasses) {
if (handlerClass.isInstance(handler)) {
return true;
}
}
}
}
// Else only apply if there are no explicit handler mappings.
return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
}
/**
* Actually resolve the given exception that got thrown during on handler execution,
* returning a ModelAndView that represents a specific error page if appropriate.
* <p>May be overridden in subclasses, in order to apply specific exception checks.
* Note that this template method will be invoked <i>after</i> checking whether
* this resolved applies ("mappedHandlers" etc), so an implementation may simply
* proceed with its actual exception handling.
* @param request current HTTP request * @param request current HTTP request
* @param response current HTTP response * @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the * @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* time of the exception (for example, if multipart resolution failed) * if multipart resolution failed)
* @param ex the exception that got thrown during handler execution * @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to, or <code>null</code> for default processing * @return a corresponding ModelAndView to forward to, or <code>null</code> for default processing
*/ */
protected ModelAndView doResolveException( @Override
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { protected ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response,
// Log exception, both at debug log level and at warn level, if desired. Object handler,
if (logger.isDebugEnabled()) { Exception ex) {
logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
}
logException(ex, request);
// Expose ModelAndView for chosen error view. // Expose ModelAndView for chosen error view.
String viewName = determineViewName(ex, request); String viewName = determineViewName(ex, request);
@ -268,40 +135,10 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
} }
} }
/** /**
* Log the given exception at warn level, provided that warn logging has been * Determine the view name for the given exception, searching the {@link #setExceptionMappings "exceptionMappings"},
* activated through the {@link #setWarnLogCategory "warnLogCategory"} property. * using the {@link #setDefaultErrorView "defaultErrorView"} as fallback.
* <p>Calls {@link #buildLogMessage} in order to determine the concrete message *
* to log. Always passes the full exception to the logger.
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @see #setWarnLogCategory
* @see #buildLogMessage
* @see org.apache.commons.logging.Log#warn(Object, Throwable)
*/
protected void logException(Exception ex, HttpServletRequest request) {
if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) {
this.warnLogger.warn(buildLogMessage(ex, request), ex);
}
}
/**
* Build a log message for the given exception, occured during processing
* the given request.
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @return the log message to use
*/
protected String buildLogMessage(Exception ex, HttpServletRequest request) {
return "Handler execution resulted in exception";
}
/**
* Determine the view name for the given exception, searching the
* {@link #setExceptionMappings "exceptionMappings"}, using the
* {@link #setDefaultErrorView "defaultErrorView"} as fallback.
* @param ex the exception that got thrown during handler execution * @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata) * @param request current HTTP request (useful for obtaining metadata)
* @return the resolved view name, or <code>null</code> if none found * @return the resolved view name, or <code>null</code> if none found
@ -315,8 +152,8 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
// Return default error view else, if defined. // Return default error view else, if defined.
if (viewName == null && this.defaultErrorView != null) { if (viewName == null && this.defaultErrorView != null) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Resolving to default view '" + this.defaultErrorView + logger.debug("Resolving to default view '" + this.defaultErrorView + "' for exception of type [" +
"' for exception of type [" + ex.getClass().getName() + "]"); ex.getClass().getName() + "]");
} }
viewName = this.defaultErrorView; viewName = this.defaultErrorView;
} }
@ -325,6 +162,7 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
/** /**
* Find a matching view name in the given exception mappings. * Find a matching view name in the given exception mappings.
*
* @param exceptionMappings mappings between exception class names and error view names * @param exceptionMappings mappings between exception class names and error view names
* @param ex the exception that got thrown during handler execution * @param ex the exception that got thrown during handler execution
* @return the view name, or <code>null</code> if none found * @return the view name, or <code>null</code> if none found
@ -351,11 +189,9 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
} }
/** /**
* Return the depth to the superclass matching. * Return the depth to the superclass matching. <p>0 means ex matches exactly. Returns -1 if there's no match.
* <p>0 means ex matches exactly. Returns -1 if there's no match. * Otherwise, returns depth. Lowest depth wins. <p>Follows the same algorithm as {@link
* Otherwise, returns depth. Lowest depth wins. * org.springframework.transaction.interceptor.RollbackRuleAttribute}.
* <p>Follows the same algorithm as
* {@link org.springframework.transaction.interceptor.RollbackRuleAttribute}.
*/ */
protected int getDepth(String exceptionMapping, Exception ex) { protected int getDepth(String exceptionMapping, Exception ex) {
return getDepth(exceptionMapping, ex.getClass(), 0); return getDepth(exceptionMapping, ex.getClass(), 0);
@ -373,17 +209,15 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1); return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
} }
/** /**
* Determine the HTTP status code to apply for the given error view. * Determine the HTTP status code to apply for the given error view. <p>The default implementation always returns the
* <p>The default implementation always returns the specified * specified {@link #setDefaultStatusCode "defaultStatusCode"}, as a common status code for all error views. Override
* {@link #setDefaultStatusCode "defaultStatusCode"}, as a common * this in a custom subclass to determine a specific status code for the given view.
* status code for all error views. Override this in a custom subclass *
* to determine a specific status code for the given view.
* @param request current HTTP request * @param request current HTTP request
* @param viewName the name of the error view * @param viewName the name of the error view
* @return the HTTP status code to use, or <code>null</code> for the * @return the HTTP status code to use, or <code>null</code> for the servlet container's default (200 in case of a
* servlet container's default (200 in case of a standard error view) * standard error view)
* @see #setDefaultStatusCode * @see #setDefaultStatusCode
* @see #applyStatusCodeIfPossible * @see #applyStatusCodeIfPossible
*/ */
@ -392,8 +226,9 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
} }
/** /**
* Apply the specified HTTP status code to the given response, if possible * Apply the specified HTTP status code to the given response, if possible (that is, if not executing within an include
* (that is, if not executing within an include request). * request).
*
* @param request current HTTP request * @param request current HTTP request
* @param response current HTTP response * @param response current HTTP response
* @param statusCode the status code to apply * @param statusCode the status code to apply
@ -412,8 +247,9 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
} }
/** /**
* Return a ModelAndView for the given request, view name and exception. * Return a ModelAndView for the given request, view name and exception. <p>The default implementation delegates to
* <p>The default implementation delegates to {@link #getModelAndView(String, Exception)}. * {@link #getModelAndView(String, Exception)}.
*
* @param viewName the name of the error view * @param viewName the name of the error view
* @param ex the exception that got thrown during handler execution * @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata) * @param request current HTTP request (useful for obtaining metadata)
@ -424,9 +260,9 @@ public class SimpleMappingExceptionResolver implements HandlerExceptionResolver,
} }
/** /**
* Return a ModelAndView for the given view name and exception. * Return a ModelAndView for the given view name and exception. <p>The default implementation adds the specified
* <p>The default implementation adds the specified exception attribute. * exception attribute. Can be overridden in subclasses.
* Can be overridden in subclasses. *
* @param viewName the name of the error view * @param viewName the name of the error view
* @param ex the exception that got thrown during handler execution * @param ex the exception that got thrown during handler execution
* @return the ModelAndView instance * @return the ModelAndView instance

View File

@ -52,7 +52,6 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
@ -68,7 +67,6 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.validation.support.BindingAwareModelMap; import org.springframework.validation.support.BindingAwareModelMap;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.HttpSessionRequiredException; import org.springframework.web.HttpSessionRequiredException;
import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.MissingServletRequestParameterException;
@ -101,38 +99,36 @@ import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils; import org.springframework.web.util.WebUtils;
/** /**
* Implementation of the {@link org.springframework.web.servlet.HandlerAdapter} * Implementation of the {@link org.springframework.web.servlet.HandlerAdapter} interface that maps handler methods
* interface that maps handler methods based on HTTP paths, HTTP methods and * based on HTTP paths, HTTP methods and request parameters expressed through the {@link RequestMapping} annotation.
* request parameters expressed through the {@link RequestMapping} annotation.
* *
* <p>Supports request parameter binding through the {@link RequestParam} annotation. * <p>Supports request parameter binding through the {@link RequestParam} annotation. Also supports the {@link
* Also supports the {@link ModelAttribute} annotation for exposing model attribute * ModelAttribute} annotation for exposing model attribute values to the view, as well as {@link InitBinder} for binder
* values to the view, as well as {@link InitBinder} for binder initialization methods * initialization methods and {@link SessionAttributes} for automatic session management of specific attributes.
* and {@link SessionAttributes} for automatic session management of specific attributes.
* *
* <p>This adapter can be customized through various bean properties. * <p>This adapter can be customized through various bean properties. A common use case is to apply shared binder
* A common use case is to apply shared binder initialization logic through * initialization logic through a custom {@link #setWebBindingInitializer WebBindingInitializer}.
* a custom {@link #setWebBindingInitializer WebBindingInitializer}.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Arjen Poutsma * @author Arjen Poutsma
* @since 2.5
* @see #setPathMatcher * @see #setPathMatcher
* @see #setMethodNameResolver * @see #setMethodNameResolver
* @see #setWebBindingInitializer * @see #setWebBindingInitializer
* @see #setSessionAttributeStore * @see #setSessionAttributeStore
* @since 2.5
*/ */
public class AnnotationMethodHandlerAdapter extends WebContentGenerator implements HandlerAdapter { public class AnnotationMethodHandlerAdapter extends WebContentGenerator implements HandlerAdapter {
/** /**
* Log category to use when no mapped handler is found for a request. * Log category to use when no mapped handler is found for a request.
*
* @see #pageNotFoundLogger * @see #pageNotFoundLogger
*/ */
public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound"; public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
/** /**
* Additional logger to use when no mapped handler is found for a request. * Additional logger to use when no mapped handler is found for a request.
*
* @see #PAGE_NOT_FOUND_LOG_CATEGORY * @see #PAGE_NOT_FOUND_LOG_CATEGORY
*/ */
protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY); protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
@ -167,12 +163,11 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
super(false); super(false);
} }
/** /**
* Set if URL lookup should always use the full path within the current servlet * Set if URL lookup should always use the full path within the current servlet context. Else, the path within the
* context. Else, the path within the current servlet mapping is used if applicable * current servlet mapping is used if applicable (that is, in the case of a ".../*" servlet mapping in web.xml).
* (that is, in the case of a ".../*" servlet mapping in web.xml).
* <p>Default is "false". * <p>Default is "false".
*
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
*/ */
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) { public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
@ -180,10 +175,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
/** /**
* Set if context path and request URI should be URL-decoded. Both are returned * Set if context path and request URI should be URL-decoded. Both are returned <i>undecoded</i> by the Servlet API, in
* <i>undecoded</i> by the Servlet API, in contrast to the servlet path. * contrast to the servlet path. <p>Uses either the request encoding or the default encoding according to the Servlet
* <p>Uses either the request encoding or the default encoding according * spec (ISO-8859-1).
* to the Servlet spec (ISO-8859-1). *
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode * @see org.springframework.web.util.UrlPathHelper#setUrlDecode
*/ */
public void setUrlDecode(boolean urlDecode) { public void setUrlDecode(boolean urlDecode) {
@ -191,10 +186,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
/** /**
* Set the UrlPathHelper to use for resolution of lookup paths. * Set the UrlPathHelper to use for resolution of lookup paths. <p>Use this to override the default UrlPathHelper with
* <p>Use this to override the default UrlPathHelper with a custom subclass, * a custom subclass, or to share common UrlPathHelper settings across multiple HandlerMappings and HandlerAdapters.
* or to share common UrlPathHelper settings across multiple HandlerMappings
* and HandlerAdapters.
*/ */
public void setUrlPathHelper(UrlPathHelper urlPathHelper) { public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null"); Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
@ -202,8 +195,9 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
/** /**
* Set the PathMatcher implementation to use for matching URL paths * Set the PathMatcher implementation to use for matching URL paths against registered URL patterns. Default is
* against registered URL patterns. Default is AntPathMatcher. * AntPathMatcher.
*
* @see org.springframework.util.AntPathMatcher * @see org.springframework.util.AntPathMatcher
*/ */
public void setPathMatcher(PathMatcher pathMatcher) { public void setPathMatcher(PathMatcher pathMatcher) {
@ -212,9 +206,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
/** /**
* Set the MethodNameResolver to use for resolving default handler methods * Set the MethodNameResolver to use for resolving default handler methods (carrying an empty
* (carrying an empty <code>@RequestMapping</code> annotation). * <code>@RequestMapping</code> annotation). <p>Will only kick in when the handler method cannot be resolved uniquely
* <p>Will only kick in when the handler method cannot be resolved uniquely
* through the annotation metadata already. * through the annotation metadata already.
*/ */
public void setMethodNameResolver(MethodNameResolver methodNameResolver) { public void setMethodNameResolver(MethodNameResolver methodNameResolver) {
@ -222,18 +215,16 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
/** /**
* Specify a WebBindingInitializer which will apply pre-configured * Specify a WebBindingInitializer which will apply pre-configured configuration to every DataBinder that this
* configuration to every DataBinder that this controller uses. * controller uses.
*/ */
public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) { public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
this.webBindingInitializer = webBindingInitializer; this.webBindingInitializer = webBindingInitializer;
} }
/** /**
* Specify the strategy to store session attributes with. * Specify the strategy to store session attributes with. <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
* <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore}, * storing session attributes in the HttpSession, using the same attribute name as in the model.
* storing session attributes in the HttpSession, using the same
* attribute name as in the model.
*/ */
public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) { public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
Assert.notNull(sessionAttributeStore, "SessionAttributeStore must not be null"); Assert.notNull(sessionAttributeStore, "SessionAttributeStore must not be null");
@ -241,11 +232,11 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
/** /**
* Cache content produced by <code>@SessionAttributes</code> annotated handlers * Cache content produced by <code>@SessionAttributes</code> annotated handlers for the given number of seconds.
* for the given number of seconds. Default is 0, preventing caching completely. * Default is 0, preventing caching completely. <p>In contrast to the "cacheSeconds" property which will apply to all
* <p>In contrast to the "cacheSeconds" property which will apply to all general * general handlers (but not to <code>@SessionAttributes</code> annotated handlers), this setting will apply to
* handlers (but not to <code>@SessionAttributes</code> annotated handlers), this * <code>@SessionAttributes</code> annotated handlers only.
* setting will apply to <code>@SessionAttributes</code> annotated handlers only. *
* @see #setCacheSeconds * @see #setCacheSeconds
* @see org.springframework.web.bind.annotation.SessionAttributes * @see org.springframework.web.bind.annotation.SessionAttributes
*/ */
@ -254,20 +245,15 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
/** /**
* Set if controller execution should be synchronized on the session, * Set if controller execution should be synchronized on the session, to serialize parallel invocations from the same
* to serialize parallel invocations from the same client. * client. <p>More specifically, the execution of each handler method will get synchronized if this flag is "true". The
* <p>More specifically, the execution of each handler method will get * best available session mutex will be used for the synchronization; ideally, this will be a mutex exposed by
* synchronized if this flag is "true". The best available session mutex * HttpSessionMutexListener. <p>The session mutex is guaranteed to be the same object during the entire lifetime of the
* will be used for the synchronization; ideally, this will be a mutex * session, available under the key defined by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a safe
* exposed by HttpSessionMutexListener. * reference to synchronize on for locking on the current session. <p>In many cases, the HttpSession reference itself
* <p>The session mutex is guaranteed to be the same object during * is a safe mutex as well, since it will always be the same object reference for the same active logical session.
* the entire lifetime of the session, available under the key defined * However, this is not guaranteed across different servlet containers; the only 100% safe way is a session mutex.
* by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a *
* safe reference to synchronize on for locking on the current session.
* <p>In many cases, the HttpSession reference itself is a safe mutex
* as well, since it will always be the same object reference for the
* same active logical session. However, this is not guaranteed across
* different servlet containers; the only 100% safe way is a session mutex.
* @see org.springframework.web.util.HttpSessionMutexListener * @see org.springframework.web.util.HttpSessionMutexListener
* @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession) * @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession)
*/ */
@ -276,44 +262,38 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
/** /**
* Set the ParameterNameDiscoverer to use for resolving method parameter * Set the ParameterNameDiscoverer to use for resolving method parameter names if needed (e.g. for default attribute
* names if needed (e.g. for default attribute names). * names). <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
* <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
*/ */
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) { public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer; this.parameterNameDiscoverer = parameterNameDiscoverer;
} }
/** /**
* Set a custom ArgumentResolvers to use for special method parameter types. * Set a custom ArgumentResolvers to use for special method parameter types. Such a custom ArgumentResolver will kick
* Such a custom ArgumentResolver will kick in first, having a chance to * in first, having a chance to resolve an argument value before the standard argument handling kicks in.
* resolve an argument value before the standard argument handling kicks in.
*/ */
public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) { public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) {
this.customArgumentResolvers = new WebArgumentResolver[] {argumentResolver}; this.customArgumentResolvers = new WebArgumentResolver[]{argumentResolver};
} }
/** /**
* Set one or more custom ArgumentResolvers to use for special method * Set one or more custom ArgumentResolvers to use for special method parameter types. Any such custom ArgumentResolver
* parameter types. Any such custom ArgumentResolver will kick in first, * will kick in first, having a chance to resolve an argument value before the standard argument handling kicks in.
* having a chance to resolve an argument value before the standard
* argument handling kicks in.
*/ */
public void setCustomArgumentResolvers(WebArgumentResolver[] argumentResolvers) { public void setCustomArgumentResolvers(WebArgumentResolver[] argumentResolvers) {
this.customArgumentResolvers = argumentResolvers; this.customArgumentResolvers = argumentResolvers;
} }
/** /**
* Set the message body converters to use. These converters are used to convert * Set the message body converters to use. These converters are used to convert from and to HTTP requests and
* from and to HTTP requests and responses. * responses.
*/ */
public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) { public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) {
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty"); Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
this.messageConverters = messageConverters; this.messageConverters = messageConverters;
} }
public boolean supports(Object handler) { public boolean supports(Object handler) {
return getMethodResolver(handler).hasHandlerMethods(); return getMethodResolver(handler).hasHandlerMethods();
} }
@ -345,32 +325,20 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
return invokeHandlerMethod(request, response, handler); return invokeHandlerMethod(request, response, handler);
} }
protected ModelAndView invokeHandlerMethod( protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { throws Exception {
try { ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
ServletHandlerMethodResolver methodResolver = getMethodResolver(handler); Method handlerMethod = methodResolver.resolveHandlerMethod(request);
Method handlerMethod = methodResolver.resolveHandlerMethod(request); ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver); ServletWebRequest webRequest = new ServletWebRequest(request, response);
ServletWebRequest webRequest = new ServletWebRequest(request, response); ExtendedModelMap implicitModel = new BindingAwareModelMap();
ExtendedModelMap implicitModel = new BindingAwareModelMap();
Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel); Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
ModelAndView mav = ModelAndView mav =
methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest); methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
methodInvoker.updateModelAttributes( methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest); return mav;
return mav;
}
catch (NoSuchRequestHandlingMethodException ex) {
return handleNoSuchRequestHandlingMethod(ex, request, response);
}
catch (HttpRequestMethodNotSupportedException ex) {
return handleHttpRequestMethodNotSupportedException(ex, request, response);
}
catch (HttpMediaTypeNotSupportedException ex) {
return handleHttpMediaTypeNotSupportedException(ex, request, response);
}
} }
public long getLastModified(HttpServletRequest request, Object handler) { public long getLastModified(HttpServletRequest request, Object handler) {
@ -378,89 +346,25 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
/** /**
* Handle the case where no request handler method was found. * Template method for creating a new ServletRequestDataBinder instance. <p>The default implementation creates a
* <p>The default implementation logs a warning and sends an HTTP 404 error. * standard ServletRequestDataBinder. This can be overridden for custom ServletRequestDataBinder subclasses.
* Alternatively, a fallback view could be chosen, or the *
* NoSuchRequestHandlingMethodException could be rethrown as-is.
* @param ex the NoSuchRequestHandlingMethodException to be handled
* @param request current HTTP request * @param request current HTTP request
* @param response current HTTP response * @param target the target object to bind onto (or <code>null</code> if the binder is just used to convert a plain
* @return a ModelAndView to render, or <code>null</code> if handled directly * parameter value)
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleNoSuchRequestHandlingMethod(
NoSuchRequestHandlingMethodException ex, HttpServletRequest request, HttpServletResponse response)
throws Exception {
pageNotFoundLogger.warn(ex.getMessage());
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
}
/**
* Handle the case where no request handler method was found for the particular HTTP request method.
* <p>The default implementation logs a warning, sends an HTTP 405 error and sets the "Allow" header.
* Alternatively, a fallback view could be chosen, or the HttpRequestMethodNotSupportedException
* could be rethrown as-is.
* @param ex the HttpRequestMethodNotSupportedException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleHttpRequestMethodNotSupportedException(
HttpRequestMethodNotSupportedException ex, HttpServletRequest request, HttpServletResponse response)
throws Exception {
pageNotFoundLogger.warn(ex.getMessage());
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
response.addHeader("Allow", StringUtils.arrayToDelimitedString(ex.getSupportedMethods(), ", "));
return null;
}
/**
* Handle the case where no {@linkplain HttpMessageConverter message converters} was found for the PUT or POSTed
* content.
* <p>The default implementation logs a warning, sends an HTTP 415 error and sets the "Allow" header.
* Alternatively, a fallback view could be chosen, or the HttpMediaTypeNotSupportedException
* could be rethrown as-is.
* @param ex the HttpMediaTypeNotSupportedException to be handled
* @param request current HTTP request
* @param response current HTTP response
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception an Exception that should be thrown as result of the servlet request
*/
protected ModelAndView handleHttpMediaTypeNotSupportedException(
HttpMediaTypeNotSupportedException ex, HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
response.addHeader("Accept", MediaType.toString(ex.getSupportedMediaTypes()));
return null;
}
/**
* Template method for creating a new ServletRequestDataBinder instance.
* <p>The default implementation creates a standard ServletRequestDataBinder.
* This can be overridden for custom ServletRequestDataBinder subclasses.
* @param request current HTTP request
* @param target the target object to bind onto (or <code>null</code>
* if the binder is just used to convert a plain parameter value)
* @param objectName the objectName of the target object * @param objectName the objectName of the target object
* @return the ServletRequestDataBinder instance to use * @return the ServletRequestDataBinder instance to use
* @throws Exception in case of invalid state or arguments * @throws Exception in case of invalid state or arguments
* @see ServletRequestDataBinder#bind(javax.servlet.ServletRequest) * @see ServletRequestDataBinder#bind(javax.servlet.ServletRequest)
* @see ServletRequestDataBinder#convertIfNecessary(Object, Class, MethodParameter) * @see ServletRequestDataBinder#convertIfNecessary(Object, Class, MethodParameter)
*/ */
protected ServletRequestDataBinder createBinder( protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object target, String objectName)
HttpServletRequest request, Object target, String objectName) throws Exception { throws Exception {
return new ServletRequestDataBinder(target, objectName); return new ServletRequestDataBinder(target, objectName);
} }
/** /** Build a HandlerMethodResolver for the given handler type. */
* Build a HandlerMethodResolver for the given handler type.
*/
private ServletHandlerMethodResolver getMethodResolver(Object handler) { private ServletHandlerMethodResolver getMethodResolver(Object handler) {
Class handlerClass = ClassUtils.getUserClass(handler); Class handlerClass = ClassUtils.getUserClass(handler);
ServletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass); ServletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass);
@ -471,10 +375,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
return resolver; return resolver;
} }
/** Servlet-specific subclass of {@link HandlerMethodResolver}. */
/**
* Servlet-specific subclass of {@link HandlerMethodResolver}.
*/
private class ServletHandlerMethodResolver extends HandlerMethodResolver { private class ServletHandlerMethodResolver extends HandlerMethodResolver {
private ServletHandlerMethodResolver(Class<?> handlerType) { private ServletHandlerMethodResolver(Class<?> handlerType) {
@ -499,7 +400,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
boolean match = false; boolean match = false;
if (mappingInfo.paths.length > 0) { if (mappingInfo.paths.length > 0) {
List<String> matchedPaths = new ArrayList<String>(mappingInfo.paths.length); List<String> matchedPaths = new ArrayList<String>(mappingInfo.paths.length);
for (String mappedPath : mappingInfo.paths) { for (String mappedPath : mappingInfo.paths) {
if (isPathMatch(mappedPath, lookupPath)) { if (isPathMatch(mappedPath, lookupPath)) {
if (checkParameters(mappingInfo, request)) { if (checkParameters(mappingInfo, request)) {
@ -515,7 +416,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
} }
Collections.sort(matchedPaths, pathComparator); Collections.sort(matchedPaths, pathComparator);
mappingInfo.matchedPaths = matchedPaths.toArray(new String[matchedPaths.size()]); mappingInfo.matchedPaths = matchedPaths.toArray(new String[matchedPaths.size()]);
} }
else { else {
// No paths specified: parameter match sufficient. // No paths specified: parameter match sufficient.
@ -548,17 +449,19 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
} }
if (oldMappedMethod != null) { if (oldMappedMethod != null) {
throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" + throw new IllegalStateException(
lookupPath + "': {" + oldMappedMethod + ", " + handlerMethod + "Ambiguous handler methods mapped for HTTP path '" + lookupPath + "': {" +
"}. If you intend to handle the same path in multiple methods, then factor " + oldMappedMethod + ", " + handlerMethod +
"them out into a dedicated handler class with that path mapped at the type level!"); "}. If you intend to handle the same path in multiple methods, then factor " +
"them out into a dedicated handler class with that path mapped at the type level!");
} }
} }
} }
} }
if (!targetHandlerMethods.isEmpty()) { if (!targetHandlerMethods.isEmpty()) {
List<RequestMappingInfo> matches = new ArrayList<RequestMappingInfo>(targetHandlerMethods.keySet()); List<RequestMappingInfo> matches = new ArrayList<RequestMappingInfo>(targetHandlerMethods.keySet());
RequestMappingInfoComparator requestMappingInfoComparator = new RequestMappingInfoComparator(pathComparator); RequestMappingInfoComparator requestMappingInfoComparator =
new RequestMappingInfoComparator(pathComparator);
Collections.sort(matches, requestMappingInfoComparator); Collections.sort(matches, requestMappingInfoComparator);
RequestMappingInfo bestMappingMatch = matches.get(0); RequestMappingInfo bestMappingMatch = matches.get(0);
if (bestMappingMatch.matchedPaths.length > 0) { if (bestMappingMatch.matchedPaths.length > 0) {
@ -597,7 +500,9 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void extractHandlerMethodUriTemplates(String mappedPath, String lookupPath, HttpServletRequest request) { private void extractHandlerMethodUriTemplates(String mappedPath,
String lookupPath,
HttpServletRequest request) {
Map<String, String> variables = null; Map<String, String> variables = null;
boolean hasSuffix = (mappedPath.indexOf('.') != -1); boolean hasSuffix = (mappedPath.indexOf('.') != -1);
if (!hasSuffix && pathMatcher.match(mappedPath + ".*", lookupPath)) { if (!hasSuffix && pathMatcher.match(mappedPath + ".*", lookupPath)) {
@ -610,7 +515,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
String realPath = "/**/" + mappedPath; String realPath = "/**/" + mappedPath;
if (pathMatcher.match(realPath, lookupPath)) { if (pathMatcher.match(realPath, lookupPath)) {
variables = pathMatcher.extractUriTemplateVariables(realPath, lookupPath); variables = pathMatcher.extractUriTemplateVariables(realPath, lookupPath);
} else { }
else {
realPath = realPath + ".*"; realPath = realPath + ".*";
if (pathMatcher.match(realPath, lookupPath)) { if (pathMatcher.match(realPath, lookupPath)) {
variables = pathMatcher.extractUriTemplateVariables(realPath, lookupPath); variables = pathMatcher.extractUriTemplateVariables(realPath, lookupPath);
@ -628,17 +534,14 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
} }
/** Servlet-specific subclass of {@link HandlerMethodInvoker}. */
/**
* Servlet-specific subclass of {@link HandlerMethodInvoker}.
*/
private class ServletHandlerMethodInvoker extends HandlerMethodInvoker { private class ServletHandlerMethodInvoker extends HandlerMethodInvoker {
private boolean responseArgumentUsed = false; private boolean responseArgumentUsed = false;
private ServletHandlerMethodInvoker(HandlerMethodResolver resolver) { private ServletHandlerMethodInvoker(HandlerMethodResolver resolver) {
super(resolver, webBindingInitializer, sessionAttributeStore, super(resolver, webBindingInitializer, sessionAttributeStore, parameterNameDiscoverer,
parameterNameDiscoverer, customArgumentResolvers, messageConverters); customArgumentResolvers, messageConverters);
} }
@Override @Override
@ -655,8 +558,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
protected WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) protected WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName)
throws Exception { throws Exception {
return AnnotationMethodHandlerAdapter.this.createBinder( return AnnotationMethodHandlerAdapter.this
(HttpServletRequest) webRequest.getNativeRequest(), target, objectName); .createBinder((HttpServletRequest) webRequest.getNativeRequest(), target, objectName);
} }
@Override @Override
@ -699,14 +602,14 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
Map<String, String> uriTemplateVariables = Map<String, String> uriTemplateVariables =
(Map<String, String>) servletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); (Map<String, String>) servletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
if (uriTemplateVariables == null || !uriTemplateVariables.containsKey(pathVarName)) { if (uriTemplateVariables == null || !uriTemplateVariables.containsKey(pathVarName)) {
throw new IllegalStateException("Could not find @PathVariable [" + pathVarName + "] in @RequestMapping"); throw new IllegalStateException(
"Could not find @PathVariable [" + pathVarName + "] in @RequestMapping");
} }
return uriTemplateVariables.get(pathVarName); return uriTemplateVariables.get(pathVarName);
} }
@Override @Override
protected Object resolveStandardArgument(Class parameterType, NativeWebRequest webRequest) protected Object resolveStandardArgument(Class parameterType, NativeWebRequest webRequest) throws Exception {
throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse(); HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse();
@ -745,8 +648,11 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ModelAndView getModelAndView(Method handlerMethod, Class handlerType, Object returnValue, public ModelAndView getModelAndView(Method handlerMethod,
ExtendedModelMap implicitModel, ServletWebRequest webRequest) { Class handlerType,
Object returnValue,
ExtendedModelMap implicitModel,
ServletWebRequest webRequest) {
if (returnValue instanceof ModelAndView) { if (returnValue instanceof ModelAndView) {
ModelAndView mav = (ModelAndView) returnValue; ModelAndView mav = (ModelAndView) returnValue;
@ -792,7 +698,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
} }
static class RequestMappingInfo { static class RequestMappingInfo {
String[] paths = new String[0]; String[] paths = new String[0];
@ -806,7 +711,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
String bestMatchedPath() { String bestMatchedPath() {
return matchedPaths.length > 0 ? matchedPaths[0] : null; return matchedPaths.length > 0 ? matchedPaths[0] : null;
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
RequestMappingInfo other = (RequestMappingInfo) obj; RequestMappingInfo other = (RequestMappingInfo) obj;
@ -823,16 +728,12 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
/** /**
* Comparator capable of sorting {@link RequestMappingInfo}s (RHIs) so that sorting a list with this comparator will * Comparator capable of sorting {@link RequestMappingInfo}s (RHIs) so that sorting a list with this comparator will
* result in: * result in: <ul> <li>RHIs with {@linkplain RequestMappingInfo#matchedPaths better matched paths} take prescedence
* <ul> * over those with a weaker match (as expressed by the {@linkplain PathMatcher#getPatternComparator(String) path
* <li>RHIs with {@linkplain RequestMappingInfo#matchedPaths better matched paths} take prescedence over those with * pattern comparator}.) Typically, this means that patterns without wild chards and uri templates will be ordered
* a weaker match (as expressed by the {@linkplain PathMatcher#getPatternComparator(String) path pattern * before those without.</li> <li>RHIs with one single {@linkplain RequestMappingInfo#methods request method} will be
* comparator}.) Typically, this means that patterns without wild chards and uri templates will be ordered before those without.</li> * ordered before those without a method, or with more than one method.</li> <li>RHIs with more {@linkplain
* <li>RHIs with one single {@linkplain RequestMappingInfo#methods request method} will be ordered before those * RequestMappingInfo#params request parameters} will be ordered before those with less parameters</li> </ol>
* without a method, or with more than one method.</li>
* <li>RHIs with more {@linkplain RequestMappingInfo#params request parameters} will be ordered before those with
* less parameters</li>
* </ol>
*/ */
static class RequestMappingInfoComparator implements Comparator<RequestMappingInfo> { static class RequestMappingInfoComparator implements Comparator<RequestMappingInfo> {
@ -867,5 +768,5 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
return (info1ParamCount < info2ParamCount ? 1 : (info1ParamCount == info2ParamCount ? 0 : -1)); return (info1ParamCount < info2ParamCount ? 1 : (info1ParamCount == info2ParamCount ? 0 : -1));
} }
} }
} }

View File

@ -0,0 +1,107 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.Collections;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
/** @author Arjen Poutsma */
public class DefaultHandlerExceptionResolverTests {
private DefaultHandlerExceptionResolver exceptionResolver;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@Before
public void setUp() {
exceptionResolver = new DefaultHandlerExceptionResolver();
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
request.setMethod("GET");
}
@Test
public void handleNoSuchRequestHandlingMethod() {
NoSuchRequestHandlingMethodException ex = new NoSuchRequestHandlingMethodException(request);
exceptionResolver.resolveException(request, response, null, ex);
assertEquals("Invalid status code", 404, response.getStatus());
}
@Test
public void handleHttpRequestMethodNotSupported() {
HttpRequestMethodNotSupportedException ex =
new HttpRequestMethodNotSupportedException("GET", new String[]{"POST", "PUT"});
exceptionResolver.resolveException(request, response, null, ex);
assertEquals("Invalid status code", 405, response.getStatus());
assertEquals("Invalid Allow header", "POST, PUT", response.getHeader("Allow"));
}
@Test
public void handleHttpMediaTypeNotSupported() {
HttpMediaTypeNotSupportedException ex = new HttpMediaTypeNotSupportedException(new MediaType("text", "plain"),
Collections.singletonList(new MediaType("application", "pdf")));
exceptionResolver.resolveException(request, response, null, ex);
assertEquals("Invalid status code", 415, response.getStatus());
assertEquals("Invalid Accept header", "application/pdf", response.getHeader("Accept"));
}
@Test
public void handleMissingServletRequestParameter() {
MissingServletRequestParameterException ex = new MissingServletRequestParameterException("foo", "bar");
exceptionResolver.resolveException(request, response, null, ex);
assertEquals("Invalid status code", 400, response.getStatus());
}
@Test
public void handleTypeMismatch() {
TypeMismatchException ex = new TypeMismatchException("foo", String.class);
exceptionResolver.resolveException(request, response, null, ex);
assertEquals("Invalid status code", 400, response.getStatus());
}
@Test
public void handleHttpMessageNotReadable() {
HttpMessageNotReadableException ex = new HttpMessageNotReadableException("foo");
exceptionResolver.resolveException(request, response, null, ex);
assertEquals("Invalid status code", 400, response.getStatus());
}
@Test
public void handleHttpMessageNotWritable() {
HttpMessageNotWritableException ex = new HttpMessageNotWritableException("foo");
exceptionResolver.resolveException(request, response, null, ex);
assertEquals("Invalid status code", 500, response.getStatus());
}
}

View File

@ -21,6 +21,7 @@ import java.io.Writer;
import java.security.Principal; import java.security.Principal;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
@ -51,6 +52,12 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.propertyeditors.CustomDateEditor; import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletConfig; import org.springframework.mock.web.MockServletConfig;
@ -62,7 +69,6 @@ import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.InitBinder;
@ -107,13 +113,25 @@ public class ServletAnnotationControllerTests {
assertEquals("test", response.getContentAsString()); assertEquals("test", response.getContentAsString());
} }
@Test(expected = MissingServletRequestParameterException.class) @Test
public void requiredParamMissing() throws Exception { public void requiredParamMissing() throws Exception {
initServlet(RequiredParamController.class); initServlet(RequiredParamController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do"); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do");
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response); servlet.service(request, response);
assertEquals("Invalid response status code", HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
}
@Test
public void typeConversionError() throws Exception {
initServlet(RequiredParamController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do");
request.addParameter("id", "foo");
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals("Invalid response status code", HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
} }
@Test @Test
@ -157,7 +175,7 @@ public class ServletAnnotationControllerTests {
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response); servlet.service(request, response);
assertEquals("Invalid response status", HttpServletResponse.SC_METHOD_NOT_ALLOWED, response.getStatus()); assertEquals("Invalid response status", HttpServletResponse.SC_METHOD_NOT_ALLOWED, response.getStatus());
String allowHeader = (String)response.getHeader("Allow"); String allowHeader = (String) response.getHeader("Allow");
assertNotNull("No Allow header", allowHeader); assertNotNull("No Allow header", allowHeader);
Set<String> allowedMethods = new HashSet<String>(); Set<String> allowedMethods = new HashSet<String>();
allowedMethods.addAll(Arrays.asList(StringUtils.delimitedListToStringArray(allowHeader, ", "))); allowedMethods.addAll(Arrays.asList(StringUtils.delimitedListToStringArray(allowHeader, ", ")));
@ -249,7 +267,6 @@ public class ServletAnnotationControllerTests {
servlet.init(new MockServletConfig()); servlet.init(new MockServletConfig());
} }
private void doTestAdaptedHandleMethods(final Class<?> controllerClass) throws Exception { private void doTestAdaptedHandleMethods(final Class<?> controllerClass) throws Exception {
initServlet(controllerClass); initServlet(controllerClass);
@ -878,6 +895,31 @@ public class ServletAnnotationControllerTests {
assertNotNull("No Accept response header set", response.getHeader("Accept")); assertNotNull("No Accept response header set", response.getHeader("Accept"));
} }
@Test
public void badRequestRequestBody() throws ServletException, IOException {
@SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() {
@Override
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
GenericWebApplicationContext wac = new GenericWebApplicationContext();
wac.registerBeanDefinition("controller", new RootBeanDefinition(RequestBodyController.class));
RootBeanDefinition adapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class);
adapterDef.getPropertyValues().addPropertyValue("messageConverters", new MyMessageConverter());
wac.registerBeanDefinition("handlerAdapter", adapterDef);
wac.refresh();
return wac;
}
};
servlet.init(new MockServletConfig());
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/something");
String requestBody = "Hello World";
request.setContent(requestBody.getBytes("UTF-8"));
request.addHeader("Content-Type", "application/pdf");
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals("Invalid response status code", HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
}
/* /*
* Controllers * Controllers
*/ */
@ -893,8 +935,7 @@ public class ServletAnnotationControllerTests {
} }
} }
/** @noinspection UnusedDeclaration */
/** @noinspection UnusedDeclaration*/
private static class BaseController { private static class BaseController {
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
@ -903,7 +944,6 @@ public class ServletAnnotationControllerTests {
} }
} }
@Controller @Controller
private static class MyAdaptedController { private static class MyAdaptedController {
@ -913,8 +953,10 @@ public class ServletAnnotationControllerTests {
} }
@RequestMapping("/myPath2.do") @RequestMapping("/myPath2.do")
public void myHandle(@RequestParam("param1") String p1, @RequestParam("param2") int p2, public void myHandle(@RequestParam("param1") String p1,
@RequestHeader("header1") long h1, @CookieValue("cookie1") Cookie c1, @RequestParam("param2") int p2,
@RequestHeader("header1") long h1,
@CookieValue("cookie1") Cookie c1,
HttpServletResponse response) throws IOException { HttpServletResponse response) throws IOException {
response.getWriter().write("test-" + p1 + "-" + p2 + "-" + h1 + "-" + c1.getValue()); response.getWriter().write("test-" + p1 + "-" + p2 + "-" + h1 + "-" + c1.getValue());
} }
@ -930,7 +972,6 @@ public class ServletAnnotationControllerTests {
} }
} }
@Controller @Controller
@RequestMapping("/*.do") @RequestMapping("/*.do")
private static class MyAdaptedController2 { private static class MyAdaptedController2 {
@ -941,8 +982,11 @@ public class ServletAnnotationControllerTests {
} }
@RequestMapping("/myPath2.do") @RequestMapping("/myPath2.do")
public void myHandle(@RequestParam("param1") String p1, int param2, HttpServletResponse response, public void myHandle(@RequestParam("param1") String p1,
@RequestHeader("header1") String h1, @CookieValue("cookie1") String c1) throws IOException { int param2,
HttpServletResponse response,
@RequestHeader("header1") String h1,
@CookieValue("cookie1") String c1) throws IOException {
response.getWriter().write("test-" + p1 + "-" + param2 + "-" + h1 + "-" + c1); response.getWriter().write("test-" + p1 + "-" + param2 + "-" + h1 + "-" + c1);
} }
@ -957,13 +1001,15 @@ public class ServletAnnotationControllerTests {
} }
} }
@Controller @Controller
private static class MyAdaptedControllerBase<T> { private static class MyAdaptedControllerBase<T> {
@RequestMapping("/myPath2.do") @RequestMapping("/myPath2.do")
public void myHandle(@RequestParam("param1") T p1, int param2, @RequestHeader Integer header1, public void myHandle(@RequestParam("param1") T p1,
@CookieValue int cookie1, HttpServletResponse response) throws IOException { int param2,
@RequestHeader Integer header1,
@CookieValue int cookie1,
HttpServletResponse response) throws IOException {
response.getWriter().write("test-" + p1 + "-" + param2 + "-" + header1 + "-" + cookie1); response.getWriter().write("test-" + p1 + "-" + param2 + "-" + header1 + "-" + cookie1);
} }
@ -976,7 +1022,6 @@ public class ServletAnnotationControllerTests {
} }
} }
@RequestMapping("/*.do") @RequestMapping("/*.do")
private static class MyAdaptedController3 extends MyAdaptedControllerBase<String> { private static class MyAdaptedController3 extends MyAdaptedControllerBase<String> {
@ -986,8 +1031,11 @@ public class ServletAnnotationControllerTests {
} }
@Override @Override
public void myHandle(@RequestParam("param1") String p1, int param2, @RequestHeader Integer header1, public void myHandle(@RequestParam("param1") String p1,
@CookieValue int cookie1, HttpServletResponse response) throws IOException { int param2,
@RequestHeader Integer header1,
@CookieValue int cookie1,
HttpServletResponse response) throws IOException {
response.getWriter().write("test-" + p1 + "-" + param2 + "-" + header1 + "-" + cookie1); response.getWriter().write("test-" + p1 + "-" + param2 + "-" + header1 + "-" + cookie1);
} }
@ -1012,7 +1060,6 @@ public class ServletAnnotationControllerTests {
} }
} }
@Controller @Controller
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
private static class EmptyParameterListHandlerMethodController { private static class EmptyParameterListHandlerMethodController {
@ -1029,7 +1076,6 @@ public class ServletAnnotationControllerTests {
} }
} }
@Controller @Controller
public static class MyFormController { public static class MyFormController {
@ -1042,7 +1088,7 @@ public class ServletAnnotationControllerTests {
} }
@RequestMapping("/myPath.do") @RequestMapping("/myPath.do")
public String myHandle(@ModelAttribute("myCommand")TestBean tb, BindingResult errors, ModelMap model) { public String myHandle(@ModelAttribute("myCommand") TestBean tb, BindingResult errors, ModelMap model) {
if (!model.containsKey("myKey")) { if (!model.containsKey("myKey")) {
model.addAttribute("myKey", "myValue"); model.addAttribute("myKey", "myValue");
} }
@ -1050,7 +1096,6 @@ public class ServletAnnotationControllerTests {
} }
} }
@Controller @Controller
public static class MyModelFormController { public static class MyModelFormController {
@ -1063,7 +1108,7 @@ public class ServletAnnotationControllerTests {
} }
@RequestMapping("/myPath.do") @RequestMapping("/myPath.do")
public String myHandle(@ModelAttribute("myCommand")TestBean tb, BindingResult errors, Model model) { public String myHandle(@ModelAttribute("myCommand") TestBean tb, BindingResult errors, Model model) {
if (!model.containsAttribute("myKey")) { if (!model.containsAttribute("myKey")) {
model.addAttribute("myKey", "myValue"); model.addAttribute("myKey", "myValue");
} }
@ -1071,13 +1116,13 @@ public class ServletAnnotationControllerTests {
} }
} }
@Controller @Controller
private static class MyCommandProvidingFormController<T, TB, TB2> extends MyFormController { private static class MyCommandProvidingFormController<T, TB, TB2> extends MyFormController {
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ModelAttribute("myCommand") @ModelAttribute("myCommand")
private TestBean createTestBean(@RequestParam T defaultName, Map<String, Object> model, private TestBean createTestBean(@RequestParam T defaultName,
Map<String, Object> model,
@RequestParam Date date) { @RequestParam Date date) {
model.put("myKey", "myOriginalValue"); model.put("myKey", "myOriginalValue");
return new TestBean(defaultName.getClass().getSimpleName() + ":" + defaultName.toString()); return new TestBean(defaultName.getClass().getSimpleName() + ":" + defaultName.toString());
@ -1085,7 +1130,7 @@ public class ServletAnnotationControllerTests {
@Override @Override
@RequestMapping("/myPath.do") @RequestMapping("/myPath.do")
public String myHandle(@ModelAttribute("myCommand")TestBean tb, BindingResult errors, ModelMap model) { public String myHandle(@ModelAttribute("myCommand") TestBean tb, BindingResult errors, ModelMap model) {
return super.myHandle(tb, errors, model); return super.myHandle(tb, errors, model);
} }
@ -1110,21 +1155,18 @@ public class ServletAnnotationControllerTests {
} }
} }
private static class MySpecialArg { private static class MySpecialArg {
public MySpecialArg(String value) { public MySpecialArg(String value) {
} }
} }
@Controller @Controller
private static class MyTypedCommandProvidingFormController private static class MyTypedCommandProvidingFormController
extends MyCommandProvidingFormController<Integer, TestBean, ITestBean> { extends MyCommandProvidingFormController<Integer, TestBean, ITestBean> {
} }
@Controller @Controller
private static class MyBinderInitializingCommandProvidingFormController extends MyCommandProvidingFormController { private static class MyBinderInitializingCommandProvidingFormController extends MyCommandProvidingFormController {
@ -1138,7 +1180,6 @@ public class ServletAnnotationControllerTests {
} }
} }
@Controller @Controller
private static class MySpecificBinderInitializingCommandProvidingFormController private static class MySpecificBinderInitializingCommandProvidingFormController
extends MyCommandProvidingFormController { extends MyCommandProvidingFormController {
@ -1155,7 +1196,6 @@ public class ServletAnnotationControllerTests {
} }
} }
private static class MyWebBindingInitializer implements WebBindingInitializer { private static class MyWebBindingInitializer implements WebBindingInitializer {
public void initBinder(WebDataBinder binder, WebRequest request) { public void initBinder(WebDataBinder binder, WebRequest request) {
@ -1166,7 +1206,6 @@ public class ServletAnnotationControllerTests {
} }
} }
private static class MySpecialArgumentResolver implements WebArgumentResolver { private static class MySpecialArgumentResolver implements WebArgumentResolver {
public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) { public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) {
@ -1177,7 +1216,6 @@ public class ServletAnnotationControllerTests {
} }
} }
@Controller @Controller
@RequestMapping("/myPath.do") @RequestMapping("/myPath.do")
private static class MyParameterDispatchingController { private static class MyParameterDispatchingController {
@ -1223,7 +1261,6 @@ public class ServletAnnotationControllerTests {
} }
} }
@Controller @Controller
@RequestMapping(value = "/myPath.do", params = {"active"}) @RequestMapping(value = "/myPath.do", params = {"active"})
private static class MyConstrainedParameterDispatchingController { private static class MyConstrainedParameterDispatchingController {
@ -1239,14 +1276,12 @@ public class ServletAnnotationControllerTests {
} }
} }
@Controller @Controller
@RequestMapping(value = "/*.do", method = RequestMethod.POST, params = "myParam=myValue") @RequestMapping(value = "/*.do", method = RequestMethod.POST, params = "myParam=myValue")
private static class MyPostMethodNameDispatchingController extends MethodNameDispatchingController { private static class MyPostMethodNameDispatchingController extends MethodNameDispatchingController {
} }
@Controller @Controller
@RequestMapping("/myApp/*") @RequestMapping("/myApp/*")
private static class MyRelativePathDispatchingController { private static class MyRelativePathDispatchingController {
@ -1272,7 +1307,6 @@ public class ServletAnnotationControllerTests {
} }
} }
@Controller @Controller
private static class MyNullCommandController { private static class MyNullCommandController {
@ -1287,8 +1321,11 @@ public class ServletAnnotationControllerTests {
} }
@RequestMapping("/myPath") @RequestMapping("/myPath")
public void handle(@ModelAttribute TestBean testBean, Errors errors, @ModelAttribute TestPrincipal modelPrinc, public void handle(@ModelAttribute TestBean testBean,
OtherPrincipal requestPrinc, Writer writer) throws IOException { Errors errors,
@ModelAttribute TestPrincipal modelPrinc,
OtherPrincipal requestPrinc,
Writer writer) throws IOException {
assertNull(testBean); assertNull(testBean);
assertNotNull(modelPrinc); assertNotNull(modelPrinc);
assertNotNull(requestPrinc); assertNotNull(requestPrinc);
@ -1298,7 +1335,6 @@ public class ServletAnnotationControllerTests {
} }
} }
private static class TestPrincipal implements Principal { private static class TestPrincipal implements Principal {
public String getName() { public String getName() {
@ -1306,7 +1342,6 @@ public class ServletAnnotationControllerTests {
} }
} }
private static class OtherPrincipal implements Principal { private static class OtherPrincipal implements Principal {
public String getName() { public String getName() {
@ -1314,7 +1349,6 @@ public class ServletAnnotationControllerTests {
} }
} }
private static class TestViewResolver implements ViewResolver { private static class TestViewResolver implements ViewResolver {
public View resolveViewName(final String viewName, Locale locale) throws Exception { public View resolveViewName(final String viewName, Locale locale) throws Exception {
@ -1345,9 +1379,9 @@ public class ServletAnnotationControllerTests {
} }
List<TestBean> testBeans = (List<TestBean>) model.get("testBeanList"); List<TestBean> testBeans = (List<TestBean>) model.get("testBeanList");
if (errors.hasFieldErrors("age")) { if (errors.hasFieldErrors("age")) {
response.getWriter().write(viewName + "-" + tb.getName() + "-" + response.getWriter()
errors.getFieldError("age").getCode() + "-" + testBeans.get(0).getName() + "-" + .write(viewName + "-" + tb.getName() + "-" + errors.getFieldError("age").getCode() +
model.get("myKey")); "-" + testBeans.get(0).getName() + "-" + model.get("myKey"));
} }
else { else {
response.getWriter().write(viewName + "-" + tb.getName() + "-" + tb.getAge() + "-" + response.getWriter().write(viewName + "-" + tb.getName() + "-" + tb.getAge() + "-" +
@ -1358,7 +1392,6 @@ public class ServletAnnotationControllerTests {
} }
} }
public static class ParentController { public static class ParentController {
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
@ -1366,7 +1399,6 @@ public class ServletAnnotationControllerTests {
} }
} }
@Controller @Controller
@RequestMapping("/child/test") @RequestMapping("/child/test")
public static class ChildController extends ParentController { public static class ChildController extends ParentController {
@ -1376,68 +1408,66 @@ public class ServletAnnotationControllerTests {
} }
} }
@Controller @Controller
public static class RequiredParamController { public static class RequiredParamController {
@RequestMapping("/myPath.do") @RequestMapping("/myPath.do")
public void myHandle(@RequestParam(value = "id", required = true) String id, public void myHandle(@RequestParam(value = "id", required = true) int id,
@RequestHeader(value = "header", required = true) String header) { @RequestHeader(value = "header", required = true) String header) {
} }
} }
@Controller @Controller
public static class OptionalParamController { public static class OptionalParamController {
@RequestMapping("/myPath.do") @RequestMapping("/myPath.do")
public void myHandle(@RequestParam(required = false) String id, @RequestParam(required = false) boolean flag, public void myHandle(@RequestParam(required = false) String id,
@RequestHeader(value = "header", required = false) String header, HttpServletResponse response) @RequestParam(required = false) boolean flag,
throws IOException { @RequestHeader(value = "header", required = false) String header,
HttpServletResponse response) throws IOException {
response.getWriter().write(String.valueOf(id) + "-" + flag + "-" + String.valueOf(header)); response.getWriter().write(String.valueOf(id) + "-" + flag + "-" + String.valueOf(header));
} }
} }
@Controller @Controller
public static class DefaultValueParamController { public static class DefaultValueParamController {
@RequestMapping("/myPath.do") @RequestMapping("/myPath.do")
public void myHandle(@RequestParam(value = "id", defaultValue = "foo") String id, public void myHandle(@RequestParam(value = "id", defaultValue = "foo") String id,
@RequestHeader(defaultValue = "bar") String header, HttpServletResponse response) @RequestHeader(defaultValue = "bar") String header,
throws IOException { HttpServletResponse response) throws IOException {
response.getWriter().write(String.valueOf(id) + "-" + String.valueOf(header)); response.getWriter().write(String.valueOf(id) + "-" + String.valueOf(header));
} }
} }
@Controller @Controller
public static class MethodNotAllowedController { public static class MethodNotAllowedController {
@RequestMapping(value="/myPath.do", method = RequestMethod.DELETE) @RequestMapping(value = "/myPath.do", method = RequestMethod.DELETE)
public void delete() { public void delete() {
} }
@RequestMapping(value="/myPath.do", method = RequestMethod.HEAD) @RequestMapping(value = "/myPath.do", method = RequestMethod.HEAD)
public void head() { public void head() {
} }
@RequestMapping(value="/myPath.do", method = RequestMethod.OPTIONS) @RequestMapping(value = "/myPath.do", method = RequestMethod.OPTIONS)
public void options() { public void options() {
} }
@RequestMapping(value="/myPath.do", method = RequestMethod.POST)
@RequestMapping(value = "/myPath.do", method = RequestMethod.POST)
public void post() { public void post() {
} }
@RequestMapping(value="/myPath.do", method = RequestMethod.PUT) @RequestMapping(value = "/myPath.do", method = RequestMethod.PUT)
public void put() { public void put() {
} }
@RequestMapping(value="/myPath.do", method = RequestMethod.TRACE) @RequestMapping(value = "/myPath.do", method = RequestMethod.TRACE)
public void trace() { public void trace() {
} }
@RequestMapping(value="/otherPath.do", method = RequestMethod.GET) @RequestMapping(value = "/otherPath.do", method = RequestMethod.GET)
public void get() { public void get() {
} }
} }
@ -1445,7 +1475,6 @@ public class ServletAnnotationControllerTests {
@Controller @Controller
public static class PathOrderingController { public static class PathOrderingController {
@RequestMapping(value = {"/dir/myPath1.do", "/**/*.do"}) @RequestMapping(value = {"/dir/myPath1.do", "/**/*.do"})
public void method1(Writer writer) throws IOException { public void method1(Writer writer) throws IOException {
writer.write("method1"); writer.write("method1");
@ -1466,4 +1495,26 @@ public class ServletAnnotationControllerTests {
} }
} }
public static class MyMessageConverter implements HttpMessageConverter {
public boolean supports(Class clazz) {
return true;
}
public List getSupportedMediaTypes() {
return Collections.singletonList(new MediaType("application", "pdf"));
}
public Object read(Class clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
throw new HttpMessageNotReadableException("Could not read");
}
public void write(Object o, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
throw new UnsupportedOperationException("Not implemented");
}
}
} }

View File

@ -5,6 +5,7 @@ import java.io.Writer;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;
@ -24,9 +25,7 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext; import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.DispatcherServlet;
/** /** @author Arjen Poutsma */
* @author Arjen Poutsma
*/
public class UriTemplateServletAnnotationControllerTests { public class UriTemplateServletAnnotationControllerTests {
private DispatcherServlet servlet; private DispatcherServlet servlet;
@ -92,6 +91,16 @@ public class UriTemplateServletAnnotationControllerTests {
} }
@Test
public void typeConversionError() throws Exception {
initServlet(SimpleUriTemplateController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.xml");
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals("Invalid response status code", HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
}
private void initServlet(final Class<?> controllerclass) throws ServletException { private void initServlet(final Class<?> controllerclass) throws ServletException {
servlet = new DispatcherServlet() { servlet = new DispatcherServlet() {
@Override @Override