Можно ли гарантировать порядок, в котором вызываются методы @PostConstruct? - PullRequest
18 голосов
/ 15 августа 2011

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

Я попытался решить эту проблему(безуспешно), когда отдельные Animal зависят от Zoo, чтобы они могли вызвать метод регистрации на Zoo в течение @PostConstruct.Это позволяет избежать зависимости Zoo, явно зависящей от явного списка Animal с.

. Проблема с этим подходом заключается в том, что клиенты Zoo хотят взаимодействовать с ним только тогда, когда все Animal зарегистрировалось .Существует конечный набор Animal с, который известен во время компиляции, и регистрация происходит во время фазы подключения Spring моего жизненного цикла, поэтому модель подписки должна быть ненужной (то есть я не хочу добавлять Animal s до Zoo во время выполнения).

К сожалению, все клиенты Zoo просто зависят от Zoo.Это точно такое же отношение, как у Animal с Zoo.Поэтому методы @PostConstruct для Animal s и ZooPatron вызываются в произвольной последовательности.Это иллюстрируется приведенным ниже примером кода - в момент, когда @PostConstruct вызывается на ZooPatron, Animal s не зарегистрировано, через несколько миллисекунд, когда все они регистрируются.

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

Спасибо за отличные ответы.

Ответы [ 5 ]

4 голосов
/ 15 августа 2011

Вместо этого вы можете иметь набор животных @Inject в зоопарке.

@Component
public class Zoo {

    @Inject
    private Set<Animal> animals = new HashSet<Animal>();

    // ...
}

Тогда зоопарк @PostConstruct следует вызывать только после введения всех животных.Единственный недостаток в том, что в системе должен быть хотя бы один Зверь, но, похоже, это не проблема.

2 голосов
/ 16 августа 2011

Перефразируйте вашу проблему, чтобы она не зависела от порядка вызова.

2 голосов
/ 15 августа 2011

Я не думаю, что есть способ обеспечить порядок @PostConstruct без введения зависимостей.

Я думаю, что вы ищете проблему, пытаясь смешать инъекцию или самостоятельную регистрацию.В какой-то степени порядок вызовов @PostConstruct не должен иметь значения - если это так, он может оказаться неподходящим инструментом для работы.

Несколько идей для вашего примера

  • попытатьсяиметь @Autowired на животных в зоопарке #: нет необходимости в самостоятельной регистрации животных, зоопарк также осведомлен о животных, но не наоборот, который чувствует себя чище.регистрация (кто-то сажает животных в зоопарк, не так ли? - они не появляются на входе сами по себе)
  • , если вам нужно вставить новых животных в любое время, но вы не хотите вручнуювставка, сделайте более динамический аксессор в зоопарке: не храните список животных, но используйте контекст весны, чтобы получить все существующие экземпляры интерфейса.

Я не думаю, что есть«правильный» ответ, все зависит от вашего варианта использования.

1 голос
/ 15 августа 2011

Лучший способ, IMO, - избегать слишком большой работы во время построения графа объектов (как и в Java, вы избегаете слишком большой работы в конструкторе) и избегать вызова методов из зависимостей, когда вы ' не уверены, что они полностью инициализированы.

Если вы просто удалите аннотацию @PostConstruct из метода Test#init() и просто вызовете ее из основного метода, после того как контекст будет создан, у вас больше не возникнет этой проблемы.

0 голосов
/ 15 августа 2011

Мне кажется, что в вашем случае существует зависимость между объектом Zoo и всеми типами животных.Если вы спроектируете свой объект Zoo, чтобы отразить эту зависимость, проблема будет решена.Например, вы можете сделать:

<bean id="zoo" class="Zoo">
<property name="animals">
<list>
<ref bean="Monkey" />
<ref bean="Tiger" />
<ref bean="Lion" />
</list>
</property>
</bean>

вместо использования метода register.

...