Add WebMvcRegistrations for custom MVC components

Add `WebMvcRegistrations` which can be used to provide custom
instances of `RequestMappingHandlerMapping`,
`RequestMappingHandlerAdapter` and `ExceptionHandlerExceptionResolver`.
Those instances are then used and processed by the Boot MVC
configuration.

Prior to this commit, developers could provide their custom instances
of MVC infrstructure components such as `RequestMappingHandlerMapping`
and `RequestMappingHandlerAdapter` only by using advanced configuration
strategies. Those advanced configurations involved subclassing
`WebMvcConfigurationSupport` which effectively turns off MVC
auto-configuration in Boot.

Fixes gh-5004
Closes gh-6100
pull/6145/head
Brian Clozel 9 years ago committed by Phillip Webb
parent 5250fb127a
commit 6dc0ecb182

@ -83,6 +83,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupp
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.i18n.FixedLocaleResolver;
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.resource.AppCacheManifestTransformer;
@ -342,10 +343,14 @@ public class WebMvcAutoConfiguration {
private final ListableBeanFactory beanFactory;
private final WebMvcRegistrations mvcRegistrations;
public EnableWebMvcConfiguration(
ObjectProvider<WebMvcProperties> mvcPropertiesProvider,
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
ListableBeanFactory beanFactory) {
this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
this.beanFactory = beanFactory;
}
@ -358,6 +363,15 @@ public class WebMvcAutoConfiguration {
return adapter;
}
@Override
protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
if (this.mvcRegistrations != null
&& this.mvcRegistrations.getRequestMappingHandlerAdapter() != null) {
return this.mvcRegistrations.getRequestMappingHandlerAdapter();
}
return super.createRequestMappingHandlerAdapter();
}
@Bean
@Primary
@Override
@ -366,6 +380,15 @@ public class WebMvcAutoConfiguration {
return super.requestMappingHandlerMapping();
}
@Override
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
if (this.mvcRegistrations != null
&& this.mvcRegistrations.getRequestMappingHandlerMapping() != null) {
return this.mvcRegistrations.getRequestMappingHandlerMapping();
}
return super.createRequestMappingHandlerMapping();
}
@Override
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
try {
@ -376,6 +399,14 @@ public class WebMvcAutoConfiguration {
}
}
@Override
protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {
if (this.mvcRegistrations != null && this.mvcRegistrations
.getExceptionHandlerExceptionResolver() != null) {
return this.mvcRegistrations.getExceptionHandlerExceptionResolver();
}
return super.createExceptionHandlerExceptionResolver();
}
}
@Configuration

@ -0,0 +1,59 @@
/*
* Copyright 2012-2016 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.boot.autoconfigure.web;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
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;
/**
* Interface to register key components of the {@link WebMvcConfigurationSupport} in place
* of the default ones provided by Spring MVC.
* <p>
* All custom instances are later processed by Boot and Spring MVC configurations. A
* single instance of this component should be registered, otherwise making it impossible
* to choose from redundant MVC components.
*
* @author Brian Clozel
* @since 1.4.0
* @see org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.EnableWebMvcConfiguration
*/
public interface WebMvcRegistrations {
/**
* Return the custom {@link RequestMappingHandlerMapping} that should be used and
* processed by the MVC configuration.
* @return the custom {@link RequestMappingHandlerMapping} instance
*/
RequestMappingHandlerMapping getRequestMappingHandlerMapping();
/**
* Return the custom {@link RequestMappingHandlerAdapter} that should be used and
* processed by the MVC configuration.
* @return the custom {@link RequestMappingHandlerAdapter} instance
*/
RequestMappingHandlerAdapter getRequestMappingHandlerAdapter();
/**
* Return the custom {@link ExceptionHandlerExceptionResolver} that should be used and
* processed by the MVC configuration.
* @return the custom {@link ExceptionHandlerExceptionResolver} instance
*/
ExceptionHandlerExceptionResolver getExceptionHandlerExceptionResolver();
}

