I want to override the default RequestMappingHandlerMapping as follows:

    @Configuration
    public static class VersionConfig extends WebMvcConfigurationSupport {


        @Override
        protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
            return new VersionRequestMappingHandlerMapping();
        }

    }

where VersionRequestMappingHandlerMapping simply overrides some stub methods:


@Component
public class VersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    @Override protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        VersionRange typeAnnotation = AnnotationUtils.findAnnotation(handlerType, VersionRange.class);
        return (typeAnnotation != null) ? new VersionRangeRequestCondition(typeAnnotation.value()) : null;
    }

    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        VersionRange methodAnnotation = AnnotationUtils.findAnnotation(
                method, VersionRange.class);
        return  (methodAnnotation != null) ? new VersionRangeRequestCondition(methodAnnotation.value()) : null;
    }
}

The issue is that because I am extending WebMvcConfigurationSupport in my config, WebMvcAutoConfiguration fails it's conditional:

@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
public class WebMvcAutoConfiguration {

I think the main issue is that the method :

    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {

in WebMvcConfigurationSupport does too much work in the sense that it doesn't simply create a new instance of RequestMappingHandlerAdapter so if I had to create my own @Bean returning a RequestMappingHandlerAdapter I would have to paste large chunks of code from WebMvcConfigurationSupport. If we had some factory available, I would just override that to inject my version instead.

There was a similar discussion reported on http://stackoverflow.com/questions/22267191/is-it-possible-to-extend-webmvcconfigurationsupport-and-use-webmvcautoconfigurat but the workaround only worked because it is invoking methods on the existing bean, not overriding them like in my case.

I'm willing to attempt a patch but I'd rather take up some comments/suggestions first.

Comment From: wilkinsona

It's unfortunate that Spring MVC requires you to subclass WebMvcConfigurationSupport to override its RequestMappingHandlerMapping. As you've observed, doing so in a Spring Boot app switches off its auto-configuration of Spring MVC. I would recommend opening a Spring Framework JIRA (https://jira.spring.io/browse/SPR) if you'd like to see Spring MVC support a more Boot-friendly way of configuring this.

In the meantime, you might be able to get away with using a BeanPostProcessor to replace the bean with your subclass:

    @Bean
    public BeanPostProcessor requestMappingHandlerMappingPostProcessor() {
        return new BeanPostProcessor() {

            @Override
            public Object postProcessBeforeInitialization(Object bean, String beanName)
                    throws BeansException {
                if (bean instanceof RequestMappingHandlerMapping) {
                    RequestMappingHandlerMapping customized = new RequestMappingHandlerMapping() {

                        @Override
                        protected RequestCondition<?> getCustomMethodCondition(
                                Method method) {
                            return null;
                        }

                        @Override
                        protected RequestCondition<?> getCustomTypeCondition(
                                Class<?> handlerType) {
                            return null;
                        }

                    };
                    customized.setApplicationContext(((RequestMappingHandlerMapping) bean)
                            .getApplicationContext());
                    return customized;
                }
                return bean;
            }

            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName)
                    throws BeansException {
                return bean;
            }

        };

    }

In my limited testing this appears to work, but I can't say that a particularly like it. It would certainly be nicer if there was a more delicate way to do this. Please do open that Spring MVC JIRA ticket if you agree.

/cc @rstoyanchev in case there's already a more sane way to do this that I have overlooked.

Comment From: rstoyanchev

Would a createRequestMappingHandlerAdapter protected method analogous to createRequestMappingHandlerMapping help?

Comment From: wilkinsona

@rstoyanchev There's some confusion between RequestMappingHandlerAdapter and RequestMappingHandlerMapping. The goal, I believe, is to customise the latter but without having to subclass WebMvcConfigurationSupport.

Comment From: rstoyanchev

I'm guessing he is extending Boot's EnableWebMvcConfiguration and then overriding createRequestMappingHandlerMapping which is why the adapter method is the "main issue".

The current split is between simple vs advanced configuration. I'd like to see if we can get Boot to a point where it doesn't need to extend DelegatingWebMvcConfiguration or perhaps simply the advice is to extend Boot's EnableWebMvcConfiguration.

Comment From: wwadge

@wilkinsona Your workaround worked for me though like you I think this can be cleaned up a little bit.

Comment From: rstoyanchev

I've gone ahead and added a couple more protected methods (https://github.com/spring-projects/spring-framework/commit/ebccfd023a7c7bb862bff96796310d8a35305f87) to allow plugging in custom sub-classes.

After giving this some more thought here is the way I see this. At the lowest level the Spring Framework provides two modes of MVC Java config. A simple callback-based configuration API (i.e. WebMvcConfigurer) that does not require seeing or knowing what beans are behind it and a more advanced option to extend directly from the bean-providing configuration (i.e. WebMvcConfigurationSupport). A user can choose one of these two modes.

Boot as an opinionated user chooses the advanced option which still allows applications to exercise control via one or more WebMvcConfigurer config classes. When a Boot user wants to do more advanced things they can extend Boot's own WebMvcAutoConfiguration which could be a top-level class called BootWebMvcConfigurationSupport. Furthermore Boot can detect and hook in custom sub-classes of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, and ExceptionHandlerExceptionResolver so that switching to advanced configuration is not needed.

I think this model would make the most sense in terms of what's expected on each level. At the Spring MVC level we can continue to make sure what needs to be extensible is. At the Spring Boot level we can then automatically detect user-provided custom sub-classes.

Comment From: ghost

@rstoyanchev What you are saying sounds like it does cover all customization needs. But, is this already implemented? And in which version? So, I mean, for example does Spring Boot currently auto detect a custom subclass of RequestMappingHandlerMapping?

Comment From: wilkinsona

@kmazut This issue is still open. Rossen has made some changes in Spring Framework, but nothing's been done in Boot as of now.

Comment From: ghost

Thanks for the reply. Could you please provide any pointers for how to currently configure a spring boot application to provide everything that WebMvcAutoConfiguration provides but use a custom RequestMappingHandlerMapping? I mean is there anything better than just copying the entire existing WebMvcAutoConfiguration into a new class, modifying it, and registering it as an auto configuration?

Comment From: michaelkrog

For anyone else ending up here to figure out how to override RequestMappingHandlerMapping in Spring boot 2+:

Instead of:

@EnableWebMvc
public class WebConfig {
}

Do this:

// @EnableWebMvc <- Remove annotation and extend from DelegatingWebMvcConfiguration directly
public class WebConfig extends DelegatingWebMvcConfiguration {
    @Override
    public RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
        return new CustomRequestMappingHandlerMapping();
    }
}

...where CustomRequestMappingHandlerMapping is a class that extends RequestMappingHandlerMapping.

Comment From: bclozel

@michaelkrog unfortunately this will turn off all Spring Boot auto-configuration for Spring MVC and defeats the purpose of this issue. I believe this should still work in Spring Boot 2.0+:

@Component
public class CustomMvcRegistrations implements WebMvcRegistrations {

