是否可以保证@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的关系完全一样。因此,以任意顺序调用AnimalZooPatron@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方法我想要的顺序发生。

感谢您的优秀答案。

+0

好吧,我只检查速度非常快......但我认为长颈鹿,猴子,狮子和tiget也应该有@PostConstuct。为什么静态? – Cygnusx1

+0

那些具体的动物不需要PostConstruct,它们的抽象父类的PostConstruct方法被Spring正确调用。 –

您可以改为将动物组@Inject编入动物园。

@Component 
public class Zoo { 

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

    // ... 
} 

然后动物园的@PostConstruct只应该被调用一次所有的动物被注入。唯一的问题是系统中必须至少有一个动物,但听起来不像是一个问题。

+0

是的,这也是@ptyx建议的最好的主意。 –

IMO最好的办法是避免在构建对象图的过程中做太多工作(就像在Java中一样,避免在构造函数中做太多工作),并避免当你调用方法时依赖关系'不确定它们是否已完全初始化。

如果您只是从Test#init()方法中删除@PostConstruct注释,并且只需在主方法中调用它,那么在创建上下文之后,您不会再遇到此问题。

+0

也许我的示例代码太简单了;实际上我的应用程序是一个web应用程序,所以我有一个XmlWebApplicationContext,由DispatcherServlet提供。所以,我不确定我有这个机会。 –

+0

您仍然可以以懒惰的方式(第一次调用其方法之一,或第一次通过工厂访问它)或使用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> 

而不是使用注册方法。

+0

啊,不 - 这正是我想要避免的:-) ZooPatron和动物园都不想拥有一个“硬”动物列表。真实世界的情况实际上是我有一系列*游戏*,它们来自各种Maven工件,以及系统'核心'中的*游戏注册表*。只有游戏注册表对系统的其他部分才是可见的,个别游戏对于使用游戏注册表的开发者来说是不知道的,但是在编译时他们知道Spring是从各种游戏组装的“发行版”和其他组件。我将编辑q来澄清。 –

+0

你是对的:在问题领域,动物园依赖动物。用任何其他方式表达会导致问题。我的愚蠢是假设我必须制定明确的动物列表,Spring何时可以为我注入一个集合。 –

我不认为有一种方法可以确保@PostConstruct顺序而不引入依赖关系。

我认为你正在寻找混合注射或自注册的麻烦。在某种程度上,@PostConstruct呼叫顺序应该没有关系 - 如果确实如此,它可能不适合工作。

一对夫妇的想法,你的榜样

  • 尝试有@Autowired对动物园动物#:无需通过动物自注册,也动物园是知道的动物,但不是相反,这感觉更清洁
  • 保持注册,但让外部演员做注册(有人把动物放在动物园里,对吗? - 他们没有出现在入口处)
  • 如果你需要插入新动物,但不希望手动插入,在动物园做一个更动态的访问者:不要存储动物列表,但使用spring上下文获取接口的所有现有实例。

我不认为有一个'正确'的答案,这一切都取决于你的用例。

+0

我认为你已经击中了头 - 我无法找到一种方法来使这项工作不会扭曲PostConstruct的目的。问题的根源在于我不希望任何人知道特定的动物(这可能与我的构建体系结构有关,而不是其他任何东西);如果我更改动物组,我不能更改代码。在类路径上放置一个动物应该足以让他连接到系统中。但是,其他人在运行时需要知道这些动物的总数。 –

+1

如果你的动物是豆类,那么使用自动装配的集合。 Spring会自动将所有声明的bean放入自动装配的Map 动物; - 你不需要在Zoo bean中逐一列出它们。 – ptyx

+0

是的,自动装配的集合是最好的解决方案。一旦依赖关系正确表达,那么PostConstruct调用的顺序就不重要了。 –

重构您的问题,以便它不会依赖于调用顺序。

+0

是的。我现在看到,代码中表示的依赖关系是反向的;修复这意味着我不必依靠调用顺序。 –