问题
I have a Spring webapp running on Java 5. This is was my applicationContext.xml file:
<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>
Both ChildOne
and ChildTwo
classes implement the IChildren
interface. And in my Main
class, I have defined a childrenList
which gets populated with child1
and child2
beans. Easy, right?
But in the future, there might be no child1
, and instead, we will maybe have a child81
based on another unknown class. And it still has to work without touching the current code or XML files, only through configuration. This child81
would be defined on its own applicationContext file (in a JAR), and we will list the bean names in a database field (child2,child81,etc
).
I guess that's not a good design pattern; most likely it's a terrible, horrible, painful, you-better-run-while-you-can design, which will haunt me in the years to come. But I didn't define it, and I can't change it, so please lets assume it's OK for the purpose of this question.
Instead of injecting the beans myself using the applicationContext.xml, I have to retrieve them somehow, in runtime. I can't use autowiring either, because I don't know what class the beans are, all I know is all of their classes implement IChildren
.
So I have made my main bean class ApplicationContextAware
and I have added this method:
public void loadChildren() {
if (childrenList == null) {
childrenList = new LinkedList<IChildren>();
for (String name : theListOfBeanNames) {
childrenList.add((IChildren) context.getBean(name));
}
}
}
It works alright; each bean is defined in its own applicationContext, and then I retrieve them by name. But... when do I call loadChildren()
? So far, I'm doing it in the first line of each one of my methods. Since there is a null-check, it will initialize the list only once. But surely, there must be an easier/cleaner/better way of doing this?
I don't think I can just use the init-method="loadChildren"
property in my applicationContext, because then it would fail if my main bean is being loaded before all the children have been... and I can't control the loading order. Or can I?
This is from my web.xml file:
<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>
(We use the "classpath* notation" to load every applicationContext-children.xml file from within the JARs). If I invert the order and place the children XML before the main ones, would it ensure they will be loaded in that specific order? No matter what version of what application server we use?
I've also seen a @PostConstruct annotation which would be useful if I could add it to my loadChildren
method. But when would it be called then? I've read that it's after the injections have been done, but... only for the current bean, right? It doesn't ensure that all the other beans are already loaded?
Is there any other option?
回答1:
Spring can do this for you:
@Component
public class MyComponent {
@Autowired
private final List<IChildren> children;
...
}
That will autowire in everything than implements IChildren
.
回答2:
Mr Spoon's answer does exactly what I asked, so I'm marking it as the correct answer. However, I forgot to mention that the children order in the list does matter, so I still can't use autowiring. Here is what I ended up doing instead, in case anybody else finds it useful.
As stated in the answers to this question, another option is to catch the ContextRefreshedEvent
by implementing the ApplicationListener<ContextRefreshedEvent>
interface. That ensures that the loadChildren
method is executed only after all the initialization is done.
Since we are using a very old Spring version (2.0.7), our ApplicationListener interface is slightly different, for example it doesn't support generic types. So, instead of doing this, my class looks like this:
public class Main implements ApplicationListener {
public void loadChildren(ApplicationContext context) {
//...
}
public void onApplicationEvent(ApplicationEvent ev) {
if (ev instanceof ContextRefreshedEvent) {
loadChildren(((ContextRefreshedEvent) ev).getApplicationContext());
}
}
}
来源:https://stackoverflow.com/questions/48149618/how-and-when-to-initialize-a-spring-bean-which-needs-to-access-other-beans-onl