18

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

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

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

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

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

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

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

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

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

所以这里有两种类型的依赖,我在 Spring 中很难表达。一旦所有的s都在其中,客户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反过来又依赖于Animals,那么一切正常。我的错误是我不想Zoo依赖明确的Animal子类列表,因此引入了这种注册方法。正如答案中所指出的,如果没有不必要的复杂性,将自注册机制与依赖注入混合将永远不会起作用。

答案是声明它Zoo依赖于 s 的集合Animal然后允许 Spring 通过自动装配来填充集合。

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

感谢您的出色回答。

4

5 回答 5

4

您可以改为将 Set of Animals@Inject编入 Zoo。

@Component
public class Zoo {

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

    // ...
}

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

于 2011-08-15T17:48:00.143 回答
2

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

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

您的示例的一些想法

  • 尝试在Zoo#animals上有一个@Autowired:不需要动物自我注册,动物园也知道动物但不知道相反,感觉更干净
  • 保留登记册,但让外部演员进行登记(有人把动物放在动物园里,对吗?-他们不会自己出现在入口处)
  • 如果您需要随时插入新动物,但又不想手动插入,请在 zoo 上做一个更动态的访问器:不要存储动物列表,而是使用 spring 上下文获取接口的所有现有实例。

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

于 2011-08-15T16:13:26.387 回答
2

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

于 2011-08-16T00:04:35.983 回答
1

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

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

于 2011-08-15T15:31:16.203 回答
0

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

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

而不是使用注册方法。

于 2011-08-15T15:53:26.150 回答