Spring 学习指南 第三章 bean的配置 (完)
第三章 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("someemail@somedomain.com").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 所利用。
bean 定义继承实例 —— 继承工厂方法配置
子 bean 定义可以使用 bean 定义继承来从父 bean 定义继承工厂方法配置。我们来看一个例子,其中父工厂方法配置由子 bean 定义继承。
在下面 代码中,ControllerFactory 类定义了一个 getController 实例工厂方法。
package sample.spring.chapter03.bankapp.controller;
public class ControllerFactory{
public Object getController(String controllerName) {
Object controller = null;
if("fixedDepositController".equalsIgnoreCase(controllerName)){
controller = new FixedDepositControllerImpl();
}
if("personalBankingController".equalsIgnoreCase(controllerName)){
controller = new PersonalBankingControllerImpl();
}
return controller;
}
}
由上面代码可知,getController 工厂方法根据传递给他的 controllerName 参数的值创建 FixeDepositControllerImpl 或 PersonalBankingControllerImpl 类的实例。如果 controllerName 参数的值为 fixedDepositController,getController 方法将创建 FixedDepositControllerImpl 类的实例。而如果 controllerName 参数的值为 personalBankingController,getController 方法将创建 PersonalBankingControllerImpl 类的实例。
在 applicationContext.xml 文件中的 bean 定义表明子 bean 定义从父 bean 定义继承了 getController 实例工厂方法配置,如下所示
<bean id="controllerFactory" class="sample.spring.chapter03.bankapp.controller.ControllerFactory">
</bean>
<bean id="controllerTemplate" factory-bean="controllerFactory" factory-method="getController" abstract="true"></bean>
<bean id="fixedDepositController" parent="controllerTemplate">
<constructor-arg index="0" value="fixedDepositController" />
<property name="personalBankingService" ref="personalBankingService" />
</bean>
在上面 xml 文件中,ControllerFactory 类表示一个定义 getController 实例工厂方法的工厂类。controllerTemplate bean 定义指定 ControllerFactory 类的 getController 工厂方法用于创建 bean 实例。getController 方法创建一个 FixedDepositControllerImpl 或 PersonalBankingControllerImpl bean 实例,具体创建哪个实例取决于传递给 gerController 方法的参数。
由于 controllerTemplate bean 定义是抽象的,他由 fixedDepositController 和 personalBankingController 子 bean 定义来使用 getBcontroller 工厂方法配置。
因此,在使用 fixedDepositController bean 定义的情况下, Spring 容器使用参数 'fixedDepositController' 调用 getController 方法,从而创建了 FixedDepositControllerImpl 实例。此外,在使用 personalBankingController bean 定义的情况下,Spring 容器使用参数 ‘personalBankingController' 调用 getController 方法,从而创建 PersonalBankingControllerImpl 实例。
构造函数参数匹配
在第二章中,我们学习了如何使用
在介绍构造函数参数匹配的细节之前,我们再来看一下如何把参数传递给一个 bean 类的构造函数。
使用 元素传递简单的值和 bean 引用。
如果构造函数参数是简单的 Java 类型 (如 int 、String 等),则
下面的代码展示了 UserRequestController 类,其构造函数接受 ServiceTemplate 类型的参数。
public class UserRequestControllerImpl implements UserRequestController{
private ServiceTemplate serviceTemplate;
public UserRequestControllerImpl(ServiceTemplate serviceTemplate){
this.serviceTemplate = serviceTemplate;
}
@Override
public void submitRequest(Request request){
//-- do something using ServiceTemplate
serviceTemplate.getJmsMessageSender();
}
}
在下面的 xml 中,使用
<bean id="serviceTemplate" class="sample.spring.chapter03.bankapp.base.ServiceTemplate">
....
</bean>
<bean id="userRequestController" class="sample.spring.chapter03.bankapp.controller.UserRequestControllerImpl">
<constructor-arg index="0" ref="serviceTemplate" />
</bean>
有了如何将简单值和 bean 引用作为构造函数参数的背景信息,现在我们来看一下 Spring 容器如何匹配构造函数参数类型,并以此定位其调用的 bean 的构造函数。
基于类型的构造方法参数匹配
如果未指定
构造函数参数表示不同的 Spring bean
下面的代码展示了 ServiceTemplate 类,该类定义了一个构造函数,该构造函数接受对 JmsMessageSender、EmailMessageSender 和 WebServiceInvoker bean 的引用。
public class ServiceTemplate {
.....
public ServiceTemplate (JmsMessageSender jmsMessageSender,EmailMessageSender emailMessageSender,WebServiceInvoker webServiceInvoker){
....}
}
下面的 xml 展示了 ServiceTemplate 类的 bean 定义和 ServiceTemplate 引用的 bean。
<bean id="serviceTemplate" class="sample.spring.chapter03.bankapp.base.ServiceTemplate">
<constructor-arg ref="emailMessageSender" />
<constructor-arg ref="jmsMessageSender" />
<constructor-arg ref="webServiceInvoker" />
</bean>
<bean id="jmsMessageSender" class="sample.spring.chapter03.bankapp.base.JmsMessageSender">
</bean>
<bean id="emailMessageSender" class="smaple.spring.chapter03.bankapp.base.EmailMessageSender">
</bean>
<bean id="webServiceInovoker" class="sample.spring.chapter03.bankapp.base.WebServiceInvoker">
</bean>
在上面 xml 中, serviceTemplate bean 的
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.controller.UserRequestController;
import sample.spring.chapter03.bankapp.domain.FixedDepositDetails;
import sample.spring.chapter03.bankapp.domain.Request;
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("someemail@somedomain.com").setId(1).setTenure(10));
logger.info("Your fixed deposit details : "
+ fixedDepositController.get());
PersonalBankingController personalBankingController = context
.getBean(PersonalBankingController.class);
logger.info(personalBankingController.getMiniStatement());
UserRequestController userRequestController = context
.getBean(UserRequestController.class);
userRequestController.submitRequest(new Request());
logger.info("Submitted user request");
}
}
执行上面代码的 main 方法,你会发现 Spring 容器成功创建了一个 ServiceTemplate bean 的实例。这是因为 JmsMessageSender 、EmailMessageSender 和 WebServiceInvoker 的类型在本质上是不同的 (即他们与继承关系无关),这使得 Spring 容器可以将他们以正确的顺序注入 ServiceTemplate 的构造函数中。
构造函数参数表示相关的 Spring bean
请看下面的 SampleBean 类,其构造函数接受与继承关系相关的参数类型。
public class SampleBean {
public SampleBean (ABean aBean ,BBean bBean) {....}
....
}
在上面代码中,SampleBean 类的构造函数接受 ABean 和 BBean 类型作为参数。ABean 和 BBean 表示与继承关系相关的 Spring bean ,BBean 是 ABean 的一个子类。
在下面的应用程序上下文 XML 文件展示了 SampleBean、ABean 和 BBean 类的 bean 定义。
<bean id="aBean" class="example.ABean" />
<bean id="bBean" class="example.BBean" />
<bean id="sampleBean" class="example.SampleBean">
<constructor-arg ref="bBean"></constructor-arg>
<constructor-arg ref="aBean"></constructor-arg>
</bean>
由于 aBean bean 和 bBean bean 与继承关系相关,因此 Spring 容器按照 SampleBean 类的 bean 定义中的
<bean id="sampleBean" class="example.SampleBean">
<constructor-arg type="sample.spring.chapter03.bankapp.controller.BBean" ref="bBean" />
<constructor-arg type="sample.spring.chapter03.bankapp.controller.ABean" ref="aBean" />
</bean>
注意: 如果两个或多个构造函数的参数类型是相同的类型,唯一的选择是选择使用 index 特性来标识每个 <constructor-arg> 元素对应的构造函数参数。
到目前为止,我们已经看到构造函数参数类型匹配的场景,其中构造函数参数表示不同或相关的 Spring bean 。现在我们来看一下构造函数参数类型是如何匹配标准的 Java 类型 (如 int 、long 、boolean 、String、Date等)和自定义 (如 Address )类型的。
构造函数参数表示标准 Java 类型和自定义类型
如果构造函数参数的类型是原始类型 (如 int 、long 、boolean 等)或 String 类型或自定义类型 (如 Address),则
下面代码展示 TransferFundsServiceImpl 类,其构造函数可以接受 String、boolean、long 和 int 类型的参数。
public class TransferFundsServiceImpl implements TransferFundsService {
public TransferFundsServiceImpl (String webServiceUrl,boolean active,long timeout,int numberOfRetrialAttempts) {....}
}
TransferFundsServiceImpl 的构造函数接受以下参数 :webServiceUrl 、 active 、 timeout 和 numberOfRetrialAttempts 。下面的 TransferFundsServiceImpl 类的 bean 定义展示了如何将构造函数参数值传递给 TransferFundsServiceImpl 的构造函数。
<bean id="transferFundsService" class="sample.spring.chapter03.bankapp.service.TransferFundsServiceImpl">
<construtor-arg value="http://someUrl.com/xyz" />
<construtor-arg value="true" />
<construtor-arg value="5" />
<construtor-arg value="200" />
</bean>
假设第三个
<bean id="transferFundsService" class="sample.spring.chapter03.bankapp.service.TransferFundsServiceImpl">
<constructor-arg type="java.lang.String" value="http://someUrl.com/xyz"></constructor-arg>
<constructor-arg type="boolean" value="true"></constructor-arg>
<constructor-arg type="int" value="5"></constructor-arg>
<constructor-arg type="long" value="200"></constructor-arg>
</bean>
在上面 TransferFundsServiceImpl 类的 bean 定义中,type 特性用于指定构造函数参数类型。Spring 容器现在可以比较构造函数参数类型,以将其正确的应用于 TransferFundsServiceImpl 的构造函数。
注意:如果两个或多个构造函数的参数是相同的类型,则唯一的选择时使用 index 特性来表示每个<constructor-arg> 元素对应的构造函数参数
在本节中,我们看到 Spring 如何执行类型匹配来解析构造函数参数。现在我们来看一下如何指示 Spring 根据构造函数参数的名称来执行构造函数参数的匹配
基于名称的构造函数参数匹配
public class TransferFundsServiceImpl implements TransferFundsService {
....
public TransferFundsServiceImpl (String webServiceUrl,boolean active,long timeout,int numberOfRetrialAttempts) {.....}
}
在上面代码中,由 TransferFundsServiceImpl 的构造函数定义的构造方法参数的名称是 webServiceUrl、active、timeout 和 numberOfRetrialAttempts
注意: TransferFundsServiceImpl 类的构造函数接受简单 Java 类型的参数 (如 int 、 long 、 boolean 、String 等),但本节中介绍的概念也适用于构造函数参数为 Spring bean 的引用的场景。
在 下面 xml 中 ,TransferFundsServiceImpl 类的 bean 定义使用
<bean id="transferFundsService" class="sample.spring.chapter03.bankapp.service.TransferFundsServiceImpl">
<constructor-arg name="webServiceUrl" value="http://someUrl.com/xyz"></constructor-arg>
<constructor-arg name="active" value="true" />
<constructor-arg name="numberOfRertrialAttempts" value="5"></constructor-arg>
<constructor-arg name="timeout" value="200" />
</bean>
上述配置将仅在 TransferFundsServiceImpl 类编译时启用了调式标志或启用了 ”参数名称发现(parameter name discovery)“ 标志时才起作用。当启用调式或 "参数名称发现"标志时,构造函数参数和方法参数的名称将保留在生成的 .class 文件中。如果不使用调式或 "参数名称发现" 标志编译类,则编译期间构造函数参数名称将丢失。在生成的 .class 文件中没有构造函数参数名称的情况下, Spring 无法通过名称来匹配构造函数参数。
如果不想使用调式或 "参数名称发现" 标志编译类,则可以使用 @ConstructorProperties 注释来清楚地显示构造函数参数的名称,如 TransferFundsServiceImpl 类,如下面代码所示
public class TransferFundsServiceImpl implements TransferFundsService {
@ConstructorProperties({"webServiceUrl","active","timeout","numberOfRetrialAttempts"})
public TransferFundsServiceImpl (String webServiceUrl,boolean active,long timeout,int numberOfRetrialAttempts) {....}
}
在上面代码中, @ConstructorProperties 注释以构造函数参数的名称指定它们在 bean 类的构造函数中显示顺序。必须确保在
现在我们来看一下 @ConstructorProperties 注释如何影响 bean 定义继承。
(1) @ConstructorProperties 注释和 bean 定义继承
如果对应于父 bean 定义的类的构造函数使用 @ConstructorProperties 注释,则对应于子 bean 定义的 bean 类也必须使用 @ConstructorProperties 注释。
下面xml serviceTemplate (父 bean) 和 FixedDepositService (子 bean)的 bean 定以
<bean id="serviceTemplate" class="sample.spring.chapter03.bankapp.base.ServiceTemplate">
<constructor-arg name="emailMessageSender" ref="emailMessageSender" />
<constructor-arg name="jmsMessageSender" ref="jmsMessageSender" />
<constructor-arg name="webServiceInvoker" ref="webServiceInvoker" />
</bean>
<bean id="fixedDepositServie" class="sample.spring.chapter03.bankapp.service.FixedDepositServiceImpl"
parent="serviceTemplate">
<property name="fixedDepositDao" ref="fixedDepositDao" />
</bean>
在上面的 xml 中展示了 sersviceTemplate bean 定义是非抽象的,这意味着 Spring 容器创建一个 serviceTemplate bean 的实例。serviceTemplate bean 定义指定 3 个
由于我们通过 serviceTemplate bean 定义中的名称指定了构造函数参数,ServiceTemplate 类的构造函数使用 @ConstructorProperties 进行注释,以确保构造函数参数名在运行时可用于 Spring ,如下面 代码所示
public class ServiceTemplate {
@ConstructorProperties({"jmsMessageSender","emailMessageSender","webServiceInvoker"})
public ServiceTemplate(JmsMessageSerder jmsMessageSender,EmailMessageSender emailMessageSender,WebServiceInvoker webServiceInvoker){....}
}
由于 FixedDepositService 是 serviceTemplate 的子 bean 定义,serviceTemplate bean 定义中的
不能使用 @ConstructorProperties 注释通过名称来将参数传递给静态或实例工厂方法,如下所述。
(2) @ConstructorProperties 注释和工厂方法
注意 : 如果使用 debug 或 "参数名称发现" 标志编译类,会导致 .class 文件比原本要大,但这只会使类的加载时间增加,对应用程序的运行性能没有影响。
配置不同类型的 bean 属性和构造函数参数
在现实世界的应用程序开发场景中, Spring bean 的属性和构造函数参数的取值范围可以是 String 类型、对另一个 bean 的引用、其他标准类型 (如 java.util.Date、java.util.Map) 或自定义类型 (例如 Address)。到目前为止,我们已经看到了如何为 bean 属性 (使用
在本节中,我们将介绍 Spring 中内置的 PropertyEditor 实现,他简化了 java.util.Date、java.util.Currency、primitive 等类型作为 bean 属性和构造函数参数的传递。我们还将介绍如何为应用程序上下文 XML 文件中的集合类型 (如 java.util.List 和 java.util.Map) 指定值,以及如何使用 Spring 注册自定义 PropertyEditor 实现。
spring 的内置属性编辑器
JavaBeans PropertyEditors 提供了将 Java 类型转换为字符串值所必需的逻辑,反之亦然。Spring 提供了几个内置的 PropertyEditor,用于将 bean 属性或构造函数参数 (通过
在学习涉及内置 PropertyEditors 的示例之前,首先需要了解 PropertyEditors 在设置 bean 属性和 构造函数参数的值时的重要性。
下面程序示例 BankDetails 类配置为具有预定义特性值的 singleton 范围的 bean
public class BankDetails {
private String bankName;
public void setBankName (String bankName) {
this.bankName = bankName;
}
}
在上面代码中,bankName 是 BankDetails 类的一个 String 类型的特性。在下面 xml 中,BankDetails 类的 bean 定义展示了如何将 bankName 特性的值设置为 "My Personal Bank"。
<bean id="bankDetails" class="BankDetails">
<property name="bankName" value="My Personal Bank" />
</bean>
在上面 xml 的 bean 定义中,
假设以下特性 (以及它们的 setter 方法)已经被添加都 BankDetails 类中:byte[ ] 类型的 bankPrimary-Business 特性、char[ ]类型 headOfficeAddress 特性、char 类型的 privateBank 特性、 java.util.Currency 类型的 primaryCurrency 特性、java.uti.Date类型的 dateOfInception 特性,以及 java.util.Properties 类型的 branchAddresses 特性。修改后 BankDetails 类如下所示
public class BankDetails {
private String bankName;
private byte[] bankPrimaryBusiness;
private char[] headOfficeAddress;
private char privateBank;
private Currency primaryCurrency;
private Date dateOfInception;
private Properties branchAddresses;
....
public void setBankName (String bankName) {
this.bankName = bankName;
}
}
可以通过为属性指定字符串来将 BankDetails 类配置为 Spring bean,并通过使用注册的 JavaBeans PropertyEditor 实现使 Spring 容器将这些字符串值转换为相应的 Java 类型的属性。
在下面的 xml 中,BankDetails 类的 bean 定义为不同的属性类型指定简单的字符串值。
<bean id="bankDetails" class="sample.spring.chapter03.beans.BankDetails">
<property name="bankName" value="My Personal Bank" />
<property name="bankPrimaryBusiness" value="Retail banking"/>
<property name="headOfficeAddress" value="Address of head office"/>
<property name="privateBank" value="Y"/>
<property name="primaryCurrency" value="INR"/>
<property name="dateOfInception" value="30-0.-2012"/>
<property name="branchAddresses" >
<value>
x = Branch X's address
y = Branch Y's address
</value>
</property>
</bean>
在上面 xml 中,为 java.util.Date、java.util.Currency、char [ ] 、byte [ ] 、char 和 java.util.Properties 类型的属性指定了字符串值。Spring 容器使用注册的 PropertyEditor 将属性和构造函数参数的字符串值转换为相应的 Java 类型。例如, Spring 容器使用 CustomDateEditor (java.util.Date 类型的内置 PropertyEditor 实现) 将 dateOfInception 属性的值 “30-01-2012" 转换为 java.util.Date 类型。
如果你观察上面 xml 中 branchAddresses 属性是如何配置的,就会注意到,属性的值是通过
Spring 带有几个内置的 PropertyEditor 实现,他们的功能是将应用程序上下文 XML 文件中指定的值转换为 bean 属性或构造函数参数的对应的 Java 类型。下表介绍了 Spring 中一些内置的 PropertyEditor实现
内置 PropertyEditor 实现 | 描述 |
---|---|
CustomBooleanEditor | 将字符串值转换为布尔型或布尔类型 |
CustomNumberEditor | 将字符串值转换为数字 |
CharacterEditor | 将字符串值转换为字符类型 |
ByteArrayPropertyEditor | 将字符串值转换为 byte [ ] |
CustomDateEditor | 将字符串值转换为 java.util.Date 类型 |
PropertiesEditor | 将字符串值转换为 java.util.Properties 类型 |
上表中仅显示了 Spring 中内置 PropertyEditor 实现的一个子集。有关完整列表,请参阅 Spring 的 org.springframework.beans.propertyeditors 包。注意,默认情况下,并非 Spring 中的所有内置 PropertyEditor 实现都注册到 Spring 容器中。例如,你需要明确注册 CustomDateEditor 以允许 Spring 容器执行从字符串值到 java.util.Date 类型的转换。稍后我们将介绍如何使用 Spring 容器注册属性编辑器。
现在来看如何为 java.util.List、java.util.Set 和 java.util.Map 类型的 bean 属性(或构造函数参数)指定值
指定不同集合类型的值
、
如下面程序中,DataTypesExample 类展示了接受不同类型参数的构造函数。
import java.beans.ConstructorProperties;
public class DataTypesExample {
private static Logger logger = Logger.getLogger(DataTypesExample.class);
@SuppressWarnings("rawtypes")
@ConstructorProperties({"byteArrayType","charTeype","charArray","classType","currencyType","booleanType","dateType","longType","doubleType","propertiesType","listType","mapType","setType","anotherPropertiesType"})
public DataTypesExample(byte[] byteArraytype,char charType,char[] charArray,Class ClassType,Currency currencyType,boolean booleanType,Date dateType,long longType,double doubleType,Properties propertiesType,List<Integer> listType,Map mapType,Set setType,Properties anotherPropertiestType){
....
logger.info("classType "+ classType.getName());
logger.info("listType "+listType);
logger.info("mapType"+mapType);
logger.info("setType"+setType);
logger.info("anotherPropertiesType "+anotherPropertiesType);
}
}
DataTypesExample 类的构造函数接受 java.util.List、java.util.Map、 java.util.Set、java.util.Properties 等类型参数,并记录每个构造函数参数的值。
DataTypesExample 类的 bean 定义如下所示
<bean id="dataTypes" class="sample.spring.charter03.beans.DataTypesExample">
....
<constructor-arg name="anotherPropertiesType">
<props>
<prop key="book">Getting started with the Spring Framework</prop>
</props>
</constructor-arg>
<constructor-arg name="listType">
<list>
<value>1</value>
<value>2</value>
</list>
</constructor-arg>
<constructor-arg name="mapType">
<map>
<entry>
<key>
<value>map key 1</value>
</key>
<value> map key 1's value</value>
</entry>
</map>
</constructor-arg>
<constructor-arg name="setType">
<set>
<value>Element 1</value>
<value>Element 2</value>
</set>
</constructor-arg>
</bean>
由上面的 xml 可以得到一些结论
-
使用
元素的 子元素指定 anotherPropertiesType (java.util.Properties 类型)的值。在 元素内,每个 子元素指定一个键值对,key 特性指定该键,而 元素的内容是该键的值。你可以使用 元素的 子元素取代 元素来指定 anotherPropertiesType 参数的值 -
使用
的 - 子元素指定 listType 构造函数参数(类型为 java.util.List
)的值。 - 元素的
子元素指定列表中包含的项目。由于 listType 构造函数参数的类型为 List ,Spring 容器使用 CustomNumberEditor(默认情况下与 Spring 容器注册的 PropertyEditor) 将由 元素指定的字符串值转换为 java.lang.Integer 类型。 -
使用
的 -
使用
的 子元素指定 setType 构造函数参数 (类型为 java.util.Set)的值。 的每个 子元素指定一个 Set 中包含的元素。如果构造函数参数被定义为参数化 Set(如 Set ),则 Spring 容器使用注册了的属性编辑器来执行将值转换为参数化 Set 接受的类型。 在 DataTypesExample 类中,List、Map 和 Set 类型的构造函数参数包含 String 或 Integer 类型的元素。在应用程序中,集合可能包含 Map 、Set 、 Class 、Properties 或任何其他 Java 类型的元素。集合中包含的元素也可以是 bean 引用。为了解决这种情况,Spring 允许以
、 、 将 List 、Map 、Set 和 Properties 类型的元素添加到集合类型
如果 bean属性或构造函数参数的类型为 List
- 、只需使用嵌套的
- 元素,如下所示
<constructor-arg name="nestedList"> <list> <list> <value>A simple String value in the nested list</value> <value>Another simple String value in nested list</value> </list> </list> </constructor-arg>
在上面的 xml 中,
元素为名为 nestedList 的构造函数参数提供了类型为 List - 的值。嵌套的
- 元素表示一个 List 类型的元素。类似地,可以通过在
- 元素中嵌套
、 和 元素来分别设置 List 、List 和List 的属性或构造函数参数的值。像 - 元素一样,
元素也可以包含 、 - 、
或 元素。在 元素中,可以使用 、 、 - 、或
元素来指定条目的键和值。 下面 xml 展示了如何为 Map <List,Set> 类型的构造函数参数指定值。
<constructor-arg name="nestedListAndSetMap"> <map> <entry> <key> <list> <value>a List element</value> </list> </key> <set> <value>a Set element</value> </set> </entry> </map> </constructor-arg>
在上面 xml 中,nestedListAndSetMap 的构造函数参数是 Map 类型,该 Map 的键为 List 类型,值为 Set 类型。
元素可以包含以下任何元素作为其子元素: 、 、 - 和
。键值可以使用 、 、 - 或
元素定义。 将 bean 引用添加到集合类型
可以通过使用
下面 xml 展示了如何将对 bean 的引用添加到 List 类型的构造函数参数中。
<bean ....> <constructor-arg name="myList"> <list> <ref bean = "aBean" /> <ref bean = "bBean" /> </list> </constructor-arg> </bean> <bean id="aBean" class="somepackage.ABean"></bean> <bean id="bBean" class="somepackage.BBean"></bean>
在上面 xml 展示了 myList 的构造函数参数是 List 类型,它包含两个元素:对 aBean bean 的引用和对 bBean bean 的引用。 元素的 bean 特性指定由 元素引用的 bean 名称
例如在
<bean ...> <constructor-arg name="myMapWithBeanRef"> <map> <entry> <key> <ref bean="aBean"></ref> </key> <ref bean="bBean" /> </entry> </map> </constructor-arg> </bean> <bean id="aBean" class="somepackage.ABean"></bean> <bean id="bBean" class="somepackage.BBean"></bean>
在上面 xml 中,
为 Map 类型的构造函数参数的键值对提供了值,其中 Key是对 aBean bean 的引用,value是对 bBean bean 的引用。 将 bean 名称添加到集合类型
如果要将一个 bean 名称(由
元素的id特性指定)添加到 List 、Map 或 Set 类型的构造函数参数或 bean 属性中,那么可以使用 、 和 - 元素的
元素。在下面xml中, 元素为 Map 类型构造函数参数提供了一个键值对,其中 bean 名称是键,而 bean 应用的是值 <constructor-arg name="myExample"> <map> <entry> <key> <idref bean="sampleBean" /> </key> <ref bean="sampleBean" /> </entry> </map> </constructor-arg>
在上面 xml 中,
为 Map 类型构造函数参数提供了一个键值对,其中 key 是字符串值 "sampleBean" ,value 是 sampleBean bean。我们可以使用 元素来设置 ‘ssampleBean’ 字符串值作为 key ,而这里使用 元素的原因是 Spring 容器会在部署应用程序时验证 sampleBean bean 是否存在。 将 null 值添加到集合类型
可以使用
元素为 Set 和 List 类型的集合添加一个空值。下面 xml 展示了如何使用 元素将一个空值添加到 Set 类型的构造函数参数。 <constructor-arg name="setWithNullElement"> <set> <value>Element 1</value> <value>Element2</value> <null /> </set> </constructor-arg>
在上面 xml 中,setWithNullElement 的构造函数参数包含 3 个元素:Element 1 ,Element 2 和 null 。
要向 Map 类型构造函数参数或属性添加 空键,可以使用
元素内的 元素。另外,要添加一个空值,可以在 元素中添加 元素。如下 xml 展示了一个包含空键和空值的 Map 类型构造函数参数。 <constructor-arg name="mapType"> <map> <entry> <null /> </entry> <null /> </map> </constructor-arg>
指定数组的值
可以使用
(或 ) 元素的 子元素为数组类型属性 (或构造函数参数) 设置值。 下面 xml 展示了如何将 bean 属性设置为 int [ ] 类型。
<property name="numbersProperty"> <array> <value>1</value> <value>2</value> </array> </property>
在上面 xml 中,
元素的每个 子元素表示 numberProperty 数组中的一个元素。由 Spring 容器使用的 CustomNumberEditor 属性编辑器,将每个 元素指定的字符串值转换为 int 类型。可以使用 - 、
和 元素中的 元素,还可以使用 元素中的 我们讨论了如何使用
- 、
、和 元素分别对 List 、Map 和 Set 类型的属性或构造函数参数进行设置。现在来看一下 Spring 中对应于这些元素创建的默认集合实现。 与
- 、
、 元素
集合元素 | 默认集合实现 |
---|---|
java.util.ArrayList | |
java.util.LinkedHashSet | |
java.util.LinkedHashMap |
有上表可知:
-
如果使用
- 元素指定属性 (或构造函数参数)的值,则 Spring 将创建一个 ArrayList 实例并将其分配给该属性(或构造函数参数);
-
如果
元素指定了属性 (或构造函数参数)的值,则 Spring 将创建一个 LinkedHashSet 的实例,并将其分配给该属性(或构造函数参数); -
如果使用
元素指定了属性(或构造函数参数)的值,Spring 将创建一个 LinkedHashMap 的实例,并将其分配给该属性(或构造函数参数)。 你可能希望将 bean 属性或构造函数参数中的 List 、 Set 或 Map 替换为其他实现。例如, 可能需要用 java.util.LinkedList 的实例来取代 java.util.ArrayList 的实例以分配给 List 类型的 bean 属性。在这种情况下,建议使用 Spring 的 util 模式的
- 、
和 元素(3.8节介绍)。Spring 的 util 模式的 - 、
和 元素提供了一个选项,用于指定要分配给该 bean 的属性或构造函数参数的具体集合类的安全限定名称。 现在来看 Spring 提供的一些内置的属性编辑器。
内置属性编辑器
Spring 提供了一组内置的属性编辑器,他们在设置 bean 属性和构造函数参数时非常有用。在本节中,我们将主要介绍 CustomCollectionEitor 、 CustomMapEditor 和 CustromDateEditor 这三种内置属性编辑器。
ByteArrayPropertyEditor——用于将字符串值转换为 byte [ ]
CurrencyEditro——用于将货币代码转换为 java.util.Currency 对象
CharacterEditor——用于将字符串值转换为 char [ ]
如果需要查看内置属性编辑器的完整列表,请参阅 org.springframework.beans.propertyeditors 包。
CustomCollectionEditor
CustomCollectionEditor 属性编辑器负责将源集合 (如 java.util.LinkedList) 类型转换为目标集合(如 java.util.ArrayList )类型。默认情况下,CustomCollectionEditor 会对 Set 、SortedSet 和 List 类型进行注册。
请看下面的 CollectionTypesExample 类,他定义了 Set 和 List 类型的特性
public class CollectionTypesExample {
private Set setType;
private List listType;
...
// -- setter methods for attributes
public void setSetType(Set setType){
this.setType = setType;
}
}
CollectionTypesExample 类定义了 SetType 和 List 类型的 setType 和 listType 特性。CollectionTypesExample 类的定义,如下程序所示
<bean class="sample.spring.chapter03.beans.ConllectionTypesExample">
<property name="listType" >
<set>
<value>set element 1</value>
<value>set element 2</value>
</set>
</property>
<property>
<list>
<value>list element 1</value>
<value>list element 2</value>
</list>
</property>
</bean>
你可能认为上述配置不正确,因为 元素已被用于设置 setType 特性 (类型为 Set )的值。
其实上述配置是完全合法的,而且 Spring 容器不会对此质疑。这是因为在设置 setType 属性之前,CustomCollectionEditor 已经将 ArrayList 实例转换为 LinkedHashSet 类型。此外,CustomCollectionEditor 在设置 listType 属性之前,已经将 LinkedHashSet 实例转换为 ArrayList 类型。
CustomMapEditor
CustomMapEditor 属性编辑器将 源 Map 类型转换为目标 Map 类型。默认情况下, CustomMapEditor 只对 SortedMap 类型进行注册。
CustomDateEditor
CustomDateEditor 是 java.util.Date 类型的 bean 属性和构造函数参数的属性编辑器。CustomDateEditor 支持自定义 java.text.DateFormat,用于将日期/时间字符串格式化为 java.util.Date 类型对象,并将 java.util.Date 类型对象解析为日期/时间字符串。