From 83a5f0bb4a5147ed65fa9cd316ba72c3664fe006 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 9 Jun 2011 11:17:45 +0000 Subject: [PATCH] Introduce base class for WebMvcConfiguration git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@4503 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../config/annotation/EnableWebMvc.java | 16 +- .../annotation/InterceptorConfigurer.java | 43 +- .../annotation/WebMvcConfiguration.java | 273 ++---------- .../WebMvcConfigurationSupport.java | 399 ++++++++++++++++++ .../config/annotation/WebMvcConfigurer.java | 16 +- .../InterceptorConfigurerTests.java | 16 +- .../annotation/WebMvcConfigurationTests.java | 24 +- 7 files changed, 503 insertions(+), 284 deletions(-) create mode 100644 org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/EnableWebMvc.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/EnableWebMvc.java index e16bd2d91e3..881204f283b 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/EnableWebMvc.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/EnableWebMvc.java @@ -20,14 +20,13 @@ import java.lang.annotation.Target; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.stereotype.Controller; import org.springframework.web.servlet.DispatcherServlet; /** * Enables default Spring MVC configuration and registers Spring MVC infrastructure components expected by the - * {@link DispatcherServlet}. To use this annotation simply place it on an application @{@link Configuration} class - * and that will in turn import default Spring MVC configuration including support for annotated methods - * in @{@link Controller} classes. + * {@link DispatcherServlet}. Add this annotation to an application @{@link Configuration} class. It will in + * turn import the @{@link Configuration} class {@link WebMvcConfiguration}, which provides default Spring MVC + * configuration. *
  * @Configuration
  * @EnableWebMvc
@@ -39,11 +38,10 @@ import org.springframework.web.servlet.DispatcherServlet;
  *
  * }
  * 
- *

To customize the imported configuration you simply implement {@link WebMvcConfigurer}, or more likely extend - * {@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 EnableWebMvc}. - * 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 customize the imported configuration implement {@link WebMvcConfigurer}, or more conveniently extend + * {@link WebMvcConfigurerAdapter} overriding specific methods. Your @{@link Configuration} class and any other + * Spring bean that implements {@link WebMvcConfigurer} will be detected and given an opportunity to customize + * the default Spring MVC configuration through the callback methods on the {@link WebMvcConfigurer} interface. *

  * @Configuration
  * @EnableWebMvc
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorConfigurer.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorConfigurer.java
index 103770131dc..3ce466121e1 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorConfigurer.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorConfigurer.java
@@ -22,77 +22,74 @@ import java.util.List;
 import org.springframework.util.Assert;
 import org.springframework.web.context.request.WebRequestInterceptor;
 import org.springframework.web.servlet.HandlerInterceptor;
-import org.springframework.web.servlet.HandlerMapping;
 import org.springframework.web.servlet.handler.MappedInterceptor;
 import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter;
 
 /**
  * Helps with configuring an ordered set of Spring MVC interceptors of type {@link HandlerInterceptor} or
- * {@link WebRequestInterceptor}. Registered interceptors will generally be detected by all {@link HandlerMapping}
- * instances in a Spring MVC web application context. Interceptors can be added with a set of path patterns to
- * which they should apply.
+ * {@link WebRequestInterceptor}. Interceptors can be registered with a set of path patterns.
  *
  * @author Rossen Stoyanchev
  * @since 3.1
  */
 public class InterceptorConfigurer {
 
-	private final List mappedInterceptors = new ArrayList();
+	private final List interceptors = new ArrayList();
 
 	/**
 	 * Add a {@link HandlerInterceptor} that should apply to any request.
 	 */
 	public void addInterceptor(HandlerInterceptor interceptor) {
-		register(null, interceptor);
+		register(interceptor);
 	}
 
 	/**
 	 * Add a {@link WebRequestInterceptor} that should apply to any request.
 	 */
 	public void addInterceptor(WebRequestInterceptor interceptor) {
-		register(null, asHandlerInterceptorArray(interceptor));
+		register(asHandlerInterceptorArray(interceptor));
 	}
 
 	/**
 	 * Add {@link HandlerInterceptor}s that should apply to any request.
 	 */
 	public void addInterceptors(HandlerInterceptor... interceptors) {
-		register(null, interceptors);
+		register( interceptors);
 	}
 
 	/**
 	 * Add {@link WebRequestInterceptor}s that should apply to any request.
 	 */
 	public void addInterceptors(WebRequestInterceptor... interceptors) {
-		register(null, asHandlerInterceptorArray(interceptors));
+		register(asHandlerInterceptorArray(interceptors));
 	}
 
 	/**
 	 * Add a {@link HandlerInterceptor} with a set of URL path patterns it should apply to.
 	 */
 	public void mapInterceptor(String[] pathPatterns, HandlerInterceptor interceptor) {
-		register(pathPatterns, interceptor);
+		registerMappedInterceptors(pathPatterns, interceptor);
 	}
 
 	/**
 	 * Add a {@link WebRequestInterceptor} with a set of URL path patterns it should apply to.
 	 */
 	public void mapInterceptor(String[] pathPatterns, WebRequestInterceptor interceptors) {
-		register(pathPatterns, asHandlerInterceptorArray(interceptors));
+		registerMappedInterceptors(pathPatterns, asHandlerInterceptorArray(interceptors));
 	}
 
 	/**
 	 * Add {@link HandlerInterceptor}s with a set of URL path patterns they should apply to.
 	 */
 	public void mapInterceptors(String[] pathPatterns, HandlerInterceptor... interceptors) {
-		register(pathPatterns, interceptors);
+		registerMappedInterceptors(pathPatterns, interceptors);
 	}
 
 	/**
 	 * Add {@link WebRequestInterceptor}s with a set of URL path patterns they should apply to.
 	 */
 	public void mapInterceptors(String[] pathPatterns, WebRequestInterceptor... interceptors) {
-		register(pathPatterns, asHandlerInterceptorArray(interceptors));
+		registerMappedInterceptors(pathPatterns, asHandlerInterceptorArray(interceptors));
 	}
 
 	private static HandlerInterceptor[] asHandlerInterceptorArray(WebRequestInterceptor...interceptors) {
@@ -103,23 +100,35 @@ public class InterceptorConfigurer {
 		return result;
 	}
 
+	/**
+	 * Stores the given set of {@link HandlerInterceptor}s internally.
+	 * @param interceptors one or more interceptors to be stored
+	 */
+	protected void register(HandlerInterceptor...interceptors) {
+		Assert.notEmpty(interceptors, "At least one interceptor must be provided");
+		for (HandlerInterceptor interceptor : interceptors) {
+			this.interceptors.add(interceptor);
+		}
+	}
+
 	/**
 	 * Stores the given set of {@link HandlerInterceptor}s and path patterns internally.
 	 * @param pathPatterns path patterns or {@code null}
 	 * @param interceptors one or more interceptors to be stored
 	 */
-	protected void register(String[] pathPatterns, HandlerInterceptor...interceptors) {
+	protected void registerMappedInterceptors(String[] pathPatterns, HandlerInterceptor...interceptors) {
 		Assert.notEmpty(interceptors, "At least one interceptor must be provided");
+		Assert.notEmpty(pathPatterns, "Path patterns must be provided");
 		for (HandlerInterceptor interceptor : interceptors) {
-			mappedInterceptors.add(new MappedInterceptor(pathPatterns, interceptor));
+			this.interceptors.add(new MappedInterceptor(pathPatterns, interceptor));
 		}
 	}
 
 	/**
 	 * Returns all registered interceptors.
 	 */
-	protected List getInterceptors() {
-		return mappedInterceptors;
+	protected List getInterceptors() {
+		return interceptors;
 	}
 
 }
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 6f93cf62cf4..445fb79c629 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
@@ -19,95 +19,31 @@ package org.springframework.web.servlet.config.annotation;
 import java.util.ArrayList;
 import java.util.List;
 
-import javax.servlet.ServletContext;
-import javax.xml.transform.Source;
-
-import org.springframework.beans.BeanUtils;
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.BeanInitializationException;
 import org.springframework.beans.factory.annotation.Autowired;
-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.format.support.DefaultFormattingConversionService;
 import org.springframework.format.support.FormattingConversionService;
-import org.springframework.http.converter.ByteArrayHttpMessageConverter;
 import org.springframework.http.converter.HttpMessageConverter;
-import org.springframework.http.converter.ResourceHttpMessageConverter;
-import org.springframework.http.converter.StringHttpMessageConverter;
-import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
-import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
-import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
-import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
-import org.springframework.http.converter.xml.SourceHttpMessageConverter;
-import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
-import org.springframework.util.ClassUtils;
-import org.springframework.validation.Errors;
 import org.springframework.validation.Validator;
-import org.springframework.web.HttpRequestHandler;
-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.HandlerExceptionResolver;
-import org.springframework.web.servlet.HandlerMapping;
-import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
-import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
 import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite;
-import org.springframework.web.servlet.handler.MappedInterceptor;
-import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
-import org.springframework.web.servlet.mvc.Controller;
-import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
-import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
-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.RequestMappingHandlerAdapter;
-import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
-import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
 
 /**
- * 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
- * enabled through the {@link EnableWebMvc} annotation.
- *
- * 

A number of options are available for customizing the default configuration provided by this class. - * See {@link EnableWebMvc} and {@link WebMvcConfigurer} for details. - * - *

Registers these handler mappings: - *

    - *
  • {@link RequestMappingHandlerMapping} ordered at 0 for mapping requests to annotated controller methods. - *
  • {@link SimpleUrlHandlerMapping} ordered at 1 to map URL paths directly to view names. - *
  • {@link BeanNameUrlHandlerMapping} ordered at 2 to map URL paths to controller bean names. - *
  • {@link SimpleUrlHandlerMapping} ordered at {@code Integer.MAX_VALUE-1} to serve static resource requests. - *
  • {@link SimpleUrlHandlerMapping} ordered at {@code Integer.MAX_VALUE} to forward requests to the default servlet. - *
- * - *

Note: that the SimpleUrlHandlerMapping instances above will have empty URL maps and - * hence no effect until explicitly configured via one of the {@link WebMvcConfigurer} callbacks. - * - *

Registers these handler adapters: - *

    - *
  • {@link RequestMappingHandlerAdapter} for processing requests using annotated controller methods. - *
  • {@link HttpRequestHandlerAdapter} for processing requests with {@link HttpRequestHandler}s. - *
  • {@link SimpleControllerHandlerAdapter} for processing requests with interface-based {@link Controller}s. - *
- * - *

Registers a {@link HandlerExceptionResolverComposite} with this chain of 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 - *
- * - *

Registers the following others: - *

    - *
  • {@link FormattingConversionService} for use with annotated controller methods and the spring:eval JSP tag. - *
  • {@link Validator} for validating model attributes on annotated controller methods. - *
+ * Provides default configuration for Spring MVC applications by registering Spring MVC infrastructure components + * to be detected by the {@link DispatcherServlet}. This class is imported whenever @{@link EnableWebMvc} is + * added to an @{@link Configuration} class. + * + *

See the base class {@link WebMvcConfigurationSupport} for a list of registered instances. This class is closed + * for extension. However, the configuration it provides can be customized by having your @{@link Configuration} + * class implement {@link WebMvcConfigurer} or more conveniently extend from {@link WebMvcConfigurerAdapter}. + * + *

This class will detect your @{@link Configuration} class and any other @{@link Configuration} classes that + * implement {@link WebMvcConfigurer} via autowiring and will allow each of them to participate in the process + * of configuring Spring MVC through the configuration callbacks defined in {@link WebMvcConfigurer}. * * @see EnableWebMvc * @see WebMvcConfigurer @@ -116,18 +52,10 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv * @since 3.1 */ @Configuration -class WebMvcConfiguration implements ApplicationContextAware, ServletContextAware { +class WebMvcConfiguration extends WebMvcConfigurationSupport { private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); - private ServletContext servletContext; - - private ApplicationContext applicationContext; - - private List mappedInterceptors; - - private List> messageConverters; - @Autowired(required = false) public void setConfigurers(List configurers) { if (configurers == null || configurers.isEmpty()) { @@ -136,75 +64,30 @@ class WebMvcConfiguration implements ApplicationContextAware, ServletContextAwar this.configurers.addWebMvcConfigurers(configurers); } - public void setServletContext(ServletContext servletContext) { - this.servletContext = servletContext; + @Override + protected void configureInterceptors(InterceptorConfigurer configurer) { + configurers.configureInterceptors(configurer); } - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - @Bean - public RequestMappingHandlerMapping requestMappingHandlerMapping() { - RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping(); - mapping.setInterceptors(getMappedInterceptors()); - mapping.setOrder(0); - return mapping; - } - - private Object[] getMappedInterceptors() { - if (mappedInterceptors == null) { - InterceptorConfigurer configurer = new InterceptorConfigurer(); - configurers.configureInterceptors(configurer); - configurer.addInterceptor(new ConversionServiceExposingInterceptor(conversionService())); - mappedInterceptors = configurer.getInterceptors(); - } - return mappedInterceptors.toArray(); - } - - @Bean - public HandlerMapping viewControllerHandlerMapping() { - ViewControllerConfigurer configurer = new ViewControllerConfigurer(); - configurer.setOrder(1); + @Override + protected void configureViewControllers(ViewControllerConfigurer configurer) { configurers.configureViewControllers(configurer); - - SimpleUrlHandlerMapping handlerMapping = configurer.getHandlerMapping(); - handlerMapping.setInterceptors(getMappedInterceptors()); - return handlerMapping; } - @Bean - public BeanNameUrlHandlerMapping beanNameHandlerMapping() { - BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping(); - mapping.setOrder(2); - mapping.setInterceptors(getMappedInterceptors()); - return mapping; - } - - @Bean - public HandlerMapping resourceHandlerMapping() { - ResourceConfigurer configurer = new ResourceConfigurer(applicationContext, servletContext); - configurer.setOrder(Integer.MAX_VALUE-1); + @Override + protected void configureResourceHandling(ResourceConfigurer configurer) { configurers.configureResourceHandling(configurer); - return configurer.getHandlerMapping(); } - @Bean - public HandlerMapping defaultServletHandlerMapping() { - DefaultServletHandlerConfigurer configurer = new DefaultServletHandlerConfigurer(servletContext); + @Override + protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurers.configureDefaultServletHandling(configurer); - return configurer.getHandlerMapping(); } + @Override @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { - RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter(); - adapter.setMessageConverters(getMessageConverters()); - - ConfigurableWebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer(); - bindingInitializer.setConversionService(conversionService()); - bindingInitializer.setValidator(validator()); - adapter.setWebBindingInitializer(bindingInitializer); + RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(); List argumentResolvers = new ArrayList(); configurers.addArgumentResolvers(argumentResolvers); @@ -213,116 +96,40 @@ class WebMvcConfiguration implements ApplicationContextAware, ServletContextAwar List returnValueHandlers = new ArrayList(); configurers.addReturnValueHandlers(returnValueHandlers); adapter.setCustomReturnValueHandlers(returnValueHandlers); - + return adapter; } - private List> getMessageConverters() { - if (messageConverters == null) { - messageConverters = new ArrayList>(); - configurers.configureMessageConverters(messageConverters); - if (messageConverters.isEmpty()) { - addDefaultHttpMessageConverters(messageConverters); - } - } - return messageConverters; + @Override + protected void configureMessageConverters(List> converters) { + configurers.configureMessageConverters(converters); } - - @Bean(name="webMvcConversionService") - public FormattingConversionService conversionService() { - FormattingConversionService conversionService = new DefaultFormattingConversionService(); + + @Override + @Bean + public FormattingConversionService mvcConversionService() { + FormattingConversionService conversionService = super.mvcConversionService(); configurers.addFormatters(conversionService); return conversionService; } - @Bean(name="webMvcValidator") - public Validator validator() { + @Override + @Bean + public Validator mvcValidator() { Validator validator = configurers.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"; - clazz = ClassUtils.forName(className, WebMvcConfiguration.class.getClassLoader()); - } catch (ClassNotFoundException e) { - throw new BeanInitializationException("Could not find default validator"); - } catch (LinkageError e) { - throw new BeanInitializationException("Could not find default validator"); - } - return (Validator) BeanUtils.instantiate(clazz); - } - else { - return NOOP_VALIDATOR; - } - } - - private void addDefaultHttpMessageConverters(List> messageConverters) { - StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(); - stringConverter.setWriteAcceptCharset(false); - - messageConverters.add(new ByteArrayHttpMessageConverter()); - messageConverters.add(stringConverter); - messageConverters.add(new ResourceHttpMessageConverter()); - messageConverters.add(new SourceHttpMessageConverter()); - messageConverters.add(new XmlAwareFormHttpMessageConverter()); - - ClassLoader classLoader = getClass().getClassLoader(); - if (ClassUtils.isPresent("javax.xml.bind.Binder", classLoader)) { - messageConverters.add(new Jaxb2RootElementHttpMessageConverter()); - } - if (ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", classLoader)) { - messageConverters.add(new MappingJacksonHttpMessageConverter()); - } - if (ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", classLoader)) { - messageConverters.add(new AtomFeedHttpMessageConverter()); - messageConverters.add(new RssChannelHttpMessageConverter()); - } + return validator != null ? validator : super.mvcValidator(); } @Bean - public HttpRequestHandlerAdapter httpRequestHandlerAdapter() { - return new HttpRequestHandlerAdapter(); - } - - @Bean - public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() { - return new SimpleControllerHandlerAdapter(); - } - - @Bean - public HandlerExceptionResolver handlerExceptionResolver() throws Exception { + public HandlerExceptionResolverComposite handlerExceptionResolver() throws Exception { List resolvers = new ArrayList(); configurers.configureHandlerExceptionResolvers(resolvers); - if (resolvers.size() == 0) { - resolvers.add(createExceptionHandlerExceptionResolver()); - resolvers.add(new ResponseStatusExceptionResolver()); - resolvers.add(new DefaultHandlerExceptionResolver()); + HandlerExceptionResolverComposite composite = super.handlerExceptionResolver(); + if (resolvers.size() != 0) { + composite.setExceptionResolvers(resolvers); } - - HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite(); - composite.setOrder(0); - composite.setExceptionResolvers(resolvers); return composite; } - - private HandlerExceptionResolver createExceptionHandlerExceptionResolver() throws Exception { - ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver(); - resolver.setMessageConverters(getMessageConverters()); - resolver.afterPropertiesSet(); - return resolver; - } - private static final Validator NOOP_VALIDATOR = new Validator() { - - public boolean supports(Class clazz) { - return false; - } - - public void validate(Object target, Errors errors) { - } - }; - } 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 new file mode 100644 index 00000000000..99559ba4b4f --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java @@ -0,0 +1,399 @@ +/* + * Copyright 2002-2011 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.config.annotation; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletContext; +import javax.xml.transform.Source; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanInitializationException; +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.format.support.DefaultFormattingConversionService; +import org.springframework.format.support.FormattingConversionService; +import org.springframework.http.converter.ByteArrayHttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.ResourceHttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter; +import org.springframework.http.converter.feed.RssChannelHttpMessageConverter; +import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; +import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; +import org.springframework.http.converter.xml.SourceHttpMessageConverter; +import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter; +import org.springframework.util.ClassUtils; +import org.springframework.validation.Errors; +import org.springframework.validation.MessageCodesResolver; +import org.springframework.validation.Validator; +import org.springframework.web.HttpRequestHandler; +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.servlet.DispatcherServlet; +import org.springframework.web.servlet.HandlerAdapter; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; +import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor; +import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite; +import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; +import org.springframework.web.servlet.mvc.Controller; +import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; +import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter; +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.RequestMappingHandlerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; + +/** + * A base class that provides default configuration for Spring MVC applications by registering Spring MVC + * infrastructure components to be detected by the {@link DispatcherServlet}. Typically applications should not + * have to start by extending this class. A much easier place to start is to annotate your @{@link Configuration} + * class with @{@link EnableWebMvc}. See @{@link EnableWebMvc} and {@link WebMvcConfigurer}. + * + *

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 + * {@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:

+ *
    + *
  • {@link RequestMappingHandlerMapping} ordered at 0 for mapping requests to annotated controller methods. + *
  • {@link SimpleUrlHandlerMapping} ordered at 1 to map URL paths directly to view names. + *
  • {@link BeanNameUrlHandlerMapping} ordered at 2 to map URL paths to controller bean names. + *
  • {@link SimpleUrlHandlerMapping} ordered at {@code Integer.MAX_VALUE-1} to serve static resource requests. + *
  • {@link SimpleUrlHandlerMapping} ordered at {@code Integer.MAX_VALUE} to forward requests to the default servlet. + *
+ * + *

Registers {@link HandlerAdapter}s: + *

    + *
  • {@link RequestMappingHandlerAdapter} for processing requests using annotated controller methods. + *
  • {@link HttpRequestHandlerAdapter} for processing requests with {@link HttpRequestHandler}s. + *
  • {@link SimpleControllerHandlerAdapter} for processing requests with interface-based {@link Controller}s. + *
+ * + *

Registers a {@link HandlerExceptionResolverComposite} with this chain of 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 + *
+ * + *

Registers the following other instances: + *

    + *
  • {@link FormattingConversionService} for use with annotated controller methods and the spring:eval JSP tag. + *
  • {@link Validator} for validating model attributes on annotated controller methods. + *
+ * + * @see EnableWebMvc + * @see WebMvcConfigurer + * @see WebMvcConfigurerAdapter + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +public abstract class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware { + + private ServletContext servletContext; + + private ApplicationContext applicationContext; + + private List interceptors; + + private List> messageConverters; + + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + /** + * Returns a {@link RequestMappingHandlerMapping} ordered at 0 for mapping requests to annotated controllers. + */ + @Bean + public RequestMappingHandlerMapping requestMappingHandlerMapping() { + RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping(); + mapping.setInterceptors(getInterceptors()); + mapping.setOrder(0); + return mapping; + } + + /** + * Provides access to the shared handler interceptors used to configure {@link HandlerMapping} instances with. + * This method cannot be overridden, use {@link #configureInterceptors(InterceptorConfigurer)} instead. + */ + protected final Object[] getInterceptors() { + if (interceptors == null) { + InterceptorConfigurer configurer = new InterceptorConfigurer(); + configureInterceptors(configurer); + configurer.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService())); + interceptors = configurer.getInterceptors(); + } + return interceptors.toArray(); + } + + /** + * Override this method to configure handler interceptors including interceptors mapped to path patterns. + * @see InterceptorConfigurer + */ + protected void configureInterceptors(InterceptorConfigurer configurer) { + } + + /** + * Returns a {@link SimpleUrlHandlerMapping} ordered at 1 to map URL paths directly to view names. + * To configure view controllers see {@link #configureViewControllers(ViewControllerConfigurer)}. + */ + @Bean + public SimpleUrlHandlerMapping viewControllerHandlerMapping() { + ViewControllerConfigurer configurer = new ViewControllerConfigurer(); + configurer.setOrder(1); + configureViewControllers(configurer); + + SimpleUrlHandlerMapping handlerMapping = configurer.getHandlerMapping(); + handlerMapping.setInterceptors(getInterceptors()); + return handlerMapping; + } + + /** + * Override this method to 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 + */ + protected void configureViewControllers(ViewControllerConfigurer configurer) { + } + + /** + * Returns a {@link BeanNameUrlHandlerMapping} ordered at 2 to map URL paths to controller bean names. + */ + @Bean + public BeanNameUrlHandlerMapping beanNameHandlerMapping() { + BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping(); + mapping.setOrder(2); + mapping.setInterceptors(getInterceptors()); + return mapping; + } + + /** + * Returns a {@link SimpleUrlHandlerMapping} ordered at Integer.MAX_VALUE-1 to serve static resource requests. + * To configure resource handling, see {@link #configureResourceHandling(ResourceConfigurer)}. + */ + @Bean + public SimpleUrlHandlerMapping resourceHandlerMapping() { + ResourceConfigurer configurer = new ResourceConfigurer(applicationContext, servletContext); + configurer.setOrder(Integer.MAX_VALUE-1); + configureResourceHandling(configurer); + return configurer.getHandlerMapping(); + } + + /** + * Override this method to configure serving static resources such as images and css files through Spring MVC. + * @see ResourceConfigurer + */ + protected void configureResourceHandling(ResourceConfigurer configurer) { + } + + /** + * Returns a {@link SimpleUrlHandlerMapping} ordered at Integer.MAX_VALUE to serve static resources by + * forwarding to the Servlet container's default servlet. To configure default servlet handling see + * {@link #configureDefaultServletHandling(DefaultServletHandlerConfigurer)}. + */ + @Bean + public SimpleUrlHandlerMapping defaultServletHandlerMapping() { + DefaultServletHandlerConfigurer configurer = new DefaultServletHandlerConfigurer(servletContext); + configureDefaultServletHandling(configurer); + return configurer.getHandlerMapping(); + } + + /** + * Override this method to configure serving static resources through the Servlet container's default Servlet. + * @see DefaultServletHandlerConfigurer + */ + protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { + } + + /** + * Returns a {@link RequestMappingHandlerAdapter} for processing requests using annotated controller methods. + * Also see {@link #initWebBindingInitializer()} for configuring data binding globally. + */ + @Bean + public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { + ConfigurableWebBindingInitializer webBindingInitializer = new ConfigurableWebBindingInitializer(); + webBindingInitializer.setConversionService(mvcConversionService()); + webBindingInitializer.setValidator(mvcValidator()); + extendWebBindingInitializer(webBindingInitializer); + + RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter(); + adapter.setMessageConverters(getMessageConverters()); + adapter.setWebBindingInitializer(webBindingInitializer); + return adapter; + } + + /** + * Override this method to customize the {@link ConfigurableWebBindingInitializer} the + * {@link RequestMappingHandlerAdapter} is configured with. + */ + protected void extendWebBindingInitializer(ConfigurableWebBindingInitializer webBindingInitializer) { + } + + /** + * 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. + */ + protected final List> getMessageConverters() { + if (messageConverters == null) { + messageConverters = new ArrayList>(); + configureMessageConverters(messageConverters); + if (messageConverters.isEmpty()) { + addDefaultHttpMessageConverters(messageConverters); + } + } + return messageConverters; + } + + /** + * 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. + * See {@link #addDefaultHttpMessageConverters(List)} for adding default converters to the list. + * @param messageConverters the list to add converters to + */ + protected void configureMessageConverters(List> converters) { + } + + /** + * A method available to subclasses for adding default {@link HttpMessageConverter}s. + * @param messageConverters the list to add converters to + */ + protected final void addDefaultHttpMessageConverters(List> messageConverters) { + StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(); + stringConverter.setWriteAcceptCharset(false); + + messageConverters.add(new ByteArrayHttpMessageConverter()); + messageConverters.add(stringConverter); + messageConverters.add(new ResourceHttpMessageConverter()); + messageConverters.add(new SourceHttpMessageConverter()); + messageConverters.add(new XmlAwareFormHttpMessageConverter()); + + ClassLoader classLoader = getClass().getClassLoader(); + if (ClassUtils.isPresent("javax.xml.bind.Binder", classLoader)) { + messageConverters.add(new Jaxb2RootElementHttpMessageConverter()); + } + if (ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", classLoader)) { + messageConverters.add(new MappingJacksonHttpMessageConverter()); + } + if (ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", classLoader)) { + messageConverters.add(new AtomFeedHttpMessageConverter()); + messageConverters.add(new RssChannelHttpMessageConverter()); + } + } + + /** + * Returns a {@link FormattingConversionService} for use with annotated controller methods and the + * {@code spring:eval} JSP tag. + */ + @Bean + public FormattingConversionService mvcConversionService() { + return new DefaultFormattingConversionService(); + } + + /** + * 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. + */ + @Bean + public Validator mvcValidator() { + if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) { + Class clazz; + try { + String className = "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"; + clazz = ClassUtils.forName(className, WebMvcConfigurationSupport.class.getClassLoader()); + } catch (ClassNotFoundException e) { + throw new BeanInitializationException("Could not find default validator"); + } catch (LinkageError e) { + throw new BeanInitializationException("Could not find default validator"); + } + return (Validator) BeanUtils.instantiate(clazz); + } + else { + return new Validator() { + public boolean supports(Class clazz) { + return false; + } + public void validate(Object target, Errors errors) { + } + }; + } + } + + /** + * Returns a {@link HttpRequestHandlerAdapter} for processing requests with {@link HttpRequestHandler}s. + */ + @Bean + public HttpRequestHandlerAdapter httpRequestHandlerAdapter() { + return new HttpRequestHandlerAdapter(); + } + + /** + * Returns a {@link SimpleControllerHandlerAdapter} for processing requests with interface-based controllers. + */ + @Bean + public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() { + return new SimpleControllerHandlerAdapter(); + } + + /** + * Returns a {@link HandlerExceptionResolverComposite} with this chain of 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 { + 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 59e392fc5b6..5076ffb3510 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 @@ -18,7 +18,6 @@ package org.springframework.web.servlet.config.annotation; import java.util.List; -import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; @@ -35,14 +34,13 @@ import org.springframework.web.servlet.HandlerInterceptor; 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 - * 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 - * 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 EnableWebMvc}. - * - *

Implementations of this interface will find it convenient to extend {@link WebMvcConfigurerAdapter} that - * provides default method implementations and allows overriding only methods of interest. + * Defines configuration callback methods for customizing the default Spring MVC configuration enabled through the + * use of @{@link EnableWebMvc}. + * + *

Classes annotated with @{@link EnableWebMvc} can implement this interface in order to be called back and + * given a chance to customize the default configuration. The most convenient way to implement this interface is + * by extending from {@link WebMvcConfigurerAdapter}, which provides empty method implementations and allows + * overriding only the callback methods you're interested in. * * @author Rossen Stoyanchev * @author Keith Donald diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorConfigurerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorConfigurerTests.java index 180a88fb1c4..1f1534f6ea1 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorConfigurerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorConfigurerTests.java @@ -18,6 +18,7 @@ package org.springframework.web.servlet.config.annotation; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Arrays; @@ -146,9 +147,18 @@ public class InterceptorConfigurerTests { private List getInterceptorsForPath(String lookupPath) { PathMatcher pathMatcher = new AntPathMatcher(); List result = new ArrayList(); - for (MappedInterceptor interceptor : configurer.getInterceptors()) { - if (interceptor.matches(lookupPath, pathMatcher)) { - result.add(interceptor.getInterceptor()); + for (Object i : configurer.getInterceptors()) { + if (i instanceof MappedInterceptor) { + MappedInterceptor mappedInterceptor = (MappedInterceptor) i; + if (mappedInterceptor.matches(lookupPath, pathMatcher)) { + result.add(mappedInterceptor.getInterceptor()); + } + } + else if (i instanceof HandlerInterceptor){ + result.add((HandlerInterceptor) i); + } + else { + fail("Unexpected interceptor type: " + i.getClass().getName()); } } return result; 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 8b51cc1b523..07e2a1ed00d 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 @@ -18,6 +18,7 @@ package org.springframework.web.servlet.config.annotation; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.isA; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import static org.junit.Assert.assertEquals; @@ -126,7 +127,7 @@ public class WebMvcConfigurationTests { replay(configurer); mvcConfiguration.setConfigurers(Arrays.asList(configurer)); - mvcConfiguration.validator(); + mvcConfiguration.mvcValidator(); verify(configurer); } @@ -137,28 +138,25 @@ public class WebMvcConfigurationTests { replay(configurer); mvcConfiguration.setConfigurers(Arrays.asList(configurer)); - mvcConfiguration.validator(); + mvcConfiguration.mvcValidator(); verify(configurer); } + @SuppressWarnings("unchecked") @Test public void handlerExceptionResolver() throws Exception { - Capture>> converters = new Capture>>(); - Capture> exceptionResolvers = new Capture>(); - - configurer.configureMessageConverters(capture(converters)); - configurer.configureHandlerExceptionResolvers(capture(exceptionResolvers)); + configurer.configureMessageConverters(isA(List.class)); + configurer.configureHandlerExceptionResolvers(isA(List.class)); replay(configurer); mvcConfiguration.setConfigurers(Arrays.asList(configurer)); - mvcConfiguration.handlerExceptionResolver(); + List actual = mvcConfiguration.handlerExceptionResolver().getExceptionResolvers(); - 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); + assertEquals(3, actual.size()); + assertTrue(actual.get(0) instanceof ExceptionHandlerExceptionResolver); + assertTrue(actual.get(1) instanceof ResponseStatusExceptionResolver); + assertTrue(actual.get(2) instanceof DefaultHandlerExceptionResolver); verify(configurer); }