本文将会介绍Spring框架中wiring beans的相关内容,包括:bean的声明与创建、在xml文件中对bean基本使用方法的配置以及Spring 3中新特性Spring Expression Language(即SpEL)的初步介绍。
在引出bean之前,不得不提到container。Container用于描述包含并维护其他组件(component)的组件。在Spring框架中,提供了两种Container,即BeanFactory和ApplicationContext。其中,BeanFactory是最简单的一种Container,能够提供基本的依赖注入(DI)和wring bean的服务。而当需要使用到更加高级的Spring框架服务,例如Spring AOP、事件传递及各种不同应用层的context实现等时,则ApplicationContext能够显示出其优势。而提及bean,则可以简单地理解为是经过实例化,编译的对象。它是由container进行管理的。
下面开始详细介绍bean在Spring中的配置。
以下代码是一个典型的Spring中beans的xml配置。其中bean的声明处于<beans></beans>之中。当然,除了beans这个命名空间外,还有例如aop,context,jee,util等更加高级和复杂的命名空间存在,他们和<beans>是同级别的。本文中只关注<beans>命名空间。例如现在存在一个类,它的定义如下:
- package org.Spring.demo;
- public class Movie {
- private movieName;
- private introInfo;
- public Movie() {
- }
- public Movie(String movieName, String introInfo) {
- this.movieName = movieName;
- this.introInfo = introInfo;
- }
- public void display() {
- system.out.println(movieName + ":" + introInfo);
- }
- }
以下是电影TheStarWar作为Movie的一个实例而被定义为一个Spring bean:
- <?xml version="1.0" encoding="UTF-8" ?>
- <beans xmlns="http://springframework.org/schmema/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 id="StarWars" class="com.Spring.demo.Movie" >
- <constructor-arg value="Star Wars" />
- <constructor-arg value="bla bla bla bla" />
- </bean>
- </beans>
其中,class="com.Spring.demo.Movie"和Java代码进行关联,从而根据xml文件中的值进行Movie的实例化,最终结果得到名为Star Wars的实例StarWars。此时,我们便可以使用ApplicationContext对象来使用已经配置过的xml:
- ApplicationContext ctx = new ClassPathXmlApplicationContext(
- "com/Spring/demo/spring-demo.xml");
- Movie starWars = ctx.getBean("StarWars");
- starWars.display();
另外情况是,Movie的构造函数的一个参数另外一个对象,例如,Movie对象的构造函数多了一个Music类型的参数(背景音乐的需要),则需在xml配置中增加一个构造函数参数:
- <constructor-arg ref="ANewHopeandEndCredits">
属性'ref'代表了一种引用关系。它是另外一个类型为Music,并已于beans内定义的实例。此处,Music类的详细定义暂且略去。
除此之外,我们知道bean是有一个生命周期的。Spring为其提供了两个beans的属性:default-init-method, default-destory-method。它们分别对应类的某个特定方法。
我们知道,在很多时候一个类里面存在setters和getters方法。例如,上述例子中,引入了Music类型的字段之后,可能出现情形:
- private Music music ;
- public void setMusic(Music music) {
- this.music = music;
- }
- public Music getMusic() {
- return music;
- }
针对这种情况,Spring提供了与construct-arg平行的标签:<property>。例如:
- <property name="music" ref="ANewHopeandEndCredits" />
当然,此处的ref表示的是引用类型,该类型一再beans中定义。如果是值类型,则可以直接使用value属性。
Spring还提供了一种内部bean的形式,即在bean的内部定义另外一个bean,以达到使用目的。这种方式和引用方式的区别在于,引用方式所引用的bean被定义在<beans></beans>之中,这种bean可以被所有的其他定义在beans下的bean所共享;而内部bean,只可以由其外层的bean使用,即独享型的。除此之外,内部bean的使用和定义和一般的bean并无区别。在次,不过不再多阐述。
接下来所说的是Spring对集合类型的装载。体现在四个集合类型中:<list>、<set>、<map>、<props>。<list>和<set>的区别是,<set>不允许其内部存在冗余数据;<map>和<props>的区别在于,<props>内部的name和value必须都是String类型,而<map>则可以是任何类型。其实,这四种定义在xml文件中的集合类型在被解析时,会构造出相应在java中的集合类型,即java.util.*。下面接着上述的例子进行。一般来讲,一部电影会穿插多种音乐。因此,应该将字段music改为集合类型。于是可以设置如下:
- <property name="music">
- <list>
- <ref bean="EnterLordVader" />
- <ref bean="ANewHopeandEndCredits" />
- <!-- <ref bean="" />-->
- </list>
- </property>
类似的,<set>的使用完全一样,前提是需要保证内部的数据没有重复。<map>是一组name-value的集合,如果我们List类型换为map类型,则xml中的配置如下:
- <property name="music">
- <map>
- <entry key="Enter Lord Vader" value="EnterLordVader" />
- <entry key="A New Hope and End Credits" value="ANewHopeandEndCredits" />
- </map>
- </property>
为了方便起见,现将music设想为Properties类型的。于是<props>的使用如下:
- <property name="music">
- <props>
- <prop key="EnterLordVade">Enter Lord Vade</prop>
- <prop key="ANewHopeandEndCredits">A New Hope and End Credits</prop>
- </props>
- </property>
需要注意的是,Spring同时也提供了null的装载。这是由于有些属性在未被赋值之前,需要保持为空。举例如下:
- <property name="someNonNullProperty"><null/></property>
最后要说的当然是SpEL。这一特性在Spring 3中得以引进。它的一般使用方式是:
- <property name="count" value="#{5}"/>
SpEL在以下的点上提供了便捷方式:通过对bean的ID来引用bean;对对象方法的调用和属性访问;提供算术,关系以及逻辑的运算;正则表达式的匹配;对集合的操作。首先,第一点,引用bean:
- <property name="ANewHopeandEndCredits" value="#{ANewHopeandEndCredits}"/>
和
- <property name="ANewHopeandEndCredits" ref="ANewHopeandEndCredits">
是等价的。但不同的是,我们可以使用如下方式:
- <property name="ANewHopeandEndCredits" value="#{ANewHopeandEndCredits.getName()}"/>
即调用对象的方法(或可以访问对象的属性值)。这就是上述所说的SpEL的优势之二。在算术、关系和逻辑运算中,类似于在“#{}”中的括号里编写Java代码。拿下例来说:
- <property name="average" value="#{counter.total / counter.count}"/>
这是一个简单的除运算。(有趣的是,SpEL提供了^操作,而Java没有。)关系运算需要注意的地方是,大于或者小于号和xml的书写规则会冲突,于是S平EL提供了eq,lt,le,gt,ge分别表示==,<,<=,>,>=。逻辑运算关键词分别有:and,or,not以及!。注意,S平EL并没有提供“&&”或者“||”符号代表and和or。另外,三木运算方式和Java的书写方式也是类似的。而对于正则表达式匹配,SpEL提供了matches关键字,这里不再过多阐述。
对集合的操作,SpEL提供了类似Java操作集合的机制。下面以一个例子为例:
- package com.Spring.cities;
- public class City {
- private String name;
- private String province;
- public String getName() {
- return this.name;
- }
- public String getProvince() {
- return this.province;
- }
- public void setName(String name) {
- this.name = name;
- }
- public void setProvince(String province) {
- this.province = province;
- }
- }
以下代码使用Spring的<util:list>定义了一组城市:
- <util:list id="cities">
- <bean class="com.Spring.cities.City"
- p:name="Chengdu" p:province="Sichuan" />
- <bean class="com.Spring.cities.City"
- p:name="Guangzhou" p:province="Guangdong" />
- <bean class="com.Spring.cities.City"
- p:name="Xian" p:province="Shanxi" />
- <bean class="com.Spring.cities.City"
- p:name="Yinchuan" p:province="Ningxia" />
- </util:list>
此时,我们就可以使用SpEL筛选具体的城市:
- <property name="chosenCity" value="#{cities[2]}"/>
与使用<util:list>类似,我们也可以这样使用<util:map>,通过name来得到value。除了直接使用“.[]”的方式过滤外,还有“.^[]”和“.$[]”两种方式,分别表示选择第一个和最后一个匹配的项。最后要说的一个是“.![]”,它用于支持集合的映射。
- <property name="cityNames" value="#{cities.![name]}"/>
当只是需要得到城市名,而不需要得到全部的城市信息时,使用以上代码,可以直接映射之前所定义的城市集合中,各城市的名称部分信息集合。
需要注意的是,虽然使用SpEL可以减少Java代码的编写工作,但是过多的使用会带来一些负面的影响。这种方式不好编写测试代码,同时也没有IDE的支持,以检查语法问题。
至此,对bean的基本使用方式已经介绍得差不多了。