diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfiguration.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfiguration.java index 445fb79c629..e87b8ced3ad 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfiguration.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfiguration.java @@ -16,21 +16,17 @@ package org.springframework.web.servlet.config.annotation; -import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.format.support.FormattingConversionService; +import org.springframework.format.FormatterRegistry; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.validation.Validator; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.HandlerExceptionResolver; -import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; /** * Provides default configuration for Spring MVC applications by registering Spring MVC infrastructure components @@ -83,21 +79,15 @@ class WebMvcConfiguration extends WebMvcConfigurationSupport { protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurers.configureDefaultServletHandling(configurer); } - + @Override - @Bean - public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { - RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(); - - List argumentResolvers = new ArrayList(); + protected void addArgumentResolvers(List argumentResolvers) { configurers.addArgumentResolvers(argumentResolvers); - adapter.setCustomArgumentResolvers(argumentResolvers); + } - List returnValueHandlers = new ArrayList(); + @Override + protected void addReturnValueHandlers(List returnValueHandlers) { configurers.addReturnValueHandlers(returnValueHandlers); - adapter.setCustomReturnValueHandlers(returnValueHandlers); - - return adapter; } @Override @@ -105,31 +95,20 @@ class WebMvcConfiguration extends WebMvcConfigurationSupport { configurers.configureMessageConverters(converters); } + @Override - @Bean - public FormattingConversionService mvcConversionService() { - FormattingConversionService conversionService = super.mvcConversionService(); - configurers.addFormatters(conversionService); - return conversionService; + protected void addFormatters(FormatterRegistry registry) { + configurers.addFormatters(registry); } @Override - @Bean - public Validator mvcValidator() { - Validator validator = configurers.getValidator(); - return validator != null ? validator : super.mvcValidator(); + protected Validator getValidator() { + return configurers.getValidator(); } - @Bean - public HandlerExceptionResolverComposite handlerExceptionResolver() throws Exception { - List resolvers = new ArrayList(); - configurers.configureHandlerExceptionResolvers(resolvers); - - HandlerExceptionResolverComposite composite = super.handlerExceptionResolver(); - if (resolvers.size() != 0) { - composite.setExceptionResolvers(resolvers); - } - return composite; + @Override + protected void configureHandlerExceptionResolvers(List exceptionResolvers) { + configurers.configureHandlerExceptionResolvers(exceptionResolvers); } - + } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java index 99559ba4b4f..dc3988d9d70 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java @@ -29,6 +29,9 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; +import org.springframework.format.Formatter; +import org.springframework.format.FormatterRegistry; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.converter.ByteArrayHttpMessageConverter; @@ -50,6 +53,8 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.context.ServletContextAware; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.HandlerExceptionResolver; @@ -75,8 +80,8 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv * *

If using @{@link EnableWebMvc} and extending from {@link WebMvcConfigurerAdapter} does not give you the level * of flexibility you need, consider extending directly from this class instead. Remember to add @{@link Configuration} - * to you subclass and @{@link Bean} to any @{@link Bean} methods you choose to override. A few example reasons for - * extending this class include providing a custom {@link MessageCodesResolver}, changing the order of + * to your subclass and @{@link Bean} to any superclass @{@link Bean} methods you choose to override. A few example + * reasons for extending this class include providing a custom {@link MessageCodesResolver}, changing the order of * {@link HandlerMapping} instances, plugging in a variant of any of the beans provided by this class, and so on. * *

This class registers the following {@link HandlerMapping}s:

@@ -159,7 +164,9 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Override this method to configure handler interceptors including interceptors mapped to path patterns. + * Override this method to configure the Spring MVC interceptors to use. Interceptors allow requests to + * be pre- and post-processed before and after controller invocation. They can be registered to apply + * to all requests or be limited to a set of path patterns. * @see InterceptorConfigurer */ protected void configureInterceptors(InterceptorConfigurer configurer) { @@ -213,7 +220,9 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Override this method to configure serving static resources such as images and css files through Spring MVC. + * Override this method to configure a handler for serving static resources such as images, js, and, css files + * through Spring MVC including setting cache headers optimized for efficient loading in a web browser. + * Resources can be served out of locations under web application root, from the classpath, and others. * @see ResourceConfigurer */ protected void configureResourceHandling(ResourceConfigurer configurer) { @@ -232,7 +241,10 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Override this method to configure serving static resources through the Servlet container's default Servlet. + * Override this method to configure a handler for delegating unhandled requests by forwarding to the + * Servlet container's default servlet. This is commonly used when the {@link DispatcherServlet} is + * mapped to "/", which results in cleaner URLs (without a servlet prefix) but may need to still allow + * some requests (e.g. static resources) to be handled by the Servlet container's default servlet. * @see DefaultServletHandlerConfigurer */ protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { @@ -240,7 +252,13 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw /** * Returns a {@link RequestMappingHandlerAdapter} for processing requests using annotated controller methods. - * Also see {@link #initWebBindingInitializer()} for configuring data binding globally. + * Also see the following other methods as an alternative to overriding this method: + *
    + *
  • {@link #initWebBindingInitializer()} for configuring data binding globally. + *
  • {@link #addArgumentResolvers(List)} for adding custom argument resolvers. + *
  • {@link #addReturnValueHandlers(List)} for adding custom return value handlers. + *
  • {@link #configureMessageConverters(List)} for adding custom message converters. + *
*/ @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { @@ -249,9 +267,17 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw webBindingInitializer.setValidator(mvcValidator()); extendWebBindingInitializer(webBindingInitializer); + List argumentResolvers = new ArrayList(); + addArgumentResolvers(argumentResolvers); + + List returnValueHandlers = new ArrayList(); + addReturnValueHandlers(returnValueHandlers); + RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter(); adapter.setMessageConverters(getMessageConverters()); adapter.setWebBindingInitializer(webBindingInitializer); + adapter.setCustomArgumentResolvers(argumentResolvers); + adapter.setCustomReturnValueHandlers(returnValueHandlers); return adapter; } @@ -262,11 +288,31 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw protected void extendWebBindingInitializer(ConfigurableWebBindingInitializer webBindingInitializer) { } + /** + * Override this method to add custom argument resolvers to use in addition to the ones registered by default + * internally by the {@link RequestMappingHandlerAdapter}. + *

Generally custom argument resolvers are invoked first. However this excludes default argument resolvers that + * rely on the presence of annotations (e.g. {@code @RequestParameter}, {@code @PathVariable}, etc.). Those + * argument resolvers are not customizable without configuring RequestMappingHandlerAdapter directly. + */ + protected void addArgumentResolvers(List argumentResolvers) { + } + + /** + * Override this method to add custom return value handlers to use in addition to the ones registered by default + * internally by the {@link RequestMappingHandlerAdapter}. + *

Generally custom return value handlers are invoked first. However this excludes default return value handlers + * that rely on the presence of annotations (e.g. {@code @ResponseBody}, {@code @ModelAttribute}, etc.). Those + * handlers are not customizable without configuring RequestMappingHandlerAdapter directly. + */ + protected void addReturnValueHandlers(List returnValueHandlers) { + } + /** * Provides access to the shared {@link HttpMessageConverter}s used by the * {@link RequestMappingHandlerAdapter} and the {@link ExceptionHandlerExceptionResolver}. - * This method cannot be extended directly, use {@link #configureMessageConverters(List)} add custom converters. - * Also see {@link #addDefaultHttpMessageConverters(List)} to easily add a set of default converters. + * This method cannot be extended directly, use {@link #configureMessageConverters(List)} add custom converters. + * For the list of message converters added by default see {@link #addDefaultHttpMessageConverters(List)}. */ protected final List> getMessageConverters() { if (messageConverters == null) { @@ -282,7 +328,7 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw /** * Override this method to add custom {@link HttpMessageConverter}s to use with * the {@link RequestMappingHandlerAdapter} and the {@link ExceptionHandlerExceptionResolver}. - * If any converters are added, default converters will not be added automatically. + * If any converters are added through this method, default converters are added automatically. * See {@link #addDefaultHttpMessageConverters(List)} for adding default converters to the list. * @param messageConverters the list to add converters to */ @@ -318,21 +364,34 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw /** * Returns a {@link FormattingConversionService} for use with annotated controller methods and the - * {@code spring:eval} JSP tag. + * {@code spring:eval} JSP tag. Also see {@link #addFormatters(FormatterRegistry)} as an alternative + * to overriding this method. */ @Bean public FormattingConversionService mvcConversionService() { - return new DefaultFormattingConversionService(); + FormattingConversionService conversionService = new DefaultFormattingConversionService(); + addFormatters(conversionService); + return conversionService; + } + + /** + * Override this method to add custom {@link Converter}s and {@link Formatter}s. + */ + protected void addFormatters(FormatterRegistry registry) { } /** * Returns {@link Validator} for validating {@code @ModelAttribute} and {@code @RequestBody} arguments of - * annotated controller methods. If a JSR-303 implementation is available on the classpath, the returned - * instance is LocalValidatorFactoryBean. Otherwise a no-op validator is returned. + * annotated controller methods. This method is closed for extension. Use {@link #getValidator()} to + * provide a custom validator. */ @Bean - public Validator mvcValidator() { - if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) { + Validator mvcValidator() { + Validator validator = getValidator(); + if (validator != null) { + return validator; + } + else if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) { Class clazz; try { String className = "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"; @@ -355,6 +414,16 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } } + /** + * Override this method to provide a custom {@link Validator} type. If this method returns {@code null}, by + * a check is made for the presence of a JSR-303 implementation on the classpath - if available a + * {@link org.springframework.validation.beanvalidation.LocalValidatorFactoryBean} instance is created. + * Otherwise if no JSR-303 implementation is detected, a no-op {@link Validator} is returned instead. + */ + protected Validator getValidator() { + return null; + } + /** * Returns a {@link HttpRequestHandlerAdapter} for processing requests with {@link HttpRequestHandler}s. */ @@ -372,28 +441,51 @@ public abstract class WebMvcConfigurationSupport implements ApplicationContextAw } /** - * Returns a {@link HandlerExceptionResolverComposite} with this chain of exception resolvers: + * Returns a {@link HandlerExceptionResolverComposite} that contains a list of exception resolvers. + * This method is closed for extension. Use {@link #configureHandlerExceptionResolvers(List) to + * customize the list of exception resolvers. + */ + @Bean + HandlerExceptionResolver handlerExceptionResolver() throws Exception { + List exceptionResolvers = new ArrayList(); + configureHandlerExceptionResolvers(exceptionResolvers); + + if (exceptionResolvers.isEmpty()) { + addDefaultHandlerExceptionResolvers(exceptionResolvers); + } + + HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite(); + composite.setOrder(0); + composite.setExceptionResolvers(exceptionResolvers); + return composite; + } + + /** + * Override this method to configure the list of {@link HandlerExceptionResolver}s to use for handling + * unresolved controller exceptions. If any exception resolvers are added through this method, default + * exception resolvers are not added automatically. For the list of exception resolvers added by + * default see {@link #addDefaultHandlerExceptionResolvers(List)}. + */ + protected void configureHandlerExceptionResolvers(List exceptionResolvers) { + } + + /** + * A method available to subclasses for adding default {@link HandlerExceptionResolver}s. + *

Adds the following exception resolvers: *

    *
  • {@link ExceptionHandlerExceptionResolver} for handling exceptions through @{@link ExceptionHandler} methods. *
  • {@link ResponseStatusExceptionResolver} for exceptions annotated with @{@link ResponseStatus}. *
  • {@link DefaultHandlerExceptionResolver} for resolving known Spring exception types *
*/ - @Bean - public HandlerExceptionResolverComposite handlerExceptionResolver() throws Exception { + protected final void addDefaultHandlerExceptionResolvers(List exceptionResolvers) { ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = new ExceptionHandlerExceptionResolver(); exceptionHandlerExceptionResolver.setMessageConverters(getMessageConverters()); exceptionHandlerExceptionResolver.afterPropertiesSet(); - List exceptionResolvers = new ArrayList(); exceptionResolvers.add(exceptionHandlerExceptionResolver); exceptionResolvers.add(new ResponseStatusExceptionResolver()); exceptionResolvers.add(new DefaultHandlerExceptionResolver()); - - HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite(); - composite.setOrder(0); - composite.setExceptionResolvers(exceptionResolvers); - return composite; } - + } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java index 5076ffb3510..8e1e9b2b3bd 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java @@ -95,16 +95,18 @@ public interface WebMvcConfigurer { void configureHandlerExceptionResolvers(List exceptionResolvers); /** - * Configure the Spring MVC interceptors to use. Interceptors can be of type {@link HandlerInterceptor} or - * {@link WebRequestInterceptor}. They allow requests to be pre/post processed before/after controller - * invocation. Interceptors can be registered to apply to all requests or limited to a set of path patterns. + * Configure the Spring MVC interceptors to use. Interceptors allow requests to be pre- and post-processed + * before and after controller invocation. They can be registered to apply to all requests or be limited + * to a set of path patterns. * @see InterceptorConfigurer */ void configureInterceptors(InterceptorConfigurer configurer); /** - * Configure the view controllers to use. A view controller is used to map a URL path directly to a view name. - * This is convenient when a request does not require controller logic. + * Configure view controllers. View controllers provide a direct mapping between a URL path and view name. + * This is useful when serving requests that don't require application-specific controller logic and can + * be forwarded directly to a view for rendering. + * @see ViewControllerConfigurer */ void configureViewControllers(ViewControllerConfigurer configurer); @@ -112,6 +114,7 @@ public interface WebMvcConfigurer { * Configure a handler for serving static resources such as images, js, and, css files through Spring MVC * including setting cache headers optimized for efficient loading in a web browser. Resources can be served * out of locations under web application root, from the classpath, and others. + * @see ResourceConfigurer */ void configureResourceHandling(ResourceConfigurer configurer); @@ -120,6 +123,7 @@ public interface WebMvcConfigurer { * servlet. This is commonly used when the {@link DispatcherServlet} is mapped to "/", which results in * cleaner URLs (without a servlet prefix) but may need to still allow some requests (e.g. static resources) * to be handled by the Servlet container's default servlet. + * @see DefaultServletHandlerConfigurer */ void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationTests.java index 07e2a1ed00d..18e8693cb79 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationTests.java @@ -143,20 +143,23 @@ public class WebMvcConfigurationTests { verify(configurer); } - @SuppressWarnings("unchecked") @Test public void handlerExceptionResolver() throws Exception { - configurer.configureMessageConverters(isA(List.class)); - configurer.configureHandlerExceptionResolvers(isA(List.class)); + Capture>> converters = new Capture>>(); + Capture> exceptionResolvers = new Capture>(); + + configurer.configureMessageConverters(capture(converters)); + configurer.configureHandlerExceptionResolvers(capture(exceptionResolvers)); replay(configurer); mvcConfiguration.setConfigurers(Arrays.asList(configurer)); - List actual = mvcConfiguration.handlerExceptionResolver().getExceptionResolvers(); + mvcConfiguration.handlerExceptionResolver(); - assertEquals(3, actual.size()); - assertTrue(actual.get(0) instanceof ExceptionHandlerExceptionResolver); - assertTrue(actual.get(1) instanceof ResponseStatusExceptionResolver); - assertTrue(actual.get(2) instanceof DefaultHandlerExceptionResolver); + assertEquals(3, exceptionResolvers.getValue().size()); + assertTrue(exceptionResolvers.getValue().get(0) instanceof ExceptionHandlerExceptionResolver); + assertTrue(exceptionResolvers.getValue().get(1) instanceof ResponseStatusExceptionResolver); + assertTrue(exceptionResolvers.getValue().get(2) instanceof DefaultHandlerExceptionResolver); + assertTrue(converters.getValue().size() > 0); verify(configurer); }