У меня есть система, которая использует 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 происходят в нужной последовательности.
Спасибо за отличные ответы.