Spring5參考指南: SpEL



SpEL的全稱叫做Spring Expression Language。通常是為了在XML或者注解里面方便求值用的,通過編寫#{ }這樣的格式,即可使用。

Bean定義中的使用

XML配置

可以用SpEL設置屬性或構造函數參數值,如下示例所示:

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

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

Spring內置了很多預定義變量,如SystemProperties, 你可以像下面這樣直接引用它:

    <bean id="taxCalculator" class="com.flydean.beans.TaxCalculator">
        <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>

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

同樣的,你還可以按名稱引用其他bean屬性,如下示例所示:

    <bean id="shapeGuess" class="com.flydean.beans.NumberGuess">
        <property name="randomNumber" value="#{ numberGuess.randomNumber }"/>

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

注解配置

要指定默認值,可以將@value注解放在字段、方法、方法或構造函數參數上。

以下示例設置字段變量的默認值:

public static class FieldValueTestBean

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

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

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

}

下面的示例顯示了在屬性設置器方法上的示例:

public static class PropertyValueTestBean

    private String defaultLocale;

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

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

}

autowired方法和構造函數也可以使用@value注解,如下示例所示:

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

    // ...
}
public class MovieRecommender {

    private String defaultLocale;

    private CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

求值

雖然SpEL通常用在Spring的XML和注解中,但是它可以脫離Spring獨立使用的,這時候需要自己去創建一些引導基礎結構類,如解析器。 大多數Spring用戶不需要處理這個基礎結構,只需要編寫表達式字符串進行求值。

支持的功能

SpELl支持很多種功能,包括:

  • 文字表達式
  • 屬性、數組、列表、映射和索引器
  • 內聯 List
  • 內聯 Map
  • Array
  • 方法
  • Operators
  • 類型
  • Constructors
  • 變量
  • 功能
  • bean引用
  • 三元運算符(if-then-else)
  • elvis
  • Safe Navigation Operator

下面分別舉例子:

文字表達式

支持的文本表達式類型包括字符串、數值(int、real、hex)、布爾值和null。字符串由單引號分隔。要將單引號本身放入字符串中,請使用兩個單引號字符。

public class LiteralApp {
    public static void main(String[] args) {
        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

Properties 通過 “.” 來訪問嵌套的屬性值。如下:

// evals to 1856
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);

String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);

屬性名稱的第一個字母允許不區分大小寫。數組和列表的內容是使用方括號表示法獲得的,如下例所示

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

映射的內容是通過在括號內指定文本鍵值獲得的:

// 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 List

你可以直接在表達式中表示列表:

// 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 Map

你還可以使用key:value表示法在表達式中直接表示映射。以下示例顯示了如何執行此操作:

// 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("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);

構造數組

可以使用熟悉的Java語法構建數組,可以選擇的提供初始化器,以便在構建時填充數組。以下示例顯示了如何執行此操作:

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

方法

可以通過使用典型的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);

類型

您可以使用特殊的T運算符來指定java.lang.class(類型)的實例。靜態方法也可以使用此運算符調用。StandardEvaluationContext使用TypeLocator來查找類型,StandardTypeLocator(可以替換)是在理解java.lang包的基礎上構建的。這意味着T()對java.lang中類型的引用不需要完全限定,但所有其他類型引用都必須是限定的。下面的示例演示如何使用T運算符:

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

構造器

可以使用new運算符調用構造函數。除了基元類型(int、float等)和字符串之外,其他類型都應該使用完全限定的類名。下面的示例演示如何使用新的運算符來調用構造函數:

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

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

變量

可以使用#variableName語法引用表達式中的變量。變量是通過在EvaluationContext實現上使用setVariable方法設置的。以下示例顯示如何使用變量:

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

函數

您可以通過注冊可以在表達式字符串中調用的用戶定義函數來擴展spel。該函數通過EvaluationContext注冊。以下示例顯示如何注冊用戶定義函數:

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 resolver配置了評估上下文,則可以使用@符號從表達式中查找bean。以下示例顯示了如何執行此操作:

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("@something").getValue(context);