  public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
    return new CustomRequestMappingHandlerMapping();
  }
}

If it doesn’t, please open a new issue with a small project reproducing the error. Thanks!

Comment From: michaelkrog

Ehm... I tried what you suggest, and that resolved in an extra RequestMappingHandlerMapping which isn’t going to work in my case. But what I needed was To replace the original RequestMapperHandlingMapping created by DelegatingWebMvcConfiguration here: https://github.com/spring-projects/spring-framework/blob/master/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java#L327

The method I used is suggested by the @EnableWebMvc annotation here: https://github.com/spring-projects/spring-framework/blob/master/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/EnableWebMvc.java#L68

And it doesn’t disable auto-configuration in my case.

Comment From: bclozel

@michaelkrog Could you create a new issue with a sample project showing the problem? Thanks!

Comment From: michaelkrog

Sure.. https://github.com/michaelkrog/spring-boot-webmvc-versioning-demo

Maybe there is a better way to do this, that I don't know of. But what I am trying to do is to add multiple (versioned) controllers with same RequestMapping. Only difference between them is a custom annotation that via a Semver range defines what api-version it belongs to.

ControllerV1_0: https://github.com/michaelkrog/spring-boot-webmvc-versioning-demo/blob/master/src/main/java/com/example/demo/ControllerV1_0.java

ControllerV1_1: https://github.com/michaelkrog/spring-boot-webmvc-versioning-demo/blob/master/src/main/java/com/example/demo/ControllerV1_1.java

ApiRequestMapping (Custom RequestMappingHandlerMapping): https://github.com/michaelkrog/spring-boot-webmvc-versioning-demo/blob/master/src/main/java/com/example/demo/ApiVersionedRequestMapping.java

I can choose the version I want served via Api-Version header fx. curl http://server/version -H 'Api-Version:1.0.0' (ControllerV1_0) curl http://server/version -H 'Api-Version:1.1.0' (ControllerV1_1)

If I register my Custom RequestMappingHandlerMapping via WebRegistrations, then I get a second RequestMappingHandlerMapping instantiated, and the first one created by DelegatingWebMvcConfiguration will complain that my RequestMapping's are ambiguous, because it doesnt know about my custom RequestCondition.

Comment From: philwebb

@michaelkrog Thanks, I've spun out #21571. We can continue the discussion there since this issue is getting quite long.

Comment From: magnettar

@michaelkrog unfortunately this will turn off all Spring Boot auto-configuration for Spring MVC and defeats the purpose of this issue. I believe this should still work in Spring Boot 2.0+:

```java @Component public class CustomMvcRegistrations implements WebMvcRegistrations {

public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return new CustomRequestMappingHandlerMapping(); } } ```

If it doesn’t, please open a new issue with a small project reproducing the error. Thanks!

Worked great for me. Many thanks. Tested on Spring Boot 2.2.7.RELEASE