Rename EnableMvcConfiguration->EnableWebMvc, refine method names in WebMvcConfigurer, fix issue with MappedInterceptors

This commit is contained in:
Rossen Stoyanchev 2011-05-11 18:02:07 +00:00
parent c8bc54e0cc
commit 726e920857
10 changed files with 250 additions and 139 deletions

View File

@ -34,7 +34,7 @@ import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler
* by the Servlet container's default servlet. * by the Servlet container's default servlet.
* *
* <p>It is important the configured handler remains last in the order of all {@link HandlerMapping} instances in * <p>It is important the configured handler remains last in the order of all {@link HandlerMapping} instances in
* the Spring MVC web application context. That is is the case if relying on @{@link EnableMvcConfiguration}. * the Spring MVC web application context. That is is the case if relying on @{@link EnableWebMvc}.
* However, if you register your own HandlerMapping instance sure to set its "order" property to a value lower * However, if you register your own HandlerMapping instance sure to set its "order" property to a value lower
* than that of the {@link DefaultServletHttpRequestHandler}, which is {@link Integer#MAX_VALUE}. * than that of the {@link DefaultServletHttpRequestHandler}, which is {@link Integer#MAX_VALUE}.
* *

View File

@ -30,28 +30,28 @@ import org.springframework.web.servlet.DispatcherServlet;
* in @{@link Controller} classes. * in @{@link Controller} classes.
* <pre> * <pre>
* &#064;Configuration * &#064;Configuration
* &#064;EnableMvcConfiguration * &#064;EnableWebMvc
* &#064;ComponentScan( * &#064;ComponentScan(
* basePackageClasses = { MyMvcConfiguration.class }, * basePackageClasses = { MyConfiguration.class },
* excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = Configuration.class) } * excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = Configuration.class) }
* ) * )
* public class MyMvcConfiguration { * public class MyConfiguration {
* *
* } * }
* </pre> * </pre>
* <p>To customize the imported configuration you simply implement {@link MvcConfigurer}, or more likely extend * <p>To customize the imported configuration you simply implement {@link WebMvcConfigurer}, or more likely extend
* {@link MvcConfigurerSupport} overriding selected methods only. The most obvious place to do this is * {@link WebMvcConfigurerAdapter} overriding selected methods only. The most obvious place to do this is
* the @{@link Configuration} class that enabled the Spring MVC configuration via @{@link EnableMvcConfiguration}. * the @{@link Configuration} class that enabled the Spring MVC configuration via @{@link EnableWebMvc}.
* However any @{@link Configuration} class and more generally any Spring bean can implement {@link MvcConfigurer} * However any @{@link Configuration} class and more generally any Spring bean can implement {@link WebMvcConfigurer}
* to be detected and given an opportunity to customize Spring MVC configuration at startup. * to be detected and given an opportunity to customize Spring MVC configuration at startup.
* <pre> * <pre>
* &#064;Configuration * &#064;Configuration
* &#064;EnableMvcConfiguration * &#064;EnableWebMvc
* &#064;ComponentScan( * &#064;ComponentScan(
* basePackageClasses = { MyMvcConfiguration.class }, * basePackageClasses = { MyConfiguration.class },
* excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = Configuration.class) } * excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = Configuration.class) }
* ) * )
* public class MyMvcConfiguration extends MvcConfigurerSupport { * public class MyConfiguration extends WebMvcConfigurerAdapter {
* *
* &#064;Override * &#064;Override
* public void registerFormatters(FormatterRegistry formatterRegistry) { * public void registerFormatters(FormatterRegistry formatterRegistry) {
@ -68,8 +68,8 @@ import org.springframework.web.servlet.DispatcherServlet;
* } * }
* </pre> * </pre>
* *
* @see MvcConfigurer * @see WebMvcConfigurer
* @see MvcConfigurerSupport * @see WebMvcConfigurerAdapter
* *
* @author Dave Syer * @author Dave Syer
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
@ -78,6 +78,6 @@ import org.springframework.web.servlet.DispatcherServlet;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Documented @Documented
@Import(MvcConfiguration.class) @Import(WebMvcConfiguration.class)
public @interface EnableMvcConfiguration { public @interface EnableWebMvc {
} }

View File

@ -24,7 +24,6 @@ import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.MappedInterceptor; import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.handler.MappedInterceptors;
import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter; import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter;
/** /**
@ -117,10 +116,10 @@ public class InterceptorConfigurer {
} }
/** /**
* Returns a {@link MappedInterceptors} instance with all registered interceptors. * Returns all registered interceptors.
*/ */
protected MappedInterceptors getMappedInterceptors() { protected MappedInterceptor[] getInterceptors() {
return new MappedInterceptors(mappedInterceptors.toArray(new MappedInterceptor[mappedInterceptors.size()])); return mappedInterceptors.toArray(new MappedInterceptor[mappedInterceptors.size()]);
} }
} }

