Spring - SpEL

1 概述

Spring表達式語言全稱為“Spring Expression Language”,縮寫為“SpEL”。在運行時構建復雜表達式、存取對象圖屬性、對象方法調用等等,并且能與Spring功能完美整合,如能用來配置Bean定義。

SpEL是單獨模塊,只依賴于core模塊,不依賴于其他模塊,可以單獨使用。

2 使用場景

  1. Bean 的香瓜屬性的配置
  2. 結合 AOP 完成業(yè)務系統(tǒng)的日志記錄

3 功能概覽

SpEL支持如下表達式:

一、基本表達式: 字面量表達式、關系,邏輯與算數運算表達式、字符串連接及截取表達式、三目運算及Elivis表達式、正則表達式、括號優(yōu)先級表達式;

二、類相關表達式: 類類型表達式、類實例化、instanceof表達式、變量定義及引用、賦值表達式、自定義函數、對象屬性存取及安全導航表達式、對象方法調用、Bean引用;

三、集合相關表達式: 內聯(lián)List、內聯(lián)數組、集合,字典訪問、列表,字典,數組修改、集合投影、集合選擇;不支持多維內聯(lián)數組初始化;不支持內聯(lián)字典定義;

四、其他表達式:模板表達式。

注:SpEL表達式中的關鍵字是不區(qū)分大小寫的。

4 基本使用

4.1 使用步驟

1)創(chuàng)建解析器:SpEL使用ExpressionParser接口表示解析器,提供 SpelExpressionParser默認實現;
2)解析表達式:使用 ExpressionParser 的 parseExpression 來解析相應的表達式為Expression對象。
3)構造上下文:準備比如變量定義等等表達式需要的上下文數據。
4)求值:通過Expression接口的getValue方法根據上下文獲得表達式值。

示例:

public class SpelTest {
    @Test
    public void test1() {
        // 創(chuàng)建解析器
        ExpressionParser parser = new SpelExpressionParser();
        // 解析表達式
        Expression expression = parser.parseExpression("('Hello' + ' World').concat(#end)");
        // 構建上下文
        EvaluationContext context = new StandardEvaluationContext();
        context.setVariable("end", "!");
        // 獲取 表達式值
        System.out.println(expression.getValue(context));
    }
}

4.2 原理

一、表達式: 表達式是表達式語言的核心,所以表達式語言都是圍繞表達式進行的,從我們角度來看是“干什么”;

二、解析器: 用于將字符串表達式解析為表達式對象,從我們角度來看是“誰來干”;

三、上下文: 表達式對象執(zhí)行的環(huán)境,該環(huán)境可能定義變量、定義自定義函數、提供類型轉換等等,從我們角度看是“在哪干”;

四、根對象及活動上下文對象: 根對象是默認的活動上下文對象,活動上下文對象表示了當前表達式操作的對象,從我們角度看是“對誰干”

4.3 基本語法

4.3.1 字面量表達式

SpEL支持的字面量包括:字符串、數字類型(int、long、float、double)、布爾類型、null類型。


image.png

4.3.2 算數運算表達式

image.png

4.3.3 關系表達式

等于(==)、不等于(!=)、大于(>)、大于等于(>=)、小于(<)、小于等于(<=),區(qū)間(between)運算。

如parser.parseExpression("1>2").getValue(boolean.class);將返回false;

而parser.parseExpression("1 between {1, 2}").getValue(boolean.class);將返回true。

between運算符右邊操作數必須是列表類型,且只能包含2個元素。第一個元素為開始,第二個元素為結束,區(qū)間運算是包含邊界值的,即 xxx>=list.get(0) && xxx<=list.get(1)。

SpEL同樣提供了等價的“EQ” 、“NE”、 “GT”、“GE”、 “LT” 、“LE”來表示等于、不等于、大于、大于等于、小于、小于等于,不區(qū)分大小寫。

4.3.4 邏輯表達式

且(and或者&&)、或(or或者||)、非(!或NOT)。

public void test4() {
    ExpressionParser parser = new SpelExpressionParser();

    boolean result1 = parser.parseExpression("2>1 and (!true or !false)").getValue(boolean.class);
    boolean result2 = parser.parseExpression("2>1 && (!true || !false)").getValue(boolean.class);

    boolean result3 = parser.parseExpression("2>1 and (NOT true or NOT false)").getValue(boolean.class);
    boolean result4 = parser.parseExpression("2>1 && (NOT true || NOT false)").getValue(boolean.class);
}

4.3.5 變量定義及引用

變量定義通過EvaluationContext接口的setVariable(variableName, value)方法定義;在表達式中使用"#variableName"引用;除了引用自定義變量,SpE還允許引用根對象及當前上下文對象,使用"#root"引用根對象,使用"#this"引用當前上下文對象;

@Test
public void testVariableExpression() {
    ExpressionParser parser = new SpelExpressionParser();
    EvaluationContext context = new StandardEvaluationContext();
    context.setVariable("name", "路人甲java");
    context.setVariable("lesson", "Spring系列");

    //獲取name變量,lesson變量
    String name = parser.parseExpression("#name").getValue(context, String.class);
    System.out.println(name);
    String lesson = parser.parseExpression("#lesson").getValue(context, String.class);
    System.out.println(lesson);

    //StandardEvaluationContext構造器傳入root對象,可以通過#root來訪問root對象
    context = new StandardEvaluationContext("我是root對象");
    String rootObj = parser.parseExpression("#root").getValue(context, String.class);
    System.out.println(rootObj);

    //#this用來訪問當前上線文中的對象
    String thisObj = parser.parseExpression("#this").getValue(context, String.class);
    System.out.println(thisObj);
}

輸出:
路人甲java
Spring系列
我是root對象
我是root對象

4.3.6 對象屬性及安全導航表達式

對象屬性獲取非常簡單,即使用如“a.property.property”這種點綴式獲取,SpEL對于屬性名首字母是不區(qū)分大小寫的;SpEL還引入了Groovy語言中的安全導航運算符“(對象|屬性)?.屬性”,用來避免“?.”前邊的表達式為null時拋出空指針異常,而是返回null;修改對象屬性值則可以通過賦值表達式或Expression接口的setValue方法修改。

4.3.7 Bean 引用

SpEL支持使用“@”符號來引用Bean,在引用Bean時需要使用BeanResolver接口實現來查找Bean,Spring提供BeanFactoryResolver實現。
示例:

public void test6() {
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    User user = new User();
    Car car = new Car();
    car.setName("保時捷");
    user.setCar(car);
    factory.registerSingleton("user", user);

    StandardEvaluationContext context = new StandardEvaluationContext();
    context.setBeanResolver(new BeanFactoryResolver(factory));

    ExpressionParser parser = new SpelExpressionParser();
    User userBean = parser.parseExpression("@user").getValue(context, User.class);
    System.out.println(userBean);
    System.out.println(userBean == factory.getBean("user"));
}

4.3.8 集合相關表達式

1. 內聯(lián)List

從Spring3.0.4開始支持內聯(lián)List,使用{表達式,……}定義內聯(lián)List,如“{1,2,3}”將返回一個整型的ArrayList,而“{}”將返回空的List,對于字面量表達式列表,SpEL會使用java.util.Collections.unmodifiableList方法將列表設置為不可修改。

public void test7() {
    ExpressionParser parser = new SpelExpressionParser();
    //將返回不可修改的空List
    List<Integer> result2 = parser.parseExpression("{}").getValue(List.class);
    //對于字面量列表也將返回不可修改的List
    List<Integer> result1 = parser.parseExpression("{1,2,3}").getValue(List.class);
    Assert.assertEquals(new Integer(1), result1.get(0));
    try {
        result1.set(0, 2);
    } catch (Exception e) {
        e.printStackTrace();
    }
    //對于列表中只要有一個不是字面量表達式,將只返回原始List,
    //不會進行不可修改處理
    String expression3 = "{{1+2,2+4},{3,4+4}}";
    List<List<Integer>> result3 = parser.parseExpression(expression3).getValue(List.class);
    result3.get(0).set(0, 1);
    System.out.println(result3);
    //聲明二維數組并初始化
    int[] result4 = parser.parseExpression("new int[2]{1,2}").getValue(int[].class);
    System.out.println(result4[1]);
    //定義一維數組并初始化
    int[] result5 = parser.parseExpression("new int[1]").getValue(int[].class);
    System.out.println(result5[0]);
}

輸出:

java.lang.UnsupportedOperationException
 at java.util.Collections$UnmodifiableList.set(Collections.java:1311)
 at com.javacode2018.spel.SpelTest.test7(SpelTest.java:315)
[[1, 6], [3, 8]]
2
0

2. 集合,字典元素訪問

SpEL目前支持所有集合類型和字典類型的元素訪問,使用“集合[索引]”訪問集合元素,使用“map[key]”訪問字典元素;

//SpEL內聯(lián)List訪問  
int result1 = parser.parseExpression("{1,2,3}[0]").getValue(int.class);  

//SpEL目前支持所有集合類型的訪問  
Collection<Integer> collection = new HashSet<Integer>();  
collection.add(1);  
collection.add(2);  

EvaluationContext context2 = new StandardEvaluationContext();  
context2.setVariable("collection", collection);  
int result2 = parser.parseExpression("#collection[1]").getValue(context2, int.class);  

//SpEL對Map字典元素訪問的支持  
Map<String, Integer> map = new HashMap<String, Integer>();  
map.put("a", 1);  

EvaluationContext context3 = new StandardEvaluationContext();  
context3.setVariable("map", map);  
int result3 = parser.parseExpression("#map['a']").getValue(context3, int.class);  

3. 列表、字典、數組元素修改

可以使用賦值表達式或Expression接口的setValue方法修改;

@Test
public void test8() {
    ExpressionParser parser = new SpelExpressionParser();

    //修改list元素值
    List<Integer> list = new ArrayList<Integer>();
    list.add(1);
    list.add(2);

    EvaluationContext context1 = new StandardEvaluationContext();
    context1.setVariable("collection", list);
    parser.parseExpression("#collection[1]").setValue(context1, 4);
    int result1 = parser.parseExpression("#collection[1]").getValue(context1, int.class);
    System.out.println(result1);

    //修改map元素值
    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("a", 1);
    EvaluationContext context2 = new StandardEvaluationContext();
    context2.setVariable("map", map);
    parser.parseExpression("#map['a']").setValue(context2, 4);
    Integer result2 = parser.parseExpression("#map['a']").getValue(context2, int.class);
    System.out.println(result2);
}
輸出:
4
4

5 示例 -- 操作日志

1. controller

@TenantLog(module = "custer", description = "get ...", content = "parameter: id = #{ #customer?.id }, "
      + "accountId = #{ #customer?.accountId}, orderNumber = #{ #customer.orderNumber }, des = #{ #customer.description }")
  @RequestMapping(value = "/create", method = RequestMethod.POST)
  public CustomerTicket createCustomerTicket(@RequestBody CustomerTicket customer) {

    log.info("customer is 888888 {}", customer.toString());

    CustomerTicket customerTicket = new CustomerTicket();

    customerTicket.setId(customer.getId());

    customerTicket.setAccountId(customer.getAccountId());

    customerTicket.setOrderNumber(customer.getOrderNumber());

    customerTicket.setDescription(customer.getDescription());

    customerTicket.setCreateTime(new Date());

    return customerTicket;
  }


  @TenantLog(module = "custer", description = "get ...", content = "parameter: #{ #customer.toString() }")
  @RequestMapping(value = "/create1", method = RequestMethod.POST)
  public CustomerTicket createCustomerTicket1(@RequestBody CustomerTicket customer) {

    log.info("customer is 888888 {}", customer.toString());

    CustomerTicket customerTicket = new CustomerTicket();

    customerTicket.setId(customer.getId());

    customerTicket.setAccountId(customer.getAccountId());

    customerTicket.setOrderNumber(customer.getOrderNumber());

    customerTicket.setDescription(customer.getDescription());

    customerTicket.setCreateTime(new Date());

    return customerTicket;
  }

2. AOP + SpEL

@Slf4j
@Aspect
@Component
public class TenantLogOperateAspect {


  // 需要被SpEl解析的模板前綴和后綴 {{ expression  }}
  public static final TemplateParserContext TEMPLATE_PARSER_CONTEXT = new TemplateParserContext("#{", "}");

  // SpEL解析器
  public static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();


  @Pointcut("@annotation(com.chenjunjie.webpro.aop.TenantLog)")
  public void tenantLogPointcut() {
  }

  @Around("tenantLogPointcut()")
  public Object addAspect(ProceedingJoinPoint joinPoint) throws Throwable {


    MethodSignature signature = (MethodSignature) joinPoint.getSignature();

    // 參數
    Object[] args = joinPoint.getArgs();

    // 參數名稱
    String[] parameterNames = signature.getParameterNames();

    // 目標方法
    Method targetMethod = signature.getMethod();

    TenantLog operationLog = targetMethod.getAnnotation(TenantLog.class);


    // request
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

    try {
      /**
       * SpEL解析的上下文,把 HandlerMethod 的形參都添加到上下文中,并且使用參數名稱作為KEY
       */
      EvaluationContext evaluationContext = new StandardEvaluationContext();
      for (int i = 0; i < args.length; i ++) {
        evaluationContext.setVariable(parameterNames[i], args[i]);
      }

      String logContent = EXPRESSION_PARSER.parseExpression(operationLog.content(), TEMPLATE_PARSER_CONTEXT).getValue(evaluationContext, String.class);

      // TODO 異步存儲日志

      System.out.println("**************************");
      log.info("operationLog={}", logContent);
      System.out.println("**************************");
      
      // 執(zhí)行方法
      Object proceed = joinPoint.proceed();

      return proceed;


    } catch (Exception e) {
      log.error("操作日志SpEL表達式解析異常: {}", e.getMessage());
    }

    return null;
  }
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容