8.1.4 属性占位符配置器

Spring提供了PropertyPlaceholderConfigurer,它是一个容器后处理器,负责读取Properties属性文件里的属性值,并将这些属性值设置成Spring配置文件的数据。
通过使用PropertyPlaceholderConfigurer后处理器,可以将Spring配置文件中的部分数据放在属性文件中设置,这种配置方式当然有其优势:可以将部分相似的配置(比如数据库的URL、用户名和密码)放在特定的属性文件中,如果只需要修改这部分配置,则无须修改Spring配置文件,修改属性文件即可。

程序示例

1
2
3
4
5
6
7
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\PropertyPlaceholderConfigurer
├─data.sql
└─src\
├─beans.xml
├─dbconn.properties
└─lee\
└─BeanTest.java

beans.xml

下面的配置文件配置了PropertyPlaceholderConfigurer后处理器,在配置数据源Bean时,使用了属性文件中的属性值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
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">
<!-- PropertyPlaceholderConfigurer是一个容器后处理器,
它会读取 属性文件信息,并将这些信息设置成Spring配置文件的数据。 -->
<bean
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>dbconn.properties</value>
<!-- 如果有多个属性文件,依次在下面列出来 -->
<!--value>wawa.properties</value -->
</list>
</property>
</bean>
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close"
p:driverClass="${jdbc.driverClassName}"
p:jdbcUrl="${jdbc.url}"
p:user="${jdbc.username}"
p:password="${jdbc.password}" />
</beans>

在上面的配置文件中,配置driverClassjdbcUrl等信息时,并未直接设置这些属性的属性值,而是设置了${jdbc.driverClassName}${jdbc.url}属性值,这表明Spring容器将从propertyConfigurer指定的属性文件中搜索这些key对应的value,并为该Bean的属性值设置这些value值。
如前所述, ApplicationContext会自动检测部署在容器中的容器后处理器,无须额外注册,容器会自动检测并注册Spring中的容器后处理器。因此,只需提供如下Properties文件:

dbconn.properties

1
2
3
4
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring?useSSL=true
jdbc.username=root
jdbc.password=root

通过这种方法,可从主XML配置文件中分离出部分配置信息。如果仅需要修改数据库连接属性,则无须修改主XML配置文件,只需要修改该属性文件即可。采用属性占位符的配置方式,可以支持使用多个属性文件,通过这种方式,可将配置文件分割成多个属性文件,从而降低修改配置文件产生错误的风险。
对于数据库连接等信息集中的配置,可以将其配置在Properties属性文件中,但不要过多地将Spring配置信息抽离到Properties属性文件中,这样可能会降低Spring配置文件的可读性。

使用context:property-placeholder元素简化配置

对于采用基于XML Schema的配置文件而言,如果导入了context:命名空间,则可采用如下方式来配置该属性占位符。

1
2
<context:property-placeholder
location="classpath:dbconn.properties" />

也就是说,<context:property-placeholder>元素是PropertyPlaceholderConfigurer的简化配置。

8.1.3 容器后处理器

除了上面提供的Bean后处理器, Spring还提供了一种容器后处理器。Bean后处理器负责处理容器中的所有Bean实例,而容器后处理器则负责处理容器本身。
容器后处理器必须实现BeanFactoryPostProcessor接口。实现该接口必须实现如下一个方法。
setProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
实现该方法的方法体就是对Spring容器进行的处理,这种处理可以对Spring容器进行自定义扩展当然也可以对Spring容器不进行任何处理。

postProcessBeanFactory没有返回值

由于开发者不可能完全替换Spring容器(如果完全替換Spring容器,那就没必要使用Spring框架了),因此postProcessBeanFactory方法只是对Spring容器进行后处理,该方法无须任何返回值。
类似于BeanPostProcessorApplicationContext可自动检测到容器中的容器后处理器,并且自动注册容器后处理器。但若使用BeanFactory作为Spring容器,则必须手动调用该容器后处理器来处理BeanFactory容器。

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\BeanFactoryPostProcessor
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
├─service\
│ ├─Axe.java
│ ├─impl\
│ │ ├─Chinese.java
│ │ └─SteelAxe.java
│ └─Person.java
└─util\
└─MyBeanFactoryPostProcessor.java

MyBeanFactoryPostProcessor.java

下面定义了一个容器后处理器,这个容器后处理器实现了BeanFactoryPostProcessor接口,但并未对Spring容器进行任何处理,只是打印出一行简单的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.crazyit.app.util;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor
{
/**
* 重写该方法,对Spring进行后处理。
* @param beanFactory Spring容器本身
*/
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException
{
System.out.println("程序对Spring所做的BeanFactory的初始化没有改变...");
System.out.println("Spring容器是:" + beanFactory);
}
}

将容器后处理器作为普通Bean部署在容器中,如果使用ApplicationContext作为容器,容器会自动调用BeanFactoryPostProcessor来处理Spring容器。但如果使用BeanFactory作为Spring容器,则必须手动调用容器后处理器来处理Spring容器。例如如下主程序。

BeanTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package lee;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.crazyit.app.service.*;

public class BeanTest
{
public static void main(String[] args)
{
// 以ApplicationContex作为Spring容器
// 它会自动注册容器后处理器、Bean后处理器
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"beans.xml");
Person p = (Person) ctx.getBean("chinese");
p.useAxe();
}
}

上面程序中代码使用了ApplicationContext作为Spring容器, Spring容器会自动检测容器中的所有Bean,如果发现某个Bean类实现了BeanFactoryPostProcessor接口, ApplicationContext会自动将其注册为容器后处理器。
Spring没有提供ApplicationContextPostProcessor。也就是说,对于ApplicationContext容器,一样使用BeanFactoryPostProcessor作为容器后处理器。

Spring常用的后处理器

Spring已提供如下几个常用的容器后处理器。

