我有一个使用Spring进行依赖注入(inject)的系统。我使用基于注释的 Autowiring 。 Bean是通过组件扫描发现的,即我的上下文XML包含以下内容:

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

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

有一个Zoo,它是Animal对象的容器。 Zoo的开发人员在开发Animal时不知道将包含哪些Zoo对象;由Spring实例化的具体Animal对象集在编译时是已知的,但是存在各种构建配置文件,导致生成了不同的Animal集,在这种情况下Zoo的代码不得更改。
Zoo的目的是允许系统的其他部分(在此处显示为ZooPatron)在运行时访问Animal对象集,而无需显式依赖某些Animal

实际上,具体的Animal类将全部由各种Maven工件提供。我希望能够仅通过包含这些具体Animal的各种工件来组合项目的发行版,并在编译时正确地 Autowiring 一切。

我试图通过使各个Animal依赖于Zoo来解决这个问题(未成功),以便他们可以在Zoo期间在@PostConstruct上调用注册方法。这避免了Zoo显式依赖于Animal的显式列表。

这种方法的问题在于,仅当所有Zoo已注册时,Animal的客户才希望与其进行交互。在编译时已知有一组有限的Animal,注册全部发生在我生命周期的Spring接线阶段,因此订阅模型应该是不必要的(即,我不希望将Animal添加到Zoo在运行时)。

不幸的是,Zoo的所有客户仅依赖Zoo。这与AnimalZoo的关系完全相同。因此,@PostConstructAnimalZooPatron方法以任意顺序调用。下面的示例代码对此进行了说明-在@PostConstruct上调用ZooPatron时,尚未注册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,则一切正常。我的错误是我不希望Zoo依赖于Animal子类的显式列表,因此引入了这种注册方法。正如答案中指出的那样,如果没有不必要的复杂性,将自注册机制与依赖项注入(inject)混合在一起将永远无法工作。

答案是声明Zoo依赖于Animal的集合,然后允许Spring通过 Autowiring 来填充该集合。

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

感谢您的出色回答。

最佳答案

您可能反而将动物组@Inject编入了Zoo。

@Component
public class Zoo {

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

    // ...
}

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

关于java - 是否可以保证@PostConstruct方法的调用顺序?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/7066683/

10-12 21:20