首页 > 其他分享 >SpEL表达式

SpEL表达式

时间:2023-08-26 23:35:09浏览次数:31  
标签:name root SpEL expressionParser new expression 表达式 String

基本原则

  • 字符串需要使用单引号引起来,否则表示的是一个变量。
  • 变量前面没有#符号,表示取根对象的属性值。如果根对象没有该属性,则会抛异常。
  • 变量前加#符号,表示访问上下文中通过setVariable方法设置的变量,其中#root是一个特殊值,表示根对象。如果找不到该变量,不会抛异常,而是返回null。
  • 变量前加@符号,表示从上下文中的BeanResolver获取变量值,一个默认实现BeanFactoryResolver是从Spring容器中获取bean。若无法找到对应的bean,则会抛异常。
  • EvaluationContext上下文中可以设置根对象,还可以通过setVariable方法设置其它变量,这些变量可以通过#符合+变量名引用。特别的,如果有设置BeanResolver实例,可以从Spring容器中获取bean实例,通过@符合+beanName引用。

使用例子

简单使用

@Test
public void simple() {
    ExpressionParser expressionParser = new SpelExpressionParser();
    // 简单计算
    Expression expression = expressionParser.parseExpression("3 + 4");
    Integer value = expression.getValue(Integer.class);
    Assert.assertEquals(Integer.valueOf(7), value);
    
    // 字面量, 字符串需要使用''引起来
    expression = expressionParser.parseExpression("'hello'");
    String literal = expression.getValue(String.class);
    Assert.assertEquals("hello", literal);
}

访问根对象

注意,若访问根对象中一个不存在的属性,会抛异常。

使用的pojo对象

@AllArgsConstructor
@Data
public class SpelObj {

    private String name;

    private Integer age;
}

api使用

/**
 * getValue方法若不明确指定EvaluationContext参数, 
 * 则会创建一个空StandardEvaluationContext实例
 */
@Test
public void accessRoot() {
    SpelObj root = new SpelObj("hello", 20);
    ExpressionParser expressionParser = new SpelExpressionParser();

    // 访问根对象
    Expression expression = expressionParser.parseExpression("#root");
    Assert.assertSame(root, expression.getValue(root));

    // 访问根对象属性
    expression = expressionParser.parseExpression("#root.name");
    String name = expression.getValue(root, String.class);
    Assert.assertEquals(name, "hello");
    
    // 访问根对象属性(简写形式, 不加#符号)
    expression = expressionParser.parseExpression("name");
    name = expression.getValue(root, String.class);
    Assert.assertEquals(name, "hello");

    // 调用方法, 此处显式创建EvaluationContext上下文对象, 并设置根对象
    expression = expressionParser.parseExpression("name.concat(' world')");
    EvaluationContext evaluationContext = new StandardEvaluationContext(root);
    String result = expression.getValue(evaluationContext, String.class);
    Assert.assertEquals("hello world", result);
}

使用#访问变量

除了默认的root(根对象),上下文对象中可以自定义其他变量,通过#符合访问。访问一个不存在的变量返回null,不会抛异常。

@Test
public void accessVar() {
    ExpressionParser expressionParser = new SpelExpressionParser();

    // 使用#访问自定义变量
    Expression expression = expressionParser.parseExpression("#name.concat(':').concat(#age)");
    StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
    // 添加自定义变量(非root变量)
    evaluationContext.setVariable("name", "zhangsan");
    evaluationContext.setVariable("age", 20);
    String result = expression.getValue(evaluationContext, String.class);
    Assert.assertEquals("zhangsan:20", result);
}

使用@符合访问bean

需要往上下文中设置BeanResolver,用于获取bean。

@Test
public void accessSpringBean() {
    // 创建容器
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    ac.registerBean("user", SpelObj.class, "user-bean", 20);
    ac.refresh();

    // 创建BeanResovler
    BeanResolver beanResolver = new BeanFactoryResolver(ac);

    ExpressionParser expressionParser = new SpelExpressionParser();
    // 通过@符号+beanName访问bean
    Expression expression = expressionParser.parseExpression("@user");
    StandardEvaluationContext evaluationContext = new StandardEvaluationContext(new SpelObj("hello", 20));
    // 设置BeanResovler
    evaluationContext.setBeanResolver(beanResolver);

    SpelObj spelObj = ac.getBean("user", SpelObj.class);
    Assert.assertSame(spelObj, expression.getValue(evaluationContext));
}

