我有一个系统,使用Spring进行依赖注入。我使用基于注解的自动装配。组件扫描发现bean,即我的上下文XML包含以下内容:
<context:component-scan base-package="org.example"/>
我创建了一个简单的示例来说明我的问题。有一个叫做“Zoo”的容器,可以容纳一些“Animal”对象。在开发“Zoo”时,开发人员并不知道会在其中包含哪些具体的“Animal”对象;由Spring实例化的具体“Animal”对象集合在编译时已知,但是有各种构建配置文件会导致不同的“Animal”集合,而在这些情况下,“Zoo”的代码必须不变。
“Zoo”的作用是允许系统中的其他部分(此处称为“ZooPatron”)在运行时访问“Animal”对象集,而不需要显式地依赖于某些“Animal”。
实际上,所有具体的“Animal”类都将由各种Maven工件贡献。我希望能够通过依赖于包含这些具体“Animal”的各种工件的不同工程,组装我的项目的发行版,并且在编译时自动连接所有东西。
我尝试通过使各个“Animal”依赖于“Zoo”来解决这个问题(但没有成功),以便它们可以在
@PostConstruct
期间调用“Zoo”上的注册方法。这避免了“Zoo”显式依赖于“Animal”的显式列表。这种方法的问题在于,“Zoo”的客户端希望仅在所有“Animal”都注册时才与之交互。已知有限的“Animal”集合在编译时已知,并且所有注册都发生在我的生命周期的Spring连接阶段,因此订阅模型应该是不必要的(即我不希望在运行时将“Animal”添加到“Zoo”中)。
不幸的是,“Zoo”的所有客户端只依赖于“Zoo”。这与“Animal”对“Zoo”的关系完全相同。因此,在任意顺序调用“Animal”和“ZooPatron”的
@PostConstruct
方法。下面的示例代码说明了这一点-当在@PostConstruct
上调用“ZooPatron”时,没有任何“Animal”已注册,但它们都会在几毫秒后注册。因此,在这里有两种依赖关系,我很难在Spring中表达。 “Zoo”的客户端只想在其中包含所有“Animal”时使用它。(也许“Ark”会是一个更好的示例......)
我的问题基本上是:解决这个问题的最佳方法是什么?
@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
,则一切都能正常工作。我的错误在于我不想让Zoo
依赖于一个明确的Animal
子类列表,因此引入了这种注册方法。正如答案中指出的那样,将自注册机制与依赖注入混合在一起将会带来不必要的复杂性。
答案是声明Zoo
依赖于一个集合的Animal
,然后允许Spring通过自动装配填充集合。
因此,没有硬编码的集合成员列表,它们由Spring发现,但是依赖关系得到了正确的表示,因此@PostConstruct
方法会按照我想要的顺序发生。
感谢各位出色的答案。