View File

@ -58,6 +58,7 @@ import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor; import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite; import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.handler.MappedInterceptors; import org.springframework.web.servlet.handler.MappedInterceptors;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.Controller; import org.springframework.web.servlet.mvc.Controller;
@ -72,10 +73,10 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
/** /**
* Provides default configuration for Spring MVC applications. Registers Spring MVC infrastructure components to be * Provides default configuration for Spring MVC applications. Registers Spring MVC infrastructure components to be
* detected by the {@link DispatcherServlet}. Further below is a list of registered instances. This configuration is * detected by the {@link DispatcherServlet}. Further below is a list of registered instances. This configuration is
* enabled through the {@link EnableMvcConfiguration} annotation. * enabled through the {@link EnableWebMvc} annotation.
* *
* <p>A number of options are available for customizing the default configuration provided by this class. * <p>A number of options are available for customizing the default configuration provided by this class.
* See {@link EnableMvcConfiguration} and {@link MvcConfigurer} for details. * See {@link EnableWebMvc} and {@link WebMvcConfigurer} for details.
* *
* <p>Registers these handler mappings: * <p>Registers these handler mappings:
* <ul> * <ul>
@ -87,7 +88,7 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
* </ul> * </ul>
* *
* <p><strong>Note:</strong> that the SimpleUrlHandlerMapping instances above will have empty URL maps and * <p><strong>Note:</strong> that the SimpleUrlHandlerMapping instances above will have empty URL maps and
* hence no effect until explicitly configured via {@link MvcConfigurer}. * hence no effect until explicitly configured via {@link WebMvcConfigurer}.
* *
* <p>Registers these handler adapters: * <p>Registers these handler adapters:
* <ul> * <ul>
@ -110,23 +111,23 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
* <li>{@link MappedInterceptors} containing a list Spring MVC lifecycle interceptors. * <li>{@link MappedInterceptors} containing a list Spring MVC lifecycle interceptors.
* </ul> * </ul>
* *
* @see EnableMvcConfiguration * @see EnableWebMvc
* @see MvcConfigurer * @see WebMvcConfigurer
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1
*/ */
@Configuration @Configuration
class MvcConfiguration implements ApplicationContextAware, ServletContextAware { class WebMvcConfiguration implements ApplicationContextAware, ServletContextAware {
private final MvcConfigurerComposite configurers = new MvcConfigurerComposite(); private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
private ServletContext servletContext; private ServletContext servletContext;
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
@Autowired(required = false) @Autowired(required = false)
public void setConfigurers(List<MvcConfigurer> configurers) { public void setConfigurers(List<WebMvcConfigurer> configurers) {
this.configurers.addConfigurers(configurers); this.configurers.addConfigurers(configurers);
} }
@ -139,29 +140,41 @@ class MvcConfiguration implements ApplicationContextAware, ServletContextAware {
} }
@Bean @Bean
RequestMappingHandlerMapping requestMappingHandlerMapping() { public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping(); RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping();
mapping.setMappedInterceptors(getMappedInterceptors());
mapping.setOrder(0); mapping.setOrder(0);
return mapping; return mapping;
} }
@Bean private MappedInterceptor[] getMappedInterceptors() {
HandlerMapping viewControllerHandlerMapping() { InterceptorConfigurer configurer = new InterceptorConfigurer();
ViewControllerConfigurer configurer = new ViewControllerConfigurer(); configurers.configureInterceptors(configurer);
configurer.setOrder(1); configurer.addInterceptor(new ConversionServiceExposingInterceptor(conversionService()));
configurers.addViewControllers(configurer); return configurer.getInterceptors();
return configurer.getHandlerMapping();
} }
@Bean @Bean
BeanNameUrlHandlerMapping beanNameHandlerMapping() { public HandlerMapping viewControllerHandlerMapping() {
ViewControllerConfigurer configurer = new ViewControllerConfigurer();
configurer.setOrder(1);
configurers.configureViewControllers(configurer);
SimpleUrlHandlerMapping handlerMapping = configurer.getHandlerMapping();
handlerMapping.setMappedInterceptors(getMappedInterceptors());
return handlerMapping;
}
@Bean
public BeanNameUrlHandlerMapping beanNameHandlerMapping() {
BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping(); BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
mapping.setOrder(2); mapping.setOrder(2);
mapping.setMappedInterceptors(getMappedInterceptors());
return mapping; return mapping;
} }
@Bean @Bean
HandlerMapping resourceHandlerMapping() { public HandlerMapping resourceHandlerMapping() {
ResourceConfigurer configurer = new ResourceConfigurer(applicationContext, servletContext); ResourceConfigurer configurer = new ResourceConfigurer(applicationContext, servletContext);
configurer.setOrder(Integer.MAX_VALUE-1); configurer.setOrder(Integer.MAX_VALUE-1);
configurers.configureResourceHandling(configurer); configurers.configureResourceHandling(configurer);
@ -169,14 +182,14 @@ class MvcConfiguration implements ApplicationContextAware, ServletContextAware {
} }
@Bean @Bean
HandlerMapping defaultServletHandlerMapping() { public HandlerMapping defaultServletHandlerMapping() {
DefaultServletHandlerConfigurer configurer = new DefaultServletHandlerConfigurer(servletContext); DefaultServletHandlerConfigurer configurer = new DefaultServletHandlerConfigurer(servletContext);
configurers.configureDefaultServletHandling(configurer); configurers.configureDefaultServletHandling(configurer);
return configurer.getHandlerMapping(); return configurer.getHandlerMapping();
} }
@Bean @Bean
RequestMappingHandlerAdapter requestMappingHandlerAdapter() { public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter(); RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
ConfigurableWebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer(); ConfigurableWebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer();
@ -185,29 +198,32 @@ class MvcConfiguration implements ApplicationContextAware, ServletContextAware {
adapter.setWebBindingInitializer(bindingInitializer); adapter.setWebBindingInitializer(bindingInitializer);
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>(); List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
configurers.addCustomArgumentResolvers(argumentResolvers); configurers.addArgumentResolvers(argumentResolvers);
adapter.setCustomArgumentResolvers(argumentResolvers); adapter.setCustomArgumentResolvers(argumentResolvers);
List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>(); List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
configurers.addCustomReturnValueHandlers(returnValueHandlers); configurers.addReturnValueHandlers(returnValueHandlers);
adapter.setCustomReturnValueHandlers(returnValueHandlers); adapter.setCustomReturnValueHandlers(returnValueHandlers);
List<HttpMessageConverter<?>> converters = getDefaultHttpMessageConverters(); List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
configurers.configureMessageConverters(converters); configurers.configureMessageConverters(converters);
if (converters.size() == 0) {
addDefaultHttpMessageConverters(converters);
}
adapter.setMessageConverters(converters); adapter.setMessageConverters(converters);
return adapter; return adapter;
} }
@Bean(name="mvcConversionService") @Bean(name="mvcConversionService")
FormattingConversionService conversionService() { public FormattingConversionService conversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService(); FormattingConversionService conversionService = new DefaultFormattingConversionService();
configurers.registerFormatters(conversionService); configurers.addFormatters(conversionService);
return conversionService; return conversionService;
} }
@Bean(name="mvcValidator") @Bean(name="mvcValidator")
Validator validator() { public Validator validator() {
Validator validator = configurers.getValidator(); Validator validator = configurers.getValidator();
if (validator != null) { if (validator != null) {
return validator; return validator;
@ -216,7 +232,7 @@ class MvcConfiguration implements ApplicationContextAware, ServletContextAware {
Class<?> clazz; Class<?> clazz;
try { try {
String className = "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"; String className = "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean";
clazz = ClassUtils.forName(className, MvcConfiguration.class.getClassLoader()); clazz = ClassUtils.forName(className, WebMvcConfiguration.class.getClassLoader());
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
throw new BeanInitializationException("Could not find default validator"); throw new BeanInitializationException("Could not find default validator");
} catch (LinkageError e) { } catch (LinkageError e) {
@ -236,11 +252,10 @@ class MvcConfiguration implements ApplicationContextAware, ServletContextAware {
} }
} }
private List<HttpMessageConverter<?>> getDefaultHttpMessageConverters() { private void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> converters) {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(); StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setWriteAcceptCharset(false); stringConverter.setWriteAcceptCharset(false);
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(new ByteArrayHttpMessageConverter()); converters.add(new ByteArrayHttpMessageConverter());
converters.add(stringConverter); converters.add(stringConverter);
converters.add(new ResourceHttpMessageConverter()); converters.add(new ResourceHttpMessageConverter());
@ -258,28 +273,29 @@ class MvcConfiguration implements ApplicationContextAware, ServletContextAware {
converters.add(new AtomFeedHttpMessageConverter()); converters.add(new AtomFeedHttpMessageConverter());
converters.add(new RssChannelHttpMessageConverter()); converters.add(new RssChannelHttpMessageConverter());
} }
return converters;
} }
@Bean @Bean
HttpRequestHandlerAdapter httpRequestHandlerAdapter() { public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
return new HttpRequestHandlerAdapter(); return new HttpRequestHandlerAdapter();
} }
@Bean @Bean
SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() { public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter(); return new SimpleControllerHandlerAdapter();
} }
@Bean @Bean
HandlerExceptionResolver handlerExceptionResolver() throws Exception { public HandlerExceptionResolver handlerExceptionResolver() throws Exception {
List<HandlerExceptionResolver> resolvers = new ArrayList<HandlerExceptionResolver>(); List<HandlerExceptionResolver> resolvers = new ArrayList<HandlerExceptionResolver>();
resolvers.add(createExceptionHandlerExceptionResolver());
resolvers.add(new ResponseStatusExceptionResolver());
resolvers.add(new DefaultHandlerExceptionResolver());
configurers.configureHandlerExceptionResolvers(resolvers); configurers.configureHandlerExceptionResolvers(resolvers);
if (resolvers.size() == 0) {
resolvers.add(createExceptionHandlerExceptionResolver());
resolvers.add(new ResponseStatusExceptionResolver());
resolvers.add(new DefaultHandlerExceptionResolver());
}
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite(); HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0); composite.setOrder(0);
composite.setExceptionResolvers(resolvers); composite.setExceptionResolvers(resolvers);
@ -288,22 +304,17 @@ class MvcConfiguration implements ApplicationContextAware, ServletContextAware {
private HandlerExceptionResolver createExceptionHandlerExceptionResolver() throws Exception { private HandlerExceptionResolver createExceptionHandlerExceptionResolver() throws Exception {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver(); ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
List<HttpMessageConverter<?>> converters = getDefaultHttpMessageConverters();
configurers.configureMessageConverters(converters);
resolver.setMessageConverters(converters);
resolver.setOrder(0); resolver.setOrder(0);
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
configurers.configureMessageConverters(converters);
if (converters.size() == 0) {
addDefaultHttpMessageConverters(converters);
}
resolver.setMessageConverters(converters);
resolver.afterPropertiesSet(); resolver.afterPropertiesSet();
return resolver; return resolver;
} }
@Bean
MappedInterceptors mappedInterceptors() {
InterceptorConfigurer configurer = new InterceptorConfigurer();
configurer.addInterceptor(new ConversionServiceExposingInterceptor(conversionService()));
configurers.addInterceptors(configurer);
return configurer.getMappedInterceptors();
}
} }

