Spring 筆記——核心-SpEL 篇


SpEL

全稱:Spring Expression Language (Spring 表達式語言)
定義:SpEL 是 Spring 定義的一套在 Spring 框架內運行的表達式語言,說是語言,理解為通過特定格式的字符串來讓 Spring 框架解析出原來的含義,可簡化很多對數據的操作動作。后端類似的有 OGNL, MVELJBoss EL。前端方面
官網地址
ThymeleafFreeMarker 的數據渲染語法也可以理解為一種表達式語言。

SpEL 大致功能

  • 簡單字符
  • boolean值 與關系運算符支持
  • 常用表達式
  • 類表達式
  • 訪問 properties, arrays, lists, maps
  • 方法調用
  • 關系運算符支持
  • 任務
  • 調用構造函數
  • Bean 引用
  • 構造數組
  • 單行配置 list
  • 單號配置 map
  • 三元運算符
  • 變量
  • 用戶自定義函數
  • Collection projection
  • Collection selection
  • 模板表達式

簡單案例

基礎字符串語法解析

解析字符串聲明語句 'Hello World' 得到字符串 Hello World
解析字符串拼接語句 'Hello World'.concat('!') 得到字符串 Hello World!

        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression("'Hello World'");
        String message = (String) exp.getValue();
        assert message.equals("Hello World");
        exp = parser.parseExpression("'Hello World'.concat('!')");
        message = (String) exp.getValue();
        assert message.equals("Hello World!");
        exp = parser.parseExpression("'Hello World'.bytes.length");
        assert exp.getValue().equals(11);

EvaluationContext 接口

相關實現

Spring 處理 SpEL 的時候會通過這個接口解析屬性,方法或字段。Spring 為該接口提供了兩個實現:

SimpleEvaluationContext

簡單實現:旨在僅支持 SpEL 語言語法的一個子集。它不包括 Java 類型引用、構造函數和 bean 引用。使用它明需要確選擇對表達式中的屬性和方法的支持級別。默認情況下,create()靜態工廠方法只允許對屬性進行讀取訪問。可以通過獲得構建器來配置所需的確切支持級別,針對以下一項或某種組合:

  • 唯一的自定義屬性訪問器
  • 只讀數據綁定
  • 允許讀寫的數據綁定

StandardEvaluationContext

標准實現:支持所有的 SpEL 語言特性和配置選項。可以使用它來指定默認根對象並配置所有的的 evaluation-relate 策略。基於 StandardEvaluationContext 還有兩種實現 MethodBasedEvaluationContextCacheEvaluationContext

類型轉換

默認情況下:Spring 解析 SpEL 的時候會使用自身 org.springframework.core.convert.ConversionService 包中可用的轉換器服務。而且由於 SpEL 是能泛型感知的,當解析數據時,會嘗試將其以正確的泛型來進行解析,下面是一個字符串 "false" 解析成布爾值 false 的簡單案例

    @Test
    public void simpleEvaluationContextTest() {
        class Simple {
            public List<Boolean> booleanList = new ArrayList<>();
        }

        Simple simple = new Simple();
        simple.booleanList.add(true);

        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

        // 設置值字符串 "false" ,發現需要的是 Boolean 類型的,就進行了類型轉換變為布爾類型 false
        // will recognize that it needs to be a Boolean and convert it accordingly.
        ExpressionParser parser = new SpelExpressionParser();
        parser.parseExpression("booleanList[0]").setValue(context, simple, "false");

        // b is false
        Boolean b = simple.booleanList.get(0);
    }

解析器配置

可以使用 org.springframework.expression.spel.SpelParserConfiguration 來配置 SpEL 的數據解析器。如下案例

    @Test
    public void spelParserConfigurationTest() {
        class Demo {
            public List<String> list;
        }

        // Turn on:
        // - 會自動調用默認初始化方法對相應的 null 進行初始化
        // - 集合大小自增
        SpelParserConfiguration config = new SpelParserConfiguration(true, true);

        ExpressionParser parser = new SpelExpressionParser(config);

        Expression expression = parser.parseExpression("list[8]");

        Demo demo = new Demo();
        // 此時 demo.list == null
        Object o = expression.getValue(demo);
        // 此時 demo.list = ["","","",""]
    }

SpEL 編譯器 性能增強