@ -0,0 +1,47 @@
/*
* Copyright 2012-2016 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.boot.autoconfigure.web;
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;
/**
* An implementation of {@link WebMvcRegistrations} with empty methods allowing
* sub-classes to override only the methods they're interested in.
*
* @author Brian Clozel
* @since 1.4.0
*/
public class WebMvcRegistrationsAdapter implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return null;
}
@Override
public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
return null;
}
@Override
public ExceptionHandlerExceptionResolver getExceptionHandlerExceptionResolver() {
return null;
}
}

@ -45,6 +45,7 @@ import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.web.filter.OrderedHttpPutFormContentFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.format.support.FormattingConversionService;
@ -65,6 +66,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.i18n.FixedLocaleResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.resource.AppCacheManifestTransformer;
import org.springframework.web.servlet.resource.CachingResourceResolver;
import org.springframework.web.servlet.resource.CachingResourceTransformer;
@ -468,6 +470,29 @@ public class WebMvcAutoConfigurationTests {
.isInstanceOf(CustomWebBindingInitializer.class);
}
@Test
public void customRequestMappingHandlerMapping() {
load(CustomRequestMappingHandlerMapping.class);
assertThat(this.context.getBean(RequestMappingHandlerMapping.class))
.isInstanceOf(MyRequestMappingHandlerMapping.class);
}
@Test
public void customRequestMappingHandlerAdapter() {
load(CustomRequestMappingHandlerAdapter.class);
assertThat(this.context.getBean(RequestMappingHandlerAdapter.class))
.isInstanceOf(MyRequestMappingHandlerAdapter.class);
}
@Test
public void multipleWebMvcRegistrations() {
load(MultipleWebMvcRegistrations.class);
assertThat(this.context.getBean(RequestMappingHandlerMapping.class))
.isNotInstanceOf(MyRequestMappingHandlerMapping.class);
assertThat(this.context.getBean(RequestMappingHandlerAdapter.class))
.isNotInstanceOf(MyRequestMappingHandlerAdapter.class);
}
private void load(Class<?> config, String... environment) {
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context, environment);
@ -594,4 +619,53 @@ public class WebMvcAutoConfigurationTests {
}
@Configuration
static class CustomRequestMappingHandlerMapping {
@Bean
public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerMapping() {
return new WebMvcRegistrationsAdapter() {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new MyRequestMappingHandlerMapping();
}
};
}
}
private static class MyRequestMappingHandlerMapping
extends RequestMappingHandlerMapping {
}
@Configuration
static class CustomRequestMappingHandlerAdapter {
@Bean
public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerAdapter() {
return new WebMvcRegistrationsAdapter() {
@Override
public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
return new MyRequestMappingHandlerAdapter();
}
};
}
}
private static class MyRequestMappingHandlerAdapter
extends RequestMappingHandlerAdapter {
}
@Configuration
@Import({ CustomRequestMappingHandlerMapping.class,
CustomRequestMappingHandlerAdapter.class })
static class MultipleWebMvcRegistrations {
}
}

@ -1457,12 +1457,16 @@ The auto-configuration adds the following features on top of Spring's defaults:
* Custom `Favicon` support.
* Automatic use of a `ConfigurableWebBindingInitializer` bean (see below).
If you want to take complete control of Spring MVC, you can add your own `@Configuration`
annotated with `@EnableWebMvc`. If you want to keep Spring Boot MVC features, and
If you want to keep Spring Boot MVC features, and
you just want to add additional {spring-reference}#mvc[MVC configuration] (interceptors,
formatters, view controllers etc.) you can add your own `@Bean` of type
`WebMvcConfigurerAdapter`, but *without* `@EnableWebMvc`.
`WebMvcConfigurerAdapter`, but *without* `@EnableWebMvc`. If you wish to provide custom
instances of `RequestMappingHandlerMapping`, `RequestMappingHandlerAdapter` or
`ExceptionHandlerExceptionResolver` you can declare a `WebMvcRegistrationsAdapter`
instance providing such components.
If you want to take complete control of Spring MVC, you can add your own `@Configuration`
annotated with `@EnableWebMvc`.
[[boot-features-spring-mvc-message-converters]]

Loading…
Cancel
Save