Spring Expression Language
概念
Spring Expression Language(簡稱SpEL)是一種強大的表達式語言,支持在運行時查詢和操作對象圖。該語言的語法類似于Unified EL,但提供了額外的特性, 最顯著的是方法調(diào)用和基本的字符串模板功能。
雖然還有其他幾種Java表達式語言可用——OGNL、MVEL和JBoss EL等,SpEL是為了向Spring社區(qū)提供一種受良好支持的表達式語言,可以跨Spring產(chǎn)品組合中的所有產(chǎn)品使用。SpEL基于一種與技術(shù)無關(guān)的API,在需要時可以集成其他表達式語言實現(xiàn)。
作用
基于SpEL我們可以實現(xiàn)下面這些功能,當(dāng)然是基于表達式,SpEL提供了表達式運行環(huán)境,而且不依賴于Spring,這樣我可以基于其強大的執(zhí)行器實現(xiàn)各種擴展。
- 支持功能
- 字面表達式
- 布爾和關(guān)系運算符
- 正則表達式
- 類表達式
- 訪問屬性、數(shù)組、列表和映射
- 方法調(diào)用
- 關(guān)系運算符
- 申明
- 調(diào)用構(gòu)造函數(shù)
- bean引用
- 數(shù)組構(gòu)造
- 內(nèi)聯(lián)的list
- 內(nèi)聯(lián)的map
- 三元運算符
- 變量
- 用戶自定義函數(shù)
- 集合投影
- 集合選擇
- 模板化表達式
關(guān)鍵接口
將表達式字符串解析為可求值的編譯表達式。支持解析模板以及標準表達式字符串。
public interface ExpressionParser {
/**
* 解析字符串表達式為Expression對象
*/
Expression parseExpression(String expressionString) throws ParseException;
/**
* 解析字符串表達式為Expression對象,基于ParserContext解析字符串,比如常見的#{exrp}
*/
Expression parseExpression(String expressionString, ParserContext context) throws ParseException;
}
表達式在求值上下文中執(zhí)行。正是在這個上下文中,表達式求值期間遇到的引用才會被解析。EvaluationContext接口有一個默認的實現(xiàn)StandardBeanExpressionResolver,可以通過繼承該類進行擴展。
public interface EvaluationContext {
/**
* 獲取Root上下文對象
*/
TypedValue getRootObject();
/**
* 返回屬性讀寫訪問器
*/
List< PropertyAccessor > getPropertyAccessors();
/**
* 返回構(gòu)造器解析器
*/
List< ConstructorResolver > getConstructorResolvers();
/**
* 返回方法解析器
*/
List< MethodResolver > getMethodResolvers();
/**
* 返回Bean解析器,用于Bean的查找
*/
@Nullable
BeanResolver getBeanResolver();
/**
* 根據(jù)類名(一般為全限名)返回一個類型定位器,比如T(java.lang.Math)
*/
TypeLocator getTypeLocator();
/**
* 返回可以將值從一種類型轉(zhuǎn)換(或強制轉(zhuǎn)換)為另一種類型的類型轉(zhuǎn)換器。
*/
TypeConverter getTypeConverter();
/**
* 返回一個類型比較器,用于比較對象對是否相等。
*/
TypeComparator getTypeComparator();
/**
* 返回一個運算符重載器,該重載器可以支持多個標準類型集之間的數(shù)學(xué)運算。
*/
OperatorOverloader getOperatorOverloader();
/**
* 為變量設(shè)值
*/
void setVariable(String name, @Nullable Object value);
/**
* 從變量取值
*/
@Nullable
Object lookupVariable(String name);
}
適用場景
- 擴展變量
正如Spring中使用的那樣,我們可以基于SpEL實現(xiàn)基于Spring容器、運行環(huán)境上下文等動態(tài)取值。 - 數(shù)據(jù)審計
通過從復(fù)雜的數(shù)據(jù)結(jié)構(gòu)中進行數(shù)據(jù)運算,一般針對數(shù)據(jù)匯總或者復(fù)雜的結(jié)構(gòu)化數(shù)據(jù)時,通過自定義特定表達式(類似于DSL)來實現(xiàn)數(shù)據(jù)運算。
Spring的使用
在Spring中有著大量使用SpEL的場景,在平時的開發(fā)中,可能會看到如下的這些配置,比如通過${}、#{}這些包裹的表達式,主要都是基于SpEL實現(xiàn)。
- @Value("#{systemProperties['pop3.port'] ?: 25}")
簡單的看下源嗎,可以看到針對@Value標記的類屬性,是如何為其注入屬性:
AutowiredAnnotationBeanPostProcessor -> AutowiredFieldElement#injectvalue = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter)
DefaultListableBeanFactory#doResolveDependency
public class DefaultListableBeanFactory {
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set< String > autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Class< ? > type = descriptor.getDependencyType();
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
if (value instanceof String) {
// 處理 ${} 占位替換
String strVal = resolveEmbeddedValue((String) value);
BeanDefinition bd = (beanName != null && containsBean(beanName) ?
getMergedBeanDefinition(beanName) : null);
// SpEL處理
value = evaluateBeanDefinitionString(strVal, bd);
}
}
// 省略...
return result;
}catch (Exception e){
// ...
}
}
}
- @Cacheable(value="users", key="#p0")
具體實現(xiàn)可以閱讀源碼:org.springframework.cache.interceptor.CacheOperationExpressionEvaluator
- @KafkaListener(topics = "#{'${topics}'.split(',')}")
具體實現(xiàn)可以閱讀源碼:org.springframework.kafka.annotation.KafkaListenerAnnotationBeanPostProcessor
示例
下面通過一些測試示例了解SpEL的基本用法,對應(yīng)上面的一些功能實現(xiàn),通過這些簡單的例子,可以大概了解其語法與使用方式:
public class Tests {
@Test
public void runParser() throws NoSuchMethodException {
SpelExpressionParser parser = new SpelExpressionParser();
Map map = new HashMap();
map.put("name", "SpEL");
StandardEvaluationContext context = new StandardEvaluationContext(map);
context.setVariable("var1", 1);
context.setVariable("func1", StringUtils.class.getMethod("hasText", String.class));
context.setVariable("func2", new Root());
context.setBeanResolver(new MyBeanResolver());
log.info("字面量:{}", parser.parseExpression("'hello'").getValue(context));
log.info("對象屬性:{}", parser.parseExpression("'hello'.bytes").getValue(context));
log.info("變量:{}", parser.parseExpression("#var1").getValue(context));
log.info("調(diào)用方法:{}", parser.parseExpression("'hello'.concat(' world')").getValue(context));
log.info("靜態(tài)方法:{}", parser.parseExpression("T(java.lang.System).currentTimeMillis()").getValue(context));
log.info("方法:{}", parser.parseExpression("#func1('1')").getValue(context));
log.info("實例方法:{}", parser.parseExpression("#func2.print('2')").getValue(context));
Root root = new Root();
root.list.add("0");
parser.parseExpression("list[0]").setValue(context, root, "1");
log.info("設(shè)值:{}", root);
log.info("ROOT: {}", parser.parseExpression("#root").getValue(context));
log.info("ROOT 取值: {}", parser.parseExpression("#root[name]").getValue(context));
log.info("ROOT 取值: {}", parser.parseExpression("[name]").getValue(context));
log.info("THIS: {}", parser.parseExpression("#this").getValue(context));
log.info("運算符: {}", parser.parseExpression("1+1").getValue(context));
log.info("操作符: {}", parser.parseExpression("1==1").getValue(context));
log.info("邏輯運算: {}", parser.parseExpression("true && false").getValue(context));
ParserContext parserContext = new ParserContext() {
@Override
public boolean isTemplate() {
return true;
}
@Override
public String getExpressionPrefix() {
return "#{";
}
@Override
public String getExpressionSuffix() {
return "}";
}
};
log.info("#{表達式}: {}", parser.parseExpression("#{1+1}", parserContext).getValue(context));
log.info("Map: {}", parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context));
log.info("List: {}", parser.parseExpression("{1,2,3,4}").getValue(context));
log.info("Array: {}", parser.parseExpression("new int[]{1,2,3}").getValue(context));
log.info("instanceof: {}", parser.parseExpression("'hello' instanceof T(Integer)").getValue(context, Boolean.class));
log.info("regex: {}", parser.parseExpression("'5.00' matches '^-?d+(.d{2})?$'").getValue(context, Boolean.class));
log.info("三目運算: {}", parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(context, String.class));
log.info("Bean: {}", parser.parseExpression("@bean1").getValue(context));
}
}
特殊處理
- T
通過T(CLASS)指定類型,可以用來類型判斷或者調(diào)用類靜態(tài)方法sec.setTypeLocator(new StandardTypeLocator(evalContext.getBeanFactory().getBeanClassLoader()));
- @
通過@Name獲取beansec.setBeanResolver(new BeanFactoryResolver(evalContext.getBeanFactory()));
- &
通過&Name獲取beanFactory
sec.setBeanResolver(new BeanFactoryResolver(evalContext.getBeanFactory()));
通過#{}標識表達式,主要在spring
BeanExpressionResolver resolver = beanFactory.getBeanExpressionResolver(); BeanExpressionContext context = new BeanExpressionContext(beanFactory, null);
從root取值:#root.name 或者 name(#root可以忽略) 從variables取值或方法:#var
擴展
下面以一個示例看下SpEL在我們項目中的具體應(yīng)用:
public class Tests{
/**
* 執(zhí)行測試
*/
@Test
public void runCalc(){
SpelExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("helper",new StudentHelper());
context.setVariable("students", genStudents(100));
log.info("max score: {}", parser.parseExpression("#helper.max(#students)").getValue(context, Double.class));
log.info("min score: {}", parser.parseExpression("#helper.min(#students)").getValue(context, Double.class));
log.info("avg score: {}", parser.parseExpression("#helper.avg(#students)").getValue(context, Double.class));
}
/**
* 生成測試數(shù)據(jù)
* @param count
* @return
*/
private List< Student > genStudents(int count){
List< Student > students = new ArrayList< >();
Faker faker = new Faker(Locale.CHINA);
Name name = faker.name();
Number number = faker.number();
IntStream.range(0, count).forEach(i- >{
students.add(new Student(name.name(), number.randomDouble(3, 60, 100)));
});
return students;
}
@Data
@AllArgsConstructor
class Student{
private String name;
private double score;
}
/**
* 工具類
*/
class StudentHelper{
public double max(List< Student > students){
if(CollectionUtils.isEmpty(students)){
return 0;
}
return students.stream().mapToDouble(Student::getScore).max().getAsDouble();
}
public double min(List< Student > students){
if(CollectionUtils.isEmpty(students)){
return 0;
}
return students.stream().mapToDouble(Student::getScore).min().getAsDouble();
}
public double avg(List< Student > students){
if(CollectionUtils.isEmpty(students)){
return 0;
}
return students.stream().mapToDouble(Student::getScore).average().getAsDouble();
}
}
}
在這個示例中,我們主要通過SpEL獲取獲取學(xué)生分值的最大值、最小值、平均值等方式,在實際的項目中使用時,絕非如此簡單,比如我們在做數(shù)據(jù)統(tǒng)計時,對各項指標數(shù)值的計算就是通過SpEL實現(xiàn), 因為具體功能的實現(xiàn)是由我們自己定義,因此在業(yè)務(wù)擴展上會非常的方便。
結(jié)束語
SpEL是一個功能非常強大的基于Java的解釋型語言解析器,如果你想基于表達式的形式,對復(fù)雜結(jié)構(gòu)數(shù)據(jù)計算或?qū)徲嫷男枨髸r,不妨試試這個輕量級工具。
-
JAVA
+關(guān)注
關(guān)注
19文章
2975瀏覽量
105201 -
字符串
+關(guān)注
關(guān)注
1文章
585瀏覽量
20611 -
執(zhí)行器
+關(guān)注
關(guān)注
5文章
378瀏覽量
19430 -
SPEL
+關(guān)注
關(guān)注
0文章
3瀏覽量
6121
發(fā)布評論請先 登錄
相關(guān)推薦
FPGA可以實現(xiàn)DSP的功能嗎?
用什么電子元件可以實現(xiàn)觸點開關(guān)的功能?
請問ADF4106可以實現(xiàn)分頻器的功能嗎?
使用Azure軟件包連接IoT中心可以實現(xiàn)哪些功能
FPGA與ARM核結(jié)合可以實現(xiàn)功能互補嗎?
SysTick定時器的計時功能可以實現(xiàn)精準延時嗎
有什么方法可以實現(xiàn)stm32的計數(shù)功能呢
USB Low Speed 設(shè)備可以實現(xiàn)CDC虛擬串口功能嗎?
并行eeprom是否可以實現(xiàn)gal芯片功能?
智能家居產(chǎn)品可以實現(xiàn)的功能和服務(wù)
通過人員定位位置數(shù)據(jù)可以實現(xiàn)哪些功能?
技術(shù)解讀 | SpEL表達式注入漏洞分析、檢查與防御
工廠人員定位系統(tǒng)可以實現(xiàn)哪些功能
Redis可以實現(xiàn)消息中間件MQ的功能
![Redis<b class='flag-5'>可以</b><b class='flag-5'>實現(xiàn)</b>消息中間件MQ的<b class='flag-5'>功能</b>](https://file1.elecfans.com/web2/M00/BE/63/wKgZomWyBKqAWqcBAAAK8S0vluU523.jpg)
評論