使用SpEL配置应用程序
本章使用的依赖基本被下文件包括:
4.0.0 org.springframework.samples.service.service SpringAOPTest 0.0.1-SNAPSHOT war 1.6 UTF-8 UTF-8 <jsp.version>2.3.11.2 3.1.0 4.3.10.RELEASE 5.2.10.Final 1.2.3 1.7.25 4.12 <aspectj.version>1.8.10org.springframework spring-webmvc ${spring-framework.version} javax.servlet jstl ${jstl.version} javax.servlet javax.servlet-api ${servlet.version} provided javax.servlet.jsp javax.servlet.jsp-api ${jsp.version} provided org.springframework spring-tx ${spring-framework.version} org.springframework spring-aop ${spring-framework.version} org.springframework spring-core ${spring-framework.version} org.springframework spring-beans ${spring-framework.version} org.springframework spring-context ${spring-framework.version} org.springframework spring-expression ${spring-framework.version} org.slf4j slf4j-api ${slf4j.version} compile ch.qos.logback logback-classic ${logback.version} runtime org.hibernate hibernate-entitymanager ${hibernate.version} org.springframework spring-test ${spring-framework.version} test junit junit ${junit.version} test org.aspectj aspectjweaver ${aspectj.version}
首先在src/main/resource文件夹下创建上下文配置文件applicationContext.xml
此处将类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文件。
该上下文文件中定义了两个类为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() { Listvalue = parser.parseExpression("{1,2,3}").getValue(List.class); assertThat(value, hasItems(1, 2, 3)); } @Test public void inlineListOfListsCreatedOK() { List > 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)); ListevenNumbers = 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))); ListbirthPlaces = 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.tld中的该标签可以将评估值显示到JSP页面或为变量分配值。