23

To my surprise I have had a difficult time finding an answer to this question. I have Seen many examples where you can use @PropertySource to load a specific properties file for a class. I have also seen examples where you can easily add different property files in spring boot projects. But what I want to do is to do this for a spring project that is NOT spring boot and load a properties file so that the values of this file can be injected in classes annotated with @Component which is dependent on the server environment. So for example if I am on development server I want a particular properties file loaded and on production a different properties file. The reason that I am doing it like this is because my data and service layers are their own modules. These modules contain their own unit tests and can be imported as their own modules in other spring boot projects. I need properties files to be loaded to serve these modules which use spring but not spring boot. I have tried the following, but this does not work.

@Configuration
@Profile("test")
@EnableJpaRepositories("com.hi.repository")
@EnableTransactionManagement
@EnableScheduling
public class InfrastructureConfig  {
...
    @Bean
    public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        Map<String, String> env = System.getenv();
        String propertiesFile=null;

        String e = env.get("SERVER_ENV");

        if (e.equals("dev")) {
            propertiesFile = "environment/development.properties";
        } else if (e.equals("prod")) {
            propertiesFile = "environment/production.properties";
        }

        configurer.setLocation(new ClassPathResource(propertiesFile));


        return configurer;

    }

Then I have a test which looks like this

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:/spring/DealServiceTest-context.xml"})
@ActiveProfiles("test")
public class LogTest {

    private static final Logger log = LogManager.getLogger(LogTest.class);

    @Autowired
    PathsService pathsService;

    @Autowired
    Environment environment;


    @Test
    public void testBeans(){
        System.out.println("********** WASSUP from LogTest");
        System.out.println(environment.getProperty("imageBucket"));

    }

Although the test prints out null which indicates to me the properties file has not been loaded and prepared for its values to be injected. How can I achieve this?

slipperypete
  • 5,358
  • 17
  • 59
  • 99
  • You can check XML config example at https://examples.javacodegeeks.com/enterprise-java/spring/load-environment-configurations-and-properties-with-spring-example/ or another example at https://stackoverflow.com/questions/34394054/loading-environment-specific-properties-file-in-spring – Amit K Bist Sep 06 '17 at 17:57
  • What is the value or SERVER_ENV property? I saw a similar question here: https://stackoverflow.com/questions/22757318/different-property-variable-for-local-and-prod-environment-spring – Seitaridis Sep 12 '17 at 18:09

4 Answers4

14

You don't really need to set properties yourself, but you can do this using spring configuration. Check the documentation: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-profile-specific-properties

If you're using spring boot - all you need to do is create multiple properties file for your environments. And only for properties you need to override.

So your main properties file would be at

src/main/resources/application.properties

Production

src/main/resources/application-prod.properties

Development

src/main/resources/application-dev.properties

Testing

src/main/resources/application-test.properties

And then just use the profile name as your environment variable

java -jar -Dspring.profiles.active=prod demo-0.0.1-SNAPSHOT.jar
Zilvinas
  • 5,518
  • 3
  • 26
  • 26
  • Thanks the problem Is that I designed the project do I would use springboot/jersey for the API later and then a separate spring module that includes the data source configuration amongst other stuff because this module needs to be used with a separate API module. Do I was hoping to make this module... I don't think I can do this at the API level and get access to it in the data layer module which is being imported as a dependent into the spring boot module – slipperypete Sep 06 '17 at 18:21
  • 1
    @Dan you would specify all of your datasource properties etc. in the resources for the actual thing running. So if (like you mentioned) you had a separate module for the API layer that pulled in the DB config module via maven, you would specify all the datasource properties in the property file for the api project (since you would be running the spring boot application from there). – Antoine Dahan Aug 30 '21 at 16:40
12

Actually, you can just use a placeholder in @PropertySource annotation. See documentation:

Any ${...} placeholders present in a @PropertySource resource location will be resolved against the set of property sources already registered against the environment.

Assuming that placeholder is present in one of the property sources already registered, e.g. system properties or environment variables, the placeholder will be resolved to the corresponding value.

I've made a simple example, it receives a 'property.environment' value to choose, which .properties file should be used as property source. I have two resource files in my classpath - application-test.properties and application-dev.properties, each one contains a 'test.property' value ('test-env' and 'dev-env' respectively).

Property configuration:

@Configuration
@PropertySource("classpath:/config/application-${property.environment}.properties")
public class PropertyConfig {
    
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
       return propertySourcesPlaceholderConfigurer;
    }        
}

Component with @Value

@Component
public class TestService {
        
    @Value("${test.property}")
    String testProperty;
    
    @PostConstruct
    void init() {
        System.out.println("---------------------------------------------------------");
        System.out.println("Running in " + testProperty + " environment");
        System.out.println("---------------------------------------------------------");
    }
}

Build command line example (it runs tests with test environment properties)

mvn clean install -DargLine="-Dproperty.environment=test"

Output

---------------------------------------------------------
Running in test-env environment
---------------------------------------------------------

Run command line example

java -jar -Dproperty.environment=dev PATH_TO_YOUR_JAR.jar

Output

---------------------------------------------------------
Running in dev-env environment
---------------------------------------------------------
Community
  • 1
  • 1
Anatoly Shamov
  • 2,608
  • 1
  • 17
  • 27
  • 1
    Thanks for the comment you. You are right using the '@PropertySource' with a placeholder was exactly what I was looking for. To elaborate on the solution for future readers. I set an environment variable in the linux environment and put the name of that variable in the placeholder of '@PropertySource.' Spring will automatically place the value of that environment variable from your linux environment in the placeholder, which allows you to designate different property files for different environements. – slipperypete Sep 17 '17 at 09:24
  • what if we dont set the variable? – TomJava Feb 10 '20 at 20:06
  • Haha ... I had this same question today and started looking at this post then realize i wrote it so I gave this the correct answer because I have used it 2 times now. – slipperypete Jun 05 '20 at 01:44
0

Don't hard code based on different environment, in spring boot you can able to maintain properties specific environment easily. Refer https://spapas.github.io/2016/03/31/spring-boot-settings/

laxman954
  • 133
  • 1
  • 8
0

I would try to take advantage of the profile mechanism already in place in Spring. You basically have done the job yourself already, the only thing you need to change is to have different configurations for "test" and "production" profiles. I prefer to keep everything related to test away from production code (allowing me to place the TestConfig class below in the test source path), so I would probably do something like this:

@Configuration
@Profile("!test")
@PropertySource(value = "classpath:/environment/production.properties")
@Import(AppConfig.class)
public class ProductionConfig
{
    // Your production-specific config goes here
}

@Configuration
@Profile("test")
@PropertySource(value = "classpath:/environment/development.properties")
@Import(AppConfig.class)
public class TestConfig
{
    // Your test-specific config goes here
}

@Configuration
public class AppConfig
{
    // Needed for spring to handle ${property:default} syntax 
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyConfigIn() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

If you prefer to have one config for both cases, you can let the AppConfig import the TestConfig and the ProductionConfig instead, but that will put test code in to production...

Good luck with your project!

Per Huss
  • 4,755
  • 12
  • 29
  • Thanks for the comment. This solution seems like it would have also worked nicely, but I found that the solution using a placeholder to be more simple, and provided the exact functionality that I needed. Thanks anyway! – slipperypete Sep 17 '17 at 09:25