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

发布于 2024-11-29 09:41:13 字数 4160 浏览 1 评论 0原文

我有一个使用 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。这与动物动物园的关系完全相同。因此,AnimalZooPatron@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 Animals, 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 Animals.

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 Animals, and have everything autowire correctly at compile-time.

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 Zoo during @PostConstruct. This avoids the Zoo depending explicitly on an explicit list of Animals.

The problem with this approach is that the customers of Zoo wish 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 Zoo at runtime).

Unfortunately, all the customers of Zoo simply depend upon Zoo. This is exactly the same relationship which the Animals have with Zoo. Therefore, the @PostConstruct methods of the Animals 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 Animals 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 Animals 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 Animals, 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 Animals, 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

赢得她心 2024-12-06 09:41:13

相反,您可以将一组动物 @Inject 添加到动物园中。

@Component
public class Zoo {

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

    // ...
}

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

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

@Component
public class Zoo {

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

    // ...
}

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.

霊感 2024-12-06 09:41:13

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

我认为您正在寻找尝试混合注入或自我注册的麻烦。在某种程度上,@PostConstruct 调用顺序应该不重要——如果重要的话,它可能不是适合这项工作的工具。

对于您的示例,尝试在 Zoo#animals 上使用 @Autowired 的几个想法

  • :不需要动物自行注册,动物园也知道动物,但不是相反,这感觉更干净,
  • 保留注册,但让外部参与者进行注册(有人正在将动物放入动物园,对吧? - 它们不会自己出现在入口处)
  • 如果您需要随时插入新动物,但不想手动插入,请执行以下操作动物园上更动态的访问器:不存储动物列表,但使用 spring 上下文来获取该接口的所有现有实例。

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

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

  • 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.

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

你的背包 2024-12-06 09:41:13

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

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

乖乖哒 2024-12-06 09:41:13

在我看来,最好的方法是避免在对象图的构造过程中做太多工作(就像在 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.

花之痕靓丽 2024-12-06 09:41:13

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

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

而不是使用 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:

<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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文