June 29, 2022

Read Values From Properties File in Spring

In this post we’ll see how to externalize your configuration to a properties file in Spring and how to read values from properties file in Spring using XML configuration and using @PropertySource annotation.

It is a best practice to put application specific settings into separate properties files rather than hardcoding them with in the configuration. For example Database related configuration properties like DB Url, Driver class, user, password can be stored in a properties file and read from there or in an application for sending emails SMTP settings like host, user, password can be stored in a properties file.

Properties file in Spring using XML configuration

You can configure property placeholders using <context:property-placeholder> in XML. The values to replace are specified as placeholders of the form ${property-name}. At runtime, a PropertySourcesPlaceholderConfigurer is applied to the metadata, it checks for placeholders in properties file and replace placeholders values that match keys in the properties file.

Note that org.springframework.context.support.PropertySourcesPlaceholderConfigurer is used from Spring framework 5.2 version before that org.springframework.beans.factory.config.PropertyPlaceholderConfigurer class was used which is deprecated from Spring 5.2.

When using <context:property-placeholder> element, a PropertySourcesPlaceholderConfigurer is registered automatically.

For example there is a app.properties file saved at location /src/main/resources/ so that it is on the classpath.

db.driverClassName=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/knpcode
db.username=user
db.password=password
XML Configuration
<?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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
        
  <!--  For properties files --> 
  <context:property-placeholder location="classpath:app.properties" />
    
  <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">  
    <property name="dataSource" ref="dataSource"></property>  
  </bean>  
  <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
    <property name="driverClassName" value = "${db.driverClassName}" />
    <property name="url" value = "${db.url}" />
    <property name="username" value = "${db.username}" />
    <property name="password" value = "${db.password}" />
  </bean>
</beans>

As you can see a properties file is registered using <context:property-placeholder> element by specifying the location of the properties file.

Specifying multiple properties file with XML

You can pass multiple properties files in location attribute as comma separated values.

<context:property-placeholder location="classpath:db.properties, classpath:mail.properties" />

Properties file in Spring using @PropertySource annotation

There is also a @PropertySource annotation which provides a convenient and declarative mechanism for adding a PropertySource to Spring’s Environment.

You can use @PropertySource annotation along with @Value annotation to inject values read from properties file but the better way is to use Spring’s Environment.

@PropertySource with @Value to inject values

@Configuration
@ComponentScan(basePackages = "com.knpcode.springproject")
@PropertySource("classpath:app.properties")
public class AppConfig {
  @Value("${db.driverClassName}")
  private String driverClassName;
  @Value("${db.url}")
  private String url;
  @Value("${db.username}")
  private String userName;
  @Value("${db.password}")
  private String pwd;
	
  @Bean
  public DataSource dataSource() {
    DriverManagerDataSource ds = new DriverManagerDataSource();
    ds.setDriverClassName(driverClassName);
    ds.setUrl(url);
    ds.setUsername(userName);
    ds.setPassword(pwd);
    return ds;
  }
}

Note that PropertySourcesPlaceholderConfigurer class is used to resolve ${...} placeholders in @Value annotations against the current Spring Environment.

Running the example using the following class-

public class App {
  public static void main( String[] args ){
    //EntityManager
    AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    DriverManagerDataSource dataSource =  context.getBean("dataSource", DriverManagerDataSource.class);
    System.out.println("Driver class name- " + dataSource.getUsername());
    context.close();
  }
}
Output
Driver class name- user

@PropertySource with Environment to read property values

@PropertySource annotation adds a PropertySource to Spring’s Environment so using using getProperty() method of Environment is a much convenient way to read properties rather than using @Value annotation.

@Configuration
@ComponentScan(basePackages = "com.knpcode.springproject")
@PropertySource("classpath:app.properties")
public class AppConfig {
  @Autowired
  private Environment env;
	
  @Bean
  public DataSource dataSource() {
    DriverManagerDataSource ds = new DriverManagerDataSource();
    ds.setDriverClassName(env.getProperty("db.driverClassName"));
    ds.setUrl(env.getProperty("db.url"));
    ds.setUsername(env.getProperty("db.username"));
    ds.setPassword(env.getProperty("db.password"));
    return ds;
  }
}

Notice how org.springframework.core.env.Environment is injected and that object is used to get properties.

Spring @PropertySource Features

  1. Any ${…} placeholders present in a @PropertySource resource location are resolved against the set of property sources already registered against the environment. For example
    @Configuration
    @PropertySource("classpath:${app.path:default/path}/app.properties")
    public class AppConfig {
        ....
        ....
    }

    If app.path is present in one of the property sources already registered (for example, system properties or environment variables), the placeholder is resolved to the corresponding value. If not, then default/path is used as a default. If no default is specified and a property cannot be resolved, an IllegalArgumentException is thrown.

    Setting as system property- System.setProperty("app.path", "config");
  2. The @PropertySource annotation is repeatable, according to Java 8 conventions. All such @PropertySource annotations need to be declared at the same level.
    @Configuration
    @ComponentScan(basePackages = "com.knpcode.springproject")
    @PropertySource("classpath:app.properties")
    @PropertySource("classpath:global.properties")
    public class AppConfig {
    	..
    	..
    }

    You can also use @PropertySources container annotation that aggregates several PropertySource annotations.

    @Configuration
    @PropertySources({
    	@PropertySource("classpath:properties/db.properties"),
    	@PropertySource("classpath:properties/mail.properties")
    })
    public class Configurations {
      ...
      ...
    
    }

Ignore exception if value not found

If properties file is not found or passed key doesn’t exist in properties file Spring framework throws an exception by default. If you have a properties file that mayy or may not exist and you don’t want exception to be thrown if it doesn’t exist then you can specify ignoreResourceNotFound attribute as true.

With @PropertySource annotation
@Configuration
@PropertySource(value="classpath:properties/db.properties", ignoreResourceNotFound=true)
public class DBConfiguration {  

}
In XML Configuration
<context:property-placeholder location="classpath:config/db.properties" ignore-resource-not-found="false" />

With XML configuration you can also specify ignore-unresolvable attribute to ignore an exception if placeholder can't be resolved.

<context:property-placeholder location="classpath:config/db.properties" ignore-resource-not-found="false" ignore-unresolvable="false" />

Property overriding with @PropertySource

If a given property key exists in more than one .properties file, the last @PropertySource annotation processed will override the values for such duplicate keys.

For example, given two properties files a.properties and b.properties, consider the following two configuration classes that reference them with @PropertySource annotations:

 @Configuration
 @PropertySource("classpath:/com/myco/a.properties")
 public class ConfigA { }

 @Configuration
 @PropertySource("classpath:/com/myco/b.properties")
 public class ConfigB { }

The override ordering depends on the order in which these classes are registered with the application context.

 AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
 ctx.register(ConfigA.class);
 ctx.register(ConfigB.class);
 ctx.refresh();

In the scenario above, the properties in b.properties will override any duplicates that exist in a.properties, because ConfigB was registered last.

That's all for the topic Read Values From Properties File in Spring. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment