第二章 Spring框架基礎 面向介面編程的設計方法 在上一章中,我們看到了一個依賴於其他類的POJO類包含了對其依賴項的具體類的引用。例如,FixedDepositController 類包含 對 FixedDepositService 類的引用,FixedDepositService 類包含 ...
第二章 Spring框架基礎
面向介面編程的設計方法
在上一章中,我們看到了一個依賴於其他類的POJO類包含了對其依賴項的具體類的引用。例如,FixedDepositController 類包含 對 FixedDepositService 類的引用,FixedDepositService 類包含對 FixedDepositDao 類的引用。如果這個依賴於其他類的類直接引用其依賴項的類,則會導致類之間的緊密耦合。這意味著如果要替換其依賴項的其他實現,則需要更改這個依賴於其他類的類本身。
我們知道 Java 介面定義了其實現類應遵循的契約。因此,如果一個類依賴於其依賴項實現的介面,那麼當替換不同的依賴項實現時,類不需要改變。一個類依賴於由其依賴項所實現的介面的應用程式設計方法稱為 "面向介面編程"。這種設計方法使得依賴類和依賴項之間松耦合。由依賴項類實現的介面稱為依賴介面。
和 ”面向類編程“ 相比,”面向介面編程“ 是更加良好設計實踐,下圖表明 ABean 類依賴於 BBean 介面而不是 BBeanImpl 類(BBean介面的實現)。
下圖中,FixedDepositJdbcDao 單純的使用 JDBC, 而 FixedDepositHibernateDao 使用 Hibernate ORM 進行資料庫交互。如果 FixedDepositService 直接依賴於 FixedDepositJdbcDao 或 FixedDepositHibernateDao,當需要切換與資料庫交互的策略時,則需要在 FixedDepositService 類中進行必要的更改。FixedDepositService 依賴於 FixedDepositJdbcDao 和 FixedDepositHibernateDao 類實現 FixedDepositDao 介面(依賴介面)。現在,通過使用單純的 JDBC 或 Hibernate ORM 框架,你可以向 FixedDepositService 實例提供 FixedDepositJdbcDao 或 FixedDepositHibernateDao實例。
由於 FixedDepositService 依賴於 FixedDepositDao 介面,因此將來可以支持其他資料庫交互策略。如果決定使用iBATIS(mybaits)持久性框架進行資料庫交互,那麼可以使用IBATIS,而不需要對FixedDepositService 類進行任何更改,只需要創建一個 FixedDepositDao 介面的 FixedDepositIbatisDao 類,並將 FixedDepositIbatisDao 的實例提供給 FixedDepositService 實例。
現在來看看 ”面向介面編程“ 是如何提高依賴類的可測試性的。
提高依賴類的可測試性
在上圖中,FixedDepositSerivce 類保留了對 FixedDepositDao 介面的引用。FixedDepositJdbcDao 和 FixedDepositHibernateDao 是 FixedDepositDao 介面的具體實現類。現在,為了簡化 FixedDepositService 類的單元測試,我們可以把原來對具體資料庫操作的實現去掉,用一個實現了 FixedDepositDao 介面但是不需要資料庫的代碼來代替。
如果 FixedDepositService 類直接引用 FixedDepositJdbcDao 或 FixeDepositHibernateDao 類,那麼測試 FixedDepositService 類則需要設置資料庫以進行測試。這表明通過對依賴介面的模擬依賴類實現,你可以減少針對單元測試的基礎設施設置的工作量。
現在來看看 Spring 如何在應用程式中使用 “面向介面編程” 的設計方法,你需要執行以下操作:
1.創建引用依賴介面,而不是依賴項的具體實現的 bean 類;
2.定義
使用 “面向介面編程” 設計方法的MyBank應用程式
上圖中,顯示了一個類依賴於依賴項實現的介面,而不是依賴於具體的依賴項實現類。例如,FixedDepositControllereImpl 類依賴於 FixedDepositService 介面, FixedDepositServiceImpl 類依賴於 FixedDepositDao 介面。
public class FixedDepositServiceImpl implements FixedDepositService{
private FixedDepositDao fixedDepositDao;
public void setFixedDepositDao(FixedDepositDao fixedDepositDao){
this.fixedDepositDao = fixedDepositDao;
}
public FixedDepositDetails getFixedDepositDetails(long id){
return fixedDepositDao.getFixedDepositDetails(id);
}
public boolean createFixedDeposit(FixedDepositetails fdd){
return fixedDepositDao.createixedDeposit(add);
}
}
在上面代碼中, FixedDepositServiceImpl 包含對 FixedDepositDao 介面的引用。要註入到 FixedDepositServiceImpl 實例中的 FixedDepositServiceImpl 實例中的 FixedDepositDao 具體實現,則在應用程式上下文 XML 文件中指定。如上圖所示。可以註入以下 FixedDepositDao 介面的具體實現:FixedDepositIbatisDao、FixedDepositJdbcDao 和 FixedDepositHibernateDao。
下麵的 展示以下將 FixedDepositHibernateDao 註入到 FixedDepositServiceImpl 中的 applicationContext.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans ....>
<bean id="controller"
class="sample.spring.chapter02.bankapp.controller.FixedDepositControllerImpl">
<property name="fixedDepositService" ref="service"/>
</bean>
<bean id="service" class="sample.spring.chater02.bankapp.service.FixedDepositServiceImpl">
<property name="fixedDepositDao" ref="dao"/>
</bean>
<bean id="dao" class="sample.spring.chapter02.bankapp.dao.FixedDepositHibernateDao">
</bean>
</beans>
上述的 applicationContext.xml 文件顯示了 FixedDepositHibernateDao (一個 FixedDepositDao介面的實現) 的一個實例被註入 FixedDepositServiceImpl 中。現在,如果決定使用 IBATIS 代替 Hibernate 進行持久化,那麼所需要做的就是將 dao bean 定義的 class 修改為 FixedDepositIbatisDao 類的完全限定名。
使用靜態和實例工廠方法創建 Spring bean
Spring 容器可以創建和管理任何類的實例。而不管類是否提供無參數構造函數。在下一個小節中。我們將介紹在告知函數中可以接收一個或多個參數的 bean 類的定義。如果現有的 Java 應用程式工廠類來創建對象實例,那麼仍然可以使用 Spring 容器來管理由這些工廠創建的對象。
現在來介紹一下 Spring 容器如何調用類的靜態或實例工廠方法來管理返回的對象實例。
1.通過靜態工廠方法實例 bean
上面的圖中,展示瞭如何使用 FixedDepositHibernateDao、FixedDepositIbatisDao 和、FixedDepositJdbcDao 類實現 FixedDepositDao 介面。下麵代碼中的 FixedDepositDaoFactory 類定義了一個靜態工廠的方法。改靜態方法根據傳入的參數來創建和返回 FixedDepositDao 實例。
public class FixedDepositDaoFactory{
private FixedDepositDaoFactory(){
}
public static FixedDepositDao getFixedDepositDao(String daoType,...){
FixedDepositDao fixedDepositDao = null;
if("jdbc".equalsIgnoreCase(daoType)){
fixedDepositDao = new FixedDepositJdbcDao();
}
if("hibernate".equisIgnoreCase(daoType)){
fixedDepositDao = new FixedDepositHibernateDao();
}
...
return fixedDepositDao;
}
}
在上面代碼中,FixedDepositDaoFactory 類定義了一個 getFixedDepositDao 靜態方法,該方法根據 daoType 參數的值創建並返回 FixedDepositJdbcDao、FixedDepositHibernateDao 或 FixedDepositIbatisDao 類的實例。
在下麵代碼中。 FixedDepositDaoFactory 類的 bean 定義指示 Spring 容器調用 FixedDepostDaoFactory 的 getFixedDepositDao 方法,以獲取 FixedDepositJdbcDao 類的實例。
<bean id="dao" class="sample.spring.spring.FixedDepositDaoFactory" factory-method="getFixedDepositDao">
<constructor-arg index="0" value="jdbc"/>
</bean>
在上述 bean 定義中,class 特性指定了定義靜態工廠方法的類的完全限定名稱。factory-method 特性指定了 Spring 容器調用的獲取 FixedDepositDao 對象實例的靜態工廠方法的名稱。
需要著重註意的是,調用 ApplicationContext 的 getBean 方法來獲取 dao bean(上面的XML文件) 將會調用 FixedDepositDaoFactory 的 getFixedDepositDao 工廠方法。這意味著調用 getBean ("dao") 返回由 getFixedDepositDao 工廠方法創建的 FixedDepositDao 實例。而不是 FixedDepositDaoFactory 類的實例。
現在我們已經看到創建了一個 FixedDepositDao 實例的工廠類的配置,下麵將展示如何將 FixedDepositDao 的實例註入 FixedDepositServiceImpl 類中。
<bean id="service" class="sample.spring.chapter02.bankapp.FixedDepositServiceImpl">
<property name="fixedDepositDao" ref="dao" />
</bean>
<bean id="dao" class="sample.spring.chapter02.basicapp.fixedDepositDaoFactory"
factory-method="getFixedDepositdDao">
<constructor-org index="0" value="jdbc" />
</bean>
在上述 xml 文件中,
通過實例工廠方法實例化 bean
下麵代碼中展示了 FixedDepositDaoFactory 類,他定義了用於創建和返回 FixedDepositDao 實例的實例工廠方法
public class FixedDepositDaoFactory{
public FixedDepositDaoFactory(){}
public FixedDepositDao getFixedDepositDao(String daoType,...){
FixedDepositDao fixedDepositDao = null;
if("jdbc".equalsIgnoreCase(daoType)){
fixedDepositDao = new FixedDepositJdbcDao();
}
if("hibernate".equalsIgnoreCase(daoType)){
fixedDepositDao = new FixedDepositHibernateDao();
}
return fixedDepositDao;
}
}
如果類定義了一個實例化工廠方法,則該類必須定義一個public 構造函數,以便 Spring 容器可以創建該類的實例。在上述代碼中,FixedDepositDaoFactory 類定義了一個 public 無參構造函數。FixedDepositDaoFactory 的 getFixedDepositDao 方法是一個創建並返回 FixedDepositDao 實例的實例工廠方法。
下麵的 XML 文件展現瞭如何指示 Spring 容器調用 FixedDepositDaoFactory 的方法getFixedDepositDao 方法來獲取 FixedDepositDao 的一個實例。
<bean id="daoFactory" class = "sample.spring.chapter02.basicapp.FixedDepositDaoFactory" />
</bean>
<bean id="dao" factory-bean="daoFactory" factory-method="getFixedDepositDao">
<constructor-arg index="0" value="jdbc" />
</bean>
<bean id="service" class="sample.spring.chapter02.bankapp.FixedDepositServiceImpl">
<property name="fixedDepositDao" ref="dao"></property>
</bean>
在上面XML 文件中,FixedDepositDaoFactory 類(包含實例工廠方法的類) 被配置為常規的 Spring bean,
並且使用單獨的
與 static 工廠方法一樣。可以使用
下麵介紹如何設置由靜態和實例工廠方法創建的 bean 的依賴項。
註入由工廠方法創建的 bean 的依賴項
可以將 bean 依賴項作為參數傳遞給工廠方法,也可以使用基於 setter 的DI 來註入由靜態或實例工廠方法返回的 bean 實例的依賴項。
public class FixedDepositJdbcDao{
private DatabaseInfo databaseInfo;
....
public FixedDepositJdbcDao();
public void setDatabaseInfo(DatabaseInfo databaseInfo){
this.databaseInfo = databaseInfo;
}
.....
}
在上面代碼中,databaseInfo 表示通過 setDatabaseInfo 方法賦值的 FixedDepositJdbcDao 類的依賴項。
FixedDepositDaoFactory 類定義了一個負責創建和返回 FixedDepositJdbcDao 類的實例的工廠方法, 如下麵代碼所示
public class FixedDepositDaoFactory{
public FixedDepositDaoFactory(){}
public FixedDepositDao getFixedDepositDao(String daoType){
FixedDepositDao fixedDepositDao = null;
if("jdbc".equalsIgnoreCase(DaoType)){
fixedDepositDao = new FixedDepositJdbcDao();
}
if("hibernate".equalsIgnoreCase(daoType)){
fixedDepositDao = new FixedDepositHibernateDao();
}
....
return fixedDepositDao;
}
}
getFixedDepositDao 方法是用於創建 FixedDepositDao 實例的實例工廠方法。如果 daoType 參數的值為 jdbc, 則 getFixedDepositDao 方法將創建一個 FixedDepositJdbcDao 的實例。請註意,getFixedDepositDao 方法沒有設置 FixedDepositJdbcDao 實例的 databaseInfo 特性。
<bean id="daoFactory" class = "sample.spring.chapter02.basicapp.FixedDepositDaoFactory" />
</bean>
<bean id="dao" factory-bean="daoFactory" factory-method="getFixedDepositDao">
<constructor-arg index="0" value="jdbc" />
</bean>
<bean id="service" class="sample.spring.chapter02.bankapp.FixedDepositServiceImpl">
<property name="fixedDepositDao" ref="dao"></property>
</bean>
如上面的xml文件所示,bean 定義指示 spring 容器通過調用 FixedDepositDaoFactory 類的 getFixedDepositDao 實例工廠方法來創建 FixedDepositJdbcDao 的實例。
<bean id="daoFactory" class="FixedDepositDaoFactory" />
<bean id="dao" factory-bean="daoFactory" factory-method="getFixedDepositDao">
<constructor-arg index="0" value="jdbc"></constructor-arg>
</bean>
dao bean 定義指示 Spring 容器調用 FixedDepositDaoFactory 的 getFixedDepositDao 方法,該方法創建並返回 FixedDepositJdbcDao 的實例。但是,FixedDepositJdbcDao 的 dataBaseInfo 特性並沒有設置。如果需要設置 databaseInfo 特性,可以在 getFixedDepositDao 方法返回的 FixedDepositJdbcDao 實例上執行基於 setter 的DI,如下麵所示。
<bean id="daoFactory" class="FixedDepositDaoFactory"></bean>
<bean id="dao" factory-bean="daoFactory" factory-method="getFixedDepositDao">
<constructor-arg index="0" value="jdbc" />
<property name="databaseInfo" ref="databaseInfo" />
</bean>
在上面示例中 ,
基於構造函數的 DI
在 Spring 中,依賴註入是通過將參數傳遞給 bean 的構造函數和 setter 方法來實現的。我們在前面的章節中介紹過,通過 setter 方法註入依賴的 DI 技術成為基於 setter 的 DI,在本節中,我們將介紹依賴項作為構造函數參數傳遞的 DI 技術(又稱為基於構造函數的 DI)
回顧基於 setter 的 DI
在基於 setter 的 DI 中,
public class PersonalBankingService {
private JmsMessageSender jmsMessageSender;
private EmailMessageSender emailMessageSender;
private WebServiceInvoker webServiceInvoker;
....
public void setJmsMessageSender(JmsMessageSender jmsMessageSender){
this.jmsMessageSender = jmsMessageSender;
}
public void setEmailMessageSender(EmailMessageSender emailMessageSender){
this.emailMessageSender = emailMessageSender;
}
public void setWebServiceInvoker(WebServiceInvoker webServiceInvoker){
this.webServiceInvoker = webSerViceInvoker;
}
....
}
在上面代碼中, PersonalBankingService 類的每個依賴項(JmsMessageSender、EmailMessageSender 和 WebServiceInvoker )都定義了一個 setter 方法。
PersonalBankingService 類為其依賴項定義了 setter 方法,因此使用了基於 setter 的DI ,如下麵 xml 所示
<bean id="personalBankingService" class="PersonalBankingService">
<property name="emailMessageSender" ref="emailMessageSender"></property>
<property name="jmsMessageSender" ref="jmsMessageSender"></property>
<property name="webServiceInvoker" ref="webServiceInvoker"></property>
</bean>
<bean id="jmsMessageSender" class="JmsMessageSender">
.....
</bean>
<bean id="webServiceInvoker" class="WebServiceInvoker">
.....
</bean>
<bean id="emailMessageSender" class="EmailMessageSender">
.....
</bean>
在 PersonalBankingService bean 的定義中,為 PersonalBankingService 類的每個依賴項都指定了一個
下麵介紹如何使用基於構造函數的 DI 來對 PersonalBankingService 類建模。
基於構造函數的 DI
在基於構造函數的 DI 中,bean 的依賴項作為參數傳遞 bean 類的構造函數。如下麵代碼所示,其構造函數接收 JmsMessageSender 、EmailMessageSender 和 WebServiceInvoker 對象。
public class PersonalBankingService {
private JmsMessageSender jmsMessageSender;
private EmailMessageSender emailMessageSener;
private WebServiceInvoker webServiceInvoker;
....
public PersonalBankingService(JmsMessageSender jmsMessageSender,EmailMessageSender emailMessageSender,WebServiceInvoker webServiceInvoker) {
this.jmsMessageSender = jmsMessageSender;
this.emailMessageSender = emailMessageSender;
this.webServiceInover = webServiceInvoker;
}
.....
}
PersonalBankingService 類的構造函數的參數代表 PersonalBankingService 類的依賴項。下麵的 xml 展示瞭如何通過
<bean id="personalBankingService" class="PersonalBankingService">
<constructor-arg index="0" ref="jmsMessageSender"></constructor-arg>
<constructor-arg index="1" ref="emailMessageSender"></constructor-arg>
<constructor-arg index="2" ref="webServiceInvoker"></constructor-arg>
</bean>
<bean id="jmsMessageSender" class="JmsMessageSender">
.....
</bean>
<bean id="webServiceInvoker" class="WebServiceInvoker">
.....
</bean>
<bean id="emailMessageSender" class="EmailMessageSender">
....
</bean>
在上面 xml 中,
下麵介紹如何結合基於構造函數的 DI 以及基於 setter 的 DI。
基於構造函數和基於 setter 的DI 機制的結合使用
如果 bean 類需要結合使用基於構造函數的 DI 機制和 基於 setter 的 DI機制,則可以使用 <constructor-arg> 和 <property> 元素的組合來註入依賴關係。
下麵代碼中展示了 PersonalBankingService 類的一個版本,其依賴項作為參數註入構造函數和 setter 方法
public class PersonalBankingService {
private JmsMessageSender jmsMessageSender;
private EmailMessageSender emailMessageSender;
private WebServiceInvoker webServiceInvoker;
.....
public PersonalBankingService (JmsMessageSender jmsMessageSender,EmailMessageSender emailMessageSender){
this.jmsMessageSender = jmsMessageSender;
this.emailMessageSender = emailMessageSender;
}
public void setWebServiceInvoker (WebServiceInvoker webServiceInvoker){
this.webServiceInvoker = webServiceInvoker;
}
.....
}
在 PersonalBankingService 類中,jmsMessageSender 和 emailMessageSender 依賴項作為構造函數註入, 而 webServiceInvoker 依賴關係通過 setWebServiceInvoker setter 方法註入。以下 bean 定義表明,
<bean id="dataSource" class="PersonalBankingService">
<constructor-arg index="0" ref="jmsMessageSender"></constructor-arg>
<constructor-arg index="1" ref="emailMessageSender"></constructor-arg>
<property name="webServiceInvoker" ref="webServiceInvoker" />
</bean>
可以看到,