第三章 bean 的配置 在本章中,我們將介紹以下內容: 1. bean 定義的繼承: 2. 如何解決 bean 類的構造函數的參數: 3. 如何配置原始類型 (如 int 、float 等) 、集合類型(如 java.util.List、java.util.Map)等以及自定義類型 (如 Ad ...
第三章 bean 的配置
在本章中,我們將介紹以下內容:
- bean 定義的繼承:
- 如何解決 bean 類的構造函數的參數:
- 如何配置原始類型 (如 int 、float 等) 、集合類型(如 java.util.List、java.util.Map)等以及自定義類型 (如 Address ) 等的 bean 屬性和構造函數參數;
- 如何通過使用 p 命名空間和 c 命名空間分別指定 bean 屬性和構造參數來使用應用程式上下文 XML 文件變得簡潔;
- Spring 的 FactoryBean 介面,運行編寫自己的工廠類來創建 bean 實例;
- 模塊化 bean 配置。
bean 定義的繼承
我們在第一章和第二章中看到,應用程式上下文 XML 文件中的 bean 定義指定了 bean 類及其依賴項的完全限定名稱。在某些場景下,為了使 bean 定義不那麼冗長,你可能希望 bean 定義從另一個 bean 定義繼承配置信息。下麵介紹 MyBank 應用中這樣的一個場景。
MyBank——bean 定義繼承實例
在上一章中,我們瞭解到 MyBank 應用通過 DAO 來訪問資料庫。假設 MyBank 應用定義了一個可以與資料庫交互的 DatabaseOperations 類,因此 MyBank 應用中的所有 DAO 都依賴於 DatabaseOperations 類來執行資料庫操作,如下圖所示。
上圖展示了 FixedDepositDao 和 PersonalBankingDao 類依賴於 DatabaseOperations 類。以下應用程式上下文 XML 文件展示了這些類的 bean 定義。
<bean id="databaseOperations"
class="sample.spring.chapter01.bankapp.utils.DatabaseOperations"></bean>
<bean id="personalBankingDao" class="sample.spring.chapter01.bankapp.dao.PersonalBankingDaoImpl">
<property name="databaseOperations" ref="databaseOperations" />
</bean>
<bean id="fixedDepositDao" class="sample.spring.chapter01.bankapp.dao.FixedDepositDaoImpl">
<property name="databaseOperations" ref="databaseOperations" />
</bean>
上面 xml 中,personalBankingDao 和 fixedDepositDao bean 定義都使用 databaseOperations 屬性來引用 DatabaseOperations 實例。這意味著 PersonalBankingDaoImpl 和 FixedDepositDaoImpl 類都定義了一個 setDatabaseOperations 方法,以允許 Spring 容器註入 DatabaseOperations 實例。
如果應用程式中的 多個 bean 共用一組公共的配置 (屬性、構造函數參數等),則可以創建一個 bean 定義,作為其他 bean 定義的父定義。在 personalBankingDao 和 fixedDepositDao bean定義中,公共的配置是 databaseOperations 屬性。下麵展示了 personalBankingDao 和 fixedDepositDao bean 定義如何從父 bean 定義繼承 databaseOperations 屬性。
<bean id="databaseOperations" class="sample.spring.chapter03.bankapp.utils.DatabaseOperations"></bean>
<bean id="daoTemplate" abstract="true">
<property name="databaseOperations" ref="databaseOperations" />
</bean>
<bean id="fixedDepositDao" parent="daoTemplate" class="sample.spring.chapter03.bankapp.dao.FixedDepositDaoImpl">
</bean>
<bean id="personalBankingDao" parent="daoTemplate"
class="sample.spring.chapter03.bankapp.dao.PersonalBankingDaoImpl"></bean>
在上面的 xml 中,daoTemplate bean 定義了 fixedDepositDao 和 personalBankingDao bean 定義共用的公共配置。由於 fixedDepositDao 和 personalBankingDao bean 定義都需要 databaseOperations 依賴項,daoTemplate bean 定義使用
如果
註意:
抽象 bean 不能作為其他 bean 定義的依賴項,也就是說,不能使用 <property> 或 <constructor-arg> 元素來引用抽象 bean 。
你可能已經註意到 daoTemplate bean 定義沒有指定 class特性。如果父 bean 定義沒有指定 class特性,則需要在子 bean 定義 (如 fixedDepositDao 和 personalBankingDao) 中指定 class 特性。註意,如果不指定 class 特性,則必須將 bean 定義為抽象的,以使 Spring 容器不會去嘗試創建與之對應的 bean實例。
要驗證 fixedDepositDao 和 personalBankingDao bean 定義是否繼承了 daoTemplate bean 定義的 databaseOperations 屬性。請執行下麵的 java 代碼。BankApp 類的 main 方法調用在 fixedDepositDao 和 personalBankingDao bean 中的方法,而這些 bean 調用 DatabaseOperations 實例上的方法。你會註意到,BankApp 的 main 方法成功運行。沒有拋出任何異常。如果沒有將 DatabaseOperations 實例註入 fixedDepositDao 和 personalBankingDao bean 中,那麼代碼將拋出 java.lang.NullPointerException。
package sample.spring.chapter03.bankapp;
import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import sample.spring.chapter03.bankapp.controller.FixedDepositController;
import sample.spring.chapter03.bankapp.controller.PersonalBankingController;
import sample.spring.chapter03.bankapp.domain.FixedDepositDetails;
public class BankApp {
private static Logger logger = Logger.getLogger(BankApp.class);
public static void main(String args[]) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"classpath:META-INF/spring/applicationContext.xml");
FixedDepositController fixedDepositController = context
.getBean(FixedDepositController.class);
fixedDepositController.submit(context
.getBean(FixedDepositDetails.class).setDepositAmount(100)
.setEmail("[email protected]").setId(1).setTenure(10));
logger.info("Your fixed deposit details : " + fixedDepositController.get());
PersonalBankingController personalBankingController = context
.getBean(PersonalBankingController.class);
logger.info(personalBankingController.getMiniStatement());
}
}
下圖 顯示了 fixedDepositDao 和 personalBankingDao bean 定義中,bean 定義繼承是如何工作的
上圖展示了 fixedDepositDao 和 personalBankingDao bean 定義從 daoTemplate bean 定義繼承了 databaseOperations 屬性(以 fixedDeopsitDa 和 personalBankingDao 標識的方框中的 property 屬性)。上圖還描述了 Spring 容器不會嘗試創建 與 daoTemplate bean 定義相對應的bean 實例,因為他被標記為 abstract。
繼承了什麼
子 bean 定義從 父 bean 定義繼承一下配置信息:
- 屬性,通過
元素指定。 - 構造函數參數,通過
元素指定。 - 方法覆蓋(見4,5節)
- 初始化和銷毀方法(見 第五章);
- 工廠方法,通過
元素的 工廠方法特性指定(見2,3節,瞭解靜態和實例工廠方法如何用於創建 bean)
bean 定義繼承實例 —— 父 bean 定義非抽象
下麵 xml 文件中展示了一個 bean 繼承實例,其中父 bean 定義不是抽象的,而且子 bean 定義了一個額外的依賴項
<bean id="serviceTemplate" class="sample.spring.chapter03.bankapp.base.ServiceTemplate">
<property name="jmsMessageSender" ref="jmsMessageSender" />
<property name="emailMessageSender" ref="emailMessageSender" />
<property name="webServiceInvoker" ref="webServiceInvoker" />
</bean>
<bean id="fixedDepositService" class=".....FixedDepositServiceImpl" parent="serviceTemplate">
<property name="fixedDepositDao" ref="personalBankingDao" />
</bean>
<bean id="personalBankingService" class="....PersonalBankingServiceImpl" parent="serviceTemplate">
<property name="personalBankingDao" ref="personalBankingDao" />
</bean>
<bean id="userRequestController" class="....UserRequestControllerImpl">
<property name="serviceTemplate" ref="serviceTemplate" />
</bean>
在深入瞭解上述配置的細節之前,有一點背景需要瞭解:MyBank 應用程式中的服務可能會將 JMS 消息發送到消息傳遞中間件或將電子郵件發送到電子郵件伺服器,或者可能會調用外部 web 服務。在上面 xml 文件中, jmsMessageSender、emailMessageSender 和 webServiceInvoker bean 通過提供一個抽象層來簡化這些任務,由 serviceTemplate bean 提供對 jmsMessageSender、emailMessageSender 和 webServiceInvoker bean 的 訪問。這就是 serviceTemplate bean 依賴於 jmsMessageSender、emailMessageSender 和 webServiceInvoker bean 的原因。
在上面 xml 中展示了 serviceTemplate bean 定義是 fixedDepositService 和 personalBankingService bean 定義的 父 bean 定義。請註意,serviceTemplate bean 定義不是抽象的,class 特性指定的類為 ServiceTemplate。在之前的 bean 定義繼承實例中,子 bean 定義沒有定義任何屬性。註意,在 XML 中,fixedDepositService 和 personalBankingService 子 bean 定義分別定義了 fixedDepositDao 和 personalBankingDao 屬性。
由於父 bean 定義的屬性由子 bean 定義繼承,FixedDepositServiceImpl 和 PersonalBankingServiceImpl 類必須為 jmsMessageSender、emailMessageSender 和 webServiceInvoker 屬性定義 setter 方法。你可以選擇在 FixedDepositServiceImpl 和 PersonalBankingServiceImpl 類中定義 setter 方法。也可以將 FixedDepositServiceImpl 和 PersonalBankingServiceImpl 類作為 ServiceTemplate 類的子類。在 下麵的 程式代碼中 FixedDepositServiceImp 和 PersonalBankingServiceImp 類是 ServiceTemplate 類的子類。
//FixedDepositServiceImpl 類繼承這 ServiceTemplate
package sample.spring.chapter03.bankapp.service;
import org.apache.log4j.Logger;
import sample.spring.chapter03.bankapp.base.ServiceTemplate;
import sample.spring.chapter03.bankapp.dao.FixedDepositDao;
import sample.spring.chapter03.bankapp.domain.FixedDepositDetails;
public class FixedDepositServiceImpl extends ServiceTemplate implements FixedDepositService {
private static Logger logger = Logger
.getLogger(FixedDepositServiceImpl.class);
private FixedDepositDao fixedDepositDao;
public FixedDepositServiceImpl() {
logger.info("initializing");
}
public FixedDepositDao getFixedDepositDao() {
return fixedDepositDao;
}
public void setFixedDepositDao(FixedDepositDao fixedDepositDao) {
logger.info("Setting fixedDepositDao property");
this.fixedDepositDao = fixedDepositDao;
}
public FixedDepositDetails getFixedDepositDetails(long id) {
return fixedDepositDao.getFixedDepositDetails(id);
}
public boolean createFixedDeposit(FixedDepositDetails fdd) {
return fixedDepositDao.createFixedDeposit(fdd);
}
}
//PersonalBankingServiceImpl 類繼承自 ServiceTemplate
package sample.spring.chapter03.bankapp.service;
import sample.spring.chapter03.bankapp.base.ServiceTemplate;
import sample.spring.chapter03.bankapp.dao.PersonalBakingDao;
import sample.spring.chapter03.bankapp.domain.BankStatement;
public class PersonalBankingServiceImpl extends ServiceTemplate implements PersonalBankingService {
private PersonalBakingDao personalBakingDao;
public void setPersonalBankingDao(PersonalBakingDao personalBakingDao) {
this.personalBakingDao = personalBakingDao;
}
@Override
public BankStatement getMiniStatement() {
return personalBakingDao.getMiniStatement();
}
}
// 父類 ServiceTemplate
package sample.spring.chapter03.bankapp.base;
public class ServiceTemplate {
private JmsMessageSender jmsMessageSender;
private EmailMessageSender emailMessageSender;
private WebServiceInvoker webServiceInvoker;
public JmsMessageSender getJmsMessageSender() {
return jmsMessageSender;
}
public void setJmsMessageSender(JmsMessageSender jmsMessageSender) {
this.jmsMessageSender = jmsMessageSender;
}
public EmailMessageSender getEmailMessageSender() {
return emailMessageSender;
}
public void setEmailMessageSender(EmailMessageSender emailMessageSender) {
this.emailMessageSender = emailMessageSender;
}
public WebServiceInvoker getWebServiceInvoker() {
return webServiceInvoker;
}
public void setWebServiceInvoker(WebServiceInvoker webServiceInvoker) {
this.webServiceInvoker = webServiceInvoker;
}
}
在上面 xml 文件中,personalBankingService bean 定義將 personalBankingDao 指定為依賴項。其中, setPersonalBankingDao setter 方法對應於 personalBankingDao 依賴項。另外,請註意 PersonalBankingServiceImpl 類是 ServiceTemplate 類的子類。
下圖展示了父 bean 定義 (如 ServiceTemplate)不必是抽象的,子 bean 定義(如 fixedDepositService 和 personalBankingService) 可以定義附加屬性,父類 (如 ServiceTemplate 類 )和 子 bean 定義(如 FixedDepositServiceImpl 和 PersonalBankingServiceImpl )所表示的類本身也可以通過繼承相關聯。
由下圖可知:
- 因為 serviceTemplate bean 沒有被定義為抽象,所以 Spring 容器將創建一個它的實例。
- FixedDepositServiceImpl 和 PersonalBankingServiceImpl 類(對應於子 bean 定義)是對應於 serviceTemplate 父bean 定義的 ServiceTemplate 類的子類。
- 而且,fixedDepositService 和 personalBankingService bean 定義分別定義了附加屬性 fixedDepositDao 和 personalBankingDao,應該註意的是,子 bean 定義還可以定義附加的構造函數參數和方法覆蓋(在4.5節中討論)。
由於 serviceTemplate bean 定義不是抽象的 ,其他 bean 可以將 serviceTemplate bean 定義為依賴項。例如,在上面 xml 文件中,serviceTemplate bean 是 userRequestController bean 的依賴項。你可以從這個討論中推斷,如果一個 父 bean 定義不是抽象的。那麼父 bean 提供的功能不僅可以被子 bean 使用,也可以被應用程式上下文中的其他 bean 所利用。