Как (и когда) инициализировать bean-компонент Spring, которому требуется доступ к другим bean-компонентам, известным только во время выполнения?
У меня есть веб-приложение Spring, работающее на Java 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
классы реализуют IChildren
интерфейс. И по моему Main
класс, я определил childrenList
который заполняется child1
а также child2
фасоль. Легко, правда?
Но в будущем, возможно, не будет child1
и вместо этого у нас может быть child81
основанный на другом неизвестном классе. И это все еще должно работать, не касаясь текущего кода или файлов XML, только через конфигурацию. это child81
будет определен в его собственном файле applicationContext (в JAR), и мы перечислим имена бинов в поле базы данных (child2,child81,etc
).
Я предполагаю, что это не хороший шаблон дизайна; Скорее всего, это ужасный, ужасный, болезненный дизайн "лучше беги, пока можешь", который будет преследовать меня в ближайшие годы. Но я не определил это, и я не могу изменить его, поэтому, пожалуйста, давайте предположим, что это нормально для целей этого вопроса.
Вместо того, чтобы самому вводить компоненты с помощью applicationContext.xml, я должен каким-то образом получать их во время выполнения. Я также не могу использовать автопроводку, потому что я не знаю, к какому классу относятся бины, я знаю только то, что все их классы реализуют IChildren
,
Итак, я сделал свой основной класс бобов ApplicationContextAware
и я добавил этот метод:
public void loadChildren() {
if (childrenList == null) {
childrenList = new LinkedList<IChildren>();
for (String name : theListOfBeanNames) {
childrenList.add((IChildren) context.getBean(name));
}
}
}
Это работает хорошо; каждый бин определяется в своем собственном applicationContext, а затем я получаю их по имени. Но... когда я звоню loadChildren()
? До сих пор я делаю это в первой строке каждого из моих методов. Так как есть нулевая проверка, он инициализирует список только один раз. Но, конечно, должен быть более простой / чистый / лучший способ сделать это?
Я не думаю, что я могу просто использовать init-method="loadChildren"
свойство в моем applicationContext, потому что тогда он не будет работать, если мой основной бин загружается до того, как все дочерние объекты были... и я не могу контролировать порядок загрузки. Или я могу?
Это из моего файла 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 * " для загрузки каждого файла applicationContext-children.xml из JAR-файлов). Если я переверну порядок и поместлю дочерний XML перед основными, будет ли он гарантировать, что они будут загружены в этом конкретном порядке? Независимо от того, какую версию сервера приложений мы используем?
Я также видел @PostConstruct
аннотация, которая была бы полезна, если бы я мог добавить его в свой loadChildren
метод. Но когда это будет тогда называться? Я читал, что это после инъекций, но... только для текущего боба, верно? Это не гарантирует, что все остальные бины уже загружены?
Есть ли другой вариант?
2 ответа
Весна может сделать это для вас:
@Component
public class MyComponent {
@Autowired
private final List<IChildren> children;
...
}
Это будет автопровода во всем, чем реализует IChildren
,
Ответ мистера Спуна делает именно то, что я просил, поэтому я отмечаю его как правильный ответ. Однако я забыл упомянуть, что порядок детей в списке имеет значение, поэтому я все еще не могу использовать автопроводку. Вот что я в итоге сделал вместо этого, на случай, если кто-нибудь еще найдет это полезным.
Как указано в ответах на этот вопрос, другой вариант заключается в том, чтобы поймать ContextRefreshedEvent
путем реализации ApplicationListener<ContextRefreshedEvent>
интерфейс. Это гарантирует, что loadChildren
Метод выполняется только после завершения инициализации.
Поскольку мы используем очень старую версию Spring (2.0.7), наш ApplicationListener
интерфейс немного отличается, например, он не поддерживает универсальные типы. Итак, вместо этого мой класс выглядит так:
public class Main implements ApplicationListener {
public void loadChildren(ApplicationContext context) {
//...
}
public void onApplicationEvent(ApplicationEvent ev) {
if (ev instanceof ContextRefreshedEvent) {
loadChildren(((ContextRefreshedEvent) ev).getApplicationContext());
}
}
}