Можно ли гарантировать порядок, в котором вызывается методы @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.