后处理器 描述
PropertyPlaceholderConfigurer 属性占位符配置器
PropertyOverrideConfigurer 重写占位符配置器。
CustomAutowireConfigurer 自定义自动装配的配置器。
CustomScopeConfigurer 自定义作用域的配置器。

从上面的介绍可以看出,容器后处理器通常用于对Spring容器进行处理,并且总是在容器实例化任何其他的Bean之前,读取配置文件的元数据,并有可能修改这些元数据
如果有需要,程序可以配置多个容器后处理器,多个容器后处理器可设置order属性来控制容器后处理器的执行次序。
为了给容器后处理器指定order属性,则要求容器后处理器必须实现Ordered接口,因此在实现BeanFactoryPostProcessor时,就应当考虑实现Ordered接口。

容器后处理器的作用域是容器级的

容器后处理器的作用域范围是容器级,它只对容器本身进行处理,而不对容器中的Bean进行处理;如果需要对容器中的Bean实例进行后处理,则应该考虑使用Bean后处理器,而不是使用容器后处理。

8.1.2 Bean后处理器的用处

上一节介绍了一个简单的Bean后处理器,上面的Bean后处理器负责对容器中的chinese bean进行后处理,不管chinese bean如何初始化,总是为chinese beanname属性添加" FKJAVA:"前缀。这种后处理看起来作用并不是特别大。
实际中Bean后处理器完成的工作更加实际,例如生成ProxySpring框架本身提供了大量的Bean后处理器,这些后处理器负责对容器中的Bean进行后处理。
下面是Spring提供的两个常用的后处理器。

常用后处理器 描述
BeanNameAutoProxyCreator 根据Bean实例的name属性,创建Bean实例的代理。
DefaultAdvisorProxyCreator 根据提供的Advisor,对容器中的所有Bean实例创建代理

上面提供的两个Bean后处理器都用于根据容器中配置的拦截器,创建代理Bean,代理Bean就是对目标Bean进行增强,在目标Bean的基础上进行修改得到的新Bean
如果需要对容器中某一批Bean进行通用的增强处理,则可以考虑使用Bean后处理器。

8.1 两种后处理器

Spring框架提供了很好的扩展性,除了可以与各种第三方框架良好整合外,其IoC容器也允许开发者进行扩展,这种扩展甚至无须实现BeanFactoryApplicationContext接口,而是允许通过两个后处理器对loC容器进行扩展。 Spring提供了两种常用的后处理器。

  1. Bean后处理器:这种后处理器会对容器中的Bean进行后处理,对Bean进行额外加强。
  2. 容器后处理器:这种后处理器对IoC容器进行后处理,用于增强容器功能。

下面将介绍这两种常用的后处理器,以及两种后处理器的相关知识。

8.1.1 Bean后处理器

Bean后处理器是一种特殊的Bean,这种特殊的Bean并不对外提供服务,它甚至无须id属性,它主要负责对容器中的其他Bean执行后处理,例如为容器中的目标Bean生成代理等,这种Bean被称为Bean后处理器.
Bean后处理器会在Bean实例创建成功之后,对Bean实例进行进一步的增强处理。

Bean后处理器必须实现BeanPostProcessor接口, BeanPostProcessor接口包含如下两个方法。

BeanPostProcessor接口方法 描述
Object postProcessBeforelnitialization(Object bean, String name) throws Beans Exception 在目标Bean初始化之前,对容器中的Bean实例进行增强处理。该方法的第一个参数是系统即将进行后处理的Bean实例,第二个参数是该Bean的配置id
Object postProcessAfterInitialization(Object bean,String name) throws Beans Exception 在目标Bean初始化之后,对容器中的Bean实例进行增强处理。该方法的第一个参数是系统即将进行后处理的Bean实例,第二个参数是该Bean的配置id.

实现该接口的Bean后处理器必须实现这两个方法。

Bean后处理器是对loC容器一种极好的扩展,Bean后处理器可以对容器中的Bean进行后处理,而到底要对Bean进行怎样的后处理则完全取决于开发者。 Spring容器负责把各Bean创建出来,而开发者提供的Bean后处理器可以依次对每个Bean进行某种修改、増强,从而可以对容器中的Bean集中増加某种功能.

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\BeanPostProcessor
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
├─service\
│ ├─Axe.java
│ ├─impl\
│ │ ├─Chinese.java
│ │ └─SteelAxe.java
│ └─Person.java
└─util\
└─MyBeanPostProcessor.java

下面将定义一个简单的Bean后处理器,该Bean后处理器将对容器中的其他Bean进行后处理。Bean后处理器的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package org.crazyit.app.util;

import java.lang.reflect.Field;
import org.crazyit.app.service.impl.Chinese;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor
{
/**
* 对容器中的Bean实例进行后处理
* @param bean 需要进行后处理的原Bean实例
* @param beanName 需要进行后处理的Bean的配置id
* @return 返回后处理完成后的Bean
*/
public Object postProcessBeforeInitialization(Object bean, String beanName)
{
System.out.println("Bean后处理器在初始化 之前 对" + beanName + "进行增强处理...");
// 返回的处理后的Bean实例,该实例就是容器中实际使用的Bean
// 该Bean实例甚至可与原Bean截然不同
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName)
{
System.out.println("Bean后处理器在初始化 之后 对" + beanName + "进行增强处理...");
// 如果该Bean是Chinese类的实例
if (bean instanceof Chinese)
{
try
{
// 通过反射修改其name成员变量
Class<?> clazz = bean.getClass();
Field f = clazz.getDeclaredField("name");
f.setAccessible(true);
f.set(bean, "FKJAVA:" + f.get(bean));
} catch (Exception ex)
{
ex.printStackTrace();
}
}
return bean;
}
}

上面程序中实现了对Bean进行增强处理的逻辑,当Spring容器实例化Bean实例之后,就会依次调用Bean后处理器的两个方法对Bean实例进行增强处理。
下面是Chinese Bean类的代码,该类实现了InitializingBean接口(并实现了该接口包含的afterPropertiesSet()方法),还额外提供了一个初始化方法(init()方法),这两个方法都用于定制该Bean实例的生命周期行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package org.crazyit.app.service.impl;