SpEL 表達式通常使用默認的基礎表達式解釋器處理。但對於 Spring Integration 這種對於性能要求高的組件來說,性能是個很重要的指標。而對於一些非動態的賦值表達式語言,Spring 提供了一個編譯器擴展處理該類表達式。
實現流程:在評估期間,編譯器生成一個體現表達式運行時行為的 Java 類,並使用該類來實現更快的表達式評估。由於沒有圍繞表達式鍵入,編譯器在執行編譯時使用在表達式的解釋評估期間收集的信息。
如下測試:在 50000 次迭代測試中,解釋器需要 75 毫秒,而編譯器方案只需要 3 毫秒。

someArray[0].someProperty.someOtherProperty < 0.1

使用案例:
SpelParserConfiguration 配置 org.springframework.expression.spel.SpelCompilerMode 枚舉值以選擇不同的模式運行

  • OFF(默認):編譯器關閉
  • IMMEDIATE:編譯器開啟,編譯失敗則調用者會收到異常
  • MIXD:混合模式,表達式會隨着時間在解釋模式和編譯模式之間靜默切換。在經過一定次數的解釋運行后,它們會切換到編譯形式,如果編譯形式出現問題(例如類型更改,如前所述),表達式會自動再次切換回解釋形式。一段時間后,它可能會生成另一個已編譯的表單並切換到它。基本上,用戶進入IMMEDIATE模式的異常是在內部處理的
    @Test
    public void spelCompilerConfigurationTest() {

        class MyMessage {
            public String payload="Hello world";
        }

        SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
                this.getClass().getClassLoader());
        SpelExpressionParser parser = new SpelExpressionParser(config);
        Expression expr = parser.parseExpression("payload");
        MyMessage message = new MyMessage();
        Object payload = expr.getValue(message);
    }

使用限制

一下情形無法使用 Spel Compilation

  • 涉及賦值的表達式
  • 依賴於轉換服務的表達式
  • 使用自定義解析器或訪問器的表達式
  • Expressions using selection or projection

Bean 定義中的表達式配置

注解方式

注解作用與字段上

public class FieldValueTestBean {

    @Value("#{ systemProperties['user.region'] }")
    private String defaultLocale;

    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public String getDefaultLocale() {
        return this.defaultLocale;
    }
}

注解作用於設值方法上

public class PropertyValueTestBean {

    private String defaultLocale;

    @Value("#{ systemProperties['user.region'] }")
    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public String getDefaultLocale() {
        return this.defaultLocale;
    }
}

注解作用構造方法或者自動裝配方法上

public class MovieRecommender {

    private String defaultLocale;

    private CustomerPreferenceDao customerPreferenceDao;

    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
            @Value("#{systemProperties['user.country']}") String defaultLocale) {
        this.customerPreferenceDao = customerPreferenceDao;
        this.defaultLocale = defaultLocale;
    }

    // ...
}
public class SimpleMovieLister {

    private MovieFinder movieFinder;
    private String defaultLocale;

    @Autowired
    public void configure(MovieFinder movieFinder,
            @Value("#{ systemProperties['user.region'] }") String defaultLocale) {
        this.movieFinder = movieFinder;
        this.defaultLocale = defaultLocale;
    }

    // ...
}

xml 配置方式

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

    <!-- other properties -->
</bean>

<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
    <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>

    <!-- other properties -->
</bean>

常用案例

基礎字符串語法表達式

可配置 字符串、數值(int、real、hex)、boolean 和 null。字符串通過英文單引號括起來聲明,兩個單引號表示一個單引號字符值。數字支持使用負號、指數表示法和小數點。默認情況下,實數使用Double.parseDouble()。

@Test
    public void BasicStringTest() {

        ExpressionParser parser = new SpelExpressionParser();

        // evals to "Hello World"
        String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();

        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();
    }

Properties, Arrays, Lists, Maps, and Indexers

獲取實體屬性、數組、list、map的內容值

通過json語法獲取實體屬性第一個字母不區分大小寫。也可以通過實體方法獲取實體屬性

// 表達式運算
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);
// json語法首字母小寫
String city1 = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
// json語法首字母大寫
String city2 = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
// 通過實體方法獲取
String city3 = (String) parser.parseExpression("getPlaceOfBirth().getCity()").getValue(context);

向量(arrsy)和序列(list)可以通過一下方式獲取

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// Inventions Array

// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
        context, tesla, String.class);

// Members List

// evaluates to "Nikola Tesla"
String name = parser.parseExpression("members[0].name").getValue(
        context, ieee, String.class);

// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
        context, ieee, String.class);

通過在括號內指定文字鍵值來獲取映射(map)的內容。在下面的示例中,因為officers映射的鍵是字符串,我們可以指定字符串文字:

// Officer's Dictionary

Inventor pupin = parser.parseExpression("officers['president']").getValue(
        societyContext, Inventor.class);

// evaluates to "Idvor"
String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
        societyContext, String.class);

// setting values
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
        societyContext, "Croatia");

構建列表(Inline Lists)

{}表示一個空序列

// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);

List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);

構建映射(Inline Maps)

{key:value}表示一個簡單的鍵值對關系,{:}表示一個內容為空的 map。需要注意的是,除非 key 的值包含英文點號 . ,不然書寫時是不需要英文單引號 ' 括起來的,當然,你要給他加上也是沒問題的。

// evaluates to a Java map containing the two entries
Map inventorInfo = (Map) parser.parseExpression("{'name':'Nikola',dob:'10-July-1856'}").getValue(context);

Map mapOfMaps = (Map) parser.parseExpression("{'na.me':{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);

數組構造 (Array Construction)

int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);

// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);

// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);

方法(Methods)

表達式通過java語法調用方法

// string literal, evaluates to "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);

// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean.class);

運算(Operators)

  • 關系運算符

lt (<)gt (>)le (<=)ge (>=)eq (==)ne (!=)div (/)mod (%)not (!) 進行大小比較時,null 比任意數據都小
instanceof 使用是會自動進行類型包裝,造成結果是: true = instanceof T(Integer) false = 1 instanceof T(int)
matches 遵循基礎正則語法

// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);

// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);

// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);

// evaluates to false
boolean falseValue = parser.parseExpression(
        "'xyz' instanceof T(Integer)").getValue(Boolean.class);

// evaluates to true
boolean trueValue = parser.parseExpression(
        "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

// evaluates to false
boolean falseValue = parser.parseExpression(
        "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
  • 邏輯運算符

and (&&)or (||)not (!)

// -- AND --

// evaluates to false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);

// evaluates to true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- OR --

// evaluates to true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);

// evaluates to true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- NOT --

// evaluates to false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);

// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
  • 數學運算符

+ 可以對數字與字符串使用,-,*,/,取余運算符%,指數冪運算符^只可以對數字使用

        // Addition
        int two = parser.parseExpression("1 + 1").getValue(Integer.class);  // 2

        String testString = parser.parseExpression(
                "'test' + ' ' + 'string'").getValue(String.class);  // 'test string'

        // Subtraction
        int four = parser.parseExpression("1 - -3").getValue(Integer.class);  // 4

        double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class);  // -9000

        // Multiplication
        int six = parser.parseExpression("-2 * -3").getValue(Integer.class);  // 6

        double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class);  // 24.0

        // Division
        int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class);  // -2

        double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class);  // 1.0

        // Modulus
        int three = parser.parseExpression("7 % 4").getValue(Integer.class);  // 3

        int one1 = parser.parseExpression("8 / 5 % 2").getValue(Integer.class);  // 1

        // Operator precedence
        int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class);  // -21
  • 賦值運算符

= 賦值運算符號可以在調用 getValue 方法的時候觸發

Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
// 直接調用 getValue 方法賦值
parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic");
// 調用 getValue 方法,通過觸發賦值運算符 `=` 的方式賦值
// alternatively
String aleks = parser.parseExpression(
        "name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);

(Types)

SpEL 中可通過特殊運算符 T 來指定 class實例,該運算符也可以調用靜態方法
StandardEvaluationContext 通過 TypeLocator 來查找 class實例. 同時由於 StandardTypeLocator 實在java.lang包下面的,所以查找這個包下面的 類實例無需寫全名字,例如 java.lang.String 只需要 String 即可.

Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);

Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);

boolean trueValue = parser.parseExpression(
        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
        .getValue(Boolean.class);

構造函數(Constructors)

構造函數的調用也是出了java.lang包下面的類,別的類的構造函數調用都需要使用全量限定類名.

Inventor einstein = p.parseExpression(
        "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor.class);

// create new Inventor instance within the add() method of List
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor(
            'Albert Einstein', 'German'))").getValue(societyContext);

變量(Variables)

在表達式中使用 #variableName 來表明一個變量.變量的實際賦值是通過EvaluationContextsetVariable()方法來賦值的.

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");

EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");

parser.parseExpression("name = #newName").getValue(context, tesla);
System.out.println(tesla.getName())  // "Mike Tesla"

#this#root變量

#this 變量始終被定義並引用當前的評估對象(針對哪些不合格的引用被解析)。#root變量始終被定義並引用根上下文對象。盡管#this評估表達式的組件時可能會有所不同,但 #root始終指的是根。以下示例顯示了如何使用 #this 和 #root`變量:

// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));

// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
context.setVariable("primes", primes);

// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
        "#primes.?[#this>10]").getValue(context);

