How does Spring 3 expression language interact with property placeholders?

JavaSpringSpring El

Java Problem Overview


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.

Java Solutions


Solution 1 - Java

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>

Solution 2 - Java

It seems you missed the colon:

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

Solution 3 - Java

${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.

Solution 4 - Java

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

Solution 5 - Java

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.

Solution 6 - Java

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.

Solution 7 - Java

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}' }"/> 
   ...

Solution 8 - Java

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

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

Solution 9 - Java

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() {
    // ...
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionskaffmanView Question on Stackoverflow
Solution 1 - JavaaxtavtView Answer on Stackoverflow
Solution 2 - JavaBozhoView Answer on Stackoverflow
Solution 3 - Javavr3CView Answer on Stackoverflow
Solution 4 - Javabtpka3View Answer on Stackoverflow
Solution 5 - JavaMyronView Answer on Stackoverflow
Solution 6 - JavamicfraView Answer on Stackoverflow
Solution 7 - JavaqxoView Answer on Stackoverflow
Solution 8 - JavaGergely TothView Answer on Stackoverflow
Solution 9 - Javawhistling_marmotView Answer on Stackoverflow