如何(以及何时)初始化需要访问仅在运行时已知的其他 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>
ChildOne
和ChildTwo
class都实现了IChildren
接口。在我的 Main
class 中,我定义了一个 childrenList
,其中填充了 child1
和 child2
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());
}
}
}
我在 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>
ChildOne
和ChildTwo
class都实现了IChildren
接口。在我的 Main
class 中,我定义了一个 childrenList
,其中填充了 child1
和 child2
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
.
如答案 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());
}
}
}