在讲解Spring配置beans之前首先想一下一部成功的电影都需要哪些成员参与。首先,最重要的是要有导演、编剧、演员和投资人;其次,还有没那么明显的成员,音乐人、特技演员和艺术指导;此外,还有其他很重要但是容易被忽略的人,调音师、服装师、化妆师、宣传员、摄影师、摄影师助手、灯光指导和外卖小哥。一部成功的电影应当是将各个人员合理而且有序的组织起来,然后各自完成他们各自的工作,他们之间会有很多联系,大部分工作也需要很多人员共同完成。
对应到一个应用中,有很多个对象,这些对象之间只有相互发送消息才能实现功能,在原始的Java程序中,使各个对象产生联系的方法就是不停地new来new去,这就导致了代码复杂化,使其难以复用和实现单元测试。而在Spring里面,一个对象当需要用到另一个对象的时候,它并不负责找到这个对象并且创建它,相反,仅仅给出需要的对象的引用就可以了,具体的对象实例是放在container里面的。
将一个应用中不同对象联系起来的行为就是依赖注入(dependency injection)的本质,称之为装配(wiring),本文讲解的就是装配beans的方式,这些内容是Spring最常用到的。
前文已经说过,容器是Spring的基础,而我们将各个beans创建出来并且将它们联系起来是通过一个或多个XML文件的配置来实现的。当在XML中声明beans的时候,基本元素就是<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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- Bean declarations go here -->
</beans>
在<beans>
里面可以做Spring的相关配置,包括<bean>
声明。但是beans
并非Spring唯一的命名空间,Spring框架的核心总共有十个配置的命名空间,如下:
首先写一个非常实例:用Spring配置一个bean,这个bean指向的类里有一个方法,用来描述一下动作:java学习者在写博客。
首先写出这个类:Javaer.java
package com.springinaction.springidol;
/** * Java程序员 * @author 白鑫 * @date 2016-1-5下午11:13:16 */
public class Javaer implements Coder {
private int blogNum = 2;//博客数
public Javaer() {
}
public Javaer(int blogNum) {
this.blogNum = blogNum;
}
public void perform() {
System.out.println("Trigl is writing " + blogNum + " blogs.");
}
}
继承的接口:Coder.java
package com.springinaction.springidol;
/** * Coder接口类 * @author 白鑫 * @date 2016-1-5下午11:15:27 */
public interface Coder {
void perform();
}
继承的目的是为了减藕,毋庸多言。
XML配置bean:spring-idol.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-3.0.xsd">
<!-- Bean declarations go here -->
<bean id="javaer" class="com.springinaction.springidol.Javaer">
</bean>
</beans>
<bean>
是Spring中最基本的配置单元,它会告诉Spring为你创建一个对象。id属性是这个bean的名字,通过它指向Spring容器。class属性说明这个bean对应的类。
当Spring的容器加载bean的时候,就会使用默认的构造器实例化名为javaer的bean,但是本质上,javaer的创建就是通过以下的Java代码:
new com.springinaction.springidol.Javaer();
在main方法中加载加载上下文环境进行测试
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"com/springinaction/springidol/spring-idol.xml");
Coder coder = (Coder) ctx.getBean("javaer");
coder.perform();
}
结果输出为:
Trigl is writing 2 blogs.
我们可以通过自定义一个构造器来创建一个对象,这个操作同样可以在Spring中实现,只要将构造器的参数写进XML的bean配置即可。
还是举一个实例来说明:用Spring配置一个bean,这个bean指向的类里有一个方法,用来描述一下动作:java学习者在写10篇博客同时还在听歌。(这里加入10和歌曲是为了在构造器中加入一个原始数据类型和一个类作为构造器参数)
首先写出主体类:SongJavaer.java
package com.springinaction.springidol;
/** * 听歌的Java程序员 * @author 白鑫 * @date 2016-1-6上午12:29:11 */
public class SongJavaer extends Javaer {
private Song song;
public SongJavaer(int blogNum, Song song) {
super(blogNum);
this.song = song;
}
public void perform() {
super.perform();
System.out.println("While listening...");
song.play();
}
}
由于这个例子与上个例子相比多了个听歌这个动作,所以我们新建一个SongJavaer类继承上面的Javaer类,可以看到与上面的例子相比多了个构造方法,这个构造方法的参数一个是int原始数据类型的,一个是类Song,在下面我会给出在XML文件中配置构造器参数的方法。
Song接口类:Song.java
package com.springinaction.springidol;
/** * Song接口 * @author 白鑫 * @date 2016-1-6上午12:36:05 */
public interface Song {
void play();
}
再写一个继承Song接口的类:SongJay.java
package com.springinaction.springidol;
/** * Jay的歌 * @author 白鑫 * @date 2016-1-6上午12:37:28 */
public class SongJay implements Song {
private static String[] LYRICS = {
"一盏离愁孤灯伫立在窗口",
"我在门后假装你人还没走",
"旧地如重游月圆更寂寞",
"夜半清醒的烛火不忍苛责我",
"一壶漂泊浪迹天涯难入喉",
"你走之后酒暖回忆思念瘦",
"水向东流时间怎么偷",
"花开就一次成熟我却错过",
"谁在用琵琶弹奏一曲东风破",
"岁月在墙上剥落看见小时候",
"犹记得那年我们都还很年幼",
"而如今琴声幽幽我的等候你没听过",
"谁在用琵琶弹奏一曲东风破",
"枫叶将故事染色结局我看透",
"篱笆外的古道我牵着你走过",
"荒烟漫草的年头就连分手都很沉默"
};
public void play() {
for (int i = 0; i < LYRICS.length; i++){
System.out.println(LYRICS[i]);
}
}
}
在XML文件spring-idol.xml中配置bean
<bean id="songJavaer" class="com.springinaction.springidol.SongJavaer">
<constructor-arg value="20" />
<constructor-arg ref="songJay" />
</bean>
<bean id="songJay" class="com.springinaction.springidol.SongJay" />
可以看到,在<bean>
中配置构造器参数是通过<constructor-arg>
属性来配置的。其中对于简单的原始数据类型如int、String类型,是通过<constructor-arg>
的value属性来传参的;对于具体的类,是通过使用<constructor-arg>
的ref属性来传参的,ref中的值等于相应bean的id。
在main方法中加载上下文环境进行测试
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"com/springinaction/springidol/spring-idol.xml");
Coder coder = (Coder) ctx.getBean("songJavaer");
coder.perform();
}
控制台输出的结果是:
Trigl is writing 20 blogs.
While listening...
一盏离愁孤灯伫立在窗口
我在门后假装你人还没走
旧地如重游月圆更寂寞
夜半清醒的烛火不忍苛责我
一壶漂泊浪迹天涯难入喉
你走之后酒暖回忆思念瘦
水向东流时间怎么偷
花开就一次成熟我却错过
谁在用琵琶弹奏一曲东风破
岁月在墙上剥落看见小时候
犹记得那年我们都还很年幼
而如今琴声幽幽我的等候你没听过
谁在用琵琶弹奏一曲东风破
枫叶将故事染色结局我看透
篱笆外的古道我牵着你走过
荒烟漫草的年头就连分手都很沉默
通过工厂方法创建单实例
在Spring中可以将一个单实例类配置成为一个bean,如下面的Head.java
package com.springinaction.springidol;
/** * 工厂方法实现单实例类 * @author 白鑫 * @date 2016-1-6下午08:46:01 */
public class Head {
private Head() {
}
private static class HeadSingletonHolder {
static Head instance = new Head();
}
public static Head getInstance() {
return HeadSingletonHolder.instance;
}
}
在XML中的配置为:
<bean id="theHead" class="com.springinaction.springidol.Head" factory-method="getInstance">
</bean>
单实例类每次创建都是以static的形式,这就保证了每次创建的对象都是一样的。在Spring中的配置是通过<bean>
元素中的factory-method属性来配置,里面的值对应于类中的一个static方法getInstance。注意factory-method不仅局限于将一个单实例配置成为bean,对于任何你想要编写一个通过static方法创造类的时候,都可以使用工厂方法。
所以的Spring的bean都默认是单实例的,即每次Spring分配一个bean以后,总是会产生完全相同的对象。有时候我们不需要单实例,而是需要每次都产生唯一的实例。比如说买票,必须保证每次创建票的bean时候都是唯一的,我们可以通过以下方式来配置:
<bean id="ticket"
class="com.springinaction.springidol.Ticket" scope="prototype" />
也就是通过设置<bean>
的scope属性值为prototype来保证每次加载的bean都会创造唯一的实例。