保持領先
VMware 提供培訓和認證,助你加速發展。
瞭解更多Spring 的依賴注入(DI)機制允許配置在應用上下文中定義的 bean。如果你想將相同的想法擴充套件到非 bean 物件呢?Spring 對領域物件 DI 的支援利用 AspectJ 織入技術將 DI 擴充套件到任何物件,即使它是由 Web 或 ORM 框架建立的。這使得建立具有豐富領域行為的物件成為可能,因為領域物件現在可以與注入的物件協作。在這篇部落格中,我將討論 Spring Framework 在這一領域的最新改進。
領域物件 DI 背後的核心思想非常簡單:透過 AspectJ 織入的切面選擇與符合特定規範的任何物件的建立或反序列化對應的連線點。這些連線點的通知將依賴項注入到正在建立或反序列化的物件中。當然,細節決定成敗。例如,如何選擇與反序列化對應的連線點?或者如何確保每個物件只注入一次依賴項?透過提供一些預先編寫好的切面,Spring 使開發人員免受這些細節的影響。
目前,大多數 Spring 使用者使用 @Configurable 註解來指定可配置的類。隨著即將釋出的 Spring 2.5.2 中的最新改進(自每夜構建 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 型別的物件。配置說明遵循原型 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 介面無疑是有效的選擇,但一個更優雅的替代方案是在另一個切面(AbstractInterfaceDrivenDependencyInjectionAspect 的子切面將是合乎邏輯的選擇)中使用 declare parents 語句。該語句將宣告一個可配置的類實現了 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 構造
The 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>
圍繞這個切面還有一些額外的模式
然而,這些想法就留待另一篇部落格文章來討論吧。
該切面聲明瞭子切面可以定義的六個切入點
此切面還定義了一個抽象方法 configureBean(Object bean),其實現應指定與依賴注入對應的邏輯。
所以,你現在擁有了在應用中啟用領域物件 DI 的所有選項。如果你正在進行 DDD 或需要將 DI 擴充套件到你的領域物件,你應該關注這組新的切面。根據你的具體需求和 AspectJ 知識,你會發現其中一種有助於建立一個優雅的解決方案。