是否可以保证调用@PostConstruct方法的顺序?

时间:2011-08-15 15:13:40

标签: java spring spring-annotations

我有一个使用Spring进行依赖注入的系统。我使用基于注释的自动装配。通过组件扫描发现bean,即我的上下文XML包含:

<context:component-scan base-package="org.example"/>

我在下面创建了一个例子来说明我的问题。

ZooAnimal个对象的容器。 Zoo的开发人员在开发Animal时不知道将包含哪些Zoo个对象; Spring实例化的具体Animal对象集在编译时是已知的,但是有各种构建配置文件导致各种Animal集合,Zoo的代码不得更改在这种情况下。

Zoo的目的是允许系统的其他部分(此处显示为ZooPatron)在运行时访问Animal个对象集,而无需明确依赖某些对象Animal秒。

实际上,具体的Animal类将全部由各种Maven工件提供。我希望能够通过简单地依赖包含这些具体Animal的各种工件来组装我的项目的分布,并在编译时使所有内容自动装配。

我试图通过让Animal个人依赖Zoo来解决此问题(失败),以便他们可以在Zoo期间调用注册方法{ {1}}。这样可以避免@PostConstruct明确依赖于Zoo s的明确列表。

此方法存在的问题是Animal的客户只有在所有Zoo已注册时才希望与进行互动。有一组有限的Animal s在编译时是已知的,并且注册都发生在我的生命周期的Spring连接阶段,所以订阅模型应该是不必要的(即我不想添加Animal {}在运行时{。}}。

不幸的是,Animal的所有客户都只依赖Zoo。这与ZooZoo的关系完全相同。因此,AnimalZoo的{​​{1}}方法以任意顺序调用。下面的示例代码对此进行了说明 - 在@PostConstruct上调用Animal时,没有注册ZooPatron,所有注册时都是几毫秒。

所以这里有两种类型的依赖,我在Spring中努力表达。 @PostConstruct的客户只想在其中包含所有ZooPatron后才使用它。 (也许“方舟”会是一个更好的例子......)

我的问题基本上是:解决这个问题的最佳方法是什么?

Animal

输出:

Zoo

已接受解决方案的说明

基本上答案是:不,你不能保证Animal调用的顺序,而不是在“外部”Spring或修改它的行为。

这里真正的问题是 not ,我想对@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依赖于@PostConstruct,则一切正常。我的错误是我不希望Zoo依赖于明确的Zoo子类列表,因此引入了这种注册方法。正如答案中所指出的,将自注册机制与依赖注入相结合将永远不会有不必要的复杂性。

答案是声明Animal依赖于Zoo集合,然后允许Spring通过自动连接填充集合。

因此,没有集合成员的硬列表,它们是由Spring发现的,但依赖关系是正确表达的,因此Animal方法按照我想要的顺序发生。

感谢您的出色答案。

5 个答案:

答案 0 :(得分:4)

您可能会将动物集@Inject编入动物园。

@Component
public class Zoo {

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

    // ...
}

然后只有在注射所有动物后才能调用Zoo @PostConstruct。唯一的问题是系统中必须至少有一个Animal,但听起来不应该是一个问题。

答案 1 :(得分:2)

我认为没有办法确保@PostConstruct顺序而不引入依赖关系。

我认为你在寻找混合注射或自我注册的麻烦。在某种程度上,@ PostConstruct调用顺序无关紧要 - 如果确实如此,它可能不是正确的工具。

您的例子有几个想法

  • 尝试在Zoo#动物上设置@Autowired:不需要动物自行注册,动物园也知道动物但不反过来感觉更清洁
  • 保留登记册,但是让外部演员进行登记(有人把动物放在动物园里,对吗? - 他们不会自己出现在入口处)
  • 如果您需要随时插入新动物,但不想手动插入,请在动物园上做一个更动态的访问者:不要存储动物列表,而是使用spring上下文来获取所有现有实例界面。

我认为没有“正确”的答案,这完全取决于您的使用案例。

答案 2 :(得分:2)

重构您的问题,使其依赖于调用顺序。

答案 3 :(得分:1)

最好的方法,IMO,是避免在构造对象图时做太多工作(就像在Java中一样,你避免在构造函数中做太多的工作),并避免在你'时从依赖中调用方法'我不确定它们是否已完全初始化。

如果您只是从Test#init()方法中删除@PostConstruct注释,并且只是从main方法调用它,则在创建上下文后,您将不再遇到此问题。

答案 4 :(得分:0)

在我看来,在您的情况下,Zoo对象与您的所有动物类型之间存在依赖关系。如果您设计Zoo对象以反映此依赖关系,则问题将得到解决。 例如,您可以这样做:

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

而不是使用寄存器方法。