Greg Kennedy
Greg Kennedy

Reputation: 825

Spring context loading twice with both xml and annotation configuration

I have a web application on Tomcat 7.0.34, Spring 3.2.3, Spring Security 3.2.0.RC1 and Spring Social 1.1.

For some reason the Spring context is being loaded twice. The second load is happening immediately after the first load has finished. The log below shows the Context Loader loading the Root WebApplicationContext. Everything progresses normally and all the RequstMappingHandlers are registering correctly. Then immediately the context is refreshed again.

I've read several solutions on SO about ensuring you don't load the configuration as part of the Context Loader and the DispatcherServlet at the same time and have tested various combinations of this but that doesn't seem to have fixed it and I'm becoming code blind as well.

All my testing on this has pushed me to an annotation only configuration both for the container and Spring components but the config classes are pretty much copy and paste from the Spring Social examples on github. Although I've included the SocialConfig.java details below, this problem has been happening before I implemented Spring Social but I can't move on without fixing it.

Also, the problem was present with a hybrid xml (web.xml, security-app-context.xml) and annotation configuration.

I'm implementing WebApplicationInitializer rather than having a web.xml

public class WebClientInitialiser implements WebApplicationInitializer {

    public void onStartup(ServletContext container) throws ServletException {

        // Create the 'root' Spring application context
        AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();  

        // Manage the lifecycle of the root application context
        appContext.setConfigLocation("com.mycompany.webclient.config");
        appContext.setServletContext(container);

        container.addListener(new ContextLoaderListener(appContext)); 
        container.addListener(new MyCompanyContextListener());

        container.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain"))
            .addMappingForUrlPatterns(null, false, "/*");

        // Register and map the dispatcher servlet
        Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(appContext));
        dispatcher.addMapping("/");
        dispatcher.setLoadOnStartup(1);
    }

}

My MainConfig.java

/**
 * Main configuration class for the application.
 * Turns on @Component scanning, loads externalized application properties
 * and imports legacy security configuration
 */
@Configuration
@ComponentScan(basePackages = "com.mycompany.webclient", excludeFilters = { @Filter(Configuration.class) })
@PropertySource("classpath:wc.properties")
@ImportResource("/WEB-INF/spring/appServlet/security-app-context.xml")
public class MainConfig {


    @Bean
    public PropertySourcesPlaceholderConfigurer propertyPlaceHolderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

}

My WebMvcConfig.java

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
    }

    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("/WEB-INF/messages/messages");
        return messageSource;
    }

    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/views/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }
}

My SocialConfig.java

@Configuration
@EnableSocial
public class SocialConfig implements SocialConfigurer {

        private SocialUserDAO socialUserDao;
        //
        // SocialConfigurer implementation methods
        //

        @Override
        public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
            String clientId="XXXXXX";
            String clientSecret="XXXXX";
            cfConfig.addConnectionFactory(new FacebookConnectionFactory(clientId, clientSecret));
        }

        @Override
        public UserIdSource getUserIdSource() {
                return new UserIdSource() {                        
                        @Override
                        public String getUserId() {
                                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                                if (authentication == null) {
                                        throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in");
                                }
                                return authentication.getName();
                        }
                };
        }

        @Override
        public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
                return new HibernateUsersConnectionRepository(socialUserDao, connectionFactoryLocator, Encryptors.noOpText());
        }

        //
        // API Binding Beans
        //

        @Bean
        @Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
        public Facebook facebook(ConnectionRepository repository) {
                Connection<Facebook> connection = repository.findPrimaryConnection(Facebook.class);
                return connection != null ? connection.getApi() : null;
        }

        @Bean
        @Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
        public Twitter twitter(ConnectionRepository repository) {
                Connection<Twitter> connection = repository.findPrimaryConnection(Twitter.class);
                return connection != null ? connection.getApi() : null;
        }

        @Bean
        @Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
        public LinkedIn linkedin(ConnectionRepository repository) {
                Connection<LinkedIn> connection = repository.findPrimaryConnection(LinkedIn.class);
                return connection != null ? connection.getApi() : null;
        }

        //
        // Web Controller and Filter Beans
        //
        @Bean
        public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator, ConnectionRepository connectionRepository) {
                ConnectController connectController = new ConnectController(connectionFactoryLocator, connectionRepository);
                connectController.addInterceptor(new PostToWallAfterConnectInterceptor());
                connectController.addInterceptor(new TweetAfterConnectInterceptor());
                return connectController;
        }

        @Bean
        public ProviderSignInController providerSignInController(ConnectionFactoryLocator connectionFactoryLocator, UsersConnectionRepository usersConnectionRepository) {
                return new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository, new SimpleSignInAdapter(new HttpSessionRequestCache()));
        }

        @Bean
        public DisconnectController disconnectController(UsersConnectionRepository usersConnectionRepository, Environment env) {
                return new DisconnectController(usersConnectionRepository, env.getProperty("facebook.clientSecret"));
        }

        @Bean
        public ReconnectFilter apiExceptionHandler(UsersConnectionRepository usersConnectionRepository, UserIdSource userIdSource) {
                return new ReconnectFilter(usersConnectionRepository, userIdSource);
        }

}

Any help, comments, pointers is greatly appreciated.

This log output is repeated twice at the start of each context refresh:

org.springframework.web.context.ContextLoader- Root WebApplicationContext: initialization started
org.springframework.web.context.support.AnnotationConfigWebApplicationContext- Refreshing Root WebApplicationContext: startup date [Mon Nov 25 22:43:39 GMT 2013]; root of context hierarchy
org.springframework.context.annotation.ClassPathBeanDefinitionScanner- JSR-330 'javax.inject.Named' annotation found and supported for component scanning
org.springframework.web.context.support.AnnotationConfigWebApplicationContext- Registering annotated classes: [class com.mycompany.webclient.config.WebMvcConfig,class com.mycompany.webclient.config.SocialConfig]

Upvotes: 1

Views: 6921

Answers (1)

M. Deinum
M. Deinum

Reputation: 125158

You are passing the same context to both the ContextLoaderListener and DispatcherServlet and hence this will trigger loading the configuration twice.

You should have 2 seperate AnnotationConfigWebApplicationContext instances one for the ContextLoaderListener loading all your generic beans (services etc.) and one for the DispatcherServlet loading the web related things.

public class WebClientInitialiser implements WebApplicationInitializer {

    public void onStartup(ServletContext container) throws ServletException {

        // Create the 'root' Spring application context
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();  
        rootContext.register(MainConfig.class);
        // Manage the lifecycle of the root application context

        container.addListener(new ContextLoaderListener(rootContext)); 
        container.addListener(new MyCompanyContextListener());

        container.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain"))
            .addMappingForUrlPatterns(null, false, "/*");

        AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();  
        dispatcherContext.register(WebMvcConfig.class, SocialConfig.class);

        // Register and map the dispatcher servlet
        Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
        dispatcher.addMapping("/");
        dispatcher.setLoadOnStartup(1);
    }

}

Something like this. Also you don't need to set the contextConfigLocation simply register the @Configuration annotated classes. Also setting the ServletContext is already done by Spring so no need for that to.

A note on your configuration, the PropertySourcesPlaceHolderConfigurer is enabled by default so no need to register that again in in MainConfig class.

Another thing to take into account is that now probably your application fails (i.e. your @Controllers don't work anymore). This is due to the fact that everything is inside the root application context whereas @Controllers should be loaded by the DispatcherServlet. To fix this you need to exclude @Controller scanning in your MainConfig and enable @Controller scanning on the WebMvcConfig class.

Upvotes: 4

Related Questions