前言
在介紹正文前,我們先來講下spel
什么是spel
Spring運算式語言(簡稱“ SpEL”)是一種功能強大的運算式語言,支持在運行時查詢和操作物件圖,
語言語法類似于Unified EL,但是提供了其他功能,最著名的是方法呼叫和基本的字串模板功能,
此外它并不直接與Spring系結,而是可以獨立使用
spel可以支持哪些功能
- 文字運算式
- 布爾運算子和關系運算子
- 常用運算式
- 類運算式
- 訪問屬性,陣列,串列和映射
- 方法呼叫
- 關系運算子
- 分配
- 呼叫建構式
- Bean參考
- 陣列構造
- 行內串列
- 行內Map
- 三元運算子
- 變數
- 用戶定義的功能
- 集合投影
- 集合選擇
- 模板運算式
上述的spel語法可以通過如下鏈接進行查閱
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#expressions-language-ref
spel決議基本流程
形如下圖

大體的步驟如下
- 創建決議器
- 決議運算式
- 構造背景關系
- 求值
spel核心介面介紹
1、org.springframework.expression.ExpressionParser
運算式決議器,其功能主要是將字串運算式轉換為Expression物件,支持決議模板以及標準運算式字串
其默認實作為
org.springframework.expression.spel.standard.SpelExpressionParser
2、org.springframework.expression.EvaluationContext
spel計算運算式值的“背景關系”,這個Context物件可以包含多個物件,但只能有一個root(根)物件,當運算式中包含變數時,spel會根據EvaluationContext中的變數的值對運算式進行計算,可以使用setRootObject方法來設定根物件,使用setVariable方法來注冊自定義變數,使用registerFunction來注冊自定義函式,
其默認實作為
org.springframework.expression.spel.support.StandardEvaluationContext
3、org.springframework.expression.Expression
代表一個運算式,通過getValue方法根據背景關系獲得運算式值
其默認實作為
org.springframework.expression.spel.standard.SpelExpression
spel官方檔案
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#expressions
正文
前邊簡要介紹一下spel,下邊我們就通過一個小例子來演示下,
該小例子主要是通過AOP+SPEL來實作,例子場景是:當產品價格大于10時,放入本地快取,并通過定時器列印出本地快取的值
1、業務邏輯實作核心代碼
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMockDao productMockDao;
@Override
@LocalCacheable(key = "#product.id",condition = "#product.price ge 10")
public Product save(Product product) {
return productMockDao.save(product);
}
}
2、aop切面撰寫
@Component
@Aspect
public class CacheAspect {
@Around("@annotation(localCacheable)")
public Object around(ProceedingJoinPoint pjp, LocalCacheable localCacheable) throws Throwable{
MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
Method method = methodSignature.getMethod();
Object[] args = pjp.getArgs();
Object result = pjp.proceed();
String key = pjp.getTarget().getClass().getName() + "_" + method.getName() + "_" + args.length;
if(!StringUtils.isEmpty(localCacheable.key())){
key = SpELParserUtils.parse(method,args,localCacheable.key(),String.class);
}
System.out.println("key:"+key);
if(!StringUtils.isEmpty(localCacheable.condition())){
boolean condition = SpELParserUtils.parse(method,args,localCacheable.condition(),Boolean.class);
if(condition){
LocalCache.INSTANCE.put(key,result);
}
}else{
LocalCache.INSTANCE.put(key,result);
}
return result;
}
}
3、決議spel核心工具類
@Slf4j
public final class SpELParserUtils {
private static final String EXPRESSION_PREFIX = "#{";
private static final String EXPRESSION_SUFFIX = "}";
/**
* 運算式決議器
*/
private static ExpressionParser expressionParser = new SpelExpressionParser();
/**
* 引數名決議器,用于獲取引數名
*/
private static DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
private SpELParserUtils(){}
/**
* 決議spel運算式
*
* @param method 方法
* @param args 引數值
* @param spelExpression 運算式
* @param clz 回傳結果的型別
* @param defaultResult 默認結果
* @return 執行spel運算式后的結果
*/
public static <T> T parse(Method method, Object[] args, String spelExpression, Class<T> clz, T defaultResult) {
String[] params = parameterNameDiscoverer.getParameterNames(method);
EvaluationContext context = new StandardEvaluationContext();
//設定背景關系變數
for (int i = 0; i < params.length; i++) {
context.setVariable(params[i], args[i]);
}
T result = getResult(context,spelExpression,clz);
if(Objects.isNull(result)){
return defaultResult;
}
return result;
}
/**
* 決議spel運算式
*
* @param method 方法
* @param args 引數值
* @param spelExpression 運算式
* @param clz 回傳結果的型別
* @return 執行spel運算式后的結果
*/
public static <T> T parse(Method method, Object[] args, String spelExpression, Class<T> clz) {
String[] params = parameterNameDiscoverer.getParameterNames(method);
EvaluationContext context = new StandardEvaluationContext();
//設定背景關系變數
for (int i = 0; i < params.length; i++) {
context.setVariable(params[i], args[i]);
}
return getResult(context,spelExpression,clz);
}
/**
* 決議spel運算式
*
* @param param 引數名
* @param paramValue 引數值
* @param spelExpression 運算式
* @param clz 回傳結果的型別
* @return 執行spel運算式后的結果
*/
public static <T> T parse(String param, Object paramValue, String spelExpression, Class<T> clz) {
EvaluationContext context = new StandardEvaluationContext();
//設定背景關系變數
context.setVariable(param, paramValue);
return getResult(context,spelExpression,clz);
}
/**
* 決議spel運算式
*
* @param param 引數名
* @param paramValue 引數值
* @param spelExpression 運算式
* @param clz 回傳結果的型別
* @param defaultResult 默認結果
* @return 執行spel運算式后的結果
*/
public static <T> T parse(String param, Object paramValue,String spelExpression, Class<T> clz, T defaultResult) {
EvaluationContext context = new StandardEvaluationContext();
//設定背景關系變數
context.setVariable(param, paramValue);
T result = getResult(context,spelExpression,clz);
if(Objects.isNull(result)){
return defaultResult;
}
return result;
}
/**
* 獲取spel運算式后的結果
*
* @param context 決議器背景關系介面
* @param spelExpression 運算式
* @param clz 回傳結果的型別
* @return 執行spel運算式后的結果
*/
private static <T> T getResult(EvaluationContext context,String spelExpression, Class<T> clz){
try {
//決議運算式
Expression expression = parseExpression(spelExpression);
//獲取運算式的值
return expression.getValue(context, clz);
} catch (Exception e) {
log.error(e.getMessage(),e);
}
return null;
}
/**
* 決議運算式
* @param spelExpression spel運算式
* @return
*/
private static Expression parseExpression(String spelExpression){
// 如果運算式是一個#{}運算式,需要為決議傳入模板決議器背景關系
if(spelExpression.startsWith(EXPRESSION_PREFIX) && spelExpression.endsWith(EXPRESSION_SUFFIX)){
return expressionParser.parseExpression(spelExpression,new TemplateParserContext());
}
return expressionParser.parseExpression(spelExpression);
}
}
4、 示例效果

總結
spel在spring應用中隨處可見,比如@cacheable、@Value等,我們也可以通過aop+spel實作出適合我們業務場景的功能
demo鏈接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-aop-spel
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/273522.html
標籤:其他
