Spring EL 表达式

作者: 简不凡 | 来源:发表于2018-08-07 16:28 被阅读0次

    一、介绍

    介绍的网上一搜 'spring 表达式' 可定有一堆。本文主要说一下spring 表达式的使用场景,我平时工作用到其中几个,这里依据官方文档做下汇总。
    一开始感觉spring 表达式有个毛用,感觉像个傻子。别着急,慢慢就感觉到这东西的用处了。
    这先定义下几个术语:

    术语 简称
    官方文档 官档
    SpEL 表达式 SpEL

    (markdown用的不熟,凑合着看哈)
    废话少说,略略略~~~

    二、使用场景

    1. 求值

    这部分介绍SpEL接口和表达式语言的简单使用。
    下面的代码段是使用SpEL api获取简单的字符串。

    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression("'Hello World'");
    String message = (String) exp.getValue();
    

    SpEL类和接口大部分在org.springframework.expression和子包spel.support下边。
    ExpressionParser 接口负责解析表达式,Expression 负责计算(后边就知道为什么说计算了)出表达式字符串的值。

    当然 表达式中不仅仅可以包含简单的字符串(这样做就像个傻子···),还可以包含运算符、方法(函数),甚至是自定义类对象。
    这段代码中,表达式中用了方法调用(在Java中String可是对象哟)。

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

    这段代码中,使用了javaBean的属性,如String的Bytes属性(这地方我也没整明白,官档这么写的就这么写了)。

    ExpressionParser parser = new SpelExpressionParser();
    
    // invokes 'getBytes()'
    Expression exp = parser.parseExpression("'Hello World'.bytes");
    byte[] bytes = (byte[]) exp.getValue();
    

    SpEL 也支持'.'的方式访问成员,如 prop1.prop2.prop3 ,前提是必须能访问到哈(修饰符是public)代码如下:

    ExpressionParser parser = new SpelExpressionParser();
    
    // invokes 'getBytes().length'
    Expression exp = parser.parseExpression("'Hello World'.bytes.length");
    int length = (Integer) exp.getValue();
    

    注意这里用的public <T> T getValue(Class<T> desiredResultType)方法,直接将String类型转化为int类型了。

    更常用的方式是先写表达式,用于解析对象。这里SpelExpressionParser使用表达式(parseExpression方法的参数)从对象中获取属性和判断属性是否等于特定值的用法。(这里表达式中判断或操作对象的属性,工作中遇到了)

    // Create and set a calendar
    GregorianCalendar c = new GregorianCalendar();
    c.set(1856, 7, 9);
    
    // The constructor arguments are name, birthday, and nationality.
    Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
    
    ExpressionParser parser = new SpelExpressionParser();
    
    Expression exp = parser.parseExpression("name");
    String name = (String) exp.getValue(tesla);
    // name == "Nikola Tesla"
    
    exp = parser.parseExpression("name == 'Nikola Tesla'");
    boolean result = exp.getValue(tesla, Boolean.class);
    // result == true
    

    这里介绍一种更加通用的方式。
    首先介绍一个接口——EvaluationContext。
    这个接口用于在解析属性、方法或者特殊字段时方便类型转换。它有两种实现方式:

    • SimpleEvaluationContext ——只包含SpEL的部分配置项,适用于不严格按照SpEL规范,包括数据绑定表达式,基于属性过滤的操作等等。
    • StandardEvaluationContext  ——可以设置SpEL的所有配置,可以指定默认的基本对象,并执行与计算相关的所有操作。
      下边这段时关于两种实现的区别(比较重要,英文水平有限,不敢造次,特copy官档)

    SimpleEvaluationContext is designed to support only a subset of the SpEL language syntax. It excludes Java type references, constructors, and bean references. It also requires that one explicitly choose the level of support for properties and methods in expressions. By default, the create() static factory method enables only read access to properties. You can also obtain a builder to configure the exact level of support needed, targeting one or some combination of the following:

    • Custom PropertyAccessor only (no reflection)
    • Data binding properties for read-only access
    • Data binding properties for read and write

    SpEL 可根据上下文自动进行类型转换(当然必须时显而易见的,符合java转换的哈)如下边这段代码中,'false'这里时字符串,但根据,上下文自动转为boolean了。

    class Simple {
        public List<Boolean> booleanList = new ArrayList<Boolean>();
    }
    
    Simple simple = new Simple();
    simple.booleanList.add(true);
    
    EvaluationContext context = SimpleEvaluationContext().forReadOnlyDataBinding().build();
    
    // false is passed in here as a string. SpEL and the conversion service will
    // correctly recognize that it needs to be a Boolean and convert it
    parser.parseExpression("booleanList[0]").setValue(context, simple, "false");
    
    // b will be false
    Boolean b = simple.booleanList.get(0);
    

    这里先暂停一下了,想要继续了解,请参见官档

    2. 注入bean时赋值

    SpEL可以在用XML配置文件或annotation注解形式注入bean时,设置属性的值,注意语法:#{ <expression string> } (一定要注意语法格式,一定要注意语法格式,一定要注意语法格式!)

    XML配置
    • 可以使用SpEL 设置属性或构造方法传参
    <bean id="numberGuess" class="org.spring.samples.NumberGuess">
        <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
    
        <!-- other properties -->
    </bean>
    
    • 使用提前设置的属性值或环境变量
    <bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
        <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
    
        <!-- other properties -->
    </bean>
    
    • 也可以在SpEL中引用其他的Bean
    <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>
    
    注解方式

    @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;
        }
    
    }
    
    • 设置形参默认值
    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;
        }
    
        // ...
    }
    

    三、参考

    使用SpEL设置或运算(符)各种类型的值

    简单值(包括字符串,不知道为什么)

    字符串要用加单引号,转义用两个单引号。

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

    复杂值(涉及到找特定值)

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

    还有好多关于值的,项目中涉及不多,这里就不多说了,有兴趣参考上述官档。

    方法

    方法的使用按照正常的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);
    

    运算符

    各种运算符都搁一块了。

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

    官档的注意事项
    Greater/less-than comparisons against null follow a simple rule: null is treated as nothing here (i.e. NOT as zero). As a consequence, any other value is always greater than null (X > null is always true) and no other value is ever less than nothing (X < null is always false).

    If you prefer numeric comparisons instead, please avoid number-based null comparisons in favor of comparisons against zero (e.g. X > 0 or X < 0).

    除了语言中常用的,还额外支持instanceofmatches,看代码就全懂了。

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

    注意事项
    Be careful with primitive types as they are immediately boxed up to the wrapper type, so 1 instanceof T(int) evaluates to false while 1 instanceof T(Integer) evaluates to true, as expected.

    这句话很重要,不敢翻译,贴这里。

    Each symbolic operator can also be specified as a purely alphabetic equivalent. This avoids problems where the symbols used have special meaning for the document type in which the expression is embedded (eg. an XML document). The textual equivalents are shown here: lt (<), gt (>), le (<=), ge (>=), eq (==), ne (!=), div (/), mod (%), not (!). These are case insensitive.

    // -- 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 one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class);  // 1
    
    // Operator precedence
    int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class);  // -21
    

    类型(涉及到自定义对象了)

    T 可用于指定特定类型的对象实例。静态方法调用时也需要添加这个。StandardEvaluationContext类使用TypeLocator来识别类型,而java.lang包下的类默认实现了StandardTypeLocator(可以被替换),所以java.lang包下的类不用加这个,其他类型(包括自定义类型)需要添加这个。这个你懂的,不同看看代码。

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

    构造方法

    表达式中可以嵌套创建对象。

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

    变量(项目中遇到了)

    表达式中可以用#变量名来引用变量。变量需要使用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"
    

    方法

    可以在表达式中使用注册的方法,和变量类似。

    Method method = ...;
    
    EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
    context.setVariable("myFunction", method);
    
    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解析器才能发现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);
    

    工厂类型的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);
    

    三元运算符

    语法

    String falseString = parser.parseExpression(
            "false ? 'trueExp' : 'falseExp'").getValue(String.class);
    
    parser.parseExpression("Name").setValue(societyContext, "IEEE");
    societyContext.setVariable("queryName", "Nikola Tesla");
    
    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(societyContext, String.class);
    // queryResultString = "Nikola Tesla is a member of the IEEE Society"
    

    还有好多类似java8中Stream、Map新特性的操作,感觉不错。

    这里就不一一介绍了。这东西出问题了参考有用,动手试试有用,其他的都没用。

    代码中所涉及到的几个Bean类

    package org.spring.samples.spel.inventor;
    
    import java.util.Date;
    import java.util.GregorianCalendar;
    
    public class Inventor {
    
        private String name;
        private String nationality;
        private String[] inventions;
        private Date birthdate;
        private PlaceOfBirth placeOfBirth;
    
        public Inventor(String name, String nationality) {
            GregorianCalendar c= new GregorianCalendar();
            this.name = name;
            this.nationality = nationality;
            this.birthdate = c.getTime();
        }
    
        public Inventor(String name, Date birthdate, String nationality) {
            this.name = name;
            this.nationality = nationality;
            this.birthdate = birthdate;
        }
    
        public Inventor() {
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getNationality() {
            return nationality;
        }
    
        public void setNationality(String nationality) {
            this.nationality = nationality;
        }
    
        public Date getBirthdate() {
            return birthdate;
        }
    
        public void setBirthdate(Date birthdate) {
            this.birthdate = birthdate;
        }
    
        public PlaceOfBirth getPlaceOfBirth() {
            return placeOfBirth;
        }
    
        public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
            this.placeOfBirth = placeOfBirth;
        }
    
        public void setInventions(String[] inventions) {
            this.inventions = inventions;
        }
    
        public String[] getInventions() {
            return inventions;
        }
    }
    
    package org.spring.samples.spel.inventor;
    
    public class PlaceOfBirth {
    
        private String city;
        private String country;
    
        public PlaceOfBirth(String city) {
            this.city=city;
        }
    
        public PlaceOfBirth(String city, String country) {
            this(city);
            this.country = country;
        }
    
        public String getCity() {
            return city;
        }
    
        public void setCity(String s) {
            this.city = s;
        }
    
        public String getCountry() {
            return country;
        }
    
        public void setCountry(String country) {
            this.country = country;
        }
    
    }
    
    package org.spring.samples.spel.inventor;
    
    import java.util.*;
    
    public class Society {
    
        private String name;
    
        public static String Advisors = "advisors";
        public static String President = "president";
    
        private List<Inventor> members = new ArrayList<Inventor>();
        private Map officers = new HashMap();
    
        public List getMembers() {
            return members;
        }
    
        public Map getOfficers() {
            return officers;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public boolean isMember(String name) {
            for (Inventor inventor : members) {
                if (inventor.getName().equals(name)) {
                    return true;
                }
            }
            return false;
        }
    
    }
    

    链接

    官方文档.


    题外话:各位看官,浪费一次宝贵的健身机会,码这些字值吗?欢迎留言!

    相关文章

      网友评论

        本文标题:Spring EL 表达式

        本文链接:https://www.haomeiwen.com/subject/yesavftx.html