Spring 3 表达式语言如何与属性占位符交互?

发布于 2024-08-17 14:46:09 字数 3208 浏览 5 评论 0原文

Spring 3 引入了一种新的表达式语言< /a> (SpEL) 可以在 bean 定义中使用。语法本身是相当明确的。

目前尚不清楚的是,SpEL 如何与先前版本中已存在的属性占位符语法进行交互(如果有的话)。 SpEL 是否支持属性占位符,或者我是否必须结合两种机制的语法并希望它们结合?

让我举一个具体的例子。我想使用属性语法 ${xyz},但添加了 elvis 运算符 处理 ${xyz} 未定义的情况。

我尝试了以下语法但没有成功:

  • #{xyz?:'defaultValue'}
  • #{${xyz}?:'defaultValue'}

第一个给了我

找不到字段或属性“x” 在类型对象上 'org.springframework.beans.factory.config.BeanExpressionContext'

这表明 SpEL 不会将其识别为属性占位符。

第二种语法抛出一个异常,表示无法识别占位符,因此正在调用占位符解析器,但由于未定义属性,因此按预期失败。

文档没有提及这种交互,因此要么这样的事情不可能,要么没有记录。

有人设法做到这一点吗?


好的,我为此想出了一个小型的、独立的测试用例。这一切都按原样工作:

首先,bean 定义:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:util="http://www.springframework.org/schema/util"
           xsi:schemaLocation="
                http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                http://www.springframework.org/schema/util    http://www.springframework.org/schema/util/spring-util.xsd
           "> 

    <context:property-placeholder properties-ref="myProps"/>

    <util:properties id="myProps">
        <prop key="x.y.z">Value A</prop>
    </util:properties>

    <bean id="testBean" class="test.Bean">
            <!-- here is where the magic is required -->
        <property name="value" value="${x.y.z}"/> 

            <!-- I want something like this
        <property name="value" value="${a.b.c}?:'Value B'"/> 
            --> 
    </bean>     
</beans>

然后,简单的 bean 类:

package test;

public class Bean {

    String value;

    public void setValue(String value) {
        this.value = value;
    }
}

最后,测试用例:

package test;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class PlaceholderTest {

    private @Resource Bean testBean;

    @Test
    public void valueCheck() {
        assertThat(testBean.value, is("Value A"));
    }
}

挑战 - 在 beans 文件中提出一个 SpEL 表达式,它允许我在无法解析 ${xyz} 的情况下指定默认值,并且此默认值必须指定为表达式的一部分,而不是在另一个属性集中具体化。

Spring 3 has introduced a new expression language (SpEL) which can be used in bean definitions. The syntax itself is fairly well specified.

What isn't clear is how, if at all, SpEL interacts with the property placeholder syntax that was already present in prior versions. Does SpEL have support for property placeholders, or do I have to combine the syntax of both mechanisms and hope they combine?

Let me give a concrete example. I want to use the property syntax ${x.y.z}, but with the addition of "default value" syntax as provided by the elvis operator to handle cases where ${x.y.z} is undefined.

I've tried the following syntaxes without success:

  • #{x.y.z?:'defaultValue'}
  • #{${x.y.z}?:'defaultValue'}

The first one gives me

Field or property 'x' cannot be found
on object of type
'org.springframework.beans.factory.config.BeanExpressionContext'

which suggests that SpEL doesn't recognise this as a property placeholder.

The second syntax throws an exception saying that the placeholder is not recognised, so the placeholder resolver is being invoked, but is failing as expected, since the property is not defined.

The docs make no mention of this interaction, so either such a thing is not possible, or it's undocumented.

Anyone managed to do this?


OK, I've come up with a small, self-contained test case for this. This all works as-is:

First, the bean definitions:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:util="http://www.springframework.org/schema/util"
           xsi:schemaLocation="
                http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                http://www.springframework.org/schema/util    http://www.springframework.org/schema/util/spring-util.xsd
           "> 

    <context:property-placeholder properties-ref="myProps"/>

    <util:properties id="myProps">
        <prop key="x.y.z">Value A</prop>
    </util:properties>

    <bean id="testBean" class="test.Bean">
            <!-- here is where the magic is required -->
        <property name="value" value="${x.y.z}"/> 

            <!-- I want something like this
        <property name="value" value="${a.b.c}?:'Value B'"/> 
            --> 
    </bean>     