import org.crazyit.app.service.Axe;
import org.crazyit.app.service.Person;
import org.springframework.beans.factory.InitializingBean;

public class Chinese implements Person, InitializingBean
{
private Axe axe;
private String name;
public Chinese()
{
System.out.println("Spring实例化主调bean:Chinese实例...");
}
public void setAxe(Axe axe)
{
this.axe = axe;
}
public void setName(String name)
{
System.out.println("Spring执行setName()方法注入依赖关系...");
this.name = name;
}
public void useAxe()
{
System.out.println(name + axe.chop());
}
// 下面是两个生命周期方法
public void init()
{
System.out.println("正在执行初始化方法 init...");
}
public void afterPropertiesSet() throws Exception
{
System.out.println("正在执行初始化方法 afterPropertiesSet...");
}
}

在配置文件中配置Bean后处理器和配置普通Bean完全一样,但有一点需要指出,通常程序无须主动获取Bean后处理器,因此配置文件可以无须为Bean后处理器指定id属性。下面的配置文件因为需要手动注册Bean后处理器,所以配置文件依然为Bean后处理器指定了id属性。配置文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
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">
<!-- 配置2个普通Bean实例 -->
<bean id="steelAxe"
class="org.crazyit.app.service.impl.SteelAxe" />
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese"
init-method="init"
p:axe-ref="steelAxe" p:name="依赖注入的值" />
<!-- 配置Bean后处理器,可以无需指定id属性 -->
<bean id="bp" class="org.crazyit.app.util.MyBeanPostProcessor" />
</beans>

上面文件的粗体字代码配置了一个Bean后处理器,这个Bean后处理器将会对容器中的所有Bean实例进行后处理。为了更好地观察到Bean后处理器的后处理方法的执行时机,程序还为chinese bean指定了如下两个初始化方法。

  1. init-method指定初始化方法
  2. 实现InitializingBean接口,提供了afterPropertiesSet初始化方法。

该示例的主程序如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package lee;

import org.crazyit.app.service.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BeanTest
{
public static void main(String[] args) throws Exception
{
// 以类加载路径下的beans.xml文件来创建Spring容器
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"beans.xml");
Person p = (Person) ctx.getBean("chinese");
p.useAxe();
}
}

ApplicationContext作为Spring容器时会自动注册Bean后处理器

从上面代码可以看出,该程序根本看不到任何关于Bean后处理器的代码,这是因为:如果使用ApplicationContext作为Spring容器, Spring容器会自动检测容器中的所有Bean,如果发现某个Bean类实现了BeanPostProcessor接口, ApplicationContext会自动将其注册为Bean后处理器。
运行上面的程序,可以看到如下运行结果:

1
2
3
4
5
6
7
8
9
10
Spring实例化依赖bean:SteelAxe实例...
Bean后处理器在初始化 之前 对steelAxe进行增强处理...
Bean后处理器在初始化 之后 对steelAxe进行增强处理...
Spring实例化主调bean:Chinese实例...
Spring执行setName()方法注入依赖关系...
Bean后处理器在初始化 之前 对chinese进行增强处理...
正在执行初始化方法 afterPropertiesSet...
正在执行初始化方法 init...
Bean后处理器在初始化 之后 对chinese进行增强处理...
FKJAVA:依赖注入的值钢斧砍柴真快

从上面的执行结果可以看出,虽然配置文件中指定chinese这个Beanname为”依赖注入的值”,但该chinese这个Beanname成员变量的值被修改了,增加了"FKJAVA:"前缀——这就是Bean后处理器的作用。
容器中一旦注册了Bean后处理器,Bean后处理器就会自动启动,在容器中每个Bean创建时自动工作,加入Bean后处理器需要完成的工作。从上面的执行过程可以看出,Bean后处理器两个方法的回调时机如图8.1所示。
这里有一张图片
实现BeanPostProcessor接口的Bean后处理器可对Bean进行任何操作,包括完全忽略这个回调。BeanPostprocessor通常用来检查标记接口,或者做如将Bean包装成一个Proxy的事情, Spring的很多工具类就是通过Bean后处理器完成的。

BeanFactory作为Spring容器时必须手动注册Bean

如果使用BeanFactory作为Spring容器,则必须手动注册Bean后处理器,程序必须获取Bean后处理器实例,然后手动注册。在这种需求下,程序可能需要在配置文件中为Bean处理器指定id属性,这样才能让Spring容器先获取Bean后处理器,然后注册它。因此,如果使用BeanFactory作为Spring容器,则需要将主程序改为如下形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package lee;

import org.crazyit.app.service.Person;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

public class BeanTest
{
public static void main(String[] args) throws Exception
{
// 搜索类加载路径下的beans.xml文件创建Resource对象
Resource isr = new ClassPathResource("beans.xml");
// 创建默认的BeanFactory容器
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 让默认的BeanFactory容器加载isr对应的XML配置文件
new XmlBeanDefinitionReader(beanFactory).loadBeanDefinitions(isr);
// 获取容器中的Bean后处理器
BeanPostProcessor bp = (BeanPostProcessor) beanFactory.getBean("bp");
// 注册Bean后处理器
beanFactory.addBeanPostProcessor(bp);
Person p = (Person) beanFactory.getBean("chinese");
p.useAxe();
}
}

正如从上面粗体字代码所看到的,程序中bp就是Bean后处理器配置id,一旦程序获取了Bean后处理器,即可调用BeanFactoryaddBeanPostProcessor()方法来注册该Bean后处理器。

第8章 深入使用Spring

