Spring 表達式語言簡稱爲 SpEL,一種相似 Ognl 的對象圖導航語言(對於 ognl 不熟悉的同窗能夠參考一下: Ognl 系列博文)java
SeEL 爲 Spring 提供了豐富的想象空間,除了一些基本的表達式操做以外,還支持git
如下內容均來自官方文檔: docs.spring.io/spring-fram…github
Spel 支持strings, numeric values (int, real, hex), boolean, and null
等基本類型,實例以下正則表達式
ExpressionParser parser = new SpelExpressionParser();
// evals to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
// double 類型
double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
// evals to 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
Object nullValue = parser.parseExpression("null").getValue();
複製代碼
請注意,字符串須要用單引號包括,浮點數默認爲 double 類型,用null
表示null object
spring
輸出結果express
str: Hello World
double: 6.0221415E23
int: 2147483647
bool: true
null: null
複製代碼
經過{}
來代表 List 表達式,一個空的列表直接用{}
表示數組
ExpressionParser parser = new SpelExpressionParser();
// Integer列表
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue();
System.out.println("list: " + numbers);
// List的元素爲List
List<List> listlOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue();
System.out.println("List<List> : " + listlOfLists);
複製代碼
輸出結果安全
list: [1, 2, 3, 4]
List<List> : [[a, b], [x, y]]
複製代碼
{key:value}
來表示 map 表達式,空 Map 直接用{:}
表示bash
private void map() {
ExpressionParser parser = new SpelExpressionParser();
Map map = (Map) parser.parseExpression("{txt:'Nikola',dob:'10-July-1856'}").getValue();
System.out.println("map: " + map);
Map mapOfMaps =
(Map) parser.parseExpression("{txt:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}")
.getValue();
System.out.println("Map<Map>: " + mapOfMaps);
}
複製代碼
輸出結果
map: {txt=Nikola, dob=10-July-1856}
Map<Map>: {txt={first=Nikola, last=Tesla}, dob={day=10, month=July, year=1856}}
複製代碼
數組能夠藉助new
構造方法來實現,經過下標ary[index]
的方式訪問數組中的元素
private void array() {
ExpressionParser parser = new SpelExpressionParser();
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue();
System.out.println("array: " + JSON.toJSONString(numbers1));
// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue();
System.out.println("array: " + JSON.toJSONString(numbers2));
// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue();
System.out.println("array: " + JSON.toJSONString(numbers3));
int[] nums = new int[]{1, 3, 5};
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("num", nums);
// 經過下標訪問數組中的元素
Integer numVal = parser.parseExpression("#num[1]").getValue(context, Integer.class);
System.out.println("numVal in array: " + numVal);
}
複製代碼
輸出以下
array: [0,0,0,0]
array: [1,2,3]
array: [[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]]
numVal in array: 3
複製代碼
Spel 支持一些 Java 語法中常規的比較判斷,算數運算,三元表達式,類型判斷,matches
正則匹配等基表表達式
下面給出一些簡單的實例
public void expression() {
ExpressionParser parser = new SpelExpressionParser();
// 運算
System.out.println("1+2= " + parser.parseExpression("1+2").getValue());
// 比較
System.out.println("1<2= " + parser.parseExpression("1<2").getValue());
System.out.println("true ? hello : false > " + parser.parseExpression("3 > 2 ? 'hello': 'false' ").getValue());
// instanceof 判斷,請注意靜態類,用T進行包裝
System.out.println("instance : " + parser.parseExpression("'a' instanceof T(String)").getValue());
//正則表達式
System.out.println("22 是否爲兩位數字 :" + parser.parseExpression("22 matches '\\d{2}'").getValue());
}
複製代碼
輸出結果
1+2= 3
1<2= true
true ? hello : false > hello
instance : true
22 是否爲兩位數字 :true
複製代碼
若是想獲取 Class 對象,或者訪問靜態成員/方法,能夠藉助T()
語法來實現
好比咱們有一個靜態類
public static class StaClz {
public static String txt = "靜態屬性";
public static String hello(String tag) {
return txt + " : " + tag;
}
}
複製代碼
若是但願訪問靜態屬性txt
, 表達式能夠寫成T(com.git.hui.boot.spel.demo.BasicSpelDemo.StaClz).txt
,請注意圓括號中的是完整簽名;訪問靜態方法方式相似
public void type() {
// class,靜態類
ExpressionParser parser = new SpelExpressionParser();
String name =
parser.parseExpression("T(com.git.hui.boot.spel.demo.BasicSpelDemo.StaClz).txt").getValue(String.class);
System.out.println("txt: " + name);
String methodReturn =
parser.parseExpression("T(com.git.hui.boot.spel.demo.BasicSpelDemo.StaClz).hello" + "('一灰灰blog')")
.getValue(String.class);
System.out.println("static method return: " + methodReturn);
// class類獲取
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
System.out.println("class: " + stringClass.getName());
}
複製代碼
輸出結果以下
txt: 靜態屬性
static method return: 靜態屬性 : 一灰灰blog
class: java.lang.String
複製代碼
上面的寫法,請重點看一下T(String)
,這裏的 String 沒有用完整的包路徑,即直接位於java.lang
包下的類,是能夠省略掉完整包名的,就像咱們平時寫代碼時,也不須要顯示的加一個import java.lang.*
上面介紹 array 的時候,就介紹了使用new
來建立數組對象,固然也能夠直接構造其餘的普通對象, 如咱們新建一個測試類
public static class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person{" + "txt='" + name + '\'' + ", age=" + age + '}';
}
}
複製代碼
經過 SpEl 建立一個對象的實例
public void construct() {
ExpressionParser parser = new SpelExpressionParser();
Person person = parser.parseExpression("new com.git.hui.boot.spel.demo.BasicSpelDemo.Person('一灰灰', 20)")
.getValue(Person.class);
System.out.println("person: " + person);
}
複製代碼
輸出結果以下:
person: Person{txt='一灰灰', age=20}
複製代碼
請注意,構造方法中類的完整簽名
細心的小夥伴,在上面介紹數組的成員演示的實例中,寫法如"#num[1]"
,這個 num 前面有一個#
,這是一個語法定義,有#
修飾的表示變量訪問
要理解這一小節,首先得理解EvaluationContext
, 在咱們的 SpEL 表達式的解析中,getValue
有一個參數就是這個 Context,你能夠將他簡單理解爲包含一些對象的上下文,咱們能夠經過 SpEL 的語法,來訪問操做 Context 中的某些成員、成員方法屬性等
通常的操做過程以下:
context.setVariable("person", person);
向EvaluationContext
中塞入成員變量parser.parseExpression(xxx).getValue(context)
解析 SpEL 表達式,context 必須做爲傳參丟進去哦一個簡單的實例
public void variable() {
ExpressionParser parser = new SpelExpressionParser();
Person person = new Person("一灰灰blog", 18);
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("person", person);
String name = parser.parseExpression("#person.getName()").getValue(context, String.class);
System.out.println("variable name: " + name);
Integer age = parser.parseExpression("#person.age").getValue(context, Integer.class);
System.out.println("variable age: " + age);
}
複製代碼
輸出結果以下
variable name: 一灰灰blog
variable age: 18
複製代碼
友情提示,若是訪問對象的私有 Field/method,會拋異常
Context 中的變量,除了是咱們常見的基本類型,普通的對象以外,還能夠是方法,在setVariable
時,設置的成員類型爲method
便可
public void function() {
try {
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// 註冊一個方法變量,參數爲method類型
context.setVariable("hello", StaClz.class.getDeclaredMethod("hello", String.class));
String ans = parser.parseExpression("#hello('一灰灰')").getValue(context, String.class);
System.out.println("function call: " + ans);
} catch (Exception e) {
e.printStackTrace();
}
}
複製代碼
輸出結果以下
function call: 靜態屬性 : 一灰灰
複製代碼
在 Spring 中,什麼對象最多見?固然是 bean, 那麼咱們能夠直接經過 SpEL 訪問 bean 的屬性、調用方法麼?
要訪問 bean 對象,因此咱們的EvaluationContext
中須要包含 bean 對象才行
BeanResolver
來實現,如context.setBeanResolver(new BeanFactoryResolver(applicationContext));
@
符號爲了演示這種場景,首先建立一個普通的 Bean 對象
@Data
@Component
public class BeanDemo {
private String blog = "https://spring.hhui.top";
private Integer num = 8;
public String hello(String name) {
return "hello " + name + ", welcome to my blog " + blog + ", now person: " + num;
}
}
複製代碼
接着咱們須要獲取ApplicationContext
,因此能夠稍微改一下咱們的測試類,讓它繼承自ApplicationContextAware
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void bean() {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
// 獲取bean對象
BeanDemo beanDemo = parser.parseExpression("@beanDemo").getValue(context, BeanDemo.class);
System.out.println("bean: " + beanDemo);
// 訪問bean方法
String ans = parser.parseExpression("@beanDemo.hello('一灰灰blog')").getValue(context, String.class);
System.out.println("bean method return: " + ans);
}
複製代碼
上面的寫法和以前的並無太大的區別,實際輸出結果以下
bean: BeanDemo(blog=https://spring.hhui.top, num=8)
bean method return: hello 一灰灰blog, welcome to my blog https://spring.hhui.top, now person: 8
複製代碼
SpEL 支持三元表達式,在上述的表達式中也給出了實例
public void ifThenElse() {
// 三元表達式,? :
ExpressionParser parser = new SpelExpressionParser();
String ans = parser.parseExpression("true ? '正確': '錯誤'").getValue(String.class);
System.out.println("ifTheElse: " + ans);
}
複製代碼
輸出結果以下
ifTheElse: 正確
複製代碼
xx != null ? xx : yy => xx?:yy
這個也屬於咱們常常遇到的一種場景,若是 xx 爲 null,則返回 yy;不然直接返回 xx;簡化寫法爲 elvis 寫法: xx?:yy
public void elvis() {
// xx != null ? xx : yy => xx?:yy
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("name", null);
String name = parser.parseExpression("#name?:'Unknown'").getValue(context, String.class);
System.out.println("elvis-before " + name);
context.setVariable("name", "Exists!");
name = parser.parseExpression("#name?:'Unknown'").getValue(context, String.class);
System.out.println("elvis-after " + name);
}
複製代碼
輸出結果以下
elvis-before Unknown
elvis-after Exists!
複製代碼
在 java 中,最多見最討厭的是一個就是 NPE 的問題,SpEL 中固然也可能出現這種狀況,可是若在 SpEL 中進行非空判斷,那就很不優雅了,SpEL 提供了xx?.yy
的寫法來避免 npe,即
xx == null ? null : xx.yy => xx?.yy
舉例說明
public void safeOperate() {
// 防npe寫法, xx == null ? null : xx.yy => xx?.yy
ExpressionParser parser = new SpelExpressionParser();
Person person = new Person(null, 18);
String name = parser.parseExpression("name?.length()").getValue(person, String.class);
System.out.println("safeOperate-before: " + name);
person.name = "一灰灰blog";
name = parser.parseExpression("name?.length()").getValue(person, String.class);
System.out.println("safeOperate-after: " + name);
}
複製代碼
輸出結果以下
safeOperate-before: null
safeOperate-after: 7
複製代碼
遍歷容器,獲取子集,至關於 jdk8 Stream 中 filter 用法,語法格式以下
xx.?[expression]
, 請注意中括弧中的表達式必須返回 boolean
舉例說明
public void collectionSelection() {
// 容器截取,返回知足條件的子集
// xx.?[expression] , 將知足expression的子元素保留,返回一個新的集合,相似容器的 filter
List<Integer> list = new ArrayList<>(Arrays.asList(1, 3, 4, 6, 7, 8, 9));
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("list", list);
// 用 #this 來指代列表中的迭代元素
List<Integer> subList = (List<Integer>) parser.parseExpression("#list.?[#this>5]").getValue(context);
System.out.println("subList: " + subList);
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 10);
map.put("c", 4);
map.put("d", 7);
context.setVariable("map", map);
// 表達式內部用key, value 來指代map的k,v
Map subMap = parser.parseExpression("#map.?[value < 5]").getValue(context, Map.class);
System.out.println("subMap: " + subMap);
subMap = parser.parseExpression("#map.?[key == 'a']").getValue(context, Map.class);
System.out.println("subMap: " + subMap);
}
複製代碼
輸出結果以下
subList: [6, 7, 8, 9]
subMap: {a=1, c=4}
subMap: {a=1}
複製代碼
注意
#this
來指代列表中的每個元素key
, value
來分別指代 map 中的k,v
將一個集合經過某種規則,映射爲另外一種集合,至關於 jdk8 Stream 中的 map 用法,語法以下
xx.![expression]
, 將表達式計算的結果做爲輸出容器中的成員
舉例以下
public void collectionProjection() {
// 容器操做以後,生成另外一個容器, 相似lambda中的map方法
// xx.![expression]
List<Integer> list = new ArrayList<>(Arrays.asList(1, 3, 4, 6, 7, 8, 9));
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("list", list);
// 用 #this 來指代列表中的迭代元素
List newList = parser.parseExpression("#list.![#this * 2]").getValue(context, List.class);
System.out.println("newList: " + newList);
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 10);
map.put("c", 4);
map.put("d", 7);
context.setVariable("map", map);
List newListByMap = parser.parseExpression("#map.![value * 2]").getValue(context, List.class);
System.out.println("newListByMap: " + newListByMap);
}
複製代碼
輸出結果以下:
newList: [2, 6, 8, 12, 14, 16, 18]
newListByMap: [2, 20, 8, 14]
複製代碼
SpEL 還提供了一種自定義表達式模板的方式,將字面量和表達式放在一塊兒使用,好比下面這一條語句
"random number is #{T(java.lang.Math).random()}"
複製代碼
其中#{T(java.lang.Math).random()}
是一個 SpEL 表達式,左邊的是普通字符串,這種寫法也常見於@Value
註解中的屬性寫法,固然直接經過上面的寫法執行這個語句會報錯,這個時候須要指定ParserContext
舉例說明
public void template() {
// 模板,混合字面文本與表達式,使用 #{} 將表達式包裹起來
ExpressionParser parser = new SpelExpressionParser();
String randomPhrase = parser.parseExpression("random number is #{T(java.lang.Math).random()}",
ParserContext.TEMPLATE_EXPRESSION).getValue(String.class);
System.out.println("template: " + randomPhrase);
}
複製代碼
輸出結果以下
template: random number is 0.10438946298113871
複製代碼
SpEL 屬於很是強大的表達式語言了,就我我的的感受而言,它和 OGNL 有些像,當它們的上下文中包含了 Spring 的上下文時,能夠訪問任何的 bean,而你能夠藉助它們的語法規範,作各類事情
推薦我以前的一個項目,https://github.com/liuyueyi/quick-fix
,利用 ognl 結合ApplicationContext
,能夠爲所欲爲的訪問控制應用中的任何 bean 對象
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