Keywords: Spring Framework | Property Placeholder | Configuration Conflict | PropertySourcesPlaceholderConfigurer | XML Configuration
Abstract: This article provides an in-depth analysis of the common 'Could not resolve placeholder' error in Spring Framework, focusing on conflicts caused by multiple PropertySourcesPlaceholderConfigurer instances. Through practical case studies, it demonstrates common pitfalls in XML configuration and offers detailed solutions and best practices to help developers avoid similar configuration errors.
Problem Background and Error Phenomenon
During Spring application development, developers frequently use the @Value annotation to inject configuration values from property files. However, when configuration is improper or multiple property configurators exist, the java.lang.IllegalArgumentException: Could not resolve placeholder 'xxx' in string value "${xxx}" error may occur.
Case Analysis: Conflicts Caused by Duplicate Configurators
Consider the following typical scenario: a Spring service class uses @Value annotation to inject FTP connection parameters:
@Service("ServiceFTP")
@Transactional
public class ServiceFTPImpl implements ServiceFTP {
@Value("${project.ftp.adresse}")
private String adresse;
@Value("${project.ftp.login}")
private String compte;
@Value("${project.ftp.password}")
private String motDePasse;
@Value("${project.ftp.root}")
private String ROOT;
}
The property placeholder is configured in infraContext.xml:
<context:property-placeholder location="classpath:context-core.properties"/>
The corresponding property file context-core.properties contains:
project.ftp.adresse = localhost
project.ftp.login = anonymous
project.ftp.password =
project.ftp.root = /anonymous/
However, another configuration exists in applicationContext.xml:
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="environment">
<bean class="org.springframework.web.context.support.StandardServletEnvironment"/>
</property>
</bean>
Root Cause Analysis
The core issue lies in the existence of two PropertySourcesPlaceholderConfigurer instances in the system:
The first instance comes from the explicit bean definition in applicationContext.xml. This instance does not specify any property file locations and therefore cannot resolve any placeholders.
The second instance comes from the <context:property-placeholder> tag in infraContext.xml, which is correctly configured with property file locations.
When processing placeholder resolution, the Spring framework adopts a fail-fast strategy by default. When the first configurator (from applicationContext.xml) fails to resolve a placeholder, it immediately throws an exception without attempting to use the second configurator.
Solution
The most direct solution is to remove the duplicate configurator. Since the configuration in infraContext.xml can correctly resolve properties, the PropertySourcesPlaceholderConfigurer definition in applicationContext.xml can be safely removed:
<!-- Remove the following configuration -->
<!--
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="environment">
<bean class="org.springframework.web.context.support.StandardServletEnvironment"/>
</property>
</bean>
-->
Configuration Best Practices
To avoid similar issues, it is recommended to follow these best practices:
Single Configuration Principle: Define only one PropertySourcesPlaceholderConfigurer or <context:property-placeholder> throughout the entire application.
Explicit Property File Locations: Ensure the configurator correctly specifies all required property files:
<context:property-placeholder
location="classpath:context-core.properties,classpath:application.properties"
ignore-unresolvable="false"/>
Property File Path Verification: Ensure property files are located in the correct classpath locations and file names are spelled correctly.
Property Key Name Consistency: Verify that placeholder names in @Value annotations exactly match key names in property files, including case sensitivity.
Advanced Configuration Options
For more complex scenarios, consider the following configuration options:
Ignore Unresolvable Placeholders: In some cases, configure the placeholder resolver to ignore unresolvable placeholders:
<context:property-placeholder
location="classpath:context-core.properties"
ignore-unresolvable="true"/>
Multi-Environment Configuration: Use Spring Profiles to support configurations for different environments:
<beans profile="dev">
<context:property-placeholder location="classpath:dev.properties"/>
</beans>
<beans profile="prod">
<context:property-placeholder location="classpath:prod.properties"/>
</beans>
Debugging Techniques
When encountering placeholder resolution issues, employ the following debugging methods:
Check Spring Container Logs: Review detailed logs during Spring startup to confirm whether property files are loaded correctly.
Verify Property File Content: Use simple test code to verify property file accessibility:
@Test
public void testPropertiesLoading() {
ClassPathResource resource = new ClassPathResource("context-core.properties");
assertTrue(resource.exists());
Properties props = new Properties();
props.load(resource.getInputStream());
assertNotNull(props.getProperty("project.ftp.adresse"));
}
Check Configuration Order: Ensure property configurators are initialized before beans that depend on them.
Conclusion
Spring placeholder resolution failures typically stem from configuration issues, particularly conflicts caused by multiple PropertySourcesPlaceholderConfigurer instances. By adhering to the single configuration principle, explicitly specifying property file locations, and implementing appropriate debugging strategies, such problems can be effectively avoided and resolved. Understanding how Spring's property resolution mechanism works is crucial for building stable and reliable applications.