網域物件依賴注入功能的新增強功能

工程 | Ramnivas Laddad | 2008 年 1 月 24 日 | ...

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 顯示了新的切面層次結構,使簡單性和靈活性相結合成為可能。

Domain Object Dependency Injection Aspects

圖 1:網域物件依賴注入切面的繼承層次結構。

那麼這些切面各提供什麼呢? 讓我們從下往上看。

簡單的事情變得簡單:AnnotationBeanConfigurerAspect

AnnotationBeanConfigurerAspect 允許網域物件 DI,而無需任何使用者 AspectJ 程式碼。因此,它是許多開發人員最容易的選擇。使用此切面,您可以使用 @Configurable 註釋來註釋需要依賴注入的類別。例如,您可以將 Order 類別註釋如下
 
@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 進行依賴注入網域物件

複雜的事情變得可能:AbstractInterfaceDrivenDependencyInjectionAspect

AnnotationBeanConfigurerAspect 的基底切面 AbstractInterfaceDrivenDependencyInjectionAspect 使用介面而不是註釋來標記可配置的類別。 雖然它看起來像是表面上的更改,但它提供了一些有趣的選項,例如使用網域介面和註釋來指定依賴注入、通過繞過反射來提高注入性能以及利用多個切面來配置物件。

在設計層面,此切面配置任何類型實現 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 建構

  1. declare parents 語句使 MailSenderClient 實現 ConfigurableObject 介面,使其有資格通過 AbstractInterfaceDrivenDependencyInjectionAspect 進行 DI。
  2. inConfigurableBean() 僅選擇 MailSenderClient 子類型中的連接點,從而將切面的適用性限制為僅匹配的類型。

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>

此切面還有一些其他模式

  • 使用多個切面來配置一個物件(例如,每個「客戶端」介面一個)。
  • 使用網域註釋而不是網域介面或 @Configurable 註釋來指定可配置的類型。
  • 使用基於 hasmethod() 的類型模式(目前是 AspectJ 5 中的實驗性功能,將成為 AspectJ 6 中的常規功能)以避免使用與 DI 相關的類型或註釋。
  • 使用基於 AspectJ 的 mixin 為客戶端介面提供預設實作,並避免重複的 setter。

但是,讓我們將這些想法留到另一篇部落格文章中。

在您需要時的靈活性:AbstractDependencyInjectionAspect

最後,這是最靈活的基底切面。 此切面要求您對 AspectJ pointcut 語言有深入的了解。 但是,除非在極端的自訂情況下(例如自訂反序列化事件),否則您不會直接建立此基底切面的子切面。 相反,您將使用我們前面討論過的子切面之一。

該切面宣告了六個子切面可以定義的 pointcut

  1. beanConstruction(Object bean):選擇 bean 建構。 典型的實作將選擇物件初始化連接點。
  2. beanDeserialization(Object bean):選擇 bean 反序列化。 典型的實作將選擇必須存在於注入物件中的 readResolve() 方法。 如果您使用的是非標準反序列化(不調用 readResolve()),您將使用此 pointcut 選擇適當的替代方法。
  3. inConfigurableBean():選擇由定義切面可配置的 bean 中的連接點。 典型的實作將使用帶有適當類型模式的 within() pointcut。
  4. preConstructionConfiguration():選擇需要建構之前注入依賴項的 bean 的連接點。 此 pointcut 的預設實作不選擇任何連接點(bean 將在建構函式執行後注入依賴項)。
  5. mostSpecificSubTypeConstruction():選擇與最特定的子類型對應的連接點。 預設實作使用連接點簽名來確定建構函式是否代表正在注入的 bean 的類型層次結構中最特定的。 然後,此資訊與 preConstructionConfiguration() pointcut 結合使用,以使用 before 或 after advice 來注入依賴項。
  6. leastSpecificSuperTypeConstruction():選擇與最不特定的超類型對應的連接點。

此切面還定義了一個抽象方法 configureBean(Object bean),其實作應指定與依賴注入對應的邏輯。

所以您有所有選項可以在您的應用程式中啟用網域物件 DI。 如果您正在進行 DDD 或以其他方式需要將 DI 擴展到您的網域物件,您必須查看這些新的切面集。 根據您的特定需求和 AspectJ 知識,您會發現其中一種對建立優雅的解決方案很有幫助。

取得 Spring 電子報

透過 Spring 電子報保持聯繫

訂閱

領先一步

VMware 提供培訓和認證,以加速您的進度。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

了解更多

即將舉行的活動

查看 Spring 社群中所有即將舉行的活動。

查看所有