如何确保 Spring 构造一个 bean 一次

How can I ensure Spring constructs a bean once

我有一个复杂的企业环境,其中有多个项目相互依赖。配置由 spring 处理,包括疯狂的数字导入等等。

看起来默认情况下 Spring 可以多次构造同名 bean。特别是,在多个 Spring 上下文的情况下。每个上下文都有自己的同一个单例 bean 实例。在Spring架构师的心目中,单例并不是真正的单例...

不幸的是,在我的例子中,有一个 bean 永远不能创建多次。

有没有办法强制 Spring 检查 bean 是否已经创建并且不再尝试再次调用其构造函数?

特别是bean在里面创建ehcache的CacheManager失败,因为同名的CacheManager不能创建两次

    <bean id="cacheService" class="somepackage.CacheServiceImpl">
        <constructor-arg index="0" value="/somepackage/ehcache.xml" />
    </bean>

我无法控制 CacheServiceImpl 代码。我只能更改它周围的配置。

不同的 Spring 应用程序上下文相互不了解。就每个上下文而言,您的对象 一个单例。但是,听起来您不希望存在多个上下文。

您如何创建上下文?您的应用程序应该创建 ApplicationContext 的单个实例(可能在 main 或附近的某个地方)。任何其他需要应用程序上下文的东西都应该注入或制作 ApplicationContextAware.

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-instantiation

你提到一个"complex enterprise environment"。在我工作的地方,对我们来说最有效的是让一个项目管理 Spring。假设您的所有项目都在同一个应用程序中 运行(否则 Spring 无法真正帮助您),可能有一些项目启动了一切。对我们来说,这是构建到 war 并部署到我们服务器的项目。

我终于明白了。加载上下文两次的原因是在第一次加载上下文时发生了隐藏错误。下面是冗长乏味的解释。

上下文应该在第一次调用 spring 方法 DefaultTestContext.getApplicationContext() 时加载。

有一个 class DefaultCacheAwareContextLoaderDelegate,它负责实际加载一次上下文并缓存它以备将来使用。方法代码如下:

@Override
public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) {
    synchronized (this.contextCache) {
        ApplicationContext context = this.contextCache.get(mergedContextConfiguration);
        if (context == null) {
            try {
                context = loadContextInternal(mergedContextConfiguration);
                if (logger.isDebugEnabled()) {
                    logger.debug(String.format("Storing ApplicationContext in cache under key [%s]",
                            mergedContextConfiguration));
                }
                this.contextCache.put(mergedContextConfiguration, context);
            }
            catch (Exception ex) {
                throw new IllegalStateException("Failed to load ApplicationContext", ex);
            }
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug(String.format("Retrieved ApplicationContext from cache with key [%s]",
                        mergedContextConfiguration));
            }
        }

        this.contextCache.logStatistics();

        return context;
    }
}

显然,当在 loadContextInternal() 调用期间抛出 Exception 以外的 Throwable 时,应用程序上下文不会放入缓存中。

显然,即使是单个 JUnit 测试 运行 getApplicationContext() 方法也被调用了不止一次,期望第二次不需要再次加载它,因为它已经被缓存了。

如果在第一次加载期间抛出错误,它将被掩埋并且 JUnit/Spring 继续测试直到它第二次调用 getApplicationContext()。这就是它捕获异常并崩溃的地方,因为 ehcache 不希望 CacheManager 被初始化多次。