使用 xml 和注释配置加载两次 Spring 上下文 [英] Spring context loading twice with both xml and annotation configuration

查看:34
本文介绍了使用 xml 和注释配置加载两次 Spring 上下文的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 Tomcat 7.0.34、Spring 3.2.3、Spring Security 3.2.0.RC1 和 Spring Social 1.1 上有一个 Web 应用程序.

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.

出于某种原因,Spring 上下文被加载了两次.第二个加载在第一个加载完成后立即发生.下面的日志显示了 Context Loader 加载 Root WebApplicationContext.一切正常,所有 RequstMappingHandlers 都在正确注册.然后立即再次刷新上下文.

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.

我已经阅读了关于 SO 的几个解决方案,以确保您不会同时将配置作为 Context Loader 和 DispatcherServlet 的一部分加载,并且已经测试了各种组合,但这似乎并没有修复它我也变得代码盲了.

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.

我对此的所有测试都促使我对容器和 Spring 组件进行仅注释配置,但配置类几乎是从 github 上的 Spring Social 示例复制和粘贴的.虽然我在下面包含了 SocialConfig.java 的详细信息,但在我实施 Spring Social 之前就已经发生了这个问题,但如果不修复它我就无法继续.

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.

此外,混合 xml(web.xml、security-app-context.xml)和注释配置也存在问题.

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

我正在实现 WebApplicationInitializer 而不是 web.xml

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);
    }

}

我的 MainConfig.java

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();
    }

}

我的 WebMvcConfig.java

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;
    }
}

我的 SocialConfig.java

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]

推荐答案

您将相同的上下文传递给 ContextLoaderListenerDispatcherServlet,因此这将触发加载配置两次.

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

您应该有 2 个单独的 AnnotationConfigWebApplicationContext 实例,一个用于 ContextLoaderListener 加载所有通用 bean(服务等),另一个用于 DispatcherServlet加载网络相关的东西.

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);
    }

}

类似的东西.此外,您不需要设置 contextConfigLocation 只需注册 @Configuration 注释类.还设置 ServletContext 已经由 Spring 完成,因此无需这样做.

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.

请注意您的配置,PropertySourcesPlaceHolderConfigurer 默认启用,因此无需在 MainConfig 类中再次注册.

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

要考虑的另一件事是,现在您的应用程序可能会失败(即您的 @Controllers 不再工作).这是因为一切都在根应用程序上下文中,而 @Controllers 应该由 DispatcherServlet 加载.要解决此问题,您需要在 MainConfig 中排除 @Controller 扫描,并在 WebMvcConfig 类上启用 @Controller 扫描.

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.

这篇关于使用 xml 和注释配置加载两次 Spring 上下文的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