SpEL表达式
简介
spel是spring表达式(Spring Expression Language,简称SpEL),
SpEL有许多特性:
- 使用Bean的ID来引用Bean
- 可调用方法和访问对象的属性
- 可对值进行算数、关系和逻辑运算
- 可使用正则表达式进行匹配
- 可进行集合操作
版本
spring3
用法&环境搭建
用法有三:1.在注解@Value里面
如
public class EmailSender {
@Value("${spring.mail.username}")
private String mailUsername;
@Value("#{ systemProperties['user.region'] }")
private String defaultLocale;
//...
}
2.是于xml配置
3.是代码中使用Expression
有些Spring CVE漏洞就是基于Expression形式的SpEL表达式注入
spel+xml联动
SpelXMLtest.java
package com.example.demo.spelTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpelXMLtest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
}
}
HelloWorld.java
package com.example.demo.spelTest;
public class HelloWorld {
private String message;
public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
}
Beans.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-3.0.xsd ">
<bean id="helloWorld" class="com.example.demo.spelTest.HelloWorld">
<property name="message" value="#{'thai'} is #{233}" />
</bean>
</beans>
有new ClassPathXmlApplicationContext就可以 context.getBean
spel SpelExpressionParser()
这种又称Spel表达式解析器(直译)
String exp = "T(Runtime).getRuntime().exec('calc')";
// String exp = "new java.util.Date()";
SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
Expression expression = spelExpressionParser.parseExpression(exp);
System.out.println(expression.getValue());
骨架就是
ExpressionParser parser = new SpelExpressionParser();
Class<String> result1 = parser.parseExpression(xxxxxxxx).getValue(Class.class);
使用这种无需\#{}
在注解@Value里面
待复现
语法
符号
#{}
界定符
位于大括号内的内容将被认为是spel表达式
${}
主要用于加载外部属性文件中的值
spel操作Bean
-
引用Bean
#{}
符号中写入Bean的名称即可
比如说Beans.xml (已复现)
<!--原来的写法,通过构造函数实现依赖注入-->
<!--<constructor-arg ref="test"/>-->
<constructor-arg value="#{test}"/>
复现demo
SpellChecker
package com.example.demo.spelTest;
public class SpellChecker {
public SpellChecker(){
System.out.println("Inside SpellChecker constructor." );
}
public void checkSpelling() {
System.out.println("Inside checkSpelling." );
}
}
TextEditor
package com.example.demo.spelTest;
public class TextEditor {
private SpellChecker spellChecker;
public TextEditor(SpellChecker spellChecker) {
System.out.println("Inside TextEditor constructor." );
this.spellChecker = spellChecker;
}
public void spellCheck() {
spellChecker.checkSpelling();
}
}
SpelXMLtest
package com.example.demo.spelTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpelXMLtest {
public static void main(String[] args) {
// ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
// HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
// obj.getMessage();
ApplicationContext context = new ClassPathXmlApplicationContext("Beans2.xml");
TextEditor te = (TextEditor) context.getBean("textEditor");
te.spellCheck();
}
}
就是说
<!--<constructor-arg ref="spellChecker"/>-->
<constructor-arg value="#{spellChecker}"/>
这个也可以访问到该对象,然后依旧是用getBean调函数证明
- 引用类属性
通过bean名称.属性
访问(已复现)
<!-- <property name="message" value="#{'thai'} is #{233}" />-->
<property name="message" value="#{helloWorld.message}" />
效果等价于helloWorld.setMessage(helloWorld.getMessage())
-
引用类方法
通过bean名称.方法名()
调用(未复现)
<property name="song" value="#{SongSelector.selectSong()}"/>
可以调用toUpperCase()
方法:
<property name="song" value="#{SongSelector.selectSong().toUpperCase()}"/>
避免遇到null的情况可以这样写
<property name="song" value="#{SongSelector.selectSong()?.toUpperCase()}"/>
T(Type)
T(xxx),这个xxx是包,需要写完整路径(类全限定名),只有”java.lang”包因为Spel内置可以不用写完整路径
比如
#{T(java.lang.Math).random()}
// 获取0~1的随机数
一些demo
ExpressionParser parser = new SpelExpressionParser();
// java.lang 包类访问
Class<String> result1 = parser.parseExpression("T(String)").getValue(Class.class);
System.out.println(result1);
//其他包类访问
String expression2 = "T(java.lang.Runtime).getRuntime().exec('calc')";
Class<Object> result2 = parser.parseExpression(expression2).getValue(Class.class);
System.out.println(result2);
//类静态字段访问
int result3 = parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class);
System.out.println(result3);
//类静态方法调用
int result4 = parser.parseExpression("T(Integer).parseInt('1')").getValue(int.class);
System.out.println(result4);
运算符
SpEL提供了以下几种运算符:
运算符类型 | 运算符 |
---|---|
算数运算 | +, -, *, /, %, ^ |
关系运算 | <, >, ==, <=, >=, lt, gt, eq, le, ge |
逻辑运算 | and, or, not, ! |
条件运算 | ?:(ternary), ?:(Elvis) |
正则表达式 | matches |
加法运算:
<property name="add" value="#{counter.total+42}"/>
加号还可以用于字符串拼接:(用于绕waf)
<property name="blogName" value="#{my blog name is+' '+mrBird }"/>
^
运算符执行幂运算,其余算数运算符和Java一毛一样,这里不再赘述。
运算符 | 符号 | 文本类型 |
---|---|---|
等于 | == | eq |
小于 | < | lt |
小于等于 | <= | le |
大于 | > | gt |
大于等于 | >= | ge |
如:
<property name="eq" value="#{counter.total le 100}"/>
逻辑运算
使用and运算符:
<property name="largeCircle" value="#{shape.kind == 'circle' and shape.perimeter gt 10000}"/>
两边为true时才返回true。
其余操作一样,只不过非运算有not
和!
两种符号可供选择。非运算:
<property name="outOfStack" value="#{!product.available}"/>
条件运算
Java的三目运算符:
<property name="instrument" value="#{songSelector.selectSong() == 'May Rain' ? piano:saxphone}"/>
正则表达式
验证邮箱demo:
<property name="email" value="#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}"/>
集合操作
访问
表达式可以操作集合,下面以取出对象列表的一号元素为例
类Bean
City
package com.example.demo.spelTest;
public class City {
private String name;
private String state;
private int population;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public int getPopulation() {
return population;
}
public void setPopulation(int population) {
this.population = population;
}
}
代理类
ChoseCity
package com.example.demo.spelTest;
public class ChoseCity {
private City city;
public void setCity(City city) {
this.city = city;
}
public City getCity() {
return city;
}
}
Beans3.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"
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-3.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<util:list id="cities">
<bean class="com.example.demo.spelTest.City" p:name="Chicago"
p:state="IL" p:population="2853114"/>
<bean class="com.example.demo.spelTest.City" p:name="Atlanta"
p:state="GA" p:population="537958"/>
<bean class="com.example.demo.spelTest.City" p:name="Dallas"
p:state="TX" p:population="1279910"/>
<bean class="com.example.demo.spelTest.City" p:name="Houston"
p:state="TX" p:population="2242193"/>
<bean class="com.example.demo.spelTest.City" p:name="Odessa"
p:state="TX" p:population="90943"/>
<bean class="com.example.demo.spelTest.City" p:name="El Paso"
p:state="TX" p:population="613190"/>
<bean class="com.example.demo.spelTest.City" p:name="Jal"
p:state="NM" p:population="1996"/>
<bean class="com.example.demo.spelTest.City" p:name="Las Cruces"
p:state="NM" p:population="91865"/>
</util:list>
<bean id="choseCity" class="com.example.demo.spelTest.ChoseCity">
<property name="city" value="#{cities[0]}"/>
</bean>
</beans>
先创建一堆对象,并存入list中
在value="#{cities[0]}"
使用spel表达式取出列表的元素
我们写一个main触发一下
package com.example.demo.spelTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ListFucker {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans3.xml");
ChoseCity obj = (ChoseCity) context.getBean("choseCity");
System.out.println(obj.getCity().getName());
}
}
随机选择City可以这样:中括号[]
运算符始终通过索引访问集合中的成员:
<property name="city" value="#{cities[T(java.lang.Math).random()*cities.size()]}"/>
假设City对象以其名字作为键放入Map集合中,在这种情况下,我们可以像下面那样获取键为Dallas的entry:
<property name="chosenCity" value="#{cities['Dallas']}"/>
[]
运算符的另一种用法是从java.util.Properties集合中取值。例如,假设我们需要通过<util:properties>
元素在Spring中加载一个properties配置文件:
<util:properties id="settings" loaction="classpath:settings.properties"/>
[]
运算符同样可以通过索引来得到某个字符串的某个字符,例如下面的表达式将返回s:
'This is a test'[3]
查询
SpEL表达式中提供了查询运算符来实现查询符合条件的集合成员:
.?[]
:返回所有符合条件的集合成员;.^[]
:从集合查询中查出第一个符合条件的集合成员;.$[]
:从集合查询中查出最后一个符合条件的集合成员;
复现一下,
修改以下几个文件
ChoseCity.java
public class ChoseCity {
private List<City> city;
public List<City> getCity() {
return city;
}
public void setCity(List<City> city) {
this.city = city;
}
}
让其返回list,因为查询的结果往往比较多
beans3.xml
<bean id="choseCity" class="com.example.demo.spelTest.ChoseCity">
<property name="city" value="#{cities.?[population gt 100000]}"/>
</bean>
查询所有符合条件的,人口大于100000的城市
main
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans3.xml");
ChoseCity obj = (ChoseCity) context.getBean("choseCity");
for (City city:obj.getCity())
System.out.println(city.getName());
}
集合投影(取出后归类)
集合投影就是从集合的每一个成员中选择特定的属性放入到一个新的集合中。SpEL的投影运算符.![]
完全可以做到这一点。
例如,我们仅需要包含城市名称的一个String类型的集合:
<property name="cityNames" value="#{cities.![name]}"/>
得到城市名字加州名的集合:
<property name="cityNames" value="#{cities.![name+','+state]}"/>
把符合条件的城市的名字和州名作为一个新的集合:
<property name="cityNames" value="#{cities.?[population gt 100000].![name+','+state]}"/>
变量
在SpEL表达式中,变量定义通过EvaluationContext类的setVariable(variableName, value)函数来实现;
除此之外还有这几个
#this
:使用当前正在计算的上下文;#root
:引用容器的root对象
SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext("tha1");
context.setVariable("variable", "fucker");
String result1 = spelExpressionParser.parseExpression("#variable").getValue(context, String.class);
String result2 = spelExpressionParser.parseExpression("#root").getValue(context, String.class);
String result3 = spelExpressionParser.parseExpression("#this").getValue(context, String.class);
System.out.println(result1);
System.out.println(result2);
System.out.println(result3);
关键词
instanceof
SpEL 支持 instanceof 运算符,跟 Java 内使用同义;如”‘haha’ instanceof T(String)”将返回 true
自定义函数
SpEL使用StandardEvaluationContext的registerFunction方法进行注册自定义函数,或者使用setVariable
自定义函数userFunc
package com.example.demo.spelTest;
public class UserFunc {
public static String reverseString(String input) {
StringBuilder backwards = new StringBuilder();
for (int i = 0; i < input.length(); i++) {
backwards.append(input.charAt(input.length() - 1 - i));
}
return backwards.toString();
}
}
main
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("reverseString", UserFunc.class.getDeclaredMethod("reverseString", new Class[] { String.class }));
String helloWorldReversed = parser.parseExpression("#reverseString('I am thai')").getValue(context, String.class);
System.out.println(helloWorldReversed);