如何(以及何时)初始化需要访问仅在运行时已知的其他 bean 的 Spring bean?

How (and when) to initialize a Spring bean which needs to access other beans only known in runtime?

我在 Java 上有一个 Spring webapp 运行 5. 这个 我的 applicationContext.xml 文件:

<bean id="child1" class="foo.ChildOne" />
<bean id="child2" class="foo.ChildTwo" />
<bean id="main" class="foo.Main">
  <property name="childrenList">
    <list value-type="foo.IChildren">
      <ref bean="child1" />
      <ref bean="child2" />
    </list>
  </property>
</bean>

ChildOneChildTwoclass都实现了IChildren接口。在我的 Main class 中,我定义了一个 childrenList,其中填充了 child1child2 bean。 Easy, right?

但在未来,可能没有child1,取而代之的是,我们可能会有一个基于另一个未知class的child81。而且它仍然必须在不触及当前代码或 XML 文件的情况下工作,仅通过配置。这个 child81 将在其自己的 applicationContext 文件(在 JAR 中)中定义,我们将在数据库字段中列出 bean 名称 (child2,child81,etc)。

我想这不是一个好的设计模式;这很可能是一个可怕的、可怕的、痛苦的、you-better-run-while-you-can 的设计,它将在未来的岁月里困扰着我。但是我没有定义它,我不能改变它,所以请假设它对这个问题的目的是可以的。

我必须在运行时以某种方式检索它们,而不是自己使用 applicationContext.xml 注入 bean。我也不能使用自动装配,因为我不知道 class 这些 bean 是什么,我只知道它们的所有 classes 实现 IChildren.

所以我制作了我的主 bean class ApplicationContextAware 并且我添加了这个方法:

public void loadChildren() {
  if (childrenList == null) {
    childrenList = new LinkedList<IChildren>();
    for (String name : theListOfBeanNames) {
      childrenList.add((IChildren) context.getBean(name));
    }
  }
}

它工作正常;每个 bean 都在其自己的 applicationContext 中定义,然后我按名称检索它们。但是...我什么时候调用 loadChildren()?到目前为止,我在我的每个方法的第一行都这样做了。由于有一个null-check,它只会初始化列表一次。但肯定有一种 easier/cleaner/better 方法可以做到这一点?

我不认为我可以在我的 applicationContext 中 使用 init-method="loadChildren" 属性,因为如果我的主 bean 被在所有 children 之前加载...我无法控制加载顺序。或者我可以吗?

这是我的 web.xml 文件:

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
    /WEB-INF/application-context/applicationContext-*.xml
    ,classpath*:WEB-INF/application-context/applicationContext-children.xml
  </param-value>
</context-param>

(我们使用“classpath* notation”从 JAR 中加载 每个 applicationContext-children.xml 文件)。如果我颠倒顺序并将 children XML 放在主要的之前,它会确保它们将按特定顺序加载吗?不管我们用什么版本的什么应用服务器?

我还看到了一个 @PostConstruct annotation,如果我可以将它添加到我的 loadChildren 方法中,它会很有用。但是什么时候调用呢?我读到它是在注射完成之后,但是……只针对当前的 bean,对吗?它不能确保所有其他 bean 都已加载?

还有其他选择吗?

Spring 可以为您做这件事:

@Component
public class MyComponent {
    @Autowired
    private final List<IChildren> children;

    ...
}

除了实现 IChildren.

之外,所有内容都会自动装配

完全符合我的要求,因此我将其标记为正确答案。但是,我忘了提到列表中的子项顺序 do 很重要,所以我仍然无法使用自动装配。这是我最后做的,以防其他人发现它有用。

如答案 to this question 中所述,另一种选择是通过实现 ApplicationListener<ContextRefreshedEvent> 接口来捕捉 ContextRefreshedEvent。这确保 loadChildren 方法仅在所有初始化完成后执行。

由于我们使用的是非常旧的 Spring 版本 (2.0.7),因此我们的 ApplicationListener interface is slightly different, for example it doesn't support generic types. So, instead of doing this、我的 class 看起来像这样:

public class Main implements ApplicationListener {

  public void loadChildren(ApplicationContext context) {
    //...
  }

  public void onApplicationEvent(ApplicationEvent ev) {
    if (ev instanceof ContextRefreshedEvent) {
      loadChildren(((ContextRefreshedEvent) ev).getApplicationContext());
    }
  }

}