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

故事扮演 提交于 2019-12-24 06:13:07

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!