注: 如果bean是一个FactoryBean,需要访问这个FactoryBean本身,则使用&符合即可。

MethodBasedEvaluationContext上下文对象

MethodBasedEvaluationContextStandardEvaluationContext的一个子类。它主要是把方法参数也加到了变量中,使得用户可以直接通过#+参数名来获取值。常常用于解析注解中的SpEL表达式。如Cache模块中@Cacheable注解中的key属性就支持SpEL表达式。

可以使用如下方式来访问方法参数

  • 直接通过#+参数名,如#name
  • 通过#+内置参数名a,#a0访问第一个参数,#a1访问第二个参数
  • 通过#+内置参数名p,#p0访问第一个参数,#p1访问第二个参数

其中a0和p0是等价的,只是设置两个参数前缀而已。而通过参数名称来访问需要ParameterNameDiscoverer的支持,默认情况下,java编译后通过反射是拿不到真实的方法参数名称的,需要带上-parameters参数编译才行,不过Spring还另外基于ASM的方式解析字节码文件,获取字节码的本地方法表来获取方法真实参数。DefaultParameterNameDiscoverer实现类同时使用上面所说的两种方式来获取方法参数名。

下面来看下使用例子

interface Samer {
    boolean isSame(String name, Integer age);
}

@Test
public void accessMethodArg() {
    // 该对象可以重复使用并且线程安全
    ExpressionParser expressionParser = new SpelExpressionParser();

    Samer proxy = (Samer) Proxy.newProxyInstance(
            SpelTest.class.getClassLoader(),
            new Class<?>[]{Samer.class},
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) {
                    // root对象
                    Object root = new Object();
                    // 创建EvaluationContext
                    MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(
                            root,
                            method,
                            args,
                            new DefaultParameterNameDiscoverer()
                    );
                    // 通过#+方法参数名
                    Expression expression = expressionParser.parseExpression("#name");
                    Assert.assertEquals("hello", expression.getValue(evaluationContext));

                    // 通过# + 内置的变量名+下标a0
                    expression = expressionParser.parseExpression("#a0");
                    Assert.assertEquals("hello", expression.getValue(evaluationContext));

                    // 通过# + 内置的变量名+下标p0
                    expression = expressionParser.parseExpression("#p1");
                    Assert.assertEquals(20, expression.getValue(evaluationContext));

                    if (method.getName().equals("isSame")) {
                        return Objects.equals("hello", args[0]) && Objects.equals(20, args[1]);
                    }
                    throw new UnsupportedOperationException(method.getName());
                }
            }
    );
    Assert.assertTrue(proxy.isSame("hello", 20));
}

该类的实现原理也非常简单,只是重写了lookupVariable方法,即寻找自定义变量的逻辑。

@Override
@Nullable
public Object lookupVariable(String name) {
    // 先查找下变量存不存在
    Object variable = super.lookupVariable(name);
    if (variable != null) {
        return variable;
    }
    if (!this.argumentsLoaded) {
        // 把方法参数放到variables变量表中
        lazyLoadArguments();
        this.argumentsLoaded = true;
        // 再次获取
        variable = super.lookupVariable(name);
    }
    return variable;
}
protected void lazyLoadArguments() {
    // Shortcut if no args need to be loaded
    if (ObjectUtils.isEmpty(this.arguments)) {
        return;
    }

    // 获取参数名
    String[] paramNames = this.parameterNameDiscoverer.getParameterNames(this.method);
    int paramCount = (paramNames != null ? paramNames.length : this.method.getParameterCount());
    int argsCount = this.arguments.length;

    for (int i = 0; i < paramCount; i++) {
        Object value = null;
        if (argsCount > paramCount && i == paramCount - 1) {
            // Expose remaining arguments as vararg array for last parameter
            value = Arrays.copyOfRange(this.arguments, i, argsCount);
        }
        else if (argsCount > i) {
            // Actual argument found - otherwise left as null
            value = this.arguments[i];
        }
        // a0、a1等
        setVariable("a" + i, value);
        // p0、p1等
        setVariable("p" + i, value);
        // 参数名
        if (paramNames != null && paramNames[i] != null) {
            setVariable(paramNames[i], value);
        }
    }
}

