本节书摘来自异步社区《Spring攻略(第2版)》一书中的第1章,第1.2节,作者: 【美】Gary Mak , Josh Long , Daniel Rubio著,更多章节内容可以访问云栖社区“异步社区”公众号查看
1.2 配置Spring IoC容器中的Bean
1.2.1 问题
Spring提供了一个强大的IoC容器来管理组成应用的bean。为了利用容器服务,你必须配置运行于Spring IoC容器中的Bean。
1.2.2 解决方案
你可以通过XML文件、属性文件、注释甚至API来配置Spring IoC容器中的Bean。
Spring允许你在一个或者多个bean配置文件中配置bean。对于简单的应用程序,可以在单个配置文件中集中配置bean。但是对于有许多bean的大型应用,你应该根据其功能(例如控制器、DAO和JMS)将它们分割到多个配置文件中。一种有益的分割方法是按照给定上下文所提供的架构层次进行分割。
1.2.3 工作原理
假定你打算开发一个生成序列号的应用程序,在这个应用程序中,为不同的用途可能生成许多系列的序列号,每个系列都有自己的前缀、后缀和初始值。因此,你必须在应用程序中创建和维护多个生成器实例。
创建Bean类
按照需求,你创建具有prefix、suffix和initial 3个属性的SequenceGenerator类,这可以通过设值方法(Setter)或者构造程序注入。私有字段counter用于保存这个生成器的当前数值。每当你在一个生成器实例上调用getSequence()方法,都将得到加上前缀和后缀的最新序列号。将这个方法声明为同步(synchronized),使其成为线程安全的方法。
package com.apress.springrecipes.sequence;
public class SequenceGenerator {
private String prefix;
private String suffix;
private int initial;
private int counter;
public SequenceGenerator() {}
public SequenceGenerator(String prefix, String suffix, int initial) {
this.prefix = prefix;
this.suffix = suffix;
this.initial = initial;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public void setInitial(int initial) {
this.initial = initial;
}
public synchronized String getSequence() {
StringBuffer buffer = new StringBuffer();
buffer.append(prefix);
buffer.append(initial + counter++);
buffer.append(suffix);
return buffer.toString();
}
}
正如你所看到的,这个SequenceGenerator类可以由取值/设值(getter/setter)方法或者构造程序配置。
使用容器进行配置时,这种方法被称为构造程序注入和设值方法注入。
创建Bean配置文件
为了在Spring IoC容器中通过XML声明bean,你首先必须创建一个具有合适名称(如beans.xml)的XML bean配置文件。为了在IDE中更易于测试,可以将这个文件放置在classpath的根目录下。
Spring配置XML使你能使用来自不同架构(tx、jndi、jee等)的自定义标记,让bean的配置更加简单和清晰。下面是最简单的XML配置的一个实例。
<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">
...
</beans>
在Bean配置文件中声明bean
每个bean都应该提供一个唯一的名称或者id,以及一个完全限定的类名,用来让Spring IoC容器对其进行实例化。对于简单类型的每个bean属性(例如String和其他简单类型),你可以为其指定一个元素。Spring会试图将你指定的值转换为该属性的声明类型。为了通过设值方法注入配置一个属性,可使用元素,并在其name特性中指定属性名称。每个要求bean包含对应的一个设值方法。
<bean name="sequenceGenerator"
class="com.apress.springrecipes.sequence.SequenceGenerator">
<property name="prefix">
<value>30</value>
</property>
<property name="suffix">
<value>A</value>
</property>
<property name="initial">
<value>100000</value>
</property>
</bean>
你也可以在元素中声明,通过构造程序来配置Bean属性。中没有name属性,因为构造程序参数是基于位置的。
<bean name="sequenceGenerator"
class="com.apress.springrecipes.sequence.SequenceGenerator">
<constructor-arg>
<value>30</value>
</constructor-arg>
<constructor-arg>
<value>A</value>
</constructor-arg>
<constructor-arg>
<value>100000</value>
</constructor-arg>
</bean>
在Spring IoC容器中,每个Bean的名称应该是唯一的。但是,如果装入了超过一个上下文,允许重复的名称覆盖Bean声明。Bean名称可以由元素的name属性定义。实际上,标识Bean的首选方法是:通过标准的XML id属性,它的目的是标识XML文档中的一个元素。这样,如果你的文本编辑器是XML感知的,就能够在设计时校验每个Bean的唯一性。
<bean id="sequenceGenerator"
class="com.apress.springrecipes.sequence.SequenceGenerator">
...
</bean>
不过,XML在可以出现在XML id属性中的字符上有限制。但是,你通常不会在Bean名称中使用这些特殊字符。而且,Spring允许你在name属性中为Bean指定多个以逗号分隔的名称。但是你不能在id属性中这么做,因为这里不允许逗号。
实际上,Bean名称和Bean ID都是必需的。没有定义名称的Bean称作匿名Bean。这样的Bean通常只用于与Spring容器交互,这种情况下将只按类型注入,或者将会在一个外部Bean的声明中嵌入它。
用简写定义Bean属性
Spring支持指定简单类型属性值的一个简写。可以在元素中提供一个Value属性代替包围在元素中的属性。
<bean id="sequenceGenerator"
class="com.apress.springrecipes.sequence.SequenceGenerator">
<property name="prefix" value="30" />
<property name="suffix" value="A" />
<property name="initial" value="100000" />
</bean>
这个简写也可用于构造程序参数。
<bean name="sequenceGenerator"
class="com.apress.springrecipes.sequence.SequenceGenerator">
<constructor-arg value="30" />
<constructor-arg value="A" />
<constructor-arg value="100000" />
</bean>
从Spring 2.0开始,添加了另一个便利的定义属性的简写。它使用p schema像元素中的属性那样定义Bean属性。这可以缩短XML配置行。
<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-3.0.xsd">
<bean id="sequenceGenerator"
class="com.apress.springrecipes.sequence.SequenceGenerator"
p:prefix="30" p:suffix="A" p:initial="100000" />
</beans>
为Bean配置集合
List、Set和 Map是代表Java SDK中3种主要集合类型的核心接口,是Java Collections框架的一部分。对于每一种集合类型,Java用不同的函数和特性提供多种可选的实现。在Spring中,这些集合类型可以很轻松地用一组内建的XML标记(如、和)配置。
假定你打算允许序列生成器具有超过一个后缀。这些后缀将附加在序列号后面,以连字号分隔。你可能考虑接受任意数据类型的后缀并在附加到序列号时转换成字符串。
List、数组和Set
首先,我们使用java.util.List集合来包含你的后缀。List(列表)是一个排序并且索引的集合,它的元素可以通过索引号或者一个for-each循环访问。
package com.apress.springrecipes.sequence;
...
public class SequenceGenerator {
...
private List<Object> suffixes;
public void setSuffixes(List<Object> suffixes) {
this.suffixes = suffixes;
}
public synchronized String getSequence() {
StringBuffer buffer = new StringBuffer();
...
for (Object suffix : suffixes) {
buffer.append("-");
buffer.append(suffix);
}
return buffer.toString();
}
}
为了在Bean配置中定义java.util.List接口的属性,须指定一个包含元素的标记。标记中所允许的元素可以是由指定的常量值、指定的Bean引用、指定的内部Bean定义、指定的ID引用定义,或者指定的空元素。你甚至可以在一个集合中嵌入另一个集合。
<bean id="sequenceGenerator"
class="com.apress.springrecipes.sequence.SequenceGenerator">
<property name="initial" value="100000" />
<property name="suffixes">
<list>
<value>A</value>
<bean class="java.net.URL">
<constructor-arg value="http" />
<constructor-arg value="www.apress.com" />
<constructor-arg value="/" />
</bean>
<null />
</list>
</property>
</bean>
概念上,数组(Array)近似于List,它也是一个排序和索引的集合,可以用索引访问。主要的不同在于数组的长度是固定的,不能动态扩展。在Java Collections框架中,数组和List可以通过Arrays.asList()和List.toArray()方法相互转换。对于你的序列生成器,可以使用Object[]数组包含后缀,并且通过索引或者for-each循环访问它们。
package com.apress.springrecipes.sequence;
...
public class SequenceGenerator {
...
private Object[] suffixes;
public void setSuffixes(Object[] suffixes) {
this.suffixes = suffixes;
}
...
}
Bean配置文件中的数组定义与标记指出的列表相同。
另一个常见的集合类型是Set。java.util.List接口和java.util.Set接口都扩展同一个接口:java.util.Collection。Set与List的不同在于既不排序也不索引,仅能存储独特的对象。这意味着Set中不能包含重复的元素。当相同的元素第二次被添加到一个Set时,它将替换旧的元素。相同的元素由equals()方法确定。
package com.apress.springrecipes.sequence;
...
public class SequenceGenerator {
...
private Set<Object> suffixes;
public void setSuffixes(Set<Object> suffixes) {
this.suffixes = suffixes;
}
...
}
为了定义java.util.Set类型的属性,使用标记来定义元素,方法与List相同。
<bean id="sequenceGenerator"
class="com.apress.springrecipes.sequence.SequenceGenerator">
...
<property name="suffixes">
<set>
<value>A</value>
<bean class="java.net.URL">
<constructor-arg value="http" />
<constructor-arg value="www.apress.com" />
<constructor-arg value="/" />
</bean>
<null />
</set>
</property>
</bean>
尽管在原始的Set语义中没有顺序的概念,Spring还是使用java.util.LinkedHashSet保留了元素的顺序,这是保留元素顺序的java.util.Set接口的实现。
Map和Properties
Map接口是一个在关键字/值对中存储项目的表。你可以通过关键字从Map中取得特定值,也可以使用for-each循环列举Map项目。关键字和值可以是任何类型。关键字之间是否相等也由equals()方法确定。例如,你可以修改序列发生器,接受包含后缀的带有关键字的java.util.Map集合。
package com.apress.springrecipes.sequence;
...
public class SequenceGenerator {
...
private Map<Object, Object> suffixes;
public void setSuffixes(Map<Object, Object> suffixes) {
this.suffixes = suffixes;
}
public synchronized String getSequence() {
StringBuffer buffer = new StringBuffer();
...
for (Map.Entry entry : suffixes.entrySet()) {
buffer.append("-");
buffer.append(entry.getKey());
buffer.append("@");
buffer.append(entry.getValue());
}
return buffer.toString();
}
}
在Spring中,Map由标记定义,带有多个标记作为子项目。每个项目包含一个关键字和一个值。关键字必须在标记中定义。关键字和值的类型没有限制,所以你可以为它们指定一个、、、或值。Spring也将使用java.util.LinkedHashMap保留Map项目的顺序。
<bean id="sequenceGenerator"
class="com.apress.springrecipes.sequence.SequenceGenerator">
...
<property name="suffixes">
<map>
<entry>
<key>
<value>type</value>
</key>
<value>A</value>
</entry>
<entry>
<key>
<value>url</value>
</key>
<bean class="java.net.URL">
<constructor-arg value="http" />
<constructor-arg value="www.apress.com" />
<constructor-arg value="/" />
</bean>
</entry>
</map>
</property>
</bean>
可以用简写将关键字和值对用标记的属性来定义。如果它们是简单的常量,可以用key和value定义,如果它们是引用,可以用key-ref和value-ref定义。
<bean id="sequenceGenerator"
class="com.apress.springrecipes.sequence.SequenceGenerator">
...
<property name="suffixes">
<map>
<entry key="type" value="A" />
<entry key="url">
<bean class="java.net.URL">
<constructor-arg value="http" />
<constructor-arg value="www.apress.com" />
<constructor-arg value="/" />
</bean>
</entry>
</map>
</property>
</bean>
在目前见过的所有集合类中,都使用值来设置属性。有时候,目标是使用Map实例配置一个Null值。Spring的XML配置方案包含对此的显式支持。以下是带有Null项目值的Map:
<property name="nulledMapValue">
<map>
<entry>
<key> <value>null</value> </key>
</entry>
</map>
</property>
java.util.Properties集合非常近似于Map。它也实现java.util.Map接口,并且以关键字/值对的形式存储项目。唯一的不同是Properties集合的关键字和值始终是字符串。
package com.apress.springrecipes.sequence;
...
public class SequenceGenerator {
...
private Properties suffixes;
public void setSuffixes(Properties suffixes) {
this.suffixes = suffixes;
}
...
}
为了在Spring中定义java.util.Properties集合,使用标记,以多个标记作为子项目。每个标记必须定义一个key属性并包含对应的值。
<bean id="sequenceGenerator"
class="com.apress.springrecipes.sequence.SequenceGenerator">
...
<property name="suffixes">
<props>
<prop key="type">A</prop>
<prop key="url">http://www.apress.com/</prop>
<prop key="null">null</prop>
</props>
</property>
</bean>
合并父Bean集合
如果你用继承定义Bean,子Bean的集合可以通过设置Merge属性为True与父Bean合并。对于集合,子元素将附加在父元素之后保持顺序。所以,下面的序列发生器将有4个后缀:A、B、A和C。
<beans ...>
<bean id="baseSequenceGenerator"
class="com.apress.springrecipes.sequence.SequenceGenerator">
<property name="prefixGenerator" ref="datePrefixGenerator" />
<property name="initial" value="100000" />
<property name="suffixes">
<list>
<value>A</value>
<value>B</value>
</list>
</property>
</bean>
<bean id="sequenceGenerator" parent="baseSequenceGenerator">
<property name="suffixes">
<list merge="true">
<value>A</value>
<value>C</value>
</list>
</property>
</bean>
...
</beans>
对于或集合,如果值相同,子元素将覆盖父元素。所以,下面的序列发生器将有3个后缀:A、B和C。
<beans ...>
<bean id="baseSequenceGenerator"
class="com.apress.springrecipes.sequence.SequenceGenerator">
<property name="prefixGenerator" ref="datePrefixGenerator" />
<property name="initial" value="100000" />
<property name="suffixes">
<set>
<value>A</value>
<value>B</value>
</set>
</property>
</bean>
<bean id="sequenceGenerator" parent="baseSequenceGenerator">
<property name="suffixes">
<set merge="true">
<value>A</value>
<value>C</value>
</set>
</property>
</bean>
...
</beans>