</beans>

Then, the trivial bean class:

package test;

public class Bean {

    String value;

    public void setValue(String value) {
        this.value = value;
    }
}

And lastly, the test case:

package test;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class PlaceholderTest {

    private @Resource Bean testBean;

    @Test
    public void valueCheck() {
        assertThat(testBean.value, is("Value A"));
    }
}

The challenge - to come up with a SpEL expression in the beans file which allows me to specify a default value in cases where ${x.y.z} cannot be resolved, and this default must be specified as part of the expression, not externalized in another property set.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

云之铃。 2024-08-24 14:46:09

要从 SpEL 表达式访问属性占位符,可以使用以下语法:#{'${xyz}'}。但是,它无法解决 elvis 运算符和默认值的问题,因为当 ${xyz} 无法解析时它会抛出异常。

但您不需要 SpEL 来声明属性的默认值:

<context:property-placeholder location="..." properties-ref="defaultValues"/>

<bean id = "defaultValues" class = "org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="properties">
        <props>
            <prop key="x.y.z">ZZZ</prop>
        </props>
    </property>
</bean>

<bean ...>
    <property name = "..." value = "${x.y.z}" />
</bean>

To access property placeholder from SpEL expression, the following syntax can be used: #{'${x.y.z}'}. Hovewer, it can't solve your problem with elvis operator and default values, because it would throw an exception when ${x.y.z} cannot be resolved.

But you don't need SpEL to declare default values for properties:

<context:property-placeholder location="..." properties-ref="defaultValues"/>

<bean id = "defaultValues" class = "org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="properties">
        <props>
            <prop key="x.y.z">ZZZ</prop>
        </props>
    </property>
</bean>

<bean ...>
    <property name = "..." value = "${x.y.z}" />
</bean>
帅的被狗咬 2024-08-24 14:46:09

看来你错过了冒号:

#{ ${x.y.z} ?: 'defaultValue' }

It seems you missed the colon:

#{ ${x.y.z} ?: 'defaultValue' }
猫瑾少女 2024-08-24 14:46:09

${myProps.item:defaultValue}表示当myProps.item不存在时,使用defaultValue。这是属性占位符的默认行为。

#{defaultValue} 表示字面值的 SpEL。

