Introduce base class for WebMvcConfiguration

This commit is contained in:
Rossen Stoyanchev 2011-06-09 11:17:45 +00:00
parent c60511bf04
commit 00d57907a3
7 changed files with 503 additions and 284 deletions

View File

@ -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.
* <pre>
* &#064;Configuration
* &#064;EnableWebMvc
@ -39,11 +38,10 @@ import org.springframework.web.servlet.DispatcherServlet;
*
* }
* </pre>
* <p>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.
* <p>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.
* <pre>
* &#064;Configuration
* &#064;EnableWebMvc

View File

@ -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<MappedInterceptor> mappedInterceptors = new ArrayList<MappedInterceptor>();
private final List<Object> interceptors = new ArrayList<Object>();
/**
* 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<MappedInterceptor> getInterceptors() {
return mappedInterceptors;
protected List<Object> getInterceptors() {
return interceptors;
}
}

View File

@ -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.
*
* <p>A number of options are available for customizing the default configuration provided by this class.
* See {@link EnableWebMvc} and {@link WebMvcConfigurer} for details.
*
* <p>Registers these handler mappings:
* <ul>
* <li>{@link RequestMappingHandlerMapping} ordered at 0 for mapping requests to annotated controller methods.
* <li>{@link SimpleUrlHandlerMapping} ordered at 1 to map URL paths directly to view names.
* <li>{@link BeanNameUrlHandlerMapping} ordered at 2 to map URL paths to controller bean names.
* <li>{@link SimpleUrlHandlerMapping} ordered at {@code Integer.MAX_VALUE-1} to serve static resource requests.
* <li>{@link SimpleUrlHandlerMapping} ordered at {@code Integer.MAX_VALUE} to forward requests to the default servlet.
* </ul>
*
* <p><strong>Note:</strong> that the SimpleUrlHandlerMapping instances above will have empty URL maps and
* hence no effect until explicitly configured via one of the {@link WebMvcConfigurer} callbacks.
*
* <p>Registers these handler adapters:
* <ul>
* <li>{@link RequestMappingHandlerAdapter} for processing requests using annotated controller methods.
* <li>{@link HttpRequestHandlerAdapter} for processing requests with {@link HttpRequestHandler}s.
* <li>{@link SimpleControllerHandlerAdapter} for processing requests with interface-based {@link Controller}s.
* </ul>
*
* <p>Registers a {@link HandlerExceptionResolverComposite} with this chain of exception resolvers:
* <ul>
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions through @{@link ExceptionHandler} methods.
* <li>{@link ResponseStatusExceptionResolver} for exceptions annotated with @{@link ResponseStatus}.
* <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring exception types
* </ul>
*
* <p>Registers the following others:
* <ul>
* <li>{@link FormattingConversionService} for use with annotated controller methods and the spring:eval JSP tag.
* <li>{@link Validator} for validating model attributes on annotated controller methods.
* </ul>
* 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.
*
* <p>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}.
*
* <p>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<MappedInterceptor> mappedInterceptors;
private List<HttpMessageConverter<?>> messageConverters;
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> 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<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
configurers.addArgumentResolvers(argumentResolvers);
@ -213,116 +96,40 @@ class WebMvcConfiguration implements ApplicationContextAware, ServletContextAwar
List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
configurers.addReturnValueHandlers(returnValueHandlers);
adapter.setCustomReturnValueHandlers(returnValueHandlers);
return adapter;
}
private List<HttpMessageConverter<?>> getMessageConverters() {
if (messageConverters == null) {
messageConverters = new ArrayList<HttpMessageConverter<?>>();
configurers.configureMessageConverters(messageConverters);
if (messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(messageConverters);
}
}
return messageConverters;
@Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> 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<HttpMessageConverter<?>> messageConverters) {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setWriteAcceptCharset(false);
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringConverter);
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new SourceHttpMessageConverter<Source>());
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<HandlerExceptionResolver> resolvers = new ArrayList<HandlerExceptionResolver>();
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) {
}
};
}

View File

@ -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}.
*
* <p>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.
*
* <p>This class registers the following {@link HandlerMapping}s:</p>
* <ul>
* <li>{@link RequestMappingHandlerMapping} ordered at 0 for mapping requests to annotated controller methods.
* <li>{@link SimpleUrlHandlerMapping} ordered at 1 to map URL paths directly to view names.
* <li>{@link BeanNameUrlHandlerMapping} ordered at 2 to map URL paths to controller bean names.
* <li>{@link SimpleUrlHandlerMapping} ordered at {@code Integer.MAX_VALUE-1} to serve static resource requests.
* <li>{@link SimpleUrlHandlerMapping} ordered at {@code Integer.MAX_VALUE} to forward requests to the default servlet.
* </ul>
*
* <p>Registers {@link HandlerAdapter}s:
* <ul>
* <li>{@link RequestMappingHandlerAdapter} for processing requests using annotated controller methods.
* <li>{@link HttpRequestHandlerAdapter} for processing requests with {@link HttpRequestHandler}s.
* <li>{@link SimpleControllerHandlerAdapter} for processing requests with interface-based {@link Controller}s.
* </ul>
*
* <p>Registers a {@link HandlerExceptionResolverComposite} with this chain of exception resolvers:
* <ul>
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions through @{@link ExceptionHandler} methods.
* <li>{@link ResponseStatusExceptionResolver} for exceptions annotated with @{@link ResponseStatus}.
* <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring exception types
* </ul>
*
* <p>Registers the following other instances:
* <ul>
* <li>{@link FormattingConversionService} for use with annotated controller methods and the spring:eval JSP tag.
* <li>{@link Validator} for validating model attributes on annotated controller methods.
* </ul>
*
* @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<Object> interceptors;
private List<HttpMessageConverter<?>> 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<HttpMessageConverter<?>> getMessageConverters() {
if (messageConverters == null) {
messageConverters = new ArrayList<HttpMessageConverter<?>>();
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<HttpMessageConverter<?>> 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<HttpMessageConverter<?>> messageConverters) {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setWriteAcceptCharset(false);
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringConverter);
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new SourceHttpMessageConverter<Source>());
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:
* <ul>
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions through @{@link ExceptionHandler} methods.
* <li>{@link ResponseStatusExceptionResolver} for exceptions annotated with @{@link ResponseStatus}.
* <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring exception types
* </ul>
*/
@Bean
public HandlerExceptionResolverComposite handlerExceptionResolver() throws Exception {
ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = new ExceptionHandlerExceptionResolver();
exceptionHandlerExceptionResolver.setMessageConverters(getMessageConverters());
exceptionHandlerExceptionResolver.afterPropertiesSet();
List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<HandlerExceptionResolver>();
exceptionResolvers.add(exceptionHandlerExceptionResolver);
exceptionResolvers.add(new ResponseStatusExceptionResolver());
exceptionResolvers.add(new DefaultHandlerExceptionResolver());
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}
}

View File

@ -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}.
*
* <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.
* Defines configuration callback methods for customizing the default Spring MVC configuration enabled through the
* use of @{@link EnableWebMvc}.
*
* <p>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

View File

@ -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<HandlerInterceptor> getInterceptorsForPath(String lookupPath) {
PathMatcher pathMatcher = new AntPathMatcher();
List<HandlerInterceptor> result = new ArrayList<HandlerInterceptor>();
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;

View File

@ -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<List<HttpMessageConverter<?>>> converters = new Capture<List<HttpMessageConverter<?>>>();
Capture<List<HandlerExceptionResolver>> exceptionResolvers = new Capture<List<HandlerExceptionResolver>>();
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<HandlerExceptionResolver> 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);
}