使用SpEL配置应用程序
本章使用的依赖基本被下文件包括:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.samples.service.service</groupId>
<artifactId>SpringAOPTest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<!-- Generic properties -->
<java.version>1.6</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Web -->
<jsp.version>2.3.1</jsp.version>
<jstl.version>1.2</jstl.version>
<servlet.version>3.1.0</servlet.version>
<!-- Spring -->
<spring-framework.version>4.3.10.RELEASE</spring-framework.version>
<!-- Hibernate / JPA -->
<hibernate.version>5.2.10.Final</hibernate.version>
<!-- Logging -->
<logback.version>1.2.3</logback.version>
<slf4j.version>1.7.25</slf4j.version>
<!-- Test -->
<junit.version>4.12</junit.version>
<!-- AspectJ -->
<aspectj.version>1.8.10</aspectj.version>
</properties>
<dependencies>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- Other Web dependencies -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>${jsp.version}</version>
<scope>provided</scope>
</dependency>
<!-- Spring and Transactions -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- Logging with SLF4J & LogBack -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<!-- Test Artifacts -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-framework.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</project>
首先在src/main/resource文件夹下创建上下文配置文件applicationContext.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-4.0.xsd">
<bean id="show1" class="com.wiley.beginningspring.ch9.MyBean">
<property name="message" value="#{systemProperties['user.language']}" />
</bean>
</beans>
此处将类MyBean定义成了名为show1的类,并向其中的message属性通过SpEL注入了系统属性中的用户语言。
接下来创建MyBean类。
public class MyBean {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
然后就可以在Main类中测试配置了。
public class Main {
public static void main(String... args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyBean myBean = context.getBean(MyBean.class);
System.out.println(myBean.getMessage());
}
}
输出结果为zh,用户语言为中文。项目目录结构很简单:
事实上,也可以用注解完成配置,此时的配置类为:
@Configuration
@ComponentScan(basePackages = {"com.wiley.beginningspring.ch9"})
public class ApplicationConfig {
}
而MyBean类需要在类定义时被定义为一个Spring Bean:
@Component
public class MyBean {
@Value("#{systemProperties['user.language']}")
private String message;
public String getMessage() {
return message;
}
}
其中message属性上通过@Value注解使用相同的SpEL语句注入了用户语言的值。
创建一个分析器
SpEL上下文中定义的表达式都应该首先被ExpressionParser解析然后被评估,该分析器对象是线程安全的。默认情况下,表达式模板以‘#’开头,‘}’结尾。分析器对象创建如下:
ExpressionParser parser = new SpELExpressionParser();
创建完分析器实例后就可以用它的parseExpression方法解析一个表达式创建一个表达式实例:
Expression expression = parser.parseExpression("'Hello World'");
然后就可以通过它的getValue方法获得表达式评估的值:
String value = expression.getValue(String.class)
下用 SpEL 解析一个 Hello World:
public class HelloWorldTest {
ExpressionParser parser;
@Before
public void setup() {
parser = new SpelExpressionParser();
}
@Test
public void helloWorldParsedOK() {
Expression expression = parser.parseExpression("'Hello World!'");
String value = expression.getValue(String.class);
assertThat(value, is("Hello World!"));
}
}
测试通过。
通过SpEL调用方法
xml配置中调用方法
项目目录结构为:
首先在src/main/resource文件夹中创建applicationContext.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-4.0.xsd">
<bean id="show1" class="com.wiley.beginningspring.ch9.Show">
<property name="instrument" value="Piano" />
<property name="song" value="Turning Tables" />
</bean>
<bean id="show2" class="com.wiley.beginningspring.ch9.Show">
<property name="instrument" value="Guitar" />
<property name="song" value="#{show2.guitarSong()}" />
</bean>
</beans>
该上下文文件中定义了两个类为Show的Bean。分别用字符串常量和SpEL表达式调用方法注入了值。
然后创建Show类:
public class Show {
private String instrument;
private String song;
public void setInstrument(String instrument) {
this.instrument = instrument;
}
public void setSong(String song) {
this.song = song;
}
public String guitarSong() {
return "More Than Words";
}
public void present() {
System.out.println("Playing " + song + " with instrument " + instrument);
}
}
然后创建Main方法执行程序:
public class Main {
public static void main(String... args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Show show1 = (Show) context.getBean("show1");
show1.present();
Show show2 = (Show) context.getBean("show2");
show2.present();
}
}
运行输出结果:
在字符串上调用方法及链接调用
public class NestedMethodInvocationStringConcatTest {
ExpressionParser parser;
@Before
public void setup() {
parser = new SpelExpressionParser();
}
@Test
public void helloParsedAndConcatenatedWithWorldAndThenLengthMethodInvoked() {
Expression exp = parser.parseExpression("'Hello'.concat(' World!').length()");
Integer value = exp.getValue(Integer.class);
assertThat(value, is(12));
}
}
调用构造函数
public class ConstructorInvocationTest {
ExpressionParser parser;
@Before
public void setup() {
parser = new SpelExpressionParser();
}
@Test
public void constructorInvocationWorksOK() {
Expression exp = parser.parseExpression("new Double(3.141592653589793)");
Double value = exp.getValue(Double.class);
assertThat(value, is(3.141592653589793));
}
}
调用静态方法
public class StaticConstantFieldAccessTest {
ExpressionParser parser;
@Before
public void setup() {
parser = new SpelExpressionParser();
}
@Test
public void staticConstantFieldAccessWorksOK() {
Expression exp = parser.parseExpression("T(java.lang.Math).PI");
Double value = exp.getValue(Double.class);
assertThat(value, is(3.141592653589793));
}
}
使用变量和函数
可以通过context.setVariable("name",...)
注册一个变量到评估上下文StandardEvaluationContext对象中,之后就可以在变量名前加#引用已经注册的变量了。
#root
可以在评估上下文中设置一个根对象,当表达式中遇到未知方法和属性时使用该对象进行查找。
public class RootVariablesTests {
ExpressionParser parser;
@Before
public void setup() {
parser = new SpelExpressionParser();
}
@Test
public void rootVariableRegisteredOK() {
StandardEvaluationContext context = new StandardEvaluationContext();
context.setRootObject(new MyBean());
assertTrue(parser.parseExpression("#root").getValue(context) instanceof MyBean);
}
}
其中MyBean为任意类。
public class MyBean {
}
#this
提供对当前评估过程的引用。
访问一同属性和环境变量
使用前缀@来访问:
String value = parser.parseExpression("@systemEnvironment[JAVA_HOME]").getValue(context, String.class);
String value = parser.parseExpression("@systemProperties['java.version']").getValue(context, String.class);
内联列表
public class InlineListTests {
ExpressionParser parser;
@Before
public void setup() {
parser = new SpelExpressionParser();
}
@Test
public void inlineListCreatedOK() {
List<Integer> value = parser.parseExpression("{1,2,3}").getValue(List.class);
assertThat(value, hasItems(1, 2, 3));
}
@Test
public void inlineListOfListsCreatedOK() {
List<List<Integer>> value = parser.parseExpression("{{1,2},{3,4},{5,6}}").getValue(List.class);
assertThat(value, hasItems(Arrays.asList(1,2), Arrays.asList(3,4), Arrays.asList(5,6)));
}
}
注册函数
除了注册变量外还可以注册函数,并在之后调用。注册方法为context.registerFunction("name",method)
public class FunctionRegistrationTests {
ExpressionParser parser;
@Before
public void setup() {
parser = new SpelExpressionParser();
}
@Test
public void functionRegisteredOK() throws NoSuchMethodException {
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("capitalize",
StringUtils.class.getDeclaredMethod("capitalize", new Class[] { String.class }));
String value = parser.parseExpression("#capitalize('hello')").getValue(context, String.class);
assertThat(value, is("Hello"));
}
}
SpEL运算符
- 关系:
<, >, <=, >=, ==, !=, lt, gt, le, ge, eq, ne
- 算数:
+, -, *, /, %, ^
- 逻辑:
&&, ||, !, and, or, not, between, instanceof
- 条件:
? : (ternary), ? : (elvis)
- 其他类型:
?.(safe navigation), ?[...](selection), , ^[...](first element), $[...](last element)
其中 instantceof 可以用来判定表达式是否为某个类的实例,如"'Hello' instanceof T(String)"
返回一个true值的Boolean变量。
安全导航运算符用于在嵌套属性上进行导航,使未初始化的属性返回null值而不是抛出SpelEvaluationException。如
public class SafeNavigationOperatorsTest {
ExpressionParser p;
@Before
public void setup() {
p = new SpelExpressionParser();
}
@Test
public void safeNavigationOperatorsWorkOK() {
Employee employee = new Employee("Mert");
StandardEvaluationContext context = new StandardEvaluationContext(employee);
assertThat(p.parseExpression("Address?.Name").getValue(context, String.class), is(nullValue()));
}
}
利用之前提到的#this还可以进行集合选择与投影将其转换为另一个集合。
@Test
public void collectionSelectedOK() {
StandardEvaluationContext context = new StandardEvaluationContext();
context.setRootObject(Arrays.asList(1,2,3,4,5,6,7,8,9));
List<Integer> evenNumbers = parser.parseExpression("#root.?[#this%2 == 0 ?: false]").getValue(context, List.class);
assertThat(evenNumbers, hasItems(2, 4, 6, 8));
}
其中#this用来遍历集合中元素。
还可以通过![…]将一个集合投影到另一个集合,如下:
public class Worker {
private String name;
private Country birthPlace;
public Worker(String name, Country birthPlace) {
this.name = name;
this.birthPlace = birthPlace;
}
public String getName() {
return name;
}
public Country getBirthPlace() {
return birthPlace;
}
}
public enum Country {
TR,
USA,
DE
}
投影Worker到Country:
@Test
public void collectionProjectedOK() {
StandardEvaluationContext context = new StandardEvaluationContext();
context.setRootObject(Arrays.asList(
new Worker("Mert", Country.DE),
new Worker("Funda", Country.TR),
new Worker("Tugce", Country.USA)));
List<Country> birthPlaces = parser.parseExpression("#root.![#this.birthPlace]").getValue(context, List.class);
assertThat(birthPlaces, hasItems(Country.TR, Country.USA, Country.DE));
}
使用SpEL中的实用工具
访问Spring Bean
在Bean名称前添加@来访问
public class SpringBeanAccessTests {
ExpressionParser parser;
@Before
public void setup() {
parser = new SpelExpressionParser();
}
@Test
public void springBeanAccessWorksOK() {
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(new AnnotationConfigApplicationContext(ApplicationConfig.class)));
Expression exp = parser.parseExpression("@myBean.sayHello()");
String value = exp.getValue(context, String.class);
assertThat(value, is("Hello!"));
}
}
@Component
public class MyBean {
public String sayHello() {
return "Hello!";
}
}
@Configuration
@ComponentScan(basePackages = {"com.wiley.beginningspring.ch9"})
public class ApplicationConfig {
}
<spring:eval>
使用spring.tld中的该标签可以将评估值显示到JSP页面或为变量分配值。
<body>
<spring:eval expression = "@MyBean.sayHi()"/>
</body>