領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多Spring 的依賴注入 (DI) 機制允許配置應用程式上下文中定義的 bean。如果您想將相同的思想擴充套件到非 bean 呢?Spring 對領域物件 DI 的支援利用 AspectJ 織入將 DI 擴充套件到任何物件,即使它是由 Web 或 ORM 框架建立的。這使得建立具有豐富領域行為的物件成為可能,因為領域物件現在可以與注入的物件協作。在本部落格中,我將討論 Spring 框架在此領域的最新改進。
域物件DI的核心思想很簡單:一個AspectJ編織的切面選擇對應於任何符合特定規範的物件的建立或反序列化的連線點。對這些連線點的通知將依賴項注入到正在建立或反序列化的物件中。當然,細節決定成敗。例如,如何選擇對應於反序列化的連線點,或者如何每個物件只注入一次依賴項?透過提供一些預編寫的切面,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 型別的物件。這些指令遵循原型bean的標準bean定義,如下所示
<context:spring-configured/>
<bean class="example.Order" scope="prototype">
<property name="mailSender" ref="externalMailSender"/>
</bean>
<bean id="externalMailSender" ...>
...
</bean>
現在,在任何 Order 的建立或反序列化時,Spring會將建立物件的 mailSender 屬性設定為 externalMailSender bean。
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 類。您可以使用一個域特定的 MailSenderClient 介面,而不是使用 @Configurable,它表示它使用 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>
圍繞這個切面還有一些額外的模式
然而,讓我們把這些想法留到另一篇部落格文章中。
該切面聲明瞭六個子切面可以定義的切入點
此切面還定義了一個抽象方法 configureBean(Object bean),其實現應指定與依賴注入對應的邏輯。
所以,您的應用程式中啟用了域物件DI的所有選項都在這裡。如果您從事DDD或者需要將DI擴充套件到您的域物件,您必須檢視這組新的切面。根據您的具體需求和AspectJ知識,您會發現其中之一有助於建立優雅的解決方案。