Search code examples
spring-integrationspringfox

Spring-Integration: Ensure that IntegrationRequestMappingHandlerMapping is initialized


IntegrationRequestMappingHandlerMapping has a special requirement to initialize on ContextRefreshedEvent. To quote the code:

@Override
public void afterPropertiesSet() {
    // No-op in favor of onApplicationEvent
}

/**
 * {@link HttpRequestHandlingEndpointSupport}s may depend on auto-created
 * {@code requestChannel}s, so MVC Handlers detection should be postponed
 * as late as possible.
 * @see RequestMappingHandlerMapping#afterPropertiesSet()
 */
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
    if (!this.initialized.getAndSet(true)) {
        super.afterPropertiesSet();
    }
}

The effect is that its mappingRegistry is empty when other beans try to evaluate it during application startup, even when they implement SmartLifeCycle with a phase of MAX_VALUE.

In my case I am trying to implement a spring-integration plugin for Springfox. Its DocumentationPluginsBootstrapper needs to access the request mappings to document them.

How can I be sure that IntegrationRequestMappingHandlerMapping has been initialized before I start asking for its mappings? Would it be the correct approach to listen for ContextRefreshedEvent, too, but with a high value for @Order? Or would you advise to use a different event?

Update: AbstractHandlerMapping already uses Order.LOWEST_PRECEDENCE. I guess I can't use the context refreshed event to be safe.

Also see the related springfox issue.


Solution

  • The ContextRefreshedEvent is really the last step in the application context initialization:

                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);
    
                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);
    
                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);
    
                // Initialize message source for this context.
                initMessageSource();
    
                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();
    
                // Initialize other special beans in specific context subclasses.
                onRefresh();
    
                // Check for listener beans and register them.
                registerListeners();
    
                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);
    
                // Last step: publish corresponding event.
                finishRefresh();
    

    It is fired in that finishRefresh().

    You indeed should consider an Ordered for your own ApplicationListener<ContextRefreshedEvent> with the Order.LOWEST_PRECEDENCE. At the same time Framework register that IntegrationRequestMappingHandlerMapping with the order == 0 :

    private void registerRequestMappingHandlerMappingIfNecessary(BeanDefinitionRegistry registry) {
        if (HttpContextUtils.WEB_MVC_PRESENT &&
                !registry.containsBeanDefinition(HttpContextUtils.HANDLER_MAPPING_BEAN_NAME)) {
            BeanDefinitionBuilder requestMappingBuilder =
                    BeanDefinitionBuilder.genericBeanDefinition(IntegrationRequestMappingHandlerMapping.class);
            requestMappingBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            requestMappingBuilder.addPropertyValue(IntegrationNamespaceUtils.ORDER, 0);
            registry.registerBeanDefinition(HttpContextUtils.HANDLER_MAPPING_BEAN_NAME,
                    requestMappingBuilder.getBeanDefinition());
        }
    }
    

    So, you really are save to handle mapping in your own listener. Just because, thanks to order = 0 the IntegrationRequestMappingHandlerMapping is going to be initialized already.

    The same is applied for the WebFluxIntegrationRequestMappingHandlerMapping:

    private void registerReactiveRequestMappingHandlerMappingIfNecessary(BeanDefinitionRegistry registry) {
        if (HttpContextUtils.WEB_FLUX_PRESENT &&
                !registry.containsBeanDefinition(WebFluxContextUtils.HANDLER_MAPPING_BEAN_NAME)) {
            BeanDefinitionBuilder requestMappingBuilder =
                    BeanDefinitionBuilder.genericBeanDefinition(WebFluxIntegrationRequestMappingHandlerMapping.class);
            requestMappingBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            requestMappingBuilder.addPropertyValue(IntegrationNamespaceUtils.ORDER, 0);
            registry.registerBeanDefinition(WebFluxContextUtils.HANDLER_MAPPING_BEAN_NAME,
                    requestMappingBuilder.getBeanDefinition());
    
            BeanDefinitionReaderUtils.registerWithGeneratedName(
                    new RootBeanDefinition(IntegrationHandlerResultHandler.class), registry);
        }
    }