标签:name,root,SpEL,expressionParser,new,expression,表达式,String
From: https://www.cnblogs.com/wt20/p/17659683.html

相关文章

  • 正则表达式
    规则符号描述样例?0次或1次runo?b->runb、runob+1次或多次runo+b->runob、runoob、runooob*0次、1次或多次runo*b->runb、runob、runoob、runooob.匹配除\n、\r之外的任何字符==[^\n\r]^匹配输入字符串的开始位置$匹配输入字符串的......
  • 身份证正则表达式|电话号码、邮箱正则表达式
    //身份证正则表达式(15位)isIDCard1=/1\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}KaTeXparseerror:Undefinedcontrolsequence:\datposition37:…IDCard2=/^[1-9]\̲d̲{5}[1-9]\d{3}((…/;身份证正则合并:(^\d{15}KaTeXparseerror:Undefinedcontrolsequence:......
  • Lamda表达式
    Lamda表达式1.为什么要用lamda表达式(函数式编程)避免匿名内部类定义过多。代码简洁。去掉没有意义代码,只留下核心逻辑。newThread(()->System.out.println("多线程学习")).start()2.函数式接口定义:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口......
  • 密码正则表达式大全
    1种只能由1种组成只能由字母组成,1-9位^[a-zA-Z]{1,9}$只能由数字组成,1-9位^\d{1,9}$只能由特殊字符组成,1-9位^[^\da-zA-Z\s]{1,9}$至少包含1种至少包含字母,1-9位^(?=.*[a-zA-Z]).{1,9}$至少包含数字,1-9位^(?=.*\d).{1,9}$至少包含特殊字符,1-9位^(?=.*[^\da-zA-Z\s])......
  • 《流畅的python》— 列表推导与生成器表达式
    列表推导是构建列表(list)的快捷方式,而生成器表达式则可以用来创建其他任何类型的序列。如果你的代码里并不经常使用它们,那么很可能你错过了许多写出可读性更好且更高效的代码的机会。很多Python程序员都把列表推导(listcomprehension)简称为listcomps,生成器表达式(generatorexpre......
  • vue常用正则表达式判断身份证格式
    判断身份证格式 /^[1-9]\d{5}(18|19|20|(3\d))\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/写成一个校验类,以后直接使用functionisIdCard(idCard){letreg=/^[1-9]\d{5}(18|19|20|(3\d))\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|3......
  • Python基础入门学习笔记 021函数:lambda表达式
    lambda表达式的作用•Python写一些执行脚本时,使用lambda就可以省下定义函数过程,比如说我们只是需要写个简单的脚本来管理服务器时间,我们就不需要专门定义一个函数然后再写调用,使用lambda就可以使得代码更加精简。•对于一些比较抽象并且整个程序执行下来只需要调用一两次的函......
  • python 使用re模块,配合正则表达式来查找字符串的想要的字符串
    一,首先:我们现了解一下python中正则表达式的基本规则有那些?1,字符串"\d"匹配0~9之间的一个数值eg:'dsas212b321321'使用r'\d'结果:(它会一次匹配一个数字依次查找)212321321  2,字符"+"重复前面一个匹配字符一次或者多次eg:'dsas212b321321'方式1:使用r'\d+'结果:(......
  • shell 正则表达式
    限定符a*出现0次或者多次a+出现1次或者多次a?出现0次或者1次a{3}出现3次a{2,4}出现2~4次a{3,}出现3次或者多次或运算符(a|b)匹配a或者b(ab)|(cd)匹配ab或者cd字符类[abc]匹配a或者b或者c[a-c]同上[a-fA-F0-9]匹配小写+大写英文字符以及数字[^0-9]匹配......
  • 正则表达式:贪婪与非贪婪模式
    正则中的三种模式,贪婪匹配、非贪婪匹配和独占模式。在这6种元字符中,我们可以用{m,n}来表示(*)(+)(?)这3种元字符:贪婪模式,简单说就是尽可能进行最长匹配。非贪婪模式呢,则会尽可能进行最短匹配。正是这两种模式产生了不同的匹配结果。贪婪匹配(Greedy)在正则中,表示次数的量词默认是贪......