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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7066683/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 18:31:07  来源:igfitidea点击:

Is it possible to guarantee the order in which @PostConstruct methods are invoked?

javaspringspring-annotations

提问by wool.in.silver

I have a system which is using Spring for dependency injection. I use annotation-based autowiring. The beans are discovered by component scanning, i.e. my context XML contains this:

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

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

I have created a noddy example below to illustrate my problem.

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

There is a Zoowhich is a container for Animalobjects. The developer of Zoodoes not know which Animalobjects will be contained whilst he is developing Zoo; the set of concrete Animalobjects instantiated by Spring is known at compile-time, but there are various build profiles resulting in various sets of Animals, and the code for Zoomust not change under these circumstances.

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

The purpose of Zoois to allow other parts of the system (illustrated here as ZooPatron) to access the set of Animalobjects at runtime, without needing to depend explicitly on certain Animals.

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

Actually, the concrete Animalclasses will all be contributed by various Maven artifacts. I want to be able to assemble a distribution of my project by simply depending on the various artifacts containing these concrete Animals, and have everything autowire correctly at compile-time.

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

I have attempted to solve this problem (unsuccessfully) by having the individual Animals depend upon the Zoo, in order that they can call a registration method on the Zooduring @PostConstruct. This avoids the Zoodepending explicitly on an explicit list of Animals.

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

The problem with this approach is that the customers of Zoowish to interact with it only when all the Animals have registered. There is a finite set of Animals which is known at compile-time, and the registration all happens during the Spring wiring phase of my lifecycle, so a subscription model should be unneccesary (i.e. I don't wish to add Animals to the Zooat runtime).

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

Unfortunately, all the customers of Zoosimply depend upon Zoo. This is exactly the same relationship which the Animals have with Zoo. Therefore, the @PostConstructmethods of the Animals and ZooPatronare called in an arbitrary sequence. This is illustrated with the example code below - at the time @PostConstructis invoked on ZooPatron, no Animals have registered, it is some milliseconds later when they all register.

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

So there are two types of dependency here, which I am struggling to express in Spring. The customers of Zooonly want to use it once all the Animals are in it. (perhaps "Ark" would have been a better example...)

所以这里有两种类型的依赖,我正在努力在 Spring 中表达。客户Zoo只有在所有Animal的东西都在里面时才想使用它。(也许“方舟”会是一个更好的例子......)

My question is basically: what is the best way to solve this problem?

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

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

}

Output:

输出:

There are 0 different animals.
There are 3 different animals.
There are 4 different animals.
There are 4 different animals.
... etc

Explanation of accepted solution

已接受解决方案的说明

Basically the answer is: no, you cannot guarantee the order of @PostConstructcalls without either going "outside" Spring or modifying its behaviour.

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

The real problem here was notthat I wanted to sequence the @PostConstructinvocations, that was merely a symptomof the dependencies being expressed incorrectly.

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

If the consumers of Zoodepend upon him, and Zooin turn depends upon Animals, everything works correctly. My mistake was that I didn't want Zooto depend upon an explicit list of Animalsubclasses, and therefore introduced this registration method. As pointed out in the answers, mixing a self-registration mechanism with dependency injection will never work without unnecessary complexity.

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

The answer is to declare that Zoois dependent upon a collectionof Animals, then allow Spring to populate the collection through auto-wiring.

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

Thus, there is no hard list of collection members, they are discovered by Spring, but the dependencies are correctly expressed and therefore the @PostConstructmethods happen in the sequence I want.

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

Thanks for the excellent answers.

感谢您的出色回答。

采纳答案by Brian Kent

You might instead have the Set of Animals @Injected into the Zoo.

您可以改为将 Set of Animals@Inject编入动物园。

@Component
public class Zoo {

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

    // ...
}

Then Zoo's @PostConstructshould only be called once all the Animals are injected. The only gotcha is that there must be at least one Animal in the system, but it doesn't sound like that should be an issue.

那么 Zoo's@PostConstruct应该只在所有 Animals 都被注入后被调用。唯一的问题是系统中必须至少有一个动物,但这听起来不应该是一个问题。

回答by user207421

Reframe your problem so that it doesn'trely on invocation order.

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

回答by ptyx

I don't think there is a way to ensure @PostConstruct order without introducing dependencies.

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

I think you're looking for trouble trying to mix injection or self registration. To some extent, @PostConstruct call order should not matter - if it does, it might not be the right tool for the job.

我认为您在尝试混合注入或自注册时遇到了麻烦。在某种程度上,@PostConstruct 调用顺序应该无关紧要 - 如果确实如此,它可能不是适合该工作的工具。

A couple ideas for your example

你的例子的几个想法

  • try to have an @Autowired on Zoo#animals: no need for self-registration by animals, also the zoo is aware of the animals but not the reverse, which feels cleaner
  • keep the register, but let external actors do the registration (someone is putting the animals in the zoo, right? - they're not showing up at the entrance all by themselves)
  • if you need to insert new animals at any time, but don't want manual insertion, do a more dynamic accessor on zoo: don't store the list of animals, but use the spring context to get all existing instances of the interface.
  • 尝试在 Zoo#animals 上使用 @Autowired:不需要动物自行注册,动物园也知道动物,但反过来却不知道,感觉更干净
  • 保留登记册,但让外部演员进行登记(有人将动物放入动物园,对吗?-他们不会自己出现在入口处)
  • 如果您需要随时插入新动物,但又不想手动插入,请在 zoo 上做一个更动态的访问器:不要存储动物列表,而是使用 spring 上下文获取界面的所有现有实例。

I don't think there is a 'right' answer, it all depends on your use case.

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

回答by JB Nizet

The best way, IMO, is to avoid doing too much work during the construction of the object graph (just as in Java, you avoid doing too much work in the constructor), and to avoid calling methods from dependencies when you're not sure they're fully initialized yet.

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

If you just remove the @PostConstruct annotation from the Test#init()method, and simply invoke it from your main method, after the context has been created, you won't have this problem anymore.

如果您只是从Test#init()方法中删除 @PostConstruct 注释,并简单地从您的主方法中调用它,那么在创建上下文之后,您将不再遇到此问题。

回答by dcernahoschi

It looks to me that in your case there is a dependency between the Zoo object and all your animal types. If you design your Zoo object to reflect this dependency the problem is solved. For example you could do:

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

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

instead of using the register method.

而不是使用注册方法。