diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java index f6fb2b585b..661e0ea344 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java @@ -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 mvcPropertiesProvider, + ObjectProvider 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 diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcRegistrations.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcRegistrations.java new file mode 100644 index 0000000000..5243dd7a21 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcRegistrations.java @@ -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. + *

+ * 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(); + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcRegistrationsAdapter.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcRegistrationsAdapter.java new file mode 100644 index 0000000000..aaa46561a0 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcRegistrationsAdapter.java @@ -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; + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java index 9d399f2281..b78c6a1265 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java @@ -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 { + + } + } diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 061b042bd2..e1b3d3bbe0 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -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]]