領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多幾個月前,我們在 www.springframework.org 上開始釋出問卷調查,徵求大家對 Spring、其功能以及使用方式的反饋。我提出的第一個問題是,大家是否會檢查必需的依賴項,如果會,他們使用了什麼機制。我很快就跟進了這個問題,詢問社群他們使用何種事務管理策略。
令我高興的是,當我第一次檢視結果時(那是在三月份),在第一個問卷中,很多人透過投票告訴我們他們正在使用 @Required 註解。第二個關於事務管理的問卷調查很快就顯示,很多人正在使用 @Transactional 註解。下面你可以看到關於檢查必需依賴項的問卷調查結果。結合關於事務管理的問卷調查(約 30% 的受訪者使用 @Transactional 註解來界定事務邊界),這些結果一致表明大家正在大量使用 Spring 2.0,這對我們來說是個非常好的訊息。由於將使用 Spring 1.x 的應用程序升級到 Spring 2.0 應該沒有任何問題,我們真心希望大家不要固守 Spring 1.x,事實上,大家也大規模地進行了升級。
| 8% | 我在業務方法中檢查它們。 |
| 9% | 使用 init-method 和斷言機制(參見 Assert)。 |
| 9% | 在 XML 中使用 dependency-check 屬性。 |
| 13% | 我無需檢查,我使用建構函式注入。 |
| 15% | 使用 InitializingBean 和斷言機制。 |
| 17% | 使用 Spring 2.0 的 @Required 註解。 |
| 29% | 我不檢查必需的依賴項。 |
但有趣的是,有 29% 的人根本不檢查必需的依賴項。在伴隨討論的論壇帖子中,出現了一些有趣的建議,解釋了為什麼有些人不做這件事以及其他人是如何解決的。讓我們來回顧一下其中的一些。
換句話說,我們可以**強制**我們的類的使用者(同樣,這可能是 Spring,但也可能是一個直接例項化你的類的單元測試)在傳遞引數的同時例項化它。
public class Service {
public Collaborator collaborator;
// constructor with arguments, you *have* to
// satisfy the argument to instantiate this class
public Service(Collaborator collaborator) {
this.collaborator = collaborator;
}
}
在需要檢查必需依賴項時,我們可以利用這一點。如果我們修改上面的程式碼示例以包含斷言,那麼我們就能 100% 確定該類在沒有注入其協作物件的情況下永遠不會被例項化。
public Service(Collaborator collaborator) {
if (collaborator == null) {
throw new IllegalArgumentException("Collaborator cannot be null");
}
this.collaborator = collaborator;
}
換句話說,如果我們使用建構函式注入並結合我上面展示的斷言機制,那麼我們就不需要一個專門的依賴項檢查機制。
這是你在 Spring 框架本身中看到大量 Setter 注入的原因之一。Setter 注入在 Spring 本身中的使用,以及我們大力提倡它,也導致了許多第三方軟體開始使用 Setter 注入,以及部落格和文章開始提及 Setter 注入。
(順便問一下,大家還記得第一代、第二代和 M 型控制反轉嗎? ;-))
出於這兩個原因,我認為建構函式注入在應用程式程式碼中的可用性遠高於在框架程式碼中。在應用程式程式碼中,你本身對需要配置的可選值的需求就比較少(你的應用程式程式碼不太可能在許多需要可配置屬性的情況下被使用)。其次,應用程式程式碼比框架程式碼更少使用類繼承。例如,應用程式中的特化不像框架程式碼中那樣頻繁發生——同樣,應用程式程式碼的使用場景要少得多。
不使用建構函式注入的其他論點之一是建構函式中缺乏引數名,以及這些引數名不會出現在 XML 中。我認為在**大多數**應用程式中,這並沒有太大影響。首先考慮使用 Setter 注入的變體:
<bean id="authenticator" class="com.mycompany.service.AuthenticatorImpl"/>
<bean id="accountService" class="com.mycompany.service.AccountService">
<property name="authenticator" ref="authenticator"/>
</bean>
這個版本提到了 authenticator 作為屬性名和 bean 名。這是我經常遇到的模式。我認為,在使用建構函式注入時,缺乏建構函式引數名(以及它們不在 XML 中顯示)並不會真正讓我們感到困惑。
<bean id="authenticator" class="com.mycompany.service.AuthenticatorImpl"/>
<bean id="accountService" class="com.mycompany.service.AccountService">
<constructor-arg ref="authenticator"/>
</bean>
public class Service {
private Collaborator collaborator;
@Required
public void setCollaborator(Collaborator c) {
this.collaborator = c;
}
}
<bean class="org.sfw.beans.factory.annotation.RequiredAnnotationBeanFactoryPostProcessor"/>
public class Service implements InitializingBean {
private Collaborator collaborator;
public void setCollaborator(Collaborator c) {
this.collaborator = c;
}
// from the InitializingBean interface
public void afterPropertiesSet() {
if (collaborator == null) {
throw new IllegalStateException("Collaborator must be set in order for service to work");
}
}
}
另一種機制,類似於 Java 中的 @Required,是在 XML 中的 dependency-check 屬性,奇怪的是,它的使用率並不高。透過調整此屬性(預設關閉)啟用依賴項檢查,將告訴 Spring 開始檢查 bean 上的某些依賴項。有關此功能的更多資訊,請參閱參考文件。
在某些情況下,我不會使用建構函式注入。例如,一個具有**大量**依賴項或其他可配置值的類。我個人不認為一個帶有 20 個引數的建構函式是一個好程式碼的例子。當然,問題在於,一個具有 20 個依賴項的類是否不承擔太多的職責……
有一件事是肯定的——透過在業務方法中檢查必需依賴項來強制它們,我肯定不會這樣做。