在装配bean的时候,还有一种可选方案。官方可能认为这种方式不好,因为非 java 代码配置,但是每个人的看法和喜好不同,也不是说不能选择的。
目录
创建XML配置规范
在使用JavaConfig的时候,这意味着要创建一个带 有@Configuration注解的类,而在XML配置中,这意味着要创建一个XML文件,并且要以<beans>元素为根。
最为简单的Spring 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">
</beans>
这个基本的XML配置已经比同等功能的JavaConfig类复杂得多了。作为起步,在JavaConfig中所需要的只是@Configuration,但在使用XML时,需要在配置文件的顶部声明多个XML模式(XSD)文件,这些文件定义了配置Spring的XML元素。
当然在 idea 中,你只需要选择 file 然后新建,然后 如图:
他会创建一个最最最基础的 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
为了做测试,让我们重新在走一遍CD样例,这次使用XML配置,而不是前几篇文章 使用JavaConfig和自动化装配。
声明一个简单的<bean>
要在基于XML的Spring配置中声明一个bean,我们要使用spring-beans模式中的另外一个元素:<bean>。
<bean>元素类似于JavaConfig中的@Bean注解。我们可以按照如下的方式声明CompactDisc bean:
<bean id="compactDisc" class="soundsystem.SgtPeppers" />
这是最最基本的 声明 bean 的方式,当然 id 可有可无,创建这个bean的类通过class属性来指定的,并且要使用绝对路径,全路径。
如果不加 id
没有明确给定ID,所以这个bean将会根据全类名来进行命名。
在本例中,bean的ID将会是“soundsystem.SgtPeppers#0”。其中,“#0”是一个计数的形式(计数就是为了区分),用来区分相同类型的其他bean。
如果你声明了另外一个SgtPeppers(另外一个的前提是不在同一个包下),并且没有明确进行标识,那么它自动得到 的ID将会是“soundsystem.SgtPeppers#1”。
不过,通常来讲更好的办法是借助id属性,为每个bean设置一个你自己选择的名字,以便于日后引用可以直接使用。
声明简单bean的一些特征
-
无需负责创建SgtPeppers的实例,也就是初始化;
-
将bean的类型以字符串的形式设置在了class属性中,但是并不安全;
-
借助IDE检查XML的合法性;
借助构造器注入初始化bean
在Spring XML配置中,只有一种声明bean的方式:
使用<bean>元素并指定class属性。
Spring会从这里获取必要的信息来创建bean。 但是,在XML中声明DI时,会有多种可选的配置方案和风格。
具体到构造器注入,有两种基本的配置方案可供选择:
- <constructor-arg>元素
- 使用Spring 3.0所引入的c-命名空间
两者的区别在很大程度就是是否冗长烦琐。可以看到,<constructor-arg>元素比使用c-命名空间会更加冗长,从而导致XML更加难以读懂。另外,有些事情<constructor-arg>可以做到,但是使用c-命名空间却无法实现。
构造器注入bean引用
在XML中声明CDPlayer并通过ID引用SgtPeppers:
<bean id="compactDisc" class="soundsystem.SgtPeppers" />
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean>
字面意思就是 名为 cdPlayer 的这个类,引用了 名为 compactDisc 这个类,当Spring遇到这个<bean>元素时,它会创建一个CDPlayer实例。
Spring的c-命名空间
c-命名空间是在Spring 3.0中引入的,它是在XML中更为简洁地描述构造器参数的方式。 要使用它的话,必须要在XML的顶部声明其模式,如下所示,在 beans 根目录下 新增:
xmlns:c="http://www.springframework.org/schema/c"
在c-命名空间和模式声明之后,我们就可以使用它来声明构造器参数了,如下所示:
<bean id="compactDisc" class="soundsystem.SgtPeppers" />
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc" />
c:写法更加简洁,当做 bean 的一个子属性来处理。
c:cd-ref命名来源
属性名以“c:”开头,也就是命名空间的前缀。
接下来就是要装配的构造器参数名,也就是 cd,这个cd 是 构造方法的传参 参数值:
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
在此之后是“-ref”,这是一个命名的约定,它会告诉Spring,正在装配的是一个bean的引用,这个bean的名字是compactDisc。
c-命名空间属性能够更加有助于使代码的长度简洁明了。
注意事项
它直接引用了构造器参数的名称。引用参数的名称看起来有些怪异, 因为这需要在编译代码的时候,将调试标志(debug symbol)保存在类代码中。如果你优化构建过程,将调试标志移除掉,那么这种方式可能 就无法正常执行了。
替代的方案
我们使用参数在整个参数列表中的位置信息:
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:_0-ref="compactDisc"/>
这种写法就更蛋疼了,c:cd 还好理解,cd 就是构造函数的传值参数,而这个需要 将参数的名称替换成了“0”,也就是参数的索引。因为在XML中不允许数字作为属性 的第一个字符,因此必须要添加一个下画线作为前缀。看上去非常古怪,不如上面的号,毕竟见名知意。但是缺点就是,参数名改了,你这边也要 改。而使用索引,则无需考虑。
如果有多个构造 器参数的话,这当然是很有用处的。在这里因为只有一个构造器参数,所以我们还有另外一个方案——根本不用去标示参数:
需要注意的是,我在自己测试的时候,这样写 idea 会检测认为报错,可能是编辑器问题。
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:_-ref="compactDisc"/>
将字面量注入到构造器中
迄今为止,我们所做的DI通常指的都是类型的装配——也就是将对象的引用装配到依赖于它们的其他对象之中——而有时候,我们需要做的只 是用一个字面量值来配置对象。
之前写的 cd 类, 在SgtPeppers中,唱片名称和艺术家的名字都是硬编码的。 很不符合 java 面向接口的思想,更违背了 多态的 核心。
接下来的做法,使他更加灵活,像现实中的空磁盘一样, 它可以设置成任意你想要的艺术家和唱片名。
package soundsystem;
/**
* 像现实中的空磁盘一样, 它可以设置成任意你想要的艺术家和唱片名
*
* @author imenger
*/
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
public BlankDisc(String title, String artist) {
this.title = title;
this.artist = artist;
}
@Override
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
我们再次使用<constructor-arg>元素进行构造器参数的注入。但是这一次我们没有使用“ref”属性来引用其他的bean,而是使用 了value属性,通过该属性表明给定的值要以字面量的形式注入到构造器之中。
<bean id="compactDisc"
class="soundsystem.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
</bean>
等同于 c-命名空间(c:_title 这种方式不再赘述):
<bean id="compactDisc" class="soundsystem.BlankDisc"
c:_0="Sgt. Pepper's Lonely Hearts Club Band"
c:_1="The Beatles"/>
假设BlankDisc只有一个构造器参数,这个参数接受唱片的名 称。在这种情况下,我们可以在Spring中这样声明它:
<bean id="compactDisc" class="soundsystem.BlankDisc"
c:_="Sgt. Pepper's Lonely Hearts Club Band"/>
装配集合(list)
到现在为止,我们假设CompactDisc在定义时只包含了唱片名称和艺术家的名字。也就是一个光盘一首歌,显然 现在技术 已经远远超过这个样子了,CD之所以值得购买是因为它上面所承载的音乐。大多数的CD都会包含十多个磁道,每个磁道上包含一首歌。
如果使用CompactDisc为真正的CD光盘的话,那么它也应该有磁道列表的概念。请考虑下面这个新的BlankDisc:
package soundsystem.collections;
import java.util.List;
import soundsystem.CompactDisc;
/**
* 空白 cd
*
* @author imenger
* @date 2021/2/25 6:33 下午
*/
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracks;
public BlankDisc(String title, String artist, List<String> tracks) {
this.title = title;
this.artist = artist;
this.tracks = tracks;
}
@Override
public void play() {
System.out.println("Playing " + title + " by " + artist);
for (String track : tracks) {
System.out.println("-Track: " + track);
}
}
}
此时的 tracks是一个列表,如果直接调用 play 方法,会报NullPointerException。所以需要先给 tracks 赋值;
使用<list>标签来声明 list
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="compactDisc" class="soundsystem.collections.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
<constructor-arg>
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
<value>She's Leaving Home</value>
<value>Being for the Benefit of Mr. Kite!</value>
<value>Within You Without You</value>
<value>When I'm Sixty-Four</value>
<value>Lovely Rita</value>
<value>Good Morning Good Morning</value>
<value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
<value>A Day in the Life</value>
</list>
</constructor-arg>
</bean>
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean>
</beans>
使用<ref>元素替代<value>
按照同样的方式使用<set>元素
<set>和<list>元素的区别不大,其中最重要的不同在于当Spring创建要装配的集合时,所创建的是java.util.Set还 是java.util.List。如果是Set的话,所有重复的值都会被忽略掉,存放顺序也不会得以保证。不过无论在哪种情况 下,<set>或<list>都可以用来装配List、Set甚至数组。
设置属性
接下来,我们就看一下如何使用Spring XML实现属性注入。假设属性注入的CDPlayer如下所示:
package soundsystem.properties;
import org.springframework.beans.factory.annotation.Autowired;
import soundsystem.CompactDisc;
import soundsystem.MediaPlayer;
public class CDPlayer implements MediaPlayer {
private CompactDisc compactDisc;
@Autowired
public void setCompactDisc(CompactDisc compactDisc) {
this.compactDisc = compactDisc;
}
@Override
public void play() {
compactDisc.play();
}
}
对强依赖使用构造器注入,对可选性的依赖使用属性注入。
目前而言,CDPlayer 是没有强依赖的,所以可以采用属性注入
<bean id="cdPlayer" class="soundsystem.properties.CDPlayer">
<property name="compactDisc" ref="compactDisc" />
</bean>
在本例中,它引用了ID 为compactDisc的bean(通过ref属性),并将其注入到compactDisc属性中(通过setCompactDisc()方法)。
同样可以支持p-命名空间
比如:
<bean id="cdPlayer" class="soundsystem.properties.CDPlayer"
p:compactDisc-ref="compactDisc" />
p-命名空间中属性所遵循的命名约定与c-命名空间中的属性类似。图2.2阐述了p-命名空间属性是如何组成的。
首先,属性的名字使用了“p:”前缀,表明我们所设置的是一个属性。接下来就是要注入的属性名。最后,属性的名称以“-ref”结尾,这会提示Spring要进行装配的是引用。
将字面量注入到属性中
与前面那个相比,本次通过属性注入进行配置。代码如下:
package soundsystem.properties;
import java.util.List;
import soundsystem.CompactDisc;
public class BlankDisc 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;
}
@Override
public void play() {
System.out.println("Playing " + title + " by " + artist);
for (String track : tracks) {
System.out.println("-Track: " + track);
}
}
}
然后在给他们赋值:
可以借 助<property>元素的value属性实现该功能
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="compactDisc"
class="soundsystem.properties.BlankDisc"
p:title="Sgt. Pepper's Lonely Hearts Club Band"
p:artist="The Beatles">
<property name="tracks">
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
<value>She's Leaving Home</value>
<value>Being for the Benefit of Mr. Kite!</value>
<value>Within You Without You</value>
<value>When I'm Sixty-Four</value>
<value>Lovely Rita</value>
<value>Good Morning Good Morning</value>
<value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
<value>A Day in the Life</value>
</list>
</property>
</bean>
<bean id="cdPlayer"
class="soundsystem.properties.CDPlayer"
p:compactDisc-ref="compactDisc" />
</beans>
也可以使用Spring util-命名空间中的一些功能来简化BlankDiscbean:
<?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:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="compactDisc"
class="soundsystem.properties.BlankDisc"
p:title="Sgt. Pepper's Lonely Hearts Club Band"
p:artist="The Beatles"
p:tracks-ref="trackList" />
<util:list id="trackList">
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
<value>She's Leaving Home</value>
<value>Being for the Benefit of Mr. Kite!</value>
<value>Within You Without You</value>
<value>When I'm Sixty-Four</value>
<value>Lovely Rita</value>
<value>Good Morning Good Morning</value>
<value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
<value>A Day in the Life</value>
</util:list>
<bean id="cdPlayer"
class="soundsystem.properties.CDPlayer"
p:compactDisc-ref="compactDisc" />
</beans>
spring 帮我们 :list 创建一个列表,然后 我们将这个 list 对象注入给 tracks 里。
p:tracks-ref="trackList" />
<util:list id="trackList">
<util:list>只是util-命名空间中的多个元素之一。表2.1列出了util-命名空间提供的所有元素。
导入和混合配置
在典型的Spring应用中,我们可能会同时使用自动化和显式配置。
在JavaConfig中引用XML配置
定义一个新的CDConfig 类:
@Configuration
public class CDConfig {
@Bean
public CompactDisc compactDisc() {
return new SgtPeppers();
}
}
compactDisc()方法已经从CDPlayerConfig中移除掉了,我们需要有一种方式将这两个类组合在一起。一种方法就是 在CDPlayerConfig中使用@Import注解导入CDConfig:
@Configuration
@Import((CDConfig.class))
public class CDPlayerConfig {
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
}
或者采用一个更好的办法,也就是不在CDPlayerConfig中使用@Import,而是创建一个更高级别的SoundSystemConfig,在这个类中 使用@Import将两个配置类组合在一起:
@Configuration
@Import({CDPlayerConfig.class,CDConfig.class})
public class SoundSystemConfig {
}
不管采用哪种方式,我们都将CDPlayer的配置与BlankDisc的配置分开了。
现在,我们假设(基于某些原因)希望通过XML来配 置BlankDisc,如下所示:
<?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:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="compactDisc"
class="soundsystem.BlankDisc"
c:_0="Sgt. Pepper's Lonely Hearts Club Band"
c:_1="The Beatles">
<constructor-arg>
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
<!-- ...other tracks omitted for brevity... -->
</list>
</constructor-arg>
</bean>
</beans>
让Spring同时加载它和其他基于Java的配置,
假设BlankDisc定义在名为cd-config.xml的文件中,该文件位于根类路径下,那么可以修
改SoundSystemConfig,让它使用@ImportResource注解,如下所示:
@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig {
}
两个bean——配置在JavaConfig中的CDPlayer以及配置在XML中BlankDisc——都会被加载到Spring容器之中。
在XML配置中引用JavaConfig
假设希望将BlankDisc bean拆分到自己的配置文件中,该文件名为cdplayer-config.xml,这与我们之前使用@ImportResource是一样的。
<?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:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="soundsystem.CDConfig" />
<bean id="cdPlayer"
class="soundsystem.CDPlayer"
c:cd-ref="compactDisc" />
</beans>
小结
Spring框架的核心是Spring容器。容器负责管理应用中组件的生命周期,它会创建这些组件并保证它们的依赖能够得到满足,这样的话,组件
才能完成预定的任务。 在本章中,我们看到了在Spring中装配bean的三种主要方式:自动化配置、基于Java的显式配置以及基于XML的显式配置。不管你采用什么方式,这些技术都描述了Spring应用中的组件以及这些组件之间的关系。