本章要点

  • 利用后处理器扩展Spring容器
  • Bean后处理器和容器后处理器
  • Spring的”零配置”支持
  • Spring的资源访问策略
  • ApplicationContext中使用资源
  • AOP的基本概念
  • AspectJ使用入门
  • 生成AOP代理和AOP代理的作用
  • 基于注解的”零配置”方式
  • 基于XML配置文件的管理方式
  • Spring的事务策略
  • Spring的事务配置
  • Spring整合MVC框架的策略
  • Spring整合Struts2
  • Spring整合Hibernate
  • Spring整合JPA

上一章已经介绍了Spring框架的基础内容,详细介绍了**Spring容器的核心机制:依赖注入**,并介绍了Spring容器对Bean的管理。实际上,上一章介绍的内容是大部分项目都需要使用的基础部分,很多时候,即使不使用Spring框架,实际项目也都会采用相同的策略。
Spring框架的功能绝不是只有这些部分, Spring框架允许开发者使用两种后处理器扩展loC容器,这两种后处理器可以后处理IoC容器本身,或者对容器中所有的Bean进行后处理。loC容器还提供了AOP功能,极好地丰富了Spring容器的功能。
Spring AOPSpring框架另一个吸引人的地方,AOP本身是一种非常前沿的编程思想,它从动态角度考虑程序运行过程,专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,能更好地抽离出各模块的交叉关注点。
Spring的声明式事务管理正是通过AOP来实现的。当然,如果仅仅想使用Spring的声明式事务管理,其实完全无须掌握AOP,但如果希望开发出结构更优雅的应用,例如集中处理应用的权限控制、系统日志等需求,则应该使用AOP来处理。
除此之外,本章还将详细介绍SpringHibernate/JPAStruts2框架的整合。

7.12.3 SpEL语法详述

虽然SpEL在功能上大致与JSP2EL类似,但SpELJSP 2EL更强大,接下来详细介绍SpEL所支持的各种语法细节。

1. 直接量表达式

直接量表达式是SpEL中最简单的表达式,直接量表达式就是在表达式中使用Java语言支持的直接量,包括字符串日期数值boolean值和null:
例如如下代码片段:

1
2
3
4
Expression exp = parser.parseExpression("'Hello World'");
System.out.println(exp.getValue(String.class));
exp = parser.parseExpression("0.23");
System.out.println(exp.getValue(Double.class));

2. 在表达式中创建数组

SpEL表达式直接支持使用静态初始化动态初始化两种语法来创建数组。例如如下代码片段:

1
2
3
4
5
6
7
// 创建一个数组
Expression exp = parser
.parseExpression("new String[]{'java' , 'Struts' , 'Spring'}");
System.out.println(exp.getValue());
// 创建二维数组
exp = parser.parseExpression("new int[2][4]");
System.out.println(exp.getValue());

3. 在表达式中创建List集合

SpEL直接使用如下语法来创建List集合:

1
{element1,element2,element3,...,}

例如如下代码:

