領先一步
VMware 提供培訓和認證,以加速您的進度。
了解更多Spring 的依賴注入 (DI) 機制允許配置在應用程式上下文中定義的 beans。如果您想將相同的想法擴展到非 beans 呢? Spring 對於網域物件 DI 的支援利用 AspectJ weaving 來將 DI 擴展到任何物件,即使它是由 Web 或 ORM 框架建立的也一樣。這使得建立行為豐富的網域物件成為可能,因為網域物件現在可以與注入的物件協同工作。在本部落格中,我將討論 Spring framework 在此領域的最新改進。
網域物件 DI 背後的核心思想非常簡單:一個 AspectJ-woven 切面選擇與任何符合特定規範的物件的建立或反序列化對應的連接點。對於這些連接點的建議將依賴項注入到正在建立或反序列化的物件中。當然,魔鬼藏在細節裡。例如,您如何選擇與反序列化對應的連接點,或者您如何僅為每個物件注入一次依賴項?通過提供一些預先編寫的切面,Spring 讓開發人員免於所有這些細節。
目前,大多數 Spring 使用者使用 @Configurable 註釋來指定可配置的類別。通過即將推出的 Spring 2.5.2 中的最新改進,可從 nightly build 379 開始使用,您有更多選擇,使此功能更加強大。新的改進遵循「使簡單的事情變得簡單,使複雜的事情變得可能」的原則。根據您對 AspectJ 的熟悉程度和預期的設計複雜性,其中一個選項會很好地為您服務。圖 1 顯示了新的切面層次結構,使簡單性和靈活性相結合成為可能。
圖 1:網域物件依賴注入切面的繼承層次結構。
那麼這些切面各提供什麼呢? 讓我們從下往上看。
@Configurable
public class Order {
private transient MailSender mailSender;
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
}
public void process() {
...
mailSender.send(...);
...
}
}
接下來,您指示 Spring 如何配置 Order 類型的物件。 這些說明遵循 prototype bean 的標準 bean 定義,如下所示
<context:spring-configured/>
<bean class="example.Order" scope="prototype">
<property name="mailSender" ref="externalMailSender"/>
</bean>
<bean id="externalMailSender" ...>
...
</bean>
現在,在任何 Order 建立或反序列化時,Spring 都將使用 externalMailSender bean 設定建立物件的 mailSender 屬性。
Spring 2.5 具有一個新的 基於註釋的配置選項,該選項允許消除或減少隨之而來的 XML。 基於 @Configurable 註釋的 DI 也從中受益。 例如,您可以將 mailSender 屬性標記為 @Autowired,如下所示
@Configurable
public class Order {
private transient MailSender mailSender;
@Autowired
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
}
public void process() {
...
mailSender.send(...);
...
}
}
您甚至可以通過註釋欄位本身來擺脫 setter,將上面的程式碼簡化為
@Configurable
public class Order {
@Autowired private transient MailSender mailSender;
public void process() {
...
mailSender.send(...);
...
}
}
在任何一種情況下,隨之而來的 XML 配置都簡化為以下內容(請注意使用 <context:annotation-config/>)
<context:spring-configured/>
<context:annotation-config/>
<bean id="externalMailSender" ...>
...
</bean>
有關此網域物件 DI 選項的更多詳細資訊,請參閱 使用 AspectJ 來使用 Spring 進行依賴注入網域物件。
在設計層面,此切面配置任何類型實現 ConfigurableObject 介面的網域物件。 雖然讓類型直接實現 ConfigurableObject 介面當然是一個有效的選擇,但一個優雅的替代方案是在另一個切面中使用 declare parents 語句(AbstractInterfaceDrivenDependencyInjectionAspect 的子切面將是一個合乎邏輯的選擇)。 該語句會將可配置的類別宣告為實現 ConfigurableObject 介面。 這使您的網域類別免於特定於 Spring 的工件,同時受益於 DI 機制。 讓我們看一個此類用法的範例。
考慮前面章節中的 Order 類別。 除了使用 @Configurable 之外,您可以讓它實現一個特定於網域的 MailSenderClient 介面,該介面表示它使用 MailSender。
public class Order implements MailSenderClient {
private transient MailSender mailSender;
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
}
public void process() {
...
mailSender.send(...);
...
}
}
接下來,您編寫一個 AbstractInterfaceDrivenDependencyInjectionAspect 的子切面,以將依賴項注入到任何 MailSenderClient 物件中。
public aspect MailClientDependencyInjectionAspect extends
AbstractInterfaceDrivenDependencyInjectionAspect {
private MailSender mailSender;
declare parents: MailSenderClient implements ConfigurableObject;
public pointcut inConfigurableBean() : within(MailSenderClient+);
public void configureBean(Object bean) {
((MailSenderClient)bean).setMailSender(this.mailSender);
}
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
}
}
切面中使用了兩個 AspectJ 建構
configureBean() 方法通過直接呼叫適當的 setter 將注入執行到 bean 中。 當然,任何其他適用於 bean 配置的邏輯,例如呼叫多參數方法或呼叫任何初始化方法,都可以正常工作。 請注意,以這種方式使用的直接呼叫避免了反射,並且如果網域物件的建立率很高,則可以產生顯著的效能提升。
您需要配置 MailClientDependencyInjectionAspect 切面實例本身以注入其依賴項 - mailSender 屬性。 Spring 的方法是為切面建立一個 bean 並在應用程式上下文中配置它
<bean class="example.MailClientDependencyInjectionAspect"
factory-method="aspectOf">
<property name="mailSender" ref="externalMailSender"/>
</bean>
<bean id="externalMailSender" ...>
...
</bean>
此切面還有一些其他模式
但是,讓我們將這些想法留到另一篇部落格文章中。
該切面宣告了六個子切面可以定義的 pointcut
此切面還定義了一個抽象方法 configureBean(Object bean),其實作應指定與依賴注入對應的邏輯。
所以您有所有選項可以在您的應用程式中啟用網域物件 DI。 如果您正在進行 DDD 或以其他方式需要將 DI 擴展到您的網域物件,您必須查看這些新的切面集。 根據您的特定需求和 AspectJ 知識,您會發現其中一種對建立優雅的解決方案很有幫助。