是否可以保证@PostConstruct方法被调用的顺序?
我有一个使用Spring进行依赖注入的系统。我使用基于注释的自动装配。是否可以保证@PostConstruct方法被调用的顺序?
<context:component-scan base-package="org.example"/>
我创建低于诺迪的例子来说明我的问题:将豆由组件扫描,即我的上下文XML包含此发现。
有一个Zoo
其是用于Animal
对象的容器。 Zoo
的开发人员不知道在开发Zoo
时将包含哪些对象;由Spring实例化的具体对象Animal
在编译时是已知的,但是存在各种构建配置文件,导致各种各样的Animal
s集合,并且Zoo
的代码在这些情况下不得改变。
的Zoo
的目的是允许系统(这里示出为ZooPatron
)的其他部分访问组在运行时Animal
的对象,而不需要对某些Animal
小号明确依赖。
实际上,具体的Animal
类将全部由各种Maven工件提供。我希望能够通过简单地取决于包含这些具体Animal
的各种工件来组装我的项目的分布,并且在编译时自动装配所有东西。
我已经尝试通过具有单个Animal
小号取决于Zoo
,为了使它们能在@PostConstruct
呼吁Zoo
注册方法来解决这个问题(失败)。这避免了Zoo
明确取决于明确的Animal
列表。
这种方法的问题是,Zoo
客户希望与它交互只有当所有的Animal
■找注册。有一个编译时已知的有限的Animal
s,并且注册全部发生在我生命周期的Spring配线阶段,所以订阅模型应该是不必要的(即,我不希望将Animal
添加到运行时为Zoo
)。
很抱歉,所有Zoo
的顾客只需依据Zoo
。这与Animal
s与Zoo
的关系完全一样。因此,以任意顺序调用Animal
和ZooPatron
的@PostConstruct
方法。下面的示例代码说明了这一点 - 在上调用@PostConstruct
时,没有Animal
已经注册,它们都在几毫秒后注册。
所以这里有两种类型的依赖关系,我在春天很难表达。 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
S,一切正常。我的错误是我不希望Zoo
依赖于Animal
子类的明确列表,因此引入了此注册方法。正如答案中指出的那样,将自注册机制与依赖注入混合在一起将不会有不必要的复杂性。
答案是声明Zoo
是依赖于Animal
的集合S,然后让春天来填充通过自动装配的集合。
因此,集合成员的无硬名单,它们被发现的春天,但依赖关系正确表达,因此@PostConstruct
方法我想要的顺序发生。
感谢您的优秀答案。
您可以改为将动物组@Inject
编入动物园。
@Component
public class Zoo {
@Inject
private Set<Animal> animals = new HashSet<Animal>();
// ...
}
然后动物园的@PostConstruct
只应该被调用一次所有的动物被注入。唯一的问题是系统中必须至少有一个动物,但听起来不像是一个问题。
是的,这也是@ptyx建议的最好的主意。 –
IMO最好的办法是避免在构建对象图的过程中做太多工作(就像在Java中一样,避免在构造函数中做太多工作),并避免当你调用方法时依赖关系'不确定它们是否已完全初始化。
如果您只是从Test#init()
方法中删除@PostConstruct注释,并且只需在主方法中调用它,那么在创建上下文之后,您不会再遇到此问题。
也许我的示例代码太简单了;实际上我的应用程序是一个web应用程序,所以我有一个XmlWebApplicationContext,由DispatcherServlet提供。所以,我不确定我有这个机会。 –
您仍然可以以懒惰的方式(第一次调用其方法之一,或第一次通过工厂访问它)或使用ServletContextListener初始化您的Test类(或任何其真实名称)。 –
在我看来,在你的情况下,Zoo对象和所有动物类型之间存在依赖关系。如果你设计你的Zoo对象来反映这个依赖关系,那么问题就解决了。 例如,你可以这样做:
<bean id="zoo" class="Zoo">
<property name="animals">
<list>
<ref bean="Monkey" />
<ref bean="Tiger" />
<ref bean="Lion" />
</list>
</property>
</bean>
而不是使用注册方法。
啊,不 - 这正是我想要避免的:-) ZooPatron和动物园都不想拥有一个“硬”动物列表。真实世界的情况实际上是我有一系列*游戏*,它们来自各种Maven工件,以及系统'核心'中的*游戏注册表*。只有游戏注册表对系统的其他部分才是可见的,个别游戏对于使用游戏注册表的开发者来说是不知道的,但是在编译时他们知道Spring是从各种游戏组装的“发行版”和其他组件。我将编辑q来澄清。 –
你是对的:在问题领域,动物园依赖动物。用任何其他方式表达会导致问题。我的愚蠢是假设我必须制定明确的动物列表,Spring何时可以为我注入一个集合。 –
我不认为有一种方法可以确保@PostConstruct顺序而不引入依赖关系。
我认为你正在寻找混合注射或自注册的麻烦。在某种程度上,@PostConstruct呼叫顺序应该没有关系 - 如果确实如此,它可能不适合工作。
一对夫妇的想法,你的榜样
- 尝试有@Autowired对动物园动物#:无需通过动物自注册,也动物园是知道的动物,但不是相反,这感觉更清洁
- 保持注册,但让外部演员做注册(有人把动物放在动物园里,对吗? - 他们没有出现在入口处)
- 如果你需要插入新动物,但不希望手动插入,在动物园做一个更动态的访问者:不要存储动物列表,但使用spring上下文获取接口的所有现有实例。
我不认为有一个'正确'的答案,这一切都取决于你的用例。
我认为你已经击中了头 - 我无法找到一种方法来使这项工作不会扭曲PostConstruct的目的。问题的根源在于我不希望任何人知道特定的动物(这可能与我的构建体系结构有关,而不是其他任何东西);如果我更改动物组,我不能更改代码。在类路径上放置一个动物应该足以让他连接到系统中。但是,其他人在运行时需要知道这些动物的总数。 –
如果你的动物是豆类,那么使用自动装配的集合。 Spring会自动将所有声明的bean放入自动装配的Map
是的,自动装配的集合是最好的解决方案。一旦依赖关系正确表达,那么PostConstruct调用的顺序就不重要了。 –
重构您的问题,以便它不会依赖于调用顺序。
是的。我现在看到,代码中表示的依赖关系是反向的;修复这意味着我不必依靠调用顺序。 –
好吧,我只检查速度非常快......但我认为长颈鹿,猴子,狮子和tiget也应该有@PostConstuct。为什么静态? – Cygnusx1
那些具体的动物不需要PostConstruct,它们的抽象父类的PostConstruct方法被Spring正确调用。 –