package com.example;
public class PlainBean {
    private int value;
    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans default-autowire="byName"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer" />
    <bean class="com.example.PlainBean" p:value="${value:10}" scope="prototype" />
</beans>
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "ctx.xml")
public class PlainBeanTest {
    @Autowired
    private ApplicationContext ctx;
    @Test
    public void test() {
        assertEquals(10, ctx.getBean(PlainBean.class).getValue()); // default value
        ((ConfigurableEnvironment) ctx.getEnvironment()).getPropertySources()
                .addFirst(new MapPropertySource("overriden", Collections.singletonMap("value", "12")));
        assertEquals(12, ctx.getBean(PlainBean.class).getValue()); // test failed
    }
}

But test will pass if using @Value("${value:10}") instead of p:value="${value:10}" , here is test project ps.zip

Comment From: sbrannen

Thanks for raising the issue.

Your analysis is correct: placeholder values in XML are evaluated only once.

This is by design.

Consequently, we are labeling this issue as a documentation issue to improve the documentation for such scenarios.

Comment From: quaff

It's weird that not keep consistency between xml and annotation, is there any workaround to fix this? I'm using spring-batch and configure job in xml, migration is hard and risky, but changing job behavior at runtime is quite desired.

Comment From: jhoeller

It's rather the other way round: ${...} placeholders are fundamentally static, meant to be resolved once-only ever since they have been introduced in the XML bean definition format (where they get resolved in a dedicated metadata post-process step on startup).

@Value supports such placeholders as well but in the course of annotation values where it falls back to on-demand resolution which effectively results in later resolution than with XML. This is technically not consistent with XML indeed (unavoidably since there is no metadata post-process step for annotation-contained values), however, those placeholders are not meant to change anyway, so the effect should not matter per the original design of such placeholders.

Note that #{...} expressions are dynamic by design and always evaluated on-demand - in XML and annotation values. For values that are expected to change at runtime, that's the recommended approach.

Comment From: quaff

Thanks for elaboration. I found only systemProperties are predefined, please guide me how to refer Environment in spel, such as #{env.getProperty('parts','10')}

UPDATED: I found #{@environment.getProperty('parts','10')} works. UPDATED: #{environment['parts']?:10} is much better.

Comment From: sbrannen

UPDATED: #{environment['parts']?:10} is much better.

Yes, that would be the recommended approach to dynamically accessing an environment variable with a default, fallback value.

I'm using spring-batch and configure job in xml, migration is hard and risky, but changing job behavior at runtime is quite desired.

Have you considered using Spring Batch's step scope and/or accessing job parameters via a SpEL expression? Perhaps @mminella can provide further insight here.

Comment From: quaff

UPDATED: #{environment['parts']?:10} is much better.

Yes, that would be the recommended approach to dynamically accessing an environment variable with a default, fallback value.

I'm using spring-batch and configure job in xml, migration is hard and risky, but changing job behavior at runtime is quite desired.

Have you considered using Spring Batch's step scope and/or accessing job parameters via a SpEL expression? Perhaps @mminella can provide further insight here.

Thanks, my puzzle is resolved by #{environment['parts']?:10} and step scope.