1
2
3
Expression exp=parser.parseExpression("{'java','Struts','Spring'}");
System.out.println(exp.getValue());
exp=parser.parseExpression(""{{'大学','中庸','论语','孟子'},{'诗经',''尚书,'礼记','周易','春秋'}}")

4. 在表达式中访问List、Map等集合元素

为了在SpEL中访问List集合的元素,可以使用如下语法格式

1
List[index]

为了在SpEL中访问Map集合的元素,可以使用如下语法格式:

1
map[key]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List<String> list = new ArrayList<String>();
list.add("Java");
list.add("Spring");
Map<String, Double> map = new HashMap<String, Double>();
map.put("Java", 80.0);
map.put("Spring", 89.0);
// 创建一个EvaluationContext对象,作为SpEL解析变量的上下文
EvaluationContext ctx = new StandardEvaluationContext();
// 设置两个变量
ctx.setVariable("mylist", list);
ctx.setVariable("mymap", map);
// 访问List集合的第二个元素
System.out.println(parser.parseExpression("#mylist[1]").getValue(ctx));
// 访问Map集合的指定元素
System.out.println(
parser.parseExpression("#mymap['Java']").getValue(ctx));

5. 调用方法

SpEL中调用方法与在Java代码中调用方法没有任何区别。如以下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 调用String对象的substring()方法
System.out.println(parser
.parseExpression("'HelloWorld'.substring(2, 5)").getValue());
List<String> list = new ArrayList<String>();
list.add("java");
list.add("struts");
list.add("spring");
list.add("hibernate");
// 创建一个EvaluationContext对象,作为SpEL解析变量的上下文
EvaluationContext ctx = new StandardEvaluationContext();
// 设置一个变量
ctx.setVariable("mylist", list);
// 调用指定变量所代表的对象的subList()方法
System.out.println(
parser.parseExpression("#mylist.subList(1, 3)").getValue(ctx));

6. 算术、比较、逻辑、赋值、三目等运算符

JSP2EL类似的是SpEL同样支持算术、比较、逻辑、赋值、三目运算符等各种运算符,值得指出的是,在SpEL中使用赋值运算符的功能比较强大,这种赋值可以直接改变表达式所引用的实际对象。
如以下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
List<String> list = new ArrayList<String>();
list.add("java");
list.add("struts");
list.add("spring");
list.add("hibernate");
// 创建一个EvaluationContext对象,作为SpEL解析变量的上下文
EvaluationContext ctx = new StandardEvaluationContext();
// 设置一个变量
ctx.setVariable("mylist", list);
// 对集合的第一个元素进行赋值
parser.parseExpression("#mylist[0]='疯狂Java讲义'").getValue(ctx);
// 下面将输出 疯狂Java讲义
System.out.println(list.get(0));
// 使用三目运算符
System.out.println(parser
.parseExpression(
"#mylist.size()>3?" + "'myList长度大于3':'myList长度不大于3'")
.getValue(ctx));

运行结果:

1
2
疯狂Java讲义
myList长度大于3

7. 类型运算符

SpEL提供了一个特殊的运算符:T(),这个运算符用于告诉SpEL将该运算符内的字符串当成”类”处理,避兔Spring对其进行其他解析。尤其是调用某个类的静态方法时,T()运算符尤其有用。

1
2
3
4
5
6
7
// 调用Math的静态方法
System.out.println(parser.parseExpression("T(java.lang.Math).random()")
.getValue());
// 调用Math的静态方法
System.out.println(
parser.parseExpression("T(System).getProperty('os.name')")
.getValue());

例如如下代码:

1
2
0.09320472533221835
Windows 10

正如在上面的代码中所看到的,在表达式中使用某个类时,推荐使用该类的全限定类名。但如果只写类名,不写包名,SpEL也可以尝试处理,SpEL使用StandardTypeLocator去定位这些类,它默认会在java.lang包下找这些类。
T()运算符使用java.lang包下的类时可以省略包名,但使用其他包下的所有类时应使用全限定类名

8. 调用构造器

SpEL允许在表达式中直接使用new来调用构造器,这种调用可以创建一个Java对象。例如如下代码

1
2
3
4
5
6
7
8
9
// 创建对象
System.out.println(parser
.parseExpression("new String('HelloWorld').substring(2, 4)")
.getValue());
// 创建对象
System.out.println(parser
.parseExpression(
"new javax.swing.JFrame('测试')" + ".setVisible('true')")
.getValue());

9. 变量

SpEL允许通过EvaluationContext来使用变量,该对象包含了一个setVariable(String name, Object value)方法,该方法用于设置一个变量。一旦在EvaluationContext中设置了变量,就可以在SpEL中通过#name来访问该变量。前面已经有不少在SpEL中使用变量的例子,故此处不再赘述。
值得指出的是,SpEL中有如下两个特殊的变量。

  • #this:引用SpEL当前正在计算的对象。
  • #root:引用SpELEvaluationContextroot对象。

10. 自定义函数

SpEL允许开发者开发自定义函数。类似于JSP 2 EL中的自定义函数,所谓自定义函数,也就是为Java方法重新起个名字而已。
通过StandardEvaluationContext的如下方法即可在SpEL中注册自定义函数。
registerFunction(String name, Method m):将m方法注册成自定义函数,该函数的名称为nameSpEL自定义函数的作用并不大,因为SpEL本身已经允许在表达式语言中调用方法,因此将方法重新定义为自定义函数的意义不大。

11. Elvis运算符

Elvis运算符只是三目运算符的特殊写法,例如对于如下三目运算符写法:

1
name != null ? name : "newValue"

上面的语句使用三目运算符需要将name变量写两次,因此比较烦琐。SpEL允许将上面写法简写为如下形式:

1
name?: "newValue"

12. 安全导航操作

SpEL中使用如下语句时可能导致NullPointerException:

1
foo.bar

如果root对象的foo属性本身已经是null,那么上面表达式尝试访问foo属性的bar属性时自然就会引发异常。
为了避免上面表达式中的NullPointerException异常,SpEL支持如下用法:

1
foo?.bar

上面表达式在计算root对象的foo属性时,如果foo属性为null,计算结果将直接返回null,而不会引发NullPointerException异常。如以下代码所示:

1
2
3
4
5
// 使用安全操作,将输出null
System.out.println(
"----" + parser.parseExpression("#foo?.bar").getValue());
// 不使用安全操作,将引发NullPointerException
System.out.println(parser.parseExpression("#foo.bar").getValue());

13. 集合选择

SpEL允许直接对集合进行选择操作,这种选择操作可以根据指定表达式对集合元素进行筛选,只有符合条件的集合元素才会被选择出来。SpEL集合选择的语法格式如下:
collection.[condition_expression]
在上面的语法格式中, condition_expression是一个根据集合元素定义的表达式,只有当该表达式返回true时,对应的集合元素才会被筛选出来。如以下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
List<String> list = new ArrayList<String>();
list.add("疯狂Java讲义");
list.add("疯狂Ajax讲义");
list.add("疯狂iOS讲义");
list.add("经典Java EE企业应用实战");
// 创建一个EvaluationContext对象,作为SpEL解析变量的上下文
EvaluationContext ctx = new StandardEvaluationContext();
ctx.setVariable("mylist", list);
// 判断集合元素length()方法的长度大于7,“疯狂iOS讲义”被剔除
Expression expr = parser.parseExpression("#mylist.?[length()>7]");
System.out.println(expr.getValue(ctx));
Map<String, Double> map = new HashMap<String, Double>();
map.put("Java", 89.0);
map.put("Spring", 82.0);
map.put("英语", 75.0);
ctx.setVariable("mymap", map);
// 判断Map集合的value值大于80,只保留前面2个Entry
expr = parser.parseExpression("#mymap.?[value>80]");
System.out.println(expr.getValue(ctx));

正如上面的粗体字代码所示,这种集合选择既可对List集合进行筛选,也可对Map集合进行筛选,当操作List集合时, condition_expression中访间的每个属性、方法都是以集合元素为主调的;当操作Map集合时,需要显式地用key引用Map Entrykey,用value引用Map Entryvalue.

1
2
[疯狂Java讲义, 疯狂Ajax讲义, 经典Java EE企业应用实战]
{Java=89.0, Spring=82.0}

14. 集合投影

SpEL允许对集合进行投影运算,这种投影运算将依次迭代每个集合元素,迭代时将根据指定表达式对集合元素进行计算得到一个新的结果,依次将每个结果收集成新的集合,这个新的集合将作为投影运算的结果。
SpEL投影运算的语法格式为:
collection.![condition_expression]
在上面的语法格式中, condition_expression是一个根据集合元素定义的表达式。上面的SpEL会把collection集合中的元素依次传入condition_expression中,每个元素得到一个新的结果,所有计算出来的结果所组成的新结果就是该表达式的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
List<String> list = new ArrayList<String>();
list.add("疯狂Java讲义");
list.add("疯狂Ajax讲义");
list.add("疯狂iOS讲义");
list.add("经典Java EE企业应用实战");
// 创建一个EvaluationContext对象,作为SpEL解析变量的上下文
EvaluationContext ctx = new StandardEvaluationContext();
ctx.setVariable("mylist", list);
// 得到的新集合的元素是原集合的每个元素length()方法返回值
Expression expr = parser.parseExpression("#mylist.![length()]");
System.out.println(expr.getValue(ctx));
List<Person> list2 = new ArrayList<Person>();
list2.add(new Person(1, "孙悟空", 162));
list2.add(new Person(2, "猪八戒", 182));
list2.add(new Person(3, "牛魔王", 195));
ctx.setVariable("mylist2", list2);
// 得到的新集合的元素是原集合的每个元素name属性值
expr = parser.parseExpression("#mylist2.![name]");
System.out.println(expr.getValue(ctx));

运行结果:

1
2
[8, 8, 7, 15]
[孙悟空, 猪八戒, 牛魔王]

15. 表达式模板

表达式模板有点类似于带占位符的国际化消息。例如如下带占位符的国际化消息:

1
今天的股票价格是:{1}

上面的消息可能生成如下字符串:

1
今天的股票价格是:123

上面字符串中的123需要每次动态改变。
这种需求可以借助于SpEL的表达式模板的支持。表达式模板的本质是对”直接量表达式”的扩展它允许在”直接量表达式”中插入一个或多个并#{expression},#{expression}将会被动态计算出来。
例如,如下程序示范了使用表达式模板:

1
2
3
4
5
6
7
8
Person p1 = new Person(1, "孙悟空", 162);
Person p2 = new Person(2, "猪八戒", 182);
Expression expr = parser.parseExpression("我的名字是#{name},身高是#{height}",
new TemplateParserContext());
// 将使用p1对象name、height填充上面表达式模板中的#{}
System.out.println(expr.getValue(p1));
// 将使用p2对象name、height填充上面表达式模板中的#{}
System.out.println(expr.getValue(p2));

正如在上面的程序中所看到的,使用ExpressionParser解析字符串模板时需要传入一个TemplateParserContext参数,该TemplateParserContext实现了ParserContext接口,它用于为表达式解析传入一些额外的信息,例如TemplateParserContext指定解析时需要计算#{}之间的值。

7.12.2 Bean定义中的表达式语言支持

SpEL的一个重要作用就是扩展Spring容器的功能,允许在Bean定义中使用SpEL。在XML配置件和注解中都可以使用SpELXML配置文件和注解中使用SpEL时,在表达式外面增加#{}包围即可

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\SpEL_XML
└─src\
├─beans.xml
├─lee\
│ └─SpELTest.java
├─org\
│ └─crazyit\
│ └─app\
│ └─service\
│ ├─Axe.java
│ ├─impl\
│ │ ├─Author.java
│ │ └─SteelAxe.java
│ └─Person.java
└─test_zh_CN.properties

Author.java

例如,有如下Author类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.crazyit.app.service.impl;
import java.util.List;
import org.crazyit.app.service.Axe;
import org.crazyit.app.service.Person;
public class Author implements Person
{
private Integer id;
private String name;
private List<String> books;
private Axe axe;

// 此处省略getter和setter方法

public void useAxe()
{
System.out.println("我是"
+ name + ",正在砍柴\n" + axe.chop());
}
}

上面的Author类需要依赖注入namebooksaxe,当然,可以按照前面介绍的方式来进行配置,但如果使用SpEL,将可以对Spring配置做进一步简化

beans.xml

下面使用SpEL对这个Bean进行配置,配置代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<!-- 指定Spring配置文件的根元素和Schema 导入p:命名空间和util:命名空间的元素 -->
<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-4.0.xsd">
<!-- 使用util.properties加载指定资源文件 -->
<util:properties id="confTest"
location="classpath:test_zh_CN.properties" />
<!-- 配置setName()的参数时,在表达式中调用方法 配置setAxe()的参数时,
在表达式中创建对象 配置调用setBooks()的参数时,在表达式中访问其他Bean的属性 -->
<bean id="author" class="org.crazyit.app.service.impl.Author"
p:name="#{T(java.lang.Math).random()}"
p:axe="#{new org.crazyit.app.service.impl.SteelAxe()}"
p:books="#{ {confTest.a , confTest.b} }" />
</beans>

上面的代码就是利用SpEL进行配置的代码,使用SpEL可以在配置文件中调用方法创建对象(这种方式可以代替嵌套Bean语法)、访问其他Bean的属性……总之,SpEL支持的语法都可以在这里使用,SpEL极大地简化了Spring配置。
需要指出的是,在注解中使用SpEL与在XML中使用SpEL基本相似,关于Spring使用注解进行配置管理的内容请参考下一章的知识。

7.12 Spring提供的表达式语言(SpEL)

Spring表达式语言(简称SpEL)是一种与JSP 2EL功能类似的表达式语言,它可以在运行时查询和操作对象。与JSP 2EL相比,SpEL功能更加强大,它甚至支持方法调用和基本字符串模板函数

  • SpEL可以独立于Spring容器使用——只是当成简单的表达式语言来使用;
  • 也可以在注解或XMI配置中使用SpEL,这样可以充分利用SpEL简化SpringBean配置

7.12.1 使用Expression接口进行表达式求值

SpringSpEL可以单独使用,可以使用SpEL对表达式计算、求值

SpEL接口

SpEL主要提供了如下三个接口。

接口 描述
ExpressionParser 该接口的实例负责解析一个SpEL表达式,返回一个Expression对象。
Expression 该接口的实例代表一个表达式
EvaluationContext 代表计算表达式值的上下文。当SpEL表达式中含有变量时,程序将需要使用该API来计算表达式的值。

Expression接口介绍

Expression实例代表一个表达式,它包含了下面这些重载的getValue方法用于计算表达式的值。

方法 描述
Object getValue() 计算表达式的值。
<T> T getValue(Class<T> desiredResultType) 计算表达式的值,而且尝试将该表达式的值当成desiredResultType类型处理。
Object getValue(Evaluation ContextContext) 使用指定的EvaluationContext来计算表达式的值。
<T> T getValue(Evaluation ContextContext, Class<T> desiredResultType) 使用指定的EvaluationContext来计算表达式的值,而且尝试将该表达式的值当成desiredResultType类型处理.
Object getValue(Object rootObject) rootObject作为表达式的root对象来计算表达式的值。
<T> T getValue(Object rootObject,Class<T> desiredResultType) rootObject作为表达式的root对象来计算表达式的值,而且尝试将该表达式的值当成desiredResultType类型处理。

程序示例

1
2
3
4
5
6
7
8
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\Expression\src
├─lee\
│ └─SpELTest.java
└─org\
└─crazyit\
└─app\
└─domain\
└─Person.java

SpELTest.java

下面的程序示范了如何利用ExpressionParserExpression来计算表达式的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package lee;

import org.springframework.expression.*;
import org.springframework.expression.spel.standard.*;
import org.springframework.expression.spel.support.*;

import java.util.*;

import org.crazyit.app.domain.*;

public class SpELTest
{
public static void main(String[] args)
{
// 创建一个ExpressionParser对象,用于解析表达式
ExpressionParser parser = new SpelExpressionParser();
// 最简单的字符串表达式
Expression exp = parser.parseExpression("'HelloWorld'");
System.out.println("'HelloWorld'的结果: " + exp.getValue());
// 调用方法的表达式
exp = parser.parseExpression("'HelloWorld'.concat('!')");
System.out.println("'HelloWorld'.concat('!')的结果: "
+ exp.getValue());
// 调用对象的getter方法
exp = parser.parseExpression("'HelloWorld'.bytes");
System.out.println("'HelloWorld'.bytes的结果: "
+ exp.getValue());
// 访问对象的属性(d相当于HelloWorld.getBytes().length)
exp = parser.parseExpression("'HelloWorld'.bytes.length");
System.out.println("'HelloWorld'.bytes.length的结果:"
+ exp.getValue());
// 使用构造器来创建对象
exp = parser.parseExpression("new String('helloworld')"
+ ".toUpperCase()");
System.out.println("new String('helloworld')"
+ ".toUpperCase()的结果是: "
+ exp.getValue(String.class));
Person person = new Person(1 , "孙悟空", new Date());
exp = parser.parseExpression("name");
// 以指定对象作为root来计算表达式的值
// 相当于调用person.name表达式的值
System.out.println("以persn为root,name表达式的值是: "
+ exp.getValue(person , String.class));
exp = parser.parseExpression("name=='孙悟空'");
StandardEvaluationContext ctx = new StandardEvaluationContext();
// 将person设为Context的root对象
ctx.setRootObject(person);
// 以指定Context来计算表达式的值
System.out.println(exp.getValue(ctx , Boolean.class));
List<Boolean> list = new ArrayList<Boolean>();
list.add(true);
EvaluationContext ctx2 = new StandardEvaluationContext();
// 将list设置成EvaluationContext的一个变量
ctx2.setVariable("list" , list);
// 修改list变量的第一个元素的值
parser.parseExpression("#list[0]").setValue(ctx2 , "false");
// list集合的第一个元素被改变
System.out.println("list集合的第一个元素为:"
+ parser.parseExpression("#list[0]").getValue(ctx2));
}
}

上面程序中的代码使用ExpressionParser多次解析了不同类型的表达式,

  • ExpressionParser调用parseExpression方法将返回一个Expression实例(表达式对象)。
  • 调用Expression对象的getValue方法即可获取该表达式的值。

EvaluationContext代表SpEL计算表达式值的”上下文”,这个Context对象可以包含多个对象,但只能有一个root(根)对象。
EvaluationContext的作用有点类似于前面介绍的OGNL中的StackContext,EvaluationContext可以包含多个对象,但只能有一个root对象。当表达式中包含变量时,SpEL就会根据EvaluationContext中变量的值对表达式进行计算。

往EvaluationContext里面放入变量

为了往EvaluationContext里放入对象(SpEL称之为变量),可以调用该对象的如下方法:
setVariable(String name,Object value):向EvaluationContext中放入value对象,该对象名为name为了在SpEL访问Evaluation context中指定对象,应采用与OGNL类似的格式:

1
#name

StandardEvaluationContext设置root对象

StandardEvaluationContext提供了setRootObject(Object rootObject)法来设置root对象。

SpEL中访问root对象的属性时,可以省略root对象前缀,例如如下代码:

1
foo.bar //访问 rootObject的foo属性的bar属性

当然,使用Expression对象计算表达式的值时,也可以直接指定root对象,例如上面程序中的粗体字代码:

1
exp.getValue(person, String.class) //以`person`对象为`root`对象计算表达式的值

Person.java

上面的程序中使用了一个简单的Person类,它只是一个普通的Java Bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.crazyit.app.domain;
import java.util.Date;
public class Person
{
private Integer id;
private String name;
private Date birth;
// 无参数的构造器
public Person()
{
}
// 初始化全部成员变量的构造器
public Person(Integer id , String name , Date birth)
{
this.id = id;
this.name = name;
this.birth = birth;
}
// 此处省略getter和setter方法
}

7.13 本章小结

本章简要介绍了Spring框架的相关方面,包括Spring框架的起源、背景及大致情况;详细介绍了如何在实际开发中使用Spring框架,以及如何利用Eclipse工具开发Spring应用。本章主要介绍了Spring框架的核心:IoC容器,详细介绍了Spring容器的种种用法。在介绍Spring容器的同时,也介绍了Spring容器中的Bean,介绍了Bean依赖的配置、各种特殊配置等,并详细介绍了Bean之间的继承、生命周期、作用域等知识.
本章也介绍了如何利用XML Schema来简化Spring配置,最后还介绍了Spring的一个重要特性SpEL(Spring表达式语言),SpEL既可单独使用,也可与Spring容器结合使用,用于扩展Spring容器的功能。
下一章将更深入地介绍Spring框架的使用,包括利用Spring loC容器扩展点,还将介绍Spring容器的另一个核心机制:AOP。除此之外,还将重点介绍SpringHibernateStruts2框架的整合。

7.11.3 使用util命名空间简化配置

Spring框架解压缩包的schema\util\路径下包含有util:命名空间的XML Schema文件,为了使用util:命令空间的元素,必须先在Spring配置文件中导入最新的spring-util.xsd,也就是需要在Spring配置配置文件中添加xmlns:util属性该属性值如下:

1
xmlns:util="http://www.springframework.org/schema/util"

,以及在xsi:schemaLocation属性中添加如下值:

1
2
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd

详细如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素和Schema
导入p:命名空间和util:命名空间的元素 -->
<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-4.0.xsd">
......
</beans>

util Schema下的元素

util Schema下提供了如下几个元素。

util:constant元素

<util:constant>元素用于获取指定类的静态Field的值。它是FieldRetrievingFactory Bean的简化配置。

util:property-path元素

<util:property-path>元素用于获取指定对象的getter方法的返回值。它是PropertyPathFactoryBean的简化配置。

util:list元素

<util:list>元素用于定义一个List bean,支持使用<value><ref><bean>等子元素来定义List集合元素。使用该标签支持如下三个属性。

属性 描述
id 该属性指定定义一个名为id的List bean实例。
list-class 该属性指定Spring使用哪个List实现类来创建Bean实例。默认使用ArrayList作为实现类。
scope 指定该List bean实例的作用域。

set元素

set:该元素用于定义一个Set Bean,支持使用<value><ref><bean>等子元素来定义set集合元素。使用该标签支持如下三个属性。

属性 描述
id 该属性指定定义一个名为id的Set bean实例。
set-class 该属性指定Spring使用哪个Set实现类来创建Bean实例。默认使用HashSet作为实现类。
scope 指定该Set bean实例的作用域。

util:map元素

<util:map>元素用于定义一个Map Bean,支持使用<entry>来定义Mapkey-value对。使用该标签支持如下三个属性。

属性 描述
id 该属性指定定义一个名为id的Map Bean实例。
map-class 该属性指定Spring使用哪个Map实现类来创建Bean实例。默认使用HashMap作为实现类
scope 指定该Map Bean实例的作用域。

util:properties元素

<util:properties>:该元素用于加载一份资源文件,并根据加载的资源文件创建一个Properties Bean实例。使用该标签可指定如下几个属性

属性 描述
id 该属性指定定义一个名为idProperties Bean实例。
location 指定资源文件的位置。
scope 指定该Properties Bean实例的作用域

假设有如下的Bean类文件,这份文件需要ListSetMap等集合属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package org.crazyit.app.service.impl;
import java.util.*;
import org.crazyit.app.service.*;
public class Chinese implements Person
{
private Axe axe;
private int age;
private List schools;
private Map scores;
private Set axes;
// 此处省略getter和setter方法,请自己补上
// 实现Person接口的useAxe()方法
public void useAxe()
{
System.out.println(axe.chop());
System.out.println("age属性值:" + age);
System.out.println(schools);
System.out.println(scores);
System.out.println(axes);
}
}

下面使用基于XML Schema的配置文件来简化这种配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素和Schema
导入p:命名空间和util:命名空间的元素 -->
<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-4.0.xsd">
<!-- 配置chinese实例,其实现类是Chinese -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese"
p:age-ref="chin.age" p:axe-ref="stoneAxe"
p:schools-ref="chin.schools"
p:axes-ref="chin.axes"
p:scores-ref="chin.scores"/>
<!-- 使用util:constant将指定类的静态Field定义成容器中的Bean -->
<util:constant id="chin.age" static-field=
"java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
<!-- 使用util.properties加载指定资源文件 -->
<util:properties id="confTest"
location="classpath:test_zh_CN.properties"/>
<!-- 使用util:list定义一个List集合,指定使用LinkedList作为实现类,
如果不指定默认使用ArrayList作为实现类 -->
<util:list id="chin.schools" list-class="java.util.LinkedList">
<!-- 每个value、ref、bean...配置一个List元素 -->
<value>小学</value>
<value>中学</value>
<value>大学</value>
</util:list>
<!-- 使用util:set定义一个Set集合,指定使用HashSet作为实现类,
如果不指定默认使用HashSet作为实现类-->
<util:set id="chin.axes" set-class="java.util.HashSet">
<!-- 每个value、ref、bean...配置一个Set元素 -->
<value>字符串</value>
<bean class="org.crazyit.app.service.impl.SteelAxe"/>
<ref bean="stoneAxe"/>
</util:set>
<!-- 使用util:map定义一个Map集合,指定使用TreeMap作为实现类,
如果不指定默认使用HashMap作为实现类 -->
<util:map id="chin.scores" map-class="java.util.TreeMap">
<entry key="数学" value="87"/>
<entry key="英语" value="89"/>
<entry key="语文" value="82"/>
</util:map>
<!-- 配置steelAxe实例,其实现类是SteelAxe -->
<bean id="steelAxe" class="org.crazyit.app.service.impl.SteelAxe"/>
<!-- 配置stoneAxe实例,其实现类是StoneAxe -->
<bean id="stoneAxe" class="org.crazyit.app.service.impl.StoneAxe"/>
</beans>

上面的配置文件完整地示范了util Schema下的各简化标签的用法。从上面的配置文件可以看出,使用这种简化标签可让Spring配置文件更加简洁

其他常用简化Schema

除此之外,关于Spring其他常用的简化Schema简要说明如下。

Schema 描述
spring-aop.xsd 用于简化Spring AOP配置的Schema
spring-jee.xsd 用于简化SpringJava EE配置的Schema
spring-jms.xsd 用于简化Spring关于JMS配置的Schema
spring-lang.xsd 用于简化Spring动态语言配置的Schema
spring-xsd 用于简化Spring事务配置的Schema