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