1. Spring的依赖注入
Spring
的核心是个IoC容器,可以用Setter或建构方法的方式来实现应用程序对象,至于对象与对象之间的关系建立,则可以透过配置文件设定(一个XML文件或是一个.properties文件),让Spring在执行时期根据配置文件的设定,建立对象之间的依赖关系。这样开发人员不必特地编写一些程序来建立这些对象之间的依赖关系,不仅减少了大量的程序编写,也降低了对象之间的耦合程度。
1.1. Bean基本管理
1.1.1.BeanWapper
我们知道,如果动态设置一个对象属性,可以借助Java的Reflection机制完成:
Class cls = Class.forName(
"com.raykey.beans.User"
);
Method mtd = cls.getMethod(
"setName"
,
new
Class[]{String.
class
});
Object obj = (Object)cls.newInstance();
mtd.invoke(obj,
new
Object[]{
"Erica"
});
return obj;
|
上面我们通过动态加载了User类,并通过Reflection调用了User.setName方法设置其name属性。对于这里的例子而言,出于简洁,我们将类名和方法名都以常量的方式硬编码。假设这些常量都是通过配置文件读入,那我们就实现了一个最简单的BeanWrapper。这个BeanWrapper的功能很简单,提供一个设置JavaBean属性的通用方法(Apache BeanUtils 类库中提供了大量针对Bean的辅助工具,如果有兴趣可以下载一份源码加以研读)。
Spring BeanWrapper
基于同样的原理,提供了一个更加完善的实现。看看如何通过Spring BeanWrapper操作一个JavaBean:
Object obj = Class.forName(
"com.raykey.beans.User"
).newInstance();
BeanWrapper
bw =
new
BeanWrapperImpl(obj);
bw.setPropertyValue(
"name"
,
"Erica"
);
System.out.println(
"User name=>"
+bw.getPropertyValue(
"name"
));
|
对比之前的代码,很容易知道BeanWrapper的实现原理。
通过这样的方式设定Java Bean属性虽然繁琐,但它却提供了一个通用的属性设定机制,而这样的机制,也正是Spring依赖注入机制所依赖的基础。通过BeanWrapper,我们可以无需在编码时就指定JavaBean的实现类和属性值,通过在配置文件加以设定,就可以在运行期动态创建对象并设定其属性(依赖关系)。
1.1.2.BeanFactory
Bean Factory
,顾名思义,负责创建并维护Bean实例。BeanFactory负责读取Bean定义文件,管理对象的加载、生成,维护Bean对象与Bean对象之间的依赖关系。负责Bean的生命周期,对于简单的应用程序来说,BeanFactory已经足够管理Bean,在对象的管理上就可以获得许多的便利性。
Bean Factory
负责根据配置文件创建Bean实例,可以配置的项目有:
1
. Bean属性值及依赖关系(对其他Bean的引用)
2
. Bean创建模式(是否Singleton模式,即是否只针对指定类维持全局唯一的实例)
3
. Bean初始化和销毁方法
4
. Bean的依赖关系
下面是一个较为完整的Bean配置示例:
<
beans
>
<
description
>
Spring Bean Configuration Sample
</
description
>
<
bean
id
=
"TheAction"
⑴
class
=
"com.raykey.spring.qs.UpperAction"
⑵
singleton
=
"true"
⑶
init-method
=
"init"
⑷
destroy-method
=
"cleanup"
⑸
depends-on
=
"ActionManager"
⑹
>
<
property
name
=
"message"
>
<
value
>
HeLLo
</
value
>
⑺
</
property
>
<
property
name
=
"desc"
>
<
null
/>
</
property
>
<
property
name
=
"dataSource"
>
<
ref
local
=
"dataSource"
/>
⑻
</
property
>
</
bean
>
<
bean
id
=
"dataSource"
class
=
"org.springframework.jndi.JndiObjectFactoryBean"
>
<
property
name
=
"jndiName"
>
<
value
>
java:comp/env/jdbc/sample
</
value
>
</
property
>
</
bean
>
</
beans
>
|
⑴
id
Java Bean
在BeanFactory中的唯一标识,代码中通过BeanFactory获取JavaBean实例时需以此作为索引名称。
⑵
class
Java Bean
类名
⑶
singleton
指定此Java Bean是否采用单例(Singleton)模式,如果设为“true”,则在BeanFactory作用范围内,只维护此Java Bean的一个实例,代码通过BeanFactory获得此Java Bean实例的引用。反之,如果设为“false”,则通过BeanFactory获取此Java Bean实例时,BeanFactory每次都将创建一个新的实例返回。
⑷
init-method
初始化方法,此方法将在BeanFactory创建JavaBean实例之后,在向应用层返回引用之前执行。一般用于一些资源的初始化工作。
⑸
destroy-method
销毁方法。此方法将在BeanFactory销毁的时候执行,一般用于资源释放。
⑹
depends-on
Bean
依赖关系。一般情况下无需设定。Spring会根据情况组织各个依赖关系的构建工作(这里示例中的depends-on属性非必须)。只有某些特殊情况下,如JavaBean中的某些静态变量需要进行初始化(这是一种BadSmell,应该在设计上应该避免)。通过depends-on指定其依赖关系可保证在此Bean加载之前,首先对depends-on所指定的资源进行加载。
⑺
<
value
>
通过<value/>节点可指定属性值。BeanFactory将自动根据Java Bean对应的属性类型加以匹配。下面的”desc”属性提供了一个null值的设定示例。注意<value></value>代表一个空字符串,如果需要将属性值设定为null,必须使用<null/>节点。
⑻
<
ref
>
指定了属性对BeanFactory中其他Bean的引用关系。示例中,TheAction的dataSource属性引用了id为dataSource的Bean。BeanFactory将在运行期创建dataSource bean实例,并将其引用传入TheAction Bean的dataSource属性。
下面的代码演示了如何通过BeanFactory获取Bean实例:
InputStream is =
new
FileInputStream(
"bean.xml"
);
XmlBeanFactory factory =
new
XmlBeanFactory(is);
Action action = (Action) factory.getBean(
"TheAction"
);
|
此时我们获得的Action实例,由BeanFactory进行加载,并根据配置文件进行了初始化和属性设定。联合上面关于BeanWrapper的内容,我们可以看到,BeanWrapper实现了针对单个Bean的属性设定操作。而BeanFactory则是针对多个Bean的管理容器,根据给定的配置文件,BeanFactory从中读取类名、属性名/值,然后通过Reflection机制进行Bean加载和属性设定。
1.1.3.ApplicationContext
BeanFactory
提供了针对Java Bean的管理功能,而ApplicationContext提供了一个更为框架化的实现(从上面的示例中可以看出,BeanFactory的使用方式更加类似一个API,而非Framework style)。ApplicationContext覆盖了BeanFactory的所有功能,并提供了更多的特性。此外,ApplicationContext为与现有应用框架相整合,提供了更为开放式的实现(如对于Web应用,我们可以在web.xml中对ApplicationContext进行配置)。
相对BeanFactory而言,ApplicationContext提供了以下扩展功能:
1
. 资源访问
提供取得资源文件更方便的方法,支持对文件和URL的访问。
2
. 消息解析
提供文字消息解析的方法。
3
. 国际化支持
我们可以在Beans.xml文件中,对程序中的语言信息(如提示信息)进行定义,将程序中的提示信息抽取到配置文件中加以定义,为我们进行应用的各语言版本转换提供了极大的灵活性。
4
. 事件的监听与传播
事件传播特性为系统中状态改变时的检测提供了良好支持。ApplicationContext可以发布事件,对事件感兴趣的Bean可以接收到这些事件。
5
. 多实例加载
可以在同一个应用中加载多个Context实例。
|
针对以上的扩展功能,将在2.3,2.4,2.5节介绍。
Spring
的创始者Rod Johnson建议用ApplicationContext来取代BeanFactory,在实现ApplicationContext的类中,最常使用的大概是以下三个:
org.springframework.context.support.FileSystemXmlApplicationContext
可指定XML定义文件的相对路径或绝对路径来读取定义文件。
org.springframework.context.support.ClassPathXmlApplicationContext
从Xlasspath设定路径中来读取XML定义文件。
org.springframework.context.support.XmlWebApplicationContext
在Web应用程序的文件架构中,指定相对位置来读取定义文件。
在我们最开始的spring的程序示例中我们用的就是ApplicationContext,并用XML定义文件的相对路径读取了定义文件。
public void
testQuickStart() {
ApplicationContext ctx=
new
FileSystemXmlApplicationContext(
"bean.xml"
);
Action action = (Action) ctx.getBean(
"TheAction"
);
System.out.println(action.execute(
"Rod Johnson"
));
}
|
1.1.4.构造子注入
在上面的spring示例程序中,用的都是Bean的Setter方法完成依赖注入,Spring鼓励的也是Setter Injection,但它也允许Constructor injection,使用哪种注入方法视需求而定,以下示例如何在Spring中使用构造子注入。
参考Type3Demo项目,在定义HelloBean类时,为了要能让Spring可以有使用无参数构造方法来生成对象的弹性,建议可以定义一个无参数的构造方法,即使目前没有编写任何的实现内容。
在Beans-config.xml定义文件中使用构造子注入时,在设定上必须指定构造方法上参数的顺序。使用<constructor-arg>标签来表示将要使用构造子注入。
运行主程序将得到和设值注入同样的效果。
使用设值注入和构造子注入各有好处,在前面已经比较过了,但如果想要让一些数据成员或资源变为只读或是私有,使用构造子注入会是个简单的选择。
1.1.5.自动绑定
除了在Bean定义文件中直接使用指定字符串值、使用<ref>直接指定参考至其它的Bean实例、或是使用<bean>标签并指定class属性来指定依赖对象之外,Spring也支持隐式的自动绑定,可以透过类型(byType)或名称(byName)将某个Bean实例绑定至其它Bean对象的属性。
HelloBean
public
class
HelloBean {
private
String
helloWord
;
private
Date
date
;
public
void
setHelloWord(String helloWord) {
this
.
helloWord
= helloWord;
}
public
String getHelloWord() {
return
helloWord
;
}
public
void
setDate(Date date) {
this
.
date
= date;
}
public
Date getDate() {
return
date
;
}
}
|
Beans-config.xml
<
beans
>
<
bean
id
=
"dateBean"
class
=
"java.util.Date"
/>
<
bean
id
=
"helloBean"
class
=
"onlyfun.caterpillar.HelloBean"
autowire
=
"byType"
>
<
property
name
=
"helloWord"
>
<
value
>
Hello!
</
value
>
</
property
>
</
bean
>
</
beans
>
|
在上边的定义文件中,并没有指定helloBean的Date属性,而是通过byType自动绑定,根据helloBean的setDate()方法所接受的类型,来判断在Bean定义文件中是否定义有类似的类型对象,并将之设定给helloBean的setDate(),使用自动绑定时,如果无法完成绑定,则抛出org.springframework.beans.factory.unstatisfied-DependencyException异常。
如果通过byName绑定,我们的beans-config.xml文件应该如下所示:
<
beans
>
<
bean
id
=
"date"
class
=
"java.util.Date"
/>
<
bean
id
=
"helloBean"
class
=
"onlyfun.caterpillar.HelloBean"
autowire
=
"byName"
>
<
property
name
=
"helloWord"
>
<
value
>
Hello!
</
value
>
</
property
>
</
bean
>
</
beans
>
|
第一个Bean的id必须为
”
date
”
名称,如果byName无法完成自动绑定,则对应的Setter仅维持未绑定状态。
另外autowire也可以采取
”
constructor
”
或
”
autodetect
”
,它们的含义请自行查阅资料。
1.1.6.集合对象注入
对于像数组、java.util.List、java.util.Set、java.util.Map等集合对象,在注入前若必须填充入一些对象至集合中,然后再将集合对象注入至所需的Bean时,也可以交由Spring的IoC容器来自动维护或生成集合对象,并完成依赖注入。
参考CollectionDemo项目,在SomeBean类中只是简单的定义一些数组、List与Map属性,稍后这些属性所需的依赖对象将由Spring来注入,在SomeBean类中还使用到Some类。
SomeBean:
public
class
SomeBean {
private
String[]
someStrArray
;
private
Some[]
someObjArray
;
private
List
someList
;
private
Map
someMap
;
//getter and setter
}
|
Some:
public
class
Some {
private
String
name
;
public
String getName() {
return
name
;
}
public
void
setName(String name) {
this
.
name
= name;
}
public
String toString() {
return
name
;
}
}
|
对于数组或List类型的依赖关系注入,在编写定义文件时是使用<list>标签,并使用<value>标签指定字符串,或是使用<ref>来参考至其它的Bean实例;对于Map类型的依赖关系注入则是使用<map>标签,map必须指定key-value,所以要用<entry>标签指定key,然后使用<value>标签来指定字符串,或是使用<ref>来参考至其它的Bean实例。
Beans-config.xml:
xml 代码
运行测试程序可以验证文件编写是否正确。
如果使用java.util.Set类型的话,可以使用<set>标签:
xml 代码