View File

@ -36,12 +36,12 @@ import com.sun.corba.se.impl.presentation.rmi.ExceptionHandler;
/** /**
* Defines options for customizing or adding to the default Spring MVC configuration enabled through the use * Defines options for customizing or adding to the default Spring MVC configuration enabled through the use
* of @{@link EnableMvcConfiguration}. The @{@link Configuration} class annotated with @{@link EnableMvcConfiguration} * of @{@link EnableWebMvc}. The @{@link Configuration} class annotated with @{@link EnableWebMvc}
* is the most obvious place to implement this interface. However all @{@link Configuration} classes and more generally * is the most obvious place to implement this interface. However all @{@link Configuration} classes and more generally
* all Spring beans that implement this interface will be detected at startup and given a chance to customize Spring * all Spring beans that implement this interface will be detected at startup and given a chance to customize Spring
* MVC configuration provided it is enabled through @{@link EnableMvcConfiguration}. * MVC configuration provided it is enabled through @{@link EnableWebMvc}.
* *
* <p>Implementations of this interface will find it convenient to extend {@link MvcConfigurerSupport} that * <p>Implementations of this interface will find it convenient to extend {@link WebMvcConfigurerAdapter} that
* provides default method implementations and allows overriding only methods of interest. * provides default method implementations and allows overriding only methods of interest.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
@ -49,17 +49,18 @@ import com.sun.corba.se.impl.presentation.rmi.ExceptionHandler;
* @author David Syer * @author David Syer
* @since 3.1 * @since 3.1
*/ */
public interface MvcConfigurer { public interface WebMvcConfigurer {
/** /**
* Register application-specific {@link Converter}s and {@link Formatter}s for use in Spring MVC. * Add {@link Converter}s and {@link Formatter}s in addition to the ones registered by default.
*/ */
void registerFormatters(FormatterRegistry formatterRegistry); void addFormatters(FormatterRegistry formatterRegistry);
/** /**
* Customize the list of {@link HttpMessageConverter}s to use when resolving method arguments or handling * Configure the list of {@link HttpMessageConverter}s to use when resolving method arguments or handling
* return values from @{@link RequestMapping} and @{@link ExceptionHandler} methods. * return values in @{@link RequestMapping} and @{@link ExceptionHandler} methods.
* @param converters the list of converters, initially populated with the default set of converters * Specifying custom converters overrides the converters registered by default.
* @param converters a list to add message converters to
*/ */
void configureMessageConverters(List<HttpMessageConverter<?>> converters); void configureMessageConverters(List<HttpMessageConverter<?>> converters);
@ -71,37 +72,43 @@ public interface MvcConfigurer {
Validator getValidator(); Validator getValidator();
/** /**
* Add custom {@link HandlerMethodArgumentResolver}s to use for resolving argument values * Add custom {@link HandlerMethodArgumentResolver}s to use in addition to the ones registered by default.
* on @{@link RequestMapping} and @{@link ExceptionHandler} methods. * <p>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.
* @param argumentResolvers the list of custom converters, initially empty * @param argumentResolvers the list of custom converters, initially empty
*/ */
void addCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers); void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers);
/** /**
* Add custom {@link HandlerMethodReturnValueHandler}s to use for handling return values * Add custom {@link HandlerMethodReturnValueHandler}s to in addition to the ones registered by default.
* from @{@link RequestMapping} and @{@link ExceptionHandler} methods. * <p>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.
* @param returnValueHandlers the list of custom handlers, initially empty * @param returnValueHandlers the list of custom handlers, initially empty
*/ */
void addCustomReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers); void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers);
/** /**
* Customize the list of {@link HandlerExceptionResolver}s to use for handling controller exceptions. * Configure the list of {@link HandlerExceptionResolver}s to use for handling unresolved controller exceptions.
* @param exceptionResolvers the list of resolvers, initially populated with the default set of resolvers * Specifying exception resolvers overrides the ones registered by default.
* @param exceptionResolvers a list to add exception resolvers to
*/ */
void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers); void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers);
/** /**
* Add Spring MVC interceptors. Interceptors can be of type {@link HandlerInterceptor} or * 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 * {@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. * invocation. Interceptors can be registered to apply to all requests or limited to a set of path patterns.
* @see InterceptorConfigurer * @see InterceptorConfigurer
*/ */
void addInterceptors(InterceptorConfigurer interceptorConfigurer); void configureInterceptors(InterceptorConfigurer interceptorConfigurer);
/** /**
* Map URL paths to view names. This is convenient when a request can be rendered without a controller. * 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.
*/ */
void addViewControllers(ViewControllerConfigurer viewControllerConfigurer); void configureViewControllers(ViewControllerConfigurer viewControllerConfigurer);
/** /**
* Configure a handler for serving static resources such as images, js, and, css files through Spring MVC * Configure a handler for serving static resources such as images, js, and, css files through Spring MVC

View File

@ -22,22 +22,23 @@ import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerExceptionResolver;
/** /**
* An abstract class with empty method implementations of the {@link MvcConfigurer} interface for a simplified * An abstract class with empty method implementations of {@link WebMvcConfigurer}.
* implementation of {@link MvcConfigurer} so that subclasses can override selected methods only. * Subclasses can override only the methods they need.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1
*/ */
public abstract class MvcConfigurerSupport implements MvcConfigurer { public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
/** /**
* {@inheritDoc} * {@inheritDoc}
* <p>This implementation is empty. * <p>This implementation is empty.
*/ */
public void registerFormatters(FormatterRegistry formatterRegistry) { public void addFormatters(FormatterRegistry formatterRegistry) {
} }
/** /**
@ -47,13 +48,6 @@ public abstract class MvcConfigurerSupport implements MvcConfigurer {
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
} }
/**
* {@inheritDoc}
* <p>This implementation is empty.
*/
public void configureValidator(Validator validator) {
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* <p>This implementation returns {@code null} * <p>This implementation returns {@code null}
@ -66,7 +60,14 @@ public abstract class MvcConfigurerSupport implements MvcConfigurer {
* {@inheritDoc} * {@inheritDoc}
* <p>This implementation is empty. * <p>This implementation is empty.
*/ */
public void addCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
}
/**
* {@inheritDoc}
* <p>This implementation is empty.
*/
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
} }
/** /**
@ -80,14 +81,14 @@ public abstract class MvcConfigurerSupport implements MvcConfigurer {
* {@inheritDoc} * {@inheritDoc}
* <p>This implementation is empty. * <p>This implementation is empty.
*/ */
public void addInterceptors(InterceptorConfigurer interceptorConfigurer) { public void configureInterceptors(InterceptorConfigurer interceptorConfigurer) {
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* <p>This implementation is empty. * <p>This implementation is empty.
*/ */
public void addViewControllers(ViewControllerConfigurer viewControllerConfigurer) { public void configureViewControllers(ViewControllerConfigurer viewControllerConfigurer) {
} }
/** /**

View File

@ -29,78 +29,78 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerExceptionResolver;
/** /**
* An {@link MvcConfigurer} implementation that delegates to other {@link MvcConfigurer} instances. * An {@link WebMvcConfigurer} implementation that delegates to other {@link WebMvcConfigurer} instances.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1
*/ */
class MvcConfigurerComposite implements MvcConfigurer { class WebMvcConfigurerComposite implements WebMvcConfigurer {
private final List<MvcConfigurer> configurers = new ArrayList<MvcConfigurer>(); private final List<WebMvcConfigurer> configurers = new ArrayList<WebMvcConfigurer>();
void addConfigurers(List<MvcConfigurer> configurers) { public void addConfigurers(List<WebMvcConfigurer> configurers) {
if (configurers != null) { if (configurers != null) {
this.configurers.addAll(configurers); this.configurers.addAll(configurers);
} }
} }
public void registerFormatters(FormatterRegistry formatterRegistry) { public void addFormatters(FormatterRegistry formatterRegistry) {
for (MvcConfigurer configurer : configurers) { for (WebMvcConfigurer configurer : configurers) {
configurer.registerFormatters(formatterRegistry); configurer.addFormatters(formatterRegistry);
} }
} }
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
for (MvcConfigurer configurer : configurers) { for (WebMvcConfigurer configurer : configurers) {
configurer.configureMessageConverters(converters); configurer.configureMessageConverters(converters);
} }
} }
public void addCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
for (MvcConfigurer configurer : configurers) { for (WebMvcConfigurer configurer : configurers) {
configurer.addCustomArgumentResolvers(argumentResolvers); configurer.addArgumentResolvers(argumentResolvers);
} }
} }
public void addCustomReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) { public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
for (MvcConfigurer configurer : configurers) { for (WebMvcConfigurer configurer : configurers) {
configurer.addCustomReturnValueHandlers(returnValueHandlers); configurer.addReturnValueHandlers(returnValueHandlers);
} }
} }
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) { public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
for (MvcConfigurer configurer : configurers) { for (WebMvcConfigurer configurer : configurers) {
configurer.configureHandlerExceptionResolvers(exceptionResolvers); configurer.configureHandlerExceptionResolvers(exceptionResolvers);
} }
} }
public void addInterceptors(InterceptorConfigurer interceptorRegistry) { public void configureInterceptors(InterceptorConfigurer interceptorRegistry) {
for (MvcConfigurer configurer : configurers) { for (WebMvcConfigurer configurer : configurers) {
configurer.addInterceptors(interceptorRegistry); configurer.configureInterceptors(interceptorRegistry);
} }
} }
public void addViewControllers(ViewControllerConfigurer viewControllerConfigurer) { public void configureViewControllers(ViewControllerConfigurer viewControllerConfigurer) {
for (MvcConfigurer configurer : configurers) { for (WebMvcConfigurer configurer : configurers) {
configurer.addViewControllers(viewControllerConfigurer); configurer.configureViewControllers(viewControllerConfigurer);
} }
} }
public void configureResourceHandling(ResourceConfigurer resourceConfigurer) { public void configureResourceHandling(ResourceConfigurer resourceConfigurer) {
for (MvcConfigurer configurer : configurers) { for (WebMvcConfigurer configurer : configurers) {
configurer.configureResourceHandling(resourceConfigurer); configurer.configureResourceHandling(resourceConfigurer);
} }
} }
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer handlerConfigurer) { public void configureDefaultServletHandling(DefaultServletHandlerConfigurer handlerConfigurer) {
for (MvcConfigurer configurer : configurers) { for (WebMvcConfigurer configurer : configurers) {
configurer.configureDefaultServletHandling(handlerConfigurer); configurer.configureDefaultServletHandling(handlerConfigurer);
} }
} }
public Validator getValidator() { public Validator getValidator() {
Map<MvcConfigurer, Validator> validators = new HashMap<MvcConfigurer, Validator>(); Map<WebMvcConfigurer, Validator> validators = new HashMap<WebMvcConfigurer, Validator>();
for (MvcConfigurer configurer : configurers) { for (WebMvcConfigurer configurer : configurers) {
Validator validator = configurer.getValidator(); Validator validator = configurer.getValidator();
if (validator != null) { if (validator != null) {
validators.put(configurer, validator); validators.put(configurer, validator);

View File

@ -1,5 +1,6 @@
package org.springframework.web.servlet.handler; package org.springframework.web.servlet.handler;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -10,7 +11,7 @@ import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
/** /**
* A {@link HandlerExceptionResolver} that delegates to a list of {@link HandlerExceptionResolver}s. * A {@link HandlerExceptionResolver} that delegates to a list of other {@link HandlerExceptionResolver}s.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1
@ -29,10 +30,24 @@ public class HandlerExceptionResolverComposite implements HandlerExceptionResolv
return this.order; return this.order;
} }
/**
* Set the list of exception resolvers to delegate to.
*/
public void setExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) { public void setExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
this.resolvers = exceptionResolvers; this.resolvers = exceptionResolvers;
} }
/**
* Return the list of exception resolvers to delegate to.
*/
public List<HandlerExceptionResolver> getExceptionResolvers() {
return Collections.unmodifiableList(resolvers);
}
/**
* Resolve the exception by iterating over the list of configured exception resolvers.
* The first one to return a ModelAndView instance wins. Otherwise {@code null} is returned.
*/
public ModelAndView resolveException(HttpServletRequest request, public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, HttpServletResponse response,
Object handler, Object handler,

View File

@ -29,6 +29,7 @@ import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor; import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.MappedInterceptors;
import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter; import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.theme.ThemeChangeInterceptor; import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
@ -139,7 +140,8 @@ public class InterceptorConfigurerTests {
} }
private HandlerInterceptor[] getInterceptorsForPath(String lookupPath) { private HandlerInterceptor[] getInterceptorsForPath(String lookupPath) {
return configurer.getMappedInterceptors().getInterceptors(lookupPath, new AntPathMatcher()); MappedInterceptors mappedInterceptors = new MappedInterceptors(configurer.getInterceptors());
return mappedInterceptors.getInterceptors(lookupPath, new AntPathMatcher());
} }
private void verifyAdaptedInterceptor(HandlerInterceptor interceptor, TestWebRequestInterceptor webInterceptor) private void verifyAdaptedInterceptor(HandlerInterceptor interceptor, TestWebRequestInterceptor webInterceptor)

View File

@ -21,44 +21,55 @@ import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify; import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.easymock.Capture; import org.easymock.Capture;
import org.easymock.EasyMock; import org.easymock.EasyMock;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.format.support.FormattingConversionService; import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite;
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver; import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
/** /**
* A test fixture with an {@link MvcConfiguration} and a mock {@link MvcConfigurer} for verifying delegation. * A test fixture for WebMvcConfiguration tests.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
*/ */
public class MvcConfigurationTests { public class WebMvcConfigurationTests {
private MvcConfiguration mvcConfiguration; private WebMvcConfiguration mvcConfiguration;
private MvcConfigurer configurer; private WebMvcConfigurer configurer;
@Before @Before
public void setUp() { public void setUp() {
configurer = EasyMock.createMock(MvcConfigurer.class); configurer = EasyMock.createMock(WebMvcConfigurer.class);
mvcConfiguration = new MvcConfiguration(); mvcConfiguration = new WebMvcConfiguration();
mvcConfiguration.setConfigurers(Arrays.asList(configurer));
} }
@Test @Test
@ -69,12 +80,13 @@ public class MvcConfigurationTests {
Capture<List<HttpMessageConverter<?>>> converters = new Capture<List<HttpMessageConverter<?>>>(); Capture<List<HttpMessageConverter<?>>> converters = new Capture<List<HttpMessageConverter<?>>>();
expect(configurer.getValidator()).andReturn(null); expect(configurer.getValidator()).andReturn(null);
configurer.registerFormatters(capture(conversionService)); configurer.addFormatters(capture(conversionService));
configurer.addCustomArgumentResolvers(capture(resolvers)); configurer.addArgumentResolvers(capture(resolvers));
configurer.addCustomReturnValueHandlers(capture(handlers)); configurer.addReturnValueHandlers(capture(handlers));
configurer.configureMessageConverters(capture(converters)); configurer.configureMessageConverters(capture(converters));
replay(configurer); replay(configurer);
mvcConfiguration.setConfigurers(Arrays.asList(configurer));
RequestMappingHandlerAdapter adapter = mvcConfiguration.requestMappingHandlerAdapter(); RequestMappingHandlerAdapter adapter = mvcConfiguration.requestMappingHandlerAdapter();
ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer(); ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer();
@ -89,11 +101,30 @@ public class MvcConfigurationTests {
verify(configurer); verify(configurer);
} }
@Test
public void configureMessageConverters() {
RequestMappingHandlerAdapter adapter = mvcConfiguration.requestMappingHandlerAdapter();
assertTrue("There should be at least two default converters ", adapter.getMessageConverters().size() > 1);
List<WebMvcConfigurer> configurers = new ArrayList<WebMvcConfigurer>();
configurers.add(new WebMvcConfigurerAdapter() {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new StringHttpMessageConverter());
}
});
mvcConfiguration.setConfigurers(configurers );
adapter = mvcConfiguration.requestMappingHandlerAdapter();
assertEquals("Only one custom converter should be registered", 1, adapter.getMessageConverters().size());
}
@Test @Test
public void getCustomValidator() { public void getCustomValidator() {
expect(configurer.getValidator()).andReturn(new LocalValidatorFactoryBean()); expect(configurer.getValidator()).andReturn(new LocalValidatorFactoryBean());
replay(configurer); replay(configurer);
mvcConfiguration.setConfigurers(Arrays.asList(configurer));
mvcConfiguration.validator(); mvcConfiguration.validator();
verify(configurer); verify(configurer);
@ -104,6 +135,7 @@ public class MvcConfigurationTests {
expect(configurer.getValidator()).andReturn(null); expect(configurer.getValidator()).andReturn(null);
replay(configurer); replay(configurer);
mvcConfiguration.setConfigurers(Arrays.asList(configurer));
mvcConfiguration.validator(); mvcConfiguration.validator();
verify(configurer); verify(configurer);
@ -118,6 +150,7 @@ public class MvcConfigurationTests {
configurer.configureHandlerExceptionResolvers(capture(exceptionResolvers)); configurer.configureHandlerExceptionResolvers(capture(exceptionResolvers));
replay(configurer); replay(configurer);
mvcConfiguration.setConfigurers(Arrays.asList(configurer));
mvcConfiguration.handlerExceptionResolver(); mvcConfiguration.handlerExceptionResolver();
assertEquals(3, exceptionResolvers.getValue().size()); assertEquals(3, exceptionResolvers.getValue().size());
@ -129,4 +162,47 @@ public class MvcConfigurationTests {
verify(configurer); verify(configurer);
} }
@Test
public void configureExceptionResolvers() throws Exception {
HandlerExceptionResolverComposite composite;
composite = (HandlerExceptionResolverComposite) mvcConfiguration.handlerExceptionResolver();
assertTrue("Expected more than one exception resolver by default", composite.getExceptionResolvers().size() > 1);
List<WebMvcConfigurer> configurers = new ArrayList<WebMvcConfigurer>();
configurers.add(new WebMvcConfigurerAdapter() {
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}
});
mvcConfiguration.setConfigurers(configurers);
composite = (HandlerExceptionResolverComposite) mvcConfiguration.handlerExceptionResolver();
assertEquals("Only one custom converter is expected", 1, composite.getExceptionResolvers().size());
}
@Test
public void configureInterceptors() throws Exception {
HttpServletRequest request = new MockHttpServletRequest("GET", "/");
StaticWebApplicationContext context = new StaticWebApplicationContext();
context.registerSingleton("controller", TestHandler.class);
RequestMappingHandlerMapping hm = mvcConfiguration.requestMappingHandlerMapping();
hm.setApplicationContext(context);
HandlerExecutionChain chain = hm.getHandler(request);
assertNotNull("Expected at one default converter", chain.getInterceptors());
}
@Controller
private static class TestHandler {
@SuppressWarnings("unused")
@RequestMapping("/")
public void handle() {
}
}
} }