所以,${myProps.item:#{defaultValue}}表示当myProps.item不存在时,计算SpEL的值,并将其分配给目标字段。

示例:

${redis.auth:#{null}} 表示当 redis.auth 属性不存在时,将其设置为 null

${myProps.item:defaultValue} means that when myProps.item does not exists, use defaultValue. That's the default behaviour of property placeholder.

#{defaultValue} means SpEL for literal value.

So,${myProps.item:#{defaultValue}} means when myProps.item does not exists, then calculate the value of SpEL, and assign it to target field.

Example:

${redis.auth:#{null}} means when redis.auth properties does not exists, set it to null.

一梦浮鱼 2024-08-24 14:46:09

如果您只想设置占位符的默认值,请参阅

   <property name="value" value="${x.y.z:defaultValue}"/> 

如果您想测试 SpEL 和占位符之间的交互,使用这个:

   <!-- set value "77-AA-BB-CC-88" when property "x.y.z" not exist -->
   <property name="value" value="77-#{'AA-${x.y.z:BB}-CC'}-88"/>

If you just want to set default value for placeholder, see this:

   <property name="value" value="${x.y.z:defaultValue}"/> 

If you want to test interaction between with SpEL and placeholder, using this:

   <!-- set value "77-AA-BB-CC-88" when property "x.y.z" not exist -->
   <property name="value" value="77-#{'AA-${x.y.z:BB}-CC'}-88"/>
潜移默化 2024-08-24 14:46:09

实际上 Property-Placeholder 本身就可以解决您的问题。
即,您可以使用属性 properties 在 Spring 上下文中显式指定默认设置。然后,您可以指定应使用的设置的位置,并将属性 localOverride 设置为 true
在这种情况下,在外部资源中找到的所有属性(在 location 属性中指定)将覆盖默认属性(在上下文中显式定义)。

希望我有所帮助。

Actually Property-Placeholder can resolve your issues itself.
I.e. you can specify default settings explicitly in Spring context, using property properties. Then you can specify location for settings that should be used, and set property localOverride to true.
In such case all properties that will be found in external resources (specified in location property) will override default ones (explicitly defined within context).

Hope I helped.

秋意浓 2024-08-24 14:46:09

您需要添加它以使其在您的示例中运行

<bean id="testBean" class="elvis.Bean">
        <!-- here is where the magic is required
    <property name="value" value="${x.y.z}"/>
    -->

        <!-- I want something like this -->
    <property name="value" value="#{myProps.get('a.b.c')?:'Value B'}"/>

</bean>

您的方法不起作用,因为 Spring 尝试将 ${abc} 计算为具有成员 的对象 a b 与成员 c 会导致 NPE,因为 a 不存在。

You need to add this to get it running within your example

<bean id="testBean" class="elvis.Bean">
        <!-- here is where the magic is required
    <property name="value" value="${x.y.z}"/>
    -->

        <!-- I want something like this -->
    <property name="value" value="#{myProps.get('a.b.c')?:'Value B'}"/>

</bean>

Your approach does not work, because Spring tries to evaluate ${a.b.c} to an object a with member b with member c which results in a NPE because a does not exist.

风吹过旳痕迹 2024-08-24 14:46:09

你可以:

<bean id="testBean" class="test.Bean">
        <!-- if 'a.b.c' not found, then value="Value B" --->
       <property name="value" value="${a.b.c:Value B}"/> 
</bean>     

或者

 ...
 <!-- if 'a.b.c' not found , but 'a.b' found ,then value=${a.b}
      if 'a.b' also not found , then value="a"     
 -->
 <property name="value" value="${a.b.c:${a.b:a}"/> 
 ...

或者
...

   <!-- if 'a.b.c' not found , but 'a.b' found ,then value=${a.b}
      if 'a.b' also not found , then value="a"     
    -->
       <property name="value" value="#{  '${a.b.c:}'  ?: '${a.b:a}' }"/> 
   ...

you may:

<bean id="testBean" class="test.Bean">
        <!-- if 'a.b.c' not found, then value="Value B" --->
       <property name="value" value="${a.b.c:Value B}"/> 
</bean>     

or

 ...
 <!-- if 'a.b.c' not found , but 'a.b' found ,then value=${a.b}
      if 'a.b' also not found , then value="a"     
 -->
 <property name="value" value="${a.b.c:${a.b:a}"/> 
 ...

or
...

   <!-- if 'a.b.c' not found , but 'a.b' found ,then value=${a.b}
      if 'a.b' also not found , then value="a"     
    -->
       <property name="value" value="#{  '${a.b.c:}'  ?: '${a.b:a}' }"/> 
   ...
你曾走过我的故事 2024-08-24 14:46:09

我尝试了以下方法,它起作用了(虽然相当难看):

#{ myProps.getProperty('xyz')?:'Value B' }

I've tried the following and it worked (pretty ugly though):

#{ myProps.getProperty('x.y.z')?:'Value B' }

述情 2024-08-24 14:46:09

无需使用 Elvis,只需在冒号后提供默认值即可。

@Value("${my.connection.timeout:5000}")
private int myTimeoutMillis;

或者

@Retryable(maxAttemptsExpression = "#{${my.max.attempts:10}}")
public void myRetryableMethod() {
    // ...
}

There's no need to use Elvis, just supply the default after a colon.

@Value("${my.connection.timeout:5000}")
private int myTimeoutMillis;

or

@Retryable(maxAttemptsExpression = "#{${my.max.attempts:10}}")
public void myRetryableMethod() {
    // ...
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文