Можно ли гарантировать порядок, в котором вызывается методы @PostConstruct?
У меня есть система, которая использует Spring для инъекции зависимостей. Я использую автообучение на основе аннотаций. beans обнаруживаются путем сканирования компонентов, то есть мой контекстный XML содержит следующее:
<context:component-scan base-package="org.example"/>
Для иллюстрации моей проблемы я создал примерный пример.
Существует Zoo
, который является контейнером для объектов Animal
. Разработчик Zoo
не знает, какие объекты Animal
будут содержаться во время разработки Zoo
; набор конкретных Animal
объектов, созданных при помощи Spring, известен во время компиляции, но существуют различные профили сборки, приводящие к различным наборам Animal
s, а код для Zoo
не должен изменяться в этих условиях.
Цель Zoo
- разрешить другим частям системы (показанному здесь как ZooPatron
) доступ к набору объектов Animal
во время выполнения, без необходимости явно указывать на определенные Animal
s.
Собственно, конкретные классы Animal
будут внесены различными артефактами Maven. Я хочу, чтобы иметь возможность собирать распределение моего проекта, просто в зависимости от различных артефактов, содержащих эти конкретные Animal
s, и иметь все autowire правильно во время компиляции.
Я попытался решить эту проблему (безуспешно), если отдельный Animal
зависит от Zoo
, чтобы они могли вызвать метод регистрации на Zoo
во время @PostConstruct
. Это позволяет избежать Zoo
, явно зависящего от явного списка Animal
s.
Проблема с этим подходом заключается в том, что клиенты Zoo
хотят взаимодействовать с ним , только когда все Animal
зарегистрировались. Существует конечный набор Animal
, который известен во время компиляции, и регистрация происходит во время фазы проводки Spring моего жизненного цикла, поэтому модель подписки должна быть ненужной (т.е. я не хочу добавлять Animal
до Zoo
во время выполнения).
К сожалению, все клиенты Zoo
просто зависят от Zoo
. Это то же самое отношение, которое имеет Animal
с Zoo
. Поэтому методы @PostConstruct
Animal
и ZooPatron
вызываются в произвольной последовательности. Это проиллюстрировано приведенным ниже примером кода - в момент, когда @PostConstruct
вызывается на ZooPatron
, no Animal
зарегистрирован, через несколько миллисекунд, когда все они регистрируются.
Итак, здесь есть два типа зависимости, которые я изо всех сил пытаюсь выразить в Spring. Клиенты Zoo
хотят использовать его только после того, как все Animal
находятся в нем. (возможно, "Ковчег" был бы лучшим примером...)
Мой вопрос в основном: какой лучший способ решить эту проблему?
@Component
public class Zoo {
private Set<Animal> animals = new HashSet<Animal>();
public void register(Animal animal) {
animals.add(animal);
}
public Collection<Animal> getAnimals() {
return animals;
}
}
public abstract class Animal {
@Autowired
private Zoo zoo;
@SuppressWarnings("unused")
@PostConstruct
private void init() {
zoo.register(this);
}
@Component
public static class Giraffe extends Animal {
}
@Component
public static class Monkey extends Animal {
}
@Component
public static class Lion extends Animal {
}
@Component
public static class Tiger extends Animal {
}
}
public class ZooPatron {
public ZooPatron(Zoo zoo) {
System.out.println("There are " + zoo.getAnimals().size()
+ " different animals.");
}
}
@Component
public class Test {
@Autowired
private Zoo zoo;
@SuppressWarnings("unused")
@PostConstruct
private void init() {
new Thread(new Runnable() {
private static final int ITERATIONS = 10;
private static final int DELAY = 5;
@Override
public void run() {
for (int i = 0; i<ITERATIONS; i++) {
new ZooPatron(zoo);
try {
Thread.sleep(DELAY);
} catch (InterruptedException e) {
// nop
}
}
}
}).start();
}
}
public class Main {
public static void main(String... args) {
new ClassPathXmlApplicationContext("/context.xml");
}
}
Вывод:
There are 0 different animals.
There are 3 different animals.
There are 4 different animals.
There are 4 different animals.
... etc
Объяснение принятого решения
В основном ответ: нет, вы не можете гарантировать порядок вызовов @PostConstruct
, не выходя из "внешнего" Spring или изменяя его поведение.
Реальная проблема здесь заключалась не в том, что я хотел упорядочить вызовы @PostConstruct
, что было всего лишь симптомом неправильных выражений зависимостей.
Если потребители Zoo
зависят от него, а Zoo
в свою очередь зависит от Animal
s, все работает правильно. Моя ошибка заключалась в том, что я не хотел, чтобы Zoo
зависел от явного списка подклассов Animal
и поэтому представил этот метод регистрации. Как указано в ответах, смешивание механизма саморегистрации с инъекцией зависимостей никогда не будет работать без излишней сложности.
Ответ заключается в том, чтобы объявить, что Zoo
зависит от коллекции Animal
s, а затем позволяет Spring заполнять коллекцию с помощью автоматической проводки.
Таким образом, нет жесткого списка членов коллекции, они обнаруживаются с помощью Spring, но зависимости корректно выражены, и поэтому методы @PostConstruct
происходят в нужной последовательности.
Спасибо за отличные ответы.
Ответы
Ответ 1
Вместо этого в Зоопарк может быть Set of Animals @Inject
ed.
@Component
public class Zoo {
@Inject
private Set<Animal> animals = new HashSet<Animal>();
// ...
}
Тогда зоопарк @PostConstruct
следует вызывать только после того, как все животные будут введены. Единственная проблема заключается в том, что в системе должно быть по крайней мере одно Animal, но это не похоже, что это должно быть проблемой.
Ответ 2
Я не думаю, что есть способ обеспечить порядок @PostConstruct без введения зависимостей.
Я думаю, вы ищете проблемы с попыткой смешать инъекции или самостоятельную регистрацию. В какой-то мере порядок вызовов @PostConstruct не должен иметь значения - если это так, это может быть не подходящий инструмент для задания.
Несколько идей для вашего примера
- попробуйте @Autowired на Zoo # животных: нет необходимости в саморегистрации животных, также зоопарк знает животных, но не наоборот, который чувствует себя чище.
- держите регистр, но пусть внешние участники делают регистрацию (кто-то кладет животных в зоопарк, верно?), они не появляются у входа все сами)
- если вам нужно вставлять новых животных в любое время, но не нужно вводить вручную, сделайте более динамичный аксессуар в зоопарке: не сохраняйте список животных, но используйте контекст spring, чтобы получить все существующие экземпляров интерфейса.
Я не думаю, что есть "правильный" ответ, все зависит от вашего варианта использования.
Ответ 3
Переформулируйте свою проблему, чтобы она не зависела от порядка вызова.
Ответ 4
Лучший способ, IMO, состоит в том, чтобы избежать слишком большой работы при построении графа объектов (как и в Java, вы избегаете слишком много работы в конструкторе) и избегать вызова методов из зависимостей, не уверен, что они полностью инициализированы.
Если вы просто удалите аннотацию @PostConstruct из метода Test#init()
и просто вызовите ее из основного метода, после создания контекста, у вас больше не будет этой проблемы.
Ответ 5
Мне кажется, что в вашем случае существует зависимость между объектом Zoo и всеми вашими типами животных. Если вы создадите объект Zoo для отражения этой зависимости, проблема будет решена.
Например, вы можете сделать:
<bean id="zoo" class="Zoo">
<property name="animals">
<list>
<ref bean="Monkey" />
<ref bean="Tiger" />
<ref bean="Lion" />
</list>
</property>
</bean>
вместо использования метода register.