是否可以保证调用@PostConstruct方法的顺序?已接受的解决方案的说明

2022-09-02 22:28:42

我有一个系统,它使用Spring进行依赖注入。我使用基于注释的自动布线。这些豆子是通过组件扫描发现的,即我的上下文XML包含以下内容:

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

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

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

的目的是允许系统的其他部分(此处显示为 )在运行时访问对象集,而无需显式依赖于某些对象。ZooZooPatronAnimalAnimal

实际上,具体类都将由各种Maven工件贡献。我希望能够通过简单地依靠包含这些具体s的各种工件来组装我的项目的发行版,并在编译时正确连接所有内容。AnimalAnimal

我试图通过让个人依赖于,以便他们可以在期间调用注册方法来解决此问题(未成功)。这避免了显式依赖于显式列表的 s。AnimalZooZoo@PostConstructZooAnimal

这种方法的问题在于,只有当所有动物都注册时,希望与它互动的客户才会与之互动。有一组有限的s在编译时是已知的,注册都发生在我生命周期的Spring连接阶段,所以订阅模型应该是不必要的(即我不希望在运行时添加s)。ZooAnimalAnimalZoo

不幸的是,所有的客户都简单地依赖于.这与 s 与 的关系完全相同。因此,s 和 的方法以任意顺序调用。这用下面的示例代码来说明 - 在 上调用时,没有 s 已注册,当它们全部注册时,几毫秒后。ZooZooAnimalZoo@PostConstructAnimalZooPatron@PostConstructZooPatronAnimal

所以这里有两种类型的依赖,我在春天很难表达出来。客户只想在所有内容都在其中后使用它。(也许“方舟”会是一个更好的例子...)ZooAnimal

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

@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

已接受的解决方案的说明

基本上答案是:不,你不能保证呼叫的顺序,而不去“外面”Spring或改变它的行为。@PostConstruct

这里真正的问题不是我想对调用进行排序,那只是依赖关系表达不正确的症状@PostConstruct

如果的消费者依赖他,反过来又依赖他,那么一切都可以正常工作。我的错误是我不想依赖于子类的显式列表,因此引入了这种注册方法。正如答案中指出的那样,将自注册机制与依赖注入混合在没有不必要的复杂性的情况下永远不会起作用。ZooZooAnimalZooAnimal

答案是声明它依赖于s的集合,然后允许Spring通过自动布线填充集合。ZooAnimal

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

感谢您的精彩回答。


答案 1

相反,你可以把《动物集》放到动物园里。@Inject

@Component
public class Zoo {

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

    // ...
}

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


答案 2

我不认为有办法在不引入依赖关系的情况下确保@PostConstruct顺序。

我认为你在尝试混合注射或自我注册时遇到了麻烦。在某种程度上,@PostConstruct呼叫顺序应该无关紧要 - 如果是这样,它可能不是这项工作的正确工具。

针对您的示例的几个想法

  • 尝试对Zoo#animals进行@Autowired:不需要动物自我注册,动物园也知道动物,但不是相反,感觉更干净
  • 保留登记册,但让外部演员进行登记(有人把动物放在动物园里,对吧?-他们不是自己出现在入口处)
  • 如果您需要随时插入新动物,但不希望手动插入,请在 zoo 上执行更动态的访问器:不要存储动物列表,而是使用 spring 上下文来获取接口的所有现有实例。

我不认为有一个“正确”的答案,这完全取决于你的用例。


推荐