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

woo*_*ver 18 java spring spring-annotations

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

<context:component-scan base-package="org.example"/>
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

我试图通过让个人Animal依赖于这个问题来解决这个问题(不成功)Zoo,以便他们可以在Zoo期间调用注册方法@PostConstruct.这避免了Zoo明确依赖于明确的Animals 列表.

这种方法的问题在于,只有当所有s已经注册,Zoo希望与其进行交互的客户.有一组有限的s在编译时是已知的,并且注册都发生在我的生命周期的Spring连接阶段,所以订阅模型应该是不必要的(即我不希望在运行时添加s)).AnimalAnimalAnimalZoo

不幸的是,所有的客户都Zoo依赖于Zoo.这与Animals的关系完全相同Zoo.因此,s和s 的@PostConstruct方法以任意顺序调用.下面的示例代码说明了这一点 - 在调用时,没有注册,在所有注册时都是几毫秒.AnimalZooPatron@PostConstructZooPatronAnimal

所以这里有两种类型的依赖,我在Spring中努力表达.Zoo一旦所有Animals都在其中,客户只想使用它.(也许"方舟"会是一个更好的例子......)

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

@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");
    }

}
Run Code Online (Sandbox Code Playgroud)

输出:

There are 0 different animals.
There are 3 different animals.
There are 4 different animals.
There are 4 different animals.
... etc
Run Code Online (Sandbox Code Playgroud)

解释已接受的解决方案

基本上答案是:不,你不能保证@PostConstruct调用的顺序,不要在"外部"Spring或修改其行为.

这里真正的问题不是我想要对@PostConstruct调用进行排序,这仅仅是错误表达依赖关系的症状.

如果消费者Zoo依赖他,Zoo反过来依赖于Animals,一切都正常.我的错误是我不想Zoo依赖于明确的Animal子类列表,因此引入了这种注册方法.正如答案中指出的那样,将自注册机制与依赖注入相结合将永远不会有不必要的复杂性.

答案是声明Zoo依赖于一个集合AnimalS,然后让春天来填充通过自动装配的集合.

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

谢谢你的出色答案.

Bri*_*ent 4

相反,您可以将动物组@Inject放入动物园。

@Component
public class Zoo {

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

    // ...
}
Run Code Online (Sandbox Code Playgroud)

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