2011-08-15 108 views
15

我有一個使用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正確調用。 –

回答

4

您可以改爲將動物組@Inject編入動物園。

@Component 
public class Zoo { 

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

    // ... 
} 

然後動物園的@PostConstruct只應該被調用一次所有的動物被注入。唯一的問題是系統中必須至少有一個動物,但聽起來不像是一個問題。

+0

是的,這也是@ptyx建議的最好的主意。 –

1

IMO最好的辦法是避免在構建對象圖的過程中做太多工作(就像在Java中一樣,避免在構造函數中做太多工作),並避免當你調用方法時依賴關係'不確定它們是否已完全初始化。

如果您只是從Test#init()方法中刪除@PostConstruct註釋,並且只需在主方法中調用它,那麼在創建上下文之後,您不會再遇到此問題。

+0

也許我的示例代碼太簡單了;實際上我的應用程序是一個web應用程序,所以我有一個XmlWebApplicationContext,由DispatcherServlet提供。所以,我不確定我有這個機會。 –

+0

您仍然可以以懶惰的方式(第一次調用其方法之一,或第一次通過工廠訪問它)或使用ServletContextListener初始化您的Test類(或任何其真實名稱)。 –

0

在我看來,在你的情況下,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何時可以爲我注入一個集合。 –

2

我不認爲有一種方法可以確保@PostConstruct順序而不引入依賴關係。

我認爲你正在尋找混合注射或自注冊的麻煩。在某種程度上,@PostConstruct呼叫順序應該沒有關係 - 如果確實如此,它可能不適合工作。

一對夫婦的想法,你的榜樣

  • 嘗試有@Autowired對動物園動物#:無需通過動物自注冊,也動物園是知道的動物,但不是相反,這感覺更清潔
  • 保持註冊,但讓外部演員做註冊(有人把動物放在動物園裏,對嗎? - 他們沒有出現在入口處)
  • 如果你需要插入新動物,但不希望手動插入,在動物園做一個更動態的訪問者:不要存儲動物列表,但使用spring上下文獲取接口的所有現有實例。

我不認爲有一個'正確'的答案,這一切都取決於你的用例。

+0

我認爲你已經擊中了頭 - 我無法找到一種方法來使這項工作不會扭曲PostConstruct的目的。問題的根源在於我不希望任何人知道特定的動物(這可能與我的構建體系結構有關,而不是其他任何東西);如果我更改動物組,我不能更改代碼。在類路徑上放置一個動物應該足以讓他連接到系統中。但是,其他人在運行時需要知道這些動物的總數。 –

+1

如果你的動物是豆類,那麼使用自動裝配的集合。 Spring會自動將所有聲明的bean放入自動裝配的Map 動物; - 你不需要在Zoo bean中逐一列出它們。 – ptyx

+0

是的,自動裝配的集合是最好的解決方案。一旦依賴關係正確表達,那麼PostConstruct調用的順序就不重要了。 –

2

重構您的問題,以便它不會依賴於調用順序。

+0

是的。我現在看到,代碼中表示的依賴關係是反向的;修復這意味着我不必依靠調用順序。 –

相關問題