Unable to have custom RequestMappingHandlerMapping #5004

Closed
wwadge opened this Issue Jan 21, 2016 · 9 comments

Comments

Projects
None yet
8 participants
@wwadge
Contributor

wwadge commented Jan 21, 2016

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.

@wilkinsona

This comment has been minimized.

Show comment
Hide comment
@wilkinsona

wilkinsona Jan 21, 2016

Member

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.

Member

wilkinsona commented Jan 21, 2016

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.

@rstoyanchev

This comment has been minimized.

Show comment
Hide comment
@rstoyanchev

rstoyanchev Jan 21, 2016

Contributor

Would a createRequestMappingHandlerAdapter protected method analogous to createRequestMappingHandlerMapping help?

Contributor

rstoyanchev commented Jan 21, 2016

Would a createRequestMappingHandlerAdapter protected method analogous to createRequestMappingHandlerMapping help?

@wilkinsona

This comment has been minimized.

Show comment
Hide comment
@wilkinsona

wilkinsona Jan 21, 2016

Member

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

Member

wilkinsona commented Jan 21, 2016

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

@wilkinsona wilkinsona changed the title from Unable to have custom RequestMappingHandlerAdapter to Unable to have custom RequestMappingHandlerMapping Jan 21, 2016

@rstoyanchev

This comment has been minimized.

Show comment
Hide comment
@rstoyanchev

rstoyanchev Jan 21, 2016

Contributor

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.

Contributor

rstoyanchev commented Jan 21, 2016

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.

@wwadge

This comment has been minimized.

Show comment
Hide comment
@wwadge

wwadge Jan 22, 2016

Contributor

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

Contributor

wwadge commented Jan 22, 2016

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

@rstoyanchev

This comment has been minimized.

Show comment
Hide comment
@rstoyanchev

rstoyanchev Jan 22, 2016

Contributor

I've gone ahead and added a couple more protected methods (spring-projects/spring-framework@ebccfd0) 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.

Contributor

rstoyanchev commented Jan 22, 2016

I've gone ahead and added a couple more protected methods (spring-projects/spring-framework@ebccfd0) 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.

@kmazut

This comment has been minimized.

Show comment
Hide comment
@kmazut

kmazut Apr 22, 2016

@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?

kmazut commented Apr 22, 2016

@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?

@wilkinsona

This comment has been minimized.

Show comment
Hide comment
@wilkinsona

wilkinsona Apr 22, 2016

Member

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

Member

wilkinsona commented Apr 22, 2016

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

@philwebb philwebb added this to the 1.4.0.M3 milestone Apr 22, 2016

@kmazut

This comment has been minimized.

Show comment
Hide comment
@kmazut

kmazut Apr 22, 2016

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?

kmazut commented Apr 22, 2016

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?

@philwebb philwebb modified the milestones: 1.4.0.RC1, 1.4.0.M3 May 17, 2016

@bclozel bclozel self-assigned this May 26, 2016

bclozel added a commit to bclozel/spring-boot that referenced this issue Jun 1, 2016

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

Declaring a `WebMvcRegistrations` component in an application allows
to provide custom instances of `RequestMappingHandlerMapping`,
`RequestMappingHandlerAdapter` and `ExceptionHandlerExceptionResolver`.
Those instances are then used and processed by the Boot MVC
configuration (i.e. all Boot configurations apply here).

Fixes gh-5004

bclozel added a commit to bclozel/spring-boot that referenced this issue Jun 2, 2016

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

Declaring a `WebMvcRegistrations` component in an application allows
to provide custom instances of `RequestMappingHandlerMapping`,
`RequestMappingHandlerAdapter` and `ExceptionHandlerExceptionResolver`.
Those instances are then used and processed by the Boot MVC
configuration (i.e. all Boot configurations apply here).

Fixes gh-5004

philwebb added a commit to philwebb/spring-boot that referenced this issue Jun 9, 2016

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

Declaring a `WebMvcRegistrations` component in an application allows
to provide custom instances of `RequestMappingHandlerMapping`,
`RequestMappingHandlerAdapter` and `ExceptionHandlerExceptionResolver`.
Those instances are then used and processed by the Boot MVC
configuration (i.e. all Boot configurations apply here).

Fixes gh-5004

@philwebb philwebb closed this in 6dc0ecb Jun 10, 2016

philwebb added a commit to philwebb/spring-boot that referenced this issue Jun 10, 2016

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

Declaring a `WebMvcRegistrations` component in an application allows
to provide custom instances of `RequestMappingHandlerMapping`,
`RequestMappingHandlerAdapter` and `ExceptionHandlerExceptionResolver`.
Those instances are then used and processed by the Boot MVC
configuration (i.e. all Boot configurations apply here).

Fixes gh-5004

@snicoll snicoll removed the in-progress label Jul 5, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment