是否可以保证@PostConstruct方法的调用顺序?
我有一个使用 Spring 进行依赖注入的系统。我使用基于注释的自动装配。这些 bean 是通过组件扫描发现的,即我的上下文 XML 包含以下内容:
<context:component-scan base-package="org.example"/>
我在下面创建了一个 noddy 示例来说明我的问题。
有一个 Zoo
,它是 Animal
对象的容器。 Zoo
的开发者在开发 Zoo
时并不知道将包含哪些 Animal
对象; Spring 实例化的具体 Animal 对象集在编译时是已知的,但是有不同的构建配置文件会导致不同的 Animal 集,并且 的代码>Zoo
在这种情况下不得改变。
Zoo
的目的是允许系统的其他部分(此处显示为 ZooPatron
)在运行时访问 Animal
对象集,而无需需要显式依赖某些Animal
。
实际上,具体的 Animal 类都将由各种 Maven 工件贡献。我希望能够通过简单地依赖包含这些具体 Animal
的各种工件来组装项目的发行版,并在编译时正确自动装配所有内容。
我试图通过让各个 Animal
依赖于 Zoo
来解决这个问题(但没有成功),以便它们可以调用 Zoo 上的注册方法
在@PostConstruct
期间。这避免了 Zoo
显式依赖于 Animal
的显式列表。
这种方法的问题在于,Zoo
的客户只有在所有Animal
都已注册时才希望与其交互。有一组有限的 Animal
在编译时已知,并且注册全部发生在我生命周期的 Spring 接线阶段,因此订阅模型应该是不必要的(即我不希望在运行时将 Animal
添加到 Zoo
中)。
不幸的是,Zoo
的所有客户都只是依赖 Zoo
。这与动物
与动物园
的关系完全相同。因此,Animal
和 ZooPatron
的 @PostConstruct
方法以任意顺序调用。下面的示例代码对此进行了说明 - 在 ZooPatron
上调用 @PostConstruct
时,没有 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
接受解决方案的解释
基本上答案是:不,如果不进入 Spring“外部”或修改其行为,就无法保证 @PostConstruct 调用的顺序。
这里真正的问题是不是我想要对@PostConstruct
调用进行排序,这只是依赖关系表达不正确的症状。
如果 Zoo
的消费者依赖于他,而 Zoo
又依赖于 Animal
,那么一切都会正常进行。我的错误是,我不希望 Zoo
依赖于 Animal
子类的显式列表,因此引入了这种注册方法。正如答案中所指出的,如果没有不必要的复杂性,将自注册机制与依赖注入混合将永远不会起作用。
答案是声明 Zoo
依赖于 Animal
的集合,然后允许 Spring 通过自动装配来填充该集合。
因此,没有集合成员的硬列表,它们是由 Spring 发现的,但依赖关系被正确表达,因此 @PostConstruct
方法按照我想要的顺序发生。
感谢您的精彩回答。
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:
<context:component-scan base-package="org.example"/>
I have created a noddy example below to illustrate my problem.
There is a Zoo
which is a container for Animal
objects. The developer of Zoo
does not know which Animal
objects will be contained whilst he is developing Zoo
; the set of concrete Animal
objects instantiated by Spring is known at compile-time, but there are various build profiles resulting in various sets of Animal
s, and the code for Zoo
must not change under these circumstances.
The purpose of Zoo
is to allow other parts of the system (illustrated here as ZooPatron
) to access the set of Animal
objects at runtime, without needing to depend explicitly on certain Animal
s.
Actually, the concrete Animal
classes 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 Animal
s, and have everything autowire correctly at compile-time.
I have attempted to solve this problem (unsuccessfully) by having the individual Animal
s depend upon the Zoo
, in order that they can call a registration method on the Zoo
during @PostConstruct
. This avoids the Zoo
depending explicitly on an explicit list of Animal
s.
The problem with this approach is that the customers of Zoo
wish to interact with it only when all the Animal
s have registered. There is a finite set of Animal
s 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 Animal
s to the Zoo
at runtime).
Unfortunately, all the customers of Zoo
simply depend upon Zoo
. This is exactly the same relationship which the Animal
s have with Zoo
. Therefore, the @PostConstruct
methods of the Animal
s and ZooPatron
are called in an arbitrary sequence. This is illustrated with the example code below - at the time @PostConstruct
is invoked on ZooPatron
, no Animal
s have registered, it is some milliseconds later when they all register.
So there are two types of dependency here, which I am struggling to express in Spring. The customers of Zoo
only want to use it once all the Animal
s are in it. (perhaps "Ark" would have been a better example...)
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 @PostConstruct
calls without either going "outside" Spring or modifying its behaviour.
The real problem here was not that I wanted to sequence the @PostConstruct
invocations, that was merely a symptom of the dependencies being expressed incorrectly.
If the consumers of Zoo
depend upon him, and Zoo
in turn depends upon Animal
s, everything works correctly. My mistake was that I didn't want Zoo
to depend upon an explicit list of Animal
subclasses, 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.
The answer is to declare that Zoo
is dependent upon a collection of Animal
s, then allow Spring to populate the collection through auto-wiring.
Thus, there is no hard list of collection members, they are discovered by Spring, but the dependencies are correctly expressed and therefore the @PostConstruct
methods happen in the sequence I want.
Thanks for the excellent answers.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
相反,您可以将一组动物
@Inject
添加到动物园中。那么 Zoo 的 @PostConstruct 仅应在所有 Animal 都被注入后才被调用。唯一的问题是系统中必须至少有一种动物,但这听起来不应该是一个问题。
You might instead have the Set of Animals
@Inject
ed into the Zoo.Then Zoo's
@PostConstruct
should 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.我认为没有办法在不引入依赖项的情况下确保 @PostConstruct 顺序。
我认为您正在寻找尝试混合注入或自我注册的麻烦。在某种程度上,@PostConstruct 调用顺序应该不重要——如果重要的话,它可能不是适合这项工作的工具。
对于您的示例,尝试在 Zoo#animals 上使用 @Autowired 的几个想法
我认为没有“正确”的答案,这完全取决于您的用例。
I don't think there is a way to ensure @PostConstruct order without introducing dependencies.
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.
A couple ideas for your example
I don't think there is a 'right' answer, it all depends on your use case.
重新构建您的问题,使其不依赖于调用顺序。
Reframe your problem so that it doesn't rely on invocation order.
在我看来,最好的方法是避免在对象图的构造过程中做太多工作(就像在 Java 中一样,避免在构造函数中做太多工作),并避免在不确定时从依赖项中调用方法他们已经完全初始化了。
如果您只是从
Test#init()
方法中删除 @PostConstruct 注释,并简单地从 main 方法中调用它,那么在创建上下文后,您将不再遇到此问题。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.
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.在我看来,在你的情况下,Zoo 对象和你所有的动物类型之间存在依赖关系。如果您设计 Zoo 对象来反映这种依赖性,那么问题就解决了。
例如,您可以这样做:
而不是使用 register 方法。
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:
instead of using the register method.