要訪問工廠bean本身,您應該在bean名稱前面加上&符號。以下示例顯示了如何執行此操作:

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

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

If-Then-Else

可以使用三元運算符在表達式中執行if-then-else條件邏輯。下面的列表顯示了一個最小的示例:

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

Elvis

ELVIS運算符是三元運算符語法的縮寫,在groovy語言中使用。對於三元運算符語法,通常必須重復變量兩次,如下示例所示:

String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");

相反,您可以使用Elvis操作符(以Elvis的發型命名)。下面的示例演示如何使用Elvis運算符:

ExpressionParser parser = new SpelExpressionParser();

String name = parser.parseExpression("name?:'Unknown'").getValue(String.class);
System.out.println(name);  // 'Unknown'

Safe Navigation 運算符

Safe Navigation操作符用於避免nullpointerException,它來自groovy語言。通常,當您引用一個對象時,您可能需要在訪問該對象的方法或屬性之前驗證它不是空的。為了避免這種情況,Safe Navigation操作符返回空值而不是拋出異常。以下示例說明如何使用Safe Navigation:

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!!!

集合選擇

Selection是一種功能強大的表達式語言功能,通過從源集合的條目中進行選擇,可以將源集合轉換為另一個集合。
Selection使用的語法為.?[selectionExpression]。它過濾集合並返回包含原始元素子集的新集合。例如,selection可以讓我們很容易地獲得塞爾維亞發明家的列表,如下示例所示:

List<Inventor> list = (List<Inventor>) parser.parseExpression(
        "Members.?[Nationality == 'Serbian']").getValue(societyContext);

在list和map上都可以Selection。對於list,將根據每個單獨的列表元素評估選擇條件。針對map,選擇標准針對每個映射條目(Java類型Map.Entry)進行評估。每個map項都有其鍵和值,可以作為屬性訪問,以便在選擇中使用。
以下表達式返回一個新map,該映射由原始map的那些元素組成,其中輸入值小於27:

Map newMap = parser.parseExpression("map.?[value<27]").getValue();

除了返回所有選定的元素之外,您還能檢索第一個或最后一個值。要獲取與所選內容匹配的第一個條目,語法為。.^ [selectionExpression]。要獲取最后一個匹配的選擇,語法為.$[SelectionExpression]。

集合投影

Projection允許集合驅動子表達式的計算,結果是一個新集合。投影的語法是.![projectionExpression]。例如,假設我們有一個發明家列表,但是想要他們出生的城市列表。實際上,我們想為發明家列表中的每個條目評估“placeofbirth.city”。下面的示例使用投影進行此操作:

// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");

您還可以使用map來驅動投影,在這種情況下,投影表達式針對map中的每個條目(表示為Java Map.Entry)進行評估。跨map投影的結果是一個列表,其中包含對每個map條目的投影表達式的計算。

表達式模板化

表達式模板允許將文本與一個或多個計算塊混合。每個評估塊都由您可以定義的前綴和后綴字符分隔。常見的選擇是使用#{ }作為分隔符,如下示例所示:

String randomPhrase = parser.parseExpression(
        "random number is #{T(java.lang.Math).random()}",
        new TemplateParserContext()).getValue(String.class);

// evaluates to "random number is 0.7038186818312008"

字符串的計算方法是將文本“random number is”與計算#{ }分隔符內表達式的結果(在本例中,是調用該random()方法的結果)連接起來。parseExpression()方法的第二個參數的類型為parserContext。ParserContext接口用於影響表達式的解析方式,以支持表達式模板化功能。TemplateParserContext的定義如下:

public class TemplateParserContext implements ParserContext {

    public String getExpressionPrefix() {
        return "#{";
    }

    public String getExpressionSuffix() {
        return "}";
    }

    public boolean isTemplate() {
        return true;
    }
}

本節的例子可以參考spel

更多教程請參考 flydean的博客


免責聲明!

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



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