函數(Functions)

自定義函數注冊到 EvaluationContext 后,就可以在 SpEL 中調用
構建自定義函數

public abstract class StringUtils {
    // 該方法必須是靜態方法
    public static String reverseString(String input) {
        StringBuilder backwards = new StringBuilder(input.length());
        for (int i = 0; i < input.length(); i++) {
            backwards.append(input.charAt(input.length() - 1 - i));
        }
        return backwards.toString();
    }
}

注冊並調用自定義函數

ExpressionParser parser = new SpelExpressionParser();

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
        StringUtils.class.getDeclaredMethod("reverseString", String.class));

String helloWorldReversed = parser.parseExpression(
        "#reverseString('hello')").getValue(context, String.class);

Bean 引用(Bean References)

通過 @ 符號指定 Bean 名稱,然后從自定義的 Bean Resolver 獲取 Bean
通過 $ 符號指定的bean名稱包含$.

        class MyBeanResolver implements BeanResolver {
            @Override
            public Object resolve(EvaluationContext context, String beanName) throws AccessException {
                if (beanName.equals("testBean"))
                    return new int[]{1, 2};
                return null;
            }
        }
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setBeanResolver(new MyBeanResolver());

        // This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
        Object bean = parser.parseExpression("@testBean").getValue(context);
        // This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
        Object factoryBean = parser.parseExpression("&foo").getValue(context);

三元運算符 (Ternary Operator (If-Then-Else))

String falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String.class);
        ExpressionParser parser = new SpelExpressionParser();
        String falseString = parser.parseExpression(
                "false ? 'trueExp' : 'falseExp'").getValue(String.class);

        StandardEvaluationContext context = new StandardEvaluationContext();
        Society tesla = new Society();
        context.setRootObject(tesla);
        parser.parseExpression("name").setValue(context, "IEEE");
        context.setVariable("queryName", "Nikola Tesla");

        String expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
                "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";

        String queryResultString = parser.parseExpression(expression)
                .getValue(context, tesla, String.class);
        // queryResultString = "Nikola Tesla is not a member of the IEEE Society"

貓王運算符 (The Elvis Operator)

其實就是三木運算符一種情形下的簡寫, "name?:'Elvis Presley'" 等價於 "name!=null?name:'Elvis Presley'" 這種語法 Kotlin 已經支持了

        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

        Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
        String name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
        System.out.println(name);  // Nikola Tesla

        tesla.setName(null);
        // "name?:'Elvis Presley'"  等價於 "name!=null?name:'Elvis Presley'"
        name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
        System.out.println(name);  // Elvis Presley

安全操作運算符 (Safe Navigation Operator)

? ,這個符號在前端 FreeMarket語法中也有 在避免空指針異常的時候,不用每次都進行判空處理

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));

String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city);  // Smiljan

tesla.setPlaceOfBirth(null);
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city);  // null - does not throw NullPointerException!!!

集合選擇(Collection Selection)

.? 組合符號
.?[selectionExpression] 取數據選擇條件表達式(selectionExpression )的所有元素
.[1] 取數據選擇條件表達式(selectionExpression )的第一個元素
.$[selectionExpression] 取數據選擇條件表達式(selectionExpression )的最后一個元素

// 在 `societyContext` 中的 `society實例` 的 `members` 屬性上進行篩選,篩選出 `nationality == 'Serbian'` 的 `inventor實例`
List<Inventor> list = (List<Inventor>) parser.parseExpression("members.?[nationality == 'Serbian']").getValue(societyContext);
// 數組和任何實現了 `java.lang.Iterable` 或 `java.util.Map` 的對象實例都支持獲取 `map` 對象的語法
// 從 `map` 里面篩選 `value 小於27` 的 `key-value` 構建一個 `newMap ` 返回
Map newMap = parser.parseExpression("map.?[value<27]").getValue();

集合映射 (Collection Projection)

目前只能由 List


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM