本系列博客为spring In Action 这本书的学习笔记
在之前的两篇博客里我们说完了自动装配和通过Java代码装配Bean, 这篇博文里我们将介绍最后一种装配Bean的方式 — 通过XML装配.
1. 创建一个XML配置文件
和上一篇通过Java装配Bean的博文里面一样, 我们先来看一下在自动装配中出现过的XML文件.
程序1: 在CD播放器里面出现过的XML配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.SoundSystem_Auto" />
</beans>
这个程序1与上一篇通过Java代码装配Bean的博客里面的那个程序1都取自Spring学习笔记(二): 装配Bean之自动化装配那篇博客里. 其意义都是通过显式配置启用组件扫描.
<context:component-scan base-package=”com.SoundSystem_Auto” />这句代码等同于JavaConfig里面的@ComponentScan, 意为在Spring中启用组件扫描.
这是在前面出现过的XML配置文件, 那么我们现在来看一下一个普通的, 没有进行任何配置的XML配置文件是什么样的.
程序2: 创建一个普通的规范的XML配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--Configuration details go here-->
<!--在这里写配置的细节-->
</beans>
这就是一个最简单的XML配置文件, 我们可以看出, 它跟同样功能的JavaConfig相比, 复杂了很多. 在JavaConfig中只需要一个@Congfiguration标注就可以声明一个Java配置文件, 但是在XML配置文件中, 需要在顶部声明多个XML模式(即XSD文件), 这些文件定义了配置Spring的XML元素.
用来装配Bean的最基本的XML元素包含在spring-beans模式之中, 在上面这个XML文件中, 它被命名为根空间. <beans>是该模式中的一个元素, 它是所有Spring配置的根元素.
2. 声明一个简单的Bean
上面介绍了怎样创建一个规范的XML配置文件, 那么现在就来看看怎样在XML配置文件中声明Bean.
要在XML中声明一个Bean, 要用到spring-beans模式中的另一个元素<bean>. <bean>元素类似于JavaConfig中的@Bean注解.
现在我们想要声明一个CompactDisc Bean, 我们可以在XML文件的<beans>标签下这样写:
程序3: 在XML中通过<bean>声明一个Bean
<bean class = "com.SoundSystem_XML.Jay" />
这样就声明了一个简单的Bean, 创佳那这个Bean是通过class属性的全限定类名来指定的. 在这个Bean中, 并没有给出明确的Bean的ID, 所以Spring将会根据全限定的类名来给这个Bean命名, 比如这个Bean的ID为:”com.SoundSystem_XML#0”. 如果使用这个全限定类名再声明一个Bean, 那么, 它的ID将为:”com.SoundSystem_XML#1”.
但是这样虽然在创建Bean的时候很方便, 但是在使用这个Bean的时候又会回很麻烦, 所以一般情况下, 我们将采用给Bean自定义ID, 比如下面这样:
程序4: 给Bean自定义ID
<bean id="jay" class = "com.SoundSystem_XML.Jay" />
这样在稍后将这个Bean装配到CDPlayer Bean中的时候, 我们就可以直接使用这个具体的名字.
现在已经知道了如何在XML配置文件中声明一个Bean, 那么, 相比于JavaConfig, Spring到底是如何通过XML配置文件来创建一个Bean的.
在JavaConfig中, 我们需要编写带有@Beam标注的方法, 这些方法回返回一个new的对象, 然后Spring将这个对象包装成Bean.
在XML中, 我们使用全限定的类名来为Spring创建Bean指定路径. 也就是说, Spring会根据这个全限定的类名找到这个类, 并调用该类的默认构造器来创建Bean.
看起来XML好像更省事一些, 但是它并没有JavaConfig那么强大, JavaConfig可以使用任何你能用Java代码实现的方式来创建一个Bean.
而且, 通过XML的全限定类名来创建Bean, 如果突然改变了类名, 那又将造成很多麻烦.
3. 注入(装配)Bean
我们可以回想一下, 在JavaConfig中有两种注入Bean方式, 那么在XML配置文件中也有两种注入Bean的方式. 分别是通过构造器注入Bean和通过setter设置属性所注入Bean.
(1) 通过构造器注入Bean
什么叫通过构造器注入Bean呢? 前面讲过了, 在XML中声明Bean时, 使用全限定的类名来指定一个类, 然后Spring调用这个类的默认构造方法new出一个对象, 然后将其包裹成Bean.
那么, 如果这个类我们给其自定义了构造方法, 我们就可以在XML中对其进行注入, 完成Bean的初始化工作. 可能你还有些不明白, 先看一个例子吧.
仍然是CD播放器的例子, 先给出一个CD播放器的类.
程序5: CD播放器的类
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd){
this.cd = cd;
}
public void play() {
cd.play();
}
}
可以看到, 在CDPlayer类的构造方法需要一个CompactDisc对象, 如果我们要初始化这个Bean, 就要完成CompactDisc Bean的注入.
在XML中, 通过构造器注入Bean有两种配置方式
- <constructor-arg>元素
- 使用Spring的c-命名空间
我们先来看一下<constructor-arg>元素配置方式
1. <constructor-arg>元素配置方式
程序6: <constructor-arg>元素配置方式注入初始化CDPlayer Bean
<bean id="cdPlayer" class="com.SoundSystem_XML.CDPlayer">
<constructor-arg ref="jay" />
</bean>
<constructor-arg>元素会告知Spring要将一个ID为jay的Bean引用传递到CDPlayer的构造器中.
同样的, 还有c-命名空间配置注入Bean
2. c-命名空间配置方式
程序7: c-命名配置方式注入Bean
<bean id="cdPlayer1" class="com.SoundSystem_XML.CDPlayer"
c:cd-ref="jay" />
c-命名空间这种[配置方式看起来有点怪异, 我们来分析一下c:cd-ref=”jay”这句代码里面的各个参数.
- c:是c-命名空间的前缀;
- cd是构造器的参数名, 也就是CDPlyer构造方法的参数列表里面的参数名;
- -ref是注入Bean引用; “jay”是要注入的Bean的ID.
关于c-命名空间, 还有一些别的使用规则. 比如, 当构造器中需要多个Bean的时候, 我们可以使用参数索引来表示参数列表里所需要的多个Bean. 比如这样:
<bean id="cdPlayer2" class="com.SoundSystem_XML.CDPlayer"
c:_0-ref="jay"/>
(2) 通过构造器注入常量
上面介绍的是通过构造器注入Bean, 但是有的时候, 构造器的参数并不是一个类变量, 而是一个常量. 那么就来看一下如何通过构造器注入常量.
假设现在有一个周杰伦的CD如下:
程序8: JayZhou CD
public class JayZhou implements CompactDisc {
private String title;
private String artist;
public JayZhou(String title, String artist){
this.title = title;
this.artist = artist;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
就这个类的构造器而言, 它的两个参数都是常量, 所以要通过XML将常量注入. 和前面一样, 将常量注入到构造器中, 也有两种方式:
- <constructor-arg>元素
- 使用Spring的c-命名空间
1. <constructor-arg>元素配置方式
程序9: 将常量注入到构造器中
<bean id="jayZhou1" class="com.SoundSystem_XML.JayZhou" >
<constructor-arg value="魔杰座" />
<constructor-arg value="周杰伦" />
</bean>
每种注入方式都是有多种书写格式的, 比如还可以像下面这样:
<bean id="jayZhou" class="com.SoundSystem_XML.JayZhou" >
<constructor-arg index="0" value="魔杰座"/>
<constructor-arg index="1" value="周杰伦"/>
</bean>
究竟使用哪种, 全凭个人喜好.
2. c-命名空间配置方式
用c-命名空间配置方式可以像下面这样注入:
程序10: 使用c-命名空间注入
<bean id="jayZhou2" class="com.SoundSystem_XML.JayZhou"
c:title="魔杰座" c:artist="周杰伦" />
这种注入方式是通过构造器的参数列表的参数名称来指定, 也可以像前面注入Bean那样, 使用索引来指定要注入的常量. 比如下面这样:
<bean id="jayZhou3" class="com.SoundSystem_XML.JayZhou"
c:_0="魔杰座" c:_1="周杰伦" />
3. 装配Bean时注入列表/集合/数组
需要特别说明的是, 在装配Bean的时候, 如果我们要注入的是一个数组/列表/集合呢? 那么我们就需要用到<list>元素.
假设现在有一个五月天的新专辑类, 我们要对这个CD进行一些更细致的处理, 比如显示出播放这个CD里面的每一首歌:
程序11: 五月天的新专辑
public class MayDayDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracks;
// private Set<String> tracks;
// 一般来说, List和Set的区别不是很大, 但是当Spring要装配的是集合的时候, 使用Set集合可以保证集合中的元素不会重复.
public MayDayDisc(String title, String artist, List<String> tracks){
this.title = title;
this.artist = artist;
this.tracks = tracks;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
for(String track : tracks){
System.out.println("-Track: " + track);
}
}
}
那么这个时候, XML文件里应该这样配置:
<bean id="mayDay1" class="com.SoundSystem_XML.MayDayDisc">
<constructor-arg value="五月天的新专辑" />
<constructor-arg value="五月天" />
<constructor-arg>
<list>
<value>歌曲1</value>
<value>歌曲2</value>
<value>歌曲3</value>
<!--等等...-->
</list>
</constructor-arg>
</bean>
在上面的代码中提到过, 在类里除了使用List, 也可以使用Set, 不过就是将相对应的XML配置文件中的<list>元素改为<set>就可以了.
同理, 如果现在有一个电台, 要播放若干CD. 我们也可以将若干CD类使用<list>元素, 注入到电台类中.
程序12: 电台
public class Discography {
private String artist;
private List<CompactDisc> cds;
public Discography(String artist, List<CompactDisc> cds){
this.artist = artist;
this.cds = cds;
}
}
而其相对应的XML应该是这样:
<bean id="discpgraphy" class="com.SoundSystem_XML.Discography">
<constructor-arg index="0" value="今日流行"/>
<constructor-arg index="1">
<list>
<ref bean="jay" />
<ref bean="jayZhou" />
<ref bean="mayDay" />
</list>
</constructor-arg>
</bean>
以上就是关于通过构造器装配(注入)Bean的内容就说到这里, 下面我们来看一下怎样通过设置属性来装配(注入)Bean.
(3) 通过设置属性注入Bean
在Java中, 除了通过构造方法给数据成员赋值以外, 我们还可以通过setter方法给数据成员赋值.
与通过构造器注入Bean一样, 通过设置属性注入Bean也有两种装配方式:
- 通过<property>元素装配
- 通过p-命名空间装配
1. 通过<property>元素装配
我们现在对CDPlyer类进行修改, 去掉它的自定义构造方法, 加上所有数据成员的setter方法:
程序13: CDPLyer
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public void setCd(CompactDisc cd){
this.cd = cd;
}
public void play() {
cd.play();
}
}
如果现在要在XML中重新声明这个类, 我们可能会这样声明:
<bean id="cdPlayer" class = "com.SoundSystem_XML.CDPlayer" />
这样看起来是没有问题的, 毕竟现在这个类没有自定义的构造方法了嘛, 默认的构造方法也是不含参数的. 可是这样真的对吗? 如果我们测试这个类, 会发现它会抛出NullPoiterException异常, 因为我们没有注入任何CompactDisc Bean, 所以运行play()方法当然就出错了.
要通过设置属性注入Bean, 这时候我们就要用到<property>元素了. <property>元素为设置属性的setter方法所提供的功能和<constructor-arg>元素为构造器提供的功能是一样的. 比如现在可以在XML中这样写:
<bean id="cdPlayer3" class="com.SoundSystem_XML.CDPlayer">
<!--这里的property的name属性要与setXXX()方法里面的XXX保持一致(XXX首字母应该小写)-->
<property name="cd" ref="jay"/>
</bean>
2. 通过p-命名空间装配
程序14: 通过p-命名空间来装配Bean
<bean id="cdPlayer4" class="com.SoundSystem_XML.CDPlayer"
p:cd-ref="jay">
</bean>
同样的, p-命名空间的参数也比较难理解, 现在我们也来分析一下p:cd-ref=”jay”里的各项参数:
- p:是p-命名空间的前缀;
- cd是属性名(即类中的数据成员名);
- -ref是注入Bean引用, 告知Spring这是注入一个Bean而不是常量;
- “jay”是所注入的Bean的ID
(4) 通过设置属性注入常量
有了前面通过构造器注入常量的例子, 这里的通过设置属性注入常量应该也不难理解.
与前面一样, 通过设置属性注入常量也有两种装配方式:
- 通过<property>元素装配
- 通过p-命名空间装配
1. 通过<property>元素装配
我们先对五月天的新专辑类进行一些修改, 去掉它的构造方法, 加上所有数据成员的setter方法.
程序15: 五月天的新专辑类
public class MayDayDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracks;
public void setTitle(String title){
this.title = title;
}
public void setArtist(String artist){
this.artist = artist;
}
public void setTracks(List<String> tracks){
this.tracks = tracks;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
for(String track : tracks){
System.out.println("-Track: " + track);
}
}
}
在XML中应该这样配置:
<bean id="mayDay3" class="com.SoundSystem_XML.MayDayDisc">
<property name="title" value="五月天的新专辑" />
<property name="artist" value="五月天" />
<property name="tracks">
<list>
<value>歌曲1</value>
<value>歌曲2</value>
<value>歌曲3</value>
<value>歌曲4</value>
<!--等等...-->
</list>
</property>
</bean>
2. 通过p-命名空间装配
使用p-命名空间装配的时候还有一丢丢小麻烦, 不过我们先把装配的代码贴出来, 再对它进行解释:
<bean id="mayDay4" class="com.SoundSystem_XML.MayDayDisc"
p:title="五月天的新专辑"
p:artist="五月天">
<property name="tracks">
<list>
<value>歌曲1</value>
<value>歌曲2</value>
<value>歌曲3</value>
<value>歌曲4</value>
<!--等等...-->
</list>
</property>
</bean>
可以看出, 再注入列表常量的时候, 没有使用p-命名空间, 而是使用了<property>元素. 事实上, p-命名空间是不能装配集合的.
但是, 我们仍然有简化书写的办法, 就是使用util-命名空间.
我们可以使用util-命名空间的<util:list>元素来创建一个Bean, 然后再通过p-命名空间的ref来引用这个Bean. 具体代码如下:
<util:list id="trackList">
<value>歌曲1</value>
<value>歌曲2</value>
<value>歌曲3</value>
<value>歌曲4</value>
<!--等等...-->
</util:list>
<bean id="mayDay5" class="com.SoundSystem_XML.MayDayDisc"
p:title="五月天的新专辑"
p:artist="五月天"
p:tracks-ref="trackList">
</bean>
<util:list>元素只是util-命名空间所提供的功能之一, 它还有许多其它的元素, 比如:
- <util:constant> : 引用某个类型的public static域, 并将其暴露为Bean;
- <util:list> : 创建一个java.util.List类型的Bean, 其中包含值或引用;
- <util:map> : 创建一个java.util.Map类型的Bean, 其中包含值或引用;
- <util:properties> : 创建一个java.util.Properties类型的Bean;
- <util:property-path> : 引用一个Bean的属性(或内嵌属性), 并将其暴露为Bean;
- <util:set> : 创建一个java.util.Set类型的Bean, 其中包含值或引用.