表达式语言
预讲知识点
1.表达式语言的基本操作形式以及处理流程;
2.在Spring之中各种表达式字符串的编写。
具体内容
SpEL(Spring Expression Language, Spring的表达式语言)这种语言类似于JSP中学习的EL,但是在整个的Spring之中其表达式语言要更加复杂,而且支持度更加的广泛,最为重要的是,它还可以进行方法调用、对象实例化、集合操作等。但是唯一的难点:代码复杂,表达式复杂。
在整个的讲解之中,一定要更加深刻的领悟————Spring中针对于字符串的改进之处。
5.1、表达式入门
在讲解具体的操作之前首先来观察一下,什么叫表达式以及表达式到底怎么操作
范例:表达式操作
·基础参考(如果不使用表达式实现同样的功能)
public static void main(String[] args) {
String str = ("hello"+"world").substring(5,9);
System.out.println(str);
}
这种实现方式使用的是硬编码的操作形式完成的,必须有Spring类对象,而后才可以利用“对象.方法()“实现
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
String pstr="(\"hello\" + \"world\").substring(5,9)";
ExpressionParser parser = new SpelExpressionParser();//定义解析器
Expression expression = parser.parseExpression(pstr);//解析给出的字符串表达式
EvaluationContext context = new StandardEvaluationContext();
System.out.println(expression.getValue(context));
输出结果
worl
通过执行可以返现最为神奇的地方在于,整个Spring的表达式操作之中可以将一个完全的字符串变为了可以用于程序执行的语句,当然,这一系列的执行语句需要有一系列的支持类完成,但是至少可以发现,字符串的功能又一次被加强了。在本程序之中给出了如下的程序类
1、表达式解析器:org.springframework.expression.Expression
·主要负责根据给定的表达式字符串内容对解析操作进行处理;
2、解析器处理类:org.springframework.expression.spel.standard.SpelExpressionParser
·ExpressionParser本身只是一个操作的标准,但是它对应的处理类必须单独设置,本次的是Spel的标准处理;
3、表达式:org.springframework.expression.Expression
·将字符串根据指定的解析器进行解析,而后使用这个生成表达式;
4、设置表达式的一些属性信息:org.springframework.expression.EvaluationContext
·因为表达式的操作之中可能会存在有某些占位符需要进行处理
范例:定义操作的参数
//1。定义要操作的表达式,使用者更多关注此部分。
String pstr="(\"hello\" + \"world\").substring(#start,#end)";
//2.要定义一个表达式解析器,本次使用的是SpEL表达式
ExpressionParser parser = new SpelExpressionParser();//定义解析器
//3。使用特定的解析器来处理指定的字符串
Expression expression = parser.parseExpression(pstr);//解析给出的字符串表达式
//4。定义相关环境属性
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("start",5);
context.setVariable("end",9);
System.out.println(expression.getValue(context));
输出结果
worl
现在就可以正常使用一个表达式了,同时也可以发现,字符串的功能继续加强。
5.2、表达式的处理原理
需要明确的是,在使用表达式的过程之中,我们除了可以利用字符串数据操作之外,也可以进行数学的操作
范例:进行数学操作
String pstr="1+2";
ExpressionParser parser = new SpelExpressionParser();//定义解析器
Expression expression = parser.parseExpression(pstr);//解析给出的字符串表达式
EvaluationContext context = new StandardEvaluationContext();
System.out.println(expression.getValue(context));
输出结果
3
可以发现除了编写字符串之外还可以编写数字,甚至各种字符串的数据,那么如果是我们自己编写这种表达式,那么一定要首先对其一个判断,判断表达式应该由那些组成,而后要拆分组成的内容,最后要进行字符串的相关数据类型的转换,从而得到最终的结果。
图片
1、首先必须明确的按照指定的结构要求定义出表达式,例如“1+2”;
2、随后需要准备出SpEL的表达式解析器,而进行解析的时候要按照如下的步骤进行
·使用一个专门的断词器将给定的表达式字符串拆分成为Spring可以认可的数据格式;
·将随后要根据断词器处理的操作结果生成相应的语法结构;
·并且在这一过程之中就需要进行表达式的对错检查;
3、将已经处理后的表达式定义到一个专门的表达式对象之中等待进行最终的结果计算
4、但是考虑到表达式里面可能会存在有部分的占位变量的内容,所以在进行表达式计算之前需要设置一个表达式上下文对象进行占位变量内容处理;
5、最后设置好了变量内容,并且利用表达式对象就可以计算出表达式最终所执行的结果。
5.3、自定义分隔符
任何的表达式其组成之中一定会包含有相应的边界形式,例如:jsp中的EL里面使用“${表达式}",其中给定的“${“作为边界开始,而“}"作为边界结束,而在Spring里面如果用户有
其中给定的org.springframework.expression.ParserContext就是由用户自己来设置边界符的。有如下的几个方法:
·是否使用此模版:public boolean isTemplate();
·边界开始符号:public String getExpressionPrefix();
·边界结束符号:public String getExpressionSuffix();
范例:定义自定义表达式边界
public static void main(String[] args) {
//1。定义要操作的表达式,使用者更多关注此部分。
String pstr="#[1+2]";
//2.要定义一个表达式解析器,本次使用的是SpEL表达式
ExpressionParser parser = new SpelExpressionParser();//定义解析器
//3。使用特定的解析器来处理指定的字符串
Expression expression = parser.parseExpression(pstr, new ParserContext() {//匿名内部类
@Override
public boolean isTemplate() {
return true;
}
@Override
public String getExpressionPrefix() {
return "#[";
}
@Override
public String getExpressionSuffix() {
return "]";
}
});//解析给出的字符串表达式
//4。定义相关环境属性
EvaluationContext context = new StandardEvaluationContext();
System.out.println(expression.getValue(context));
}
此时在进行表达式解析的过程之中,所出现的边界都会自动的忽略掉。
5.4、基本表达式
基本表达式本身本身非常容易理解,只是提供了一些简单的数学计算
、逻辑计算等应用。
5.4.1、字面表达式
表达式 范例 结果
字符串 'hello ' + 'world' hello world
\"hello \" + \"world\" hello world
数值型 “1” 1
“1.2” 1.2
“1.2E10". 12000000000
布尔型 “true“ true
null ”null“ null
5.4.2、数学表达式
表达式 范例 结果
四则运算 “1+2*3/6” 2
求模 “10%3” 1
”10 MOD 3" 1
幂运算 "2^3" 8
除法 “10 DIV 2” 5
5.4.3、关系表达式
表达式 范例 结果
等于 “1==2” false
“1EQ2” false
不等于 “1!=2" true
"1NQ2" true
大于 "10>2" true
"10GT2" true
大于等于 “10>=2" true
"10GE2" true
小于 "10<2" false
"10LT2" false
小于等于"10<=2" false
"10LE2" false
区间 “10 BETWEEN {5,20}" true
5.4.4、逻辑表达式
逻辑表达式只有三种方式
表达式 范例 结果
与 “'a'EQ'a' && 10<5" false
“'a'EQ'a' AND 10<5" false
或 “'a'EQ'a' || 10<5" true
“'a'EQ'a' OR 10<5" true
非 “'a'EQ'a' && !(10<5)" true
5.4.5、字符串表达式
在Spring类中所有的操作方法都是在开发之中最为常见的。
表达式 范例 结果
连接 “‘hello'.concat('world')" helloworld
取内容 "'Hello'[1]" e
替换 “‘hello’.replaceAll('l','_')" he__o
只要是String类的操作记熟练,那么基本上这些的操作都可以直接在表达式上写出来
5.4.6三目运算符
三目运算符的重要性是不言而喻的,而且可以发现,使用了三目之后可以节约不少的代码空间。
表达式 范例 结果
三目 “1>2?'hello' :'world'" world
NULL处理 “null==null?'hello':'world'" hello
“null ?: 'world'" worldj(将null作为false)
true操作 "true ?'hello':'world'" hello
"true ?:'world" true
5.4.7、正则运算
理论上而言,正则运算应该属于String
表达式 范例 结果
正则 “‘100’ matches '\\d{3}'" true
“‘100’.matches('\\d{3}')" true
使用哪种方式都表示正则的操作
5.4.8、括号表达式
利用括号表达式可以实现优先级的改变
表达式 范例 结果
括号 "(1+2)/3" 1
5.5、Class类型表达式
之前所见到的都是最基础的表达式,其与程序的结构式相同,但是在Spring里面对于Class反射机制也有自己的表达式处理。
范例:获取Class类型的对象
public static void main(String[] args) {
String pstr="T(String)";
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(pstr);
EvaluationContext context = new StandardEvaluationContext();
Class<String> cls = expression.getValue(context,Class.class);
System.out.println(cls);
}
表达式 范例 结果
class "T(String)" class java.lang.String
"T(java.util.Date)" class java.util.Date
静态属性 "T(Integer).MAX_VALUE" 2147483647
静态方法 "T(Integer).parseInt('123')" 123
实例化对象(无参操作) "new java.util.Date()" Tue Feb 26 14:58:44 CST 2019
实例化对象(有参操作) "new String('hello')" hello
instanceof 'hello' instanceof T(String) true
使用“T(类)”的形式可以取得一个指定泛型类型的Class类的对象
如果要调用静态属性则使用“T(类型).静态属性名称”
public static void main(String[] args) {
String pstr="T(Integer).MAX_VALUE";
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(pstr);
EvaluationContext context = new StandardEvaluationContext();
System.out.println(expression.getValue(context));
}
既然静态属性可以调用,也可以调用静态方法,例如在Integer类里面存在的parseInt()可以接受一个字符串同时返回一个int型的数据。
虽然给出了静态操作,但是严格来讲使用最多的情况一定是类产生实例化对象,那么此处依然可以使用同样的方式完成,可以直接使用"new 类型()"的方式来实例化对象,可以进行有参或无参构造方法
对象的开发过程之中,也可以进行实例化对象类型的判断
利用字符串完整的实现了反射机制的各种操作。当然,它的操作不可能很智能化,只能够处理很简单的功能。
5.6、变量操作
在表达式里面所有的操作都是可以变量的形式出现的,但是一般情况下只会出现在基本的操作之中
范例:观察变量的定义
String pstr="#myvar";
context.setVariable("myvar","hello");
输出信息
hello
如果要想设置变量则必须依靠“EvaluationContext“类完成,而在“StandardEvaluationContext”的构造方法上也可以接收一个变量:StandardEvaluationContext(java.lang.Object rootObject),这个构造方法表示的是设置根变量的内容,整个的变量体系之中会自动存在一个“#root”的根变量
范例:观察根变量的操作
String pstr="#root";
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(pstr);
EvaluationContext context = new StandardEvaluationContext(“edu”);
System.out.println(expression.getValue(context));
输出结果
edu
现在的程序并没有针对于根变量调用“context.setVariable("root","edu");",那么也就是说此时根变量的设置,直接通过构造方法传递到程序之中。
实际上设置的变量可以做一些基础的表达式的操作处理。
范例:进行比较
String pstr="#root =='edu' ? 'hello' : 'world'";
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(pstr);
EvaluationContext context = new StandardEvaluationContext("edu");
System.out.println(expression.getValue (context));
输出结果
hello
可能大部分情况下见到最多的都是使用一些自定义的变量进行使用。(区分大小写)
实际上如果进一步去研究表达式可以发现,它还可以针对于方法进行引用操作。
下面将“Integer.parseInt()"方法设置为"myInt()"的引用
范例:实现方法引用
String pstr="#root =='edu' ? 'hello' : 'world'";
//找到Integer.parseInt()z这个操作方法的对象
Method method = Integer.class.getMethod("parseInt", String.class);//定义解析器
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#myInt('123')");//利用引用方法转换处理
//利用EvalutionContext类进行方法的引用注册
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("myInt",method);//方法进行引用
System.out.println(expression.getValue (context));
输出结果
123
使用myInt方法在表达式之中就相当于调用Integer.parseInt()方法实现了字符串于int之间的转型操作。
实际上除了以上的基本调用之外,还可以利用表达式的特征调用类中的属性,
范例:调用属性
public static void main(String[] args) throws Exception{
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("time");
EvaluationContext context = new StandardEvaluationContext(new Date());
System.out.println(expression.getValue (context));
}
输出结果
1551179326776
此时将java.util.Date类型的对象设置到了根变量中,所以一点表达式之中初夏了“time”单词,就表示要调用getTime()方法,需要特别提醒的是,这个时候表达式不区分大小写(只有开头不区分,其他保持小写)。
但是这种调用本身有风险,此时的调用必须有一个前提:根变量有内容,那么如果根变量为空呢?
范例:观察根变量为空的情况
public static void main(String[] args) throws Exception{
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("time");
EvaluationContext context = new StandardEvaluationContext();
System.out.println(expression.getValue (context));
}
输出信息
Exception in thread "main"……Property or field 'time' cannot be found on null
如果此时使用的是此类取得属性信息的话,它是不能进行访问的,因为根变量为空就会出现异常,这个时候最好的解决方式不是增加什么判断,而是使用Groovy安全导航操作,利用Groovy安全运算符号避免空异常。
范例:使用”?."访问属性
public static void main(String[] args) throws Exception{
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#root?.time");
EvaluationContext context = new StandardEvaluationContext();
System.out.println(expression.getValue (context));
}
输出信息
null
如果此时根变量的内容为空,那么则返回“null”,如果现在不为空,那么就可以返回具体的操作内容。
以上所有的操作变量都是在程序之中直接定义的,那么也可以引用applicationContext.xml文件里面定义的内容。
范例:观察引用配置中的变量
·定义一个Message类,里面只有一个属性,同时提供好setter和getter
public class Message {
private String info;
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
现在msg对象中的info属性里面是包含有配置内容的,随后希望可以在表达式里买呢去引用这部分操作内容。
·随后在applicationContext.xml文件中配置这个类对象;
<bean id="message" class="cn.edu.vo.Message">
<property name="info" value="HELLOWORLD"/>
</bean>
·引用配置内容,如果要进行导入外部配置,使用“@名称.方法()"
public static void main(String[] args) throws Exception{
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("@message.getInfo()");
StandardEvaluationContext context = new StandardEvaluationContext(new Date());
context.setBeanResolver(new BeanFactoryResolver(ctx));//将整个配置文件的读取交给上下文
System.out.println(expression.getValue(context));
}
输出信息
HELLOWORLD
就相当于此时,所有在外部配置的对象可以直接在表达式之中使用,并且利用表达式的语法调用对象的所提供的方法。
5.7、集合表达式
只要是开发框架的操作,永远都不可避免的要去进行集合数据的操作处理,在之前一直强调过,Spring认为数组和List集合是等价的,所以如果要想操作List集合,利用“{内容,内容}"的形式就可以完成。
范例:
public static void main(String[] args) throws Exception{
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("{10,20,30}");
EvaluationContext context = new StandardEvaluationContext(new Date());
List<Integer> all = expression.getValue(context,List.class);
System.out.println(all);
}
输出结果
[10, 20, 30]
如果只是定义一个空的List集合,那么就不设置内容,例如“{}“。需要记住的是,此时的List集合严格来讲相当于使用了Collections,这个工具类下面可以创建空集合,但是许多的方法都是不支持实现的。
当然,如果现在真定义除了集合,也可以利用表达式采用索引的方式进行
范例:索引访问集合操作
"{10,20,30}[1]"
输出结果
20
正常来讲,如果真的要进行开发操作,往往都可以将集合设置为操作的变量进行处理
范例:设置集合内容
public static void main(String[] args) throws Exception{
List<String> all = new ArrayList<String>();
all.add("edu");
all.add("edu.com");
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#allData[1]");
EvaluationContext context = new StandardEvaluationContext(new Date());
context.setVariable("allData",all);//设置allData的值
System.out.println(expression.getValue(context));
}
显示内容
edu.com
以上的操作设置的是List集合,既然是集合,那么Set集合也一定可以设置。
范例:观察Set集合的配置
Set<String> all = new HashSet<String>();
显示内容
edu.com
除了List和Set集合之外,Map集合一定是不能少的集合。
范例:Map集合操作
Map<Integer,String> all = new HashMap<Integer, String>();
all.put(1,"edu");
all.put(2,"edu.com");
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#allData[1]");
显示内容
edu
数据的设置应当对应
除了数据的设置之外,还可以进行数据的修改操作。
范例:修改List集合
List<String> all = new ArrayList<String>();
all.add("edu");
all.add("edu.com");
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#allData[1]='www.edu.com'");
EvaluationContext context = new StandardEvaluationContext(new Date());
context.setVariable("allData",all);//设置allData的值
System.out.println(expression.getValue(context));
System.out.println(all);
输出结果
www.edu.com
[edu, www.edu.com]
范例:修改Map集合
Map<Integer,String> all = new HashMap<Integer, String>();
all.put(1,"edu");
all.put(2,"edu.com");
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#allData[1]='www.edu.com'");
EvaluationContext context = new StandardEvaluationContext(new Date());
context.setVariable("allData",all);//设置allData的值
System.out.println(expression.getValue(context));
System.out.println(all);
输出信息
www.edu.com
{1=www.edu.com, 2=edu.com}
实际上在Spring又考虑到集合数据的批量处理问题,所以此处也可以针对集合数据进行处理。
范例:处理List集合
List<String> all = new ArrayList<String>();
all.add("edu");
all.add("edu.com");
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#allData.!['你好'+#this]");
EvaluationContext context = new StandardEvaluationContext(new Date());
context.setVariable("allData",all);//设置allData的值
System.out.println(expression.getValue(context));//创建了一个新的List集合
System.out.println(all);
输出结果
[你好edu, 你好edu.com]
[edu, edu.com]
现在处理完成之后改变的并不是已有的集合,已有的集合不会发生变化。
修改完成之后相当于重新创建了一个新的List集合。
在整个过程中不要忘记,Map集合也可以进行处理。
范例:处理Map集合
Map<Integer,String> all = new HashMap<Integer, String>();
all.put(1,"edu");
all.put(2,"edu.com");
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#allData.![#this.key + '-' + #this.value]");
EvaluationContext context = new StandardEvaluationContext(new Date());
context.setVariable("allData",all);//设置allData的值
System.out.println(expression.getValue(context));
输出信息
[1-edu, 2-edu.com]
在表达式之中将Map中的key与value进行混合的处理,所以处理完的就是单值的数据也就形成了新的List集合的操作数据。
是加上在整个表达式的集合操作之重也提供有数据的筛选操作支持。
范例:筛选操作
Map<Integer,String> all = new HashMap<Integer, String>();
all.put(1,"edu");
all.put(2,"edu.com");
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("#allData.?[#this.key == 1]");
EvaluationContext context = new StandardEvaluationContext(new Date());
context.setVariable("allData",all);//设置allData的值
System.out.println(expression.getValue(context));
输出结果
{1=edu}
若为"#allData.?[#this.key.contains('1')]"
则筛选的类型为字符型,都是筛选key中的值
此时应当注意如果为?则表示筛选,!表示输出,#表示引用
整个筛选的过程里面就可以进行各种类方法的调用(主要是String类的支持方法)。
5.8、实际使用:配置文件
整个Spring的核心就是一个配置文件(增加了Annotation),所以只有将表达式应用在配置文件上才会特别的有意义。
范例:利用配置文件编写表达式应用
<bean id="str" class="java.lang.String">
<constructor-arg value="HELLOWORLD"/>
</bean>
<bean id="msg" class="cn.edu.vo.Message">
<property name="info" value="#{str.substring(0,5)+'!!!'}"/>
</bean>
测试程序
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Message message = ctx.getBean("msg", Message.class);
System.out.println(message.getInfo());
}
输出结果
HELLO!!!
表达式分解符#{}
可以发现所有的处理都是围绕字符串进行的,但是后面有表达式的支持。
总结
1.Spring的字符串支持的确是最好的,利用表达式可以大量简化代码的编写。
2.Spring的核心理念就在于利用字符串来解决一切的程序开发问题。