Setting Active Profile And Property Sources In Spring MVC

  • access_time 5 years ago
  • date_range 07/11/2013
  • comment 0
  • share 0
  • label_outlineSpring, Spring MVC

Update 12/2017: Just use Spring Boot profiles.

Often there are times when we need to access settings stored in property files depending on the environment we are in. For example database settings are likely to be different in development, testing and production environments. For this reason Spring offers a concept profile that appeared since version 3.1. That allows to selectively enable @Configuration classes for active profile thanks to @Profile annotation.

Setting the active profile is an environment property of ApplicationContext. While for command-line apps this is easy thing to do, for web applications that could be little dirty, unless they take advantage of WebApplicationInitializer and Servlet 3 API to programmaticaly configure their ApplicationContext rather than in traditional way with web.xml. I'm about to show you how that can be implemented.

Back to Initializer

Time to make few additions to AppInitializer class.

public class AppInitializer implements WebApplicationInitializer {

	private static final PropertiesLoader propertiesLoader = new PropertiesLoader();

	@Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        WebApplicationContext context = getContext();
        servletContext.addListener(new ContextLoaderListener(context));
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", new DispatcherServlet(context));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/*");
    }
    
    private WebApplicationContext getContext() {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.setConfigLocation("eu.kielczewski.example.config");
        Properties prop = propertiesLoader.load("spring.properties");
        context.getEnvironment().setActiveProfiles(prop.getProperty("spring.profiles.active", "dev"));
        return context;
    }

}

After creating WebApplicationContext the context environment can be accessed, and there is setActiveProfiles() method that can be used to set active profile. This active profile will be stored in spring.properties file:

spring.profiles.active=dev

The file is loaded using PropertiesLoader, which is a class I wrote for that purpose (any alternatives out there?)

class PropertiesLoader {

    public Properties load(String fileName) {
        Properties prop = new Properties();
        InputStream im = null;
        try {
            im = findFile(fileName);
            prop.load(im);
        } catch (IOException ignore) {
        } finally {
            if (im != null) {
                try {
                    im.close();
                } catch (IOException ignore) {
                }
            }
        }
        return prop;
    }

    private InputStream findFile(String fileName) throws FileNotFoundException {
        InputStream im = findInWorkingDirectory(fileName);
        if (im == null) im = findInClasspath(fileName);
        if (im == null) im = findInSourceDirectory(fileName);
        if (im == null) throw new FileNotFoundException(String.format("File %s not found", fileName));
        return im;
    }

    private InputStream findInSourceDirectory(String fileName) throws FileNotFoundException {
        return new FileInputStream("src/main/resources/" + fileName);
    }

    private InputStream findInClasspath(String fileName) {
        return Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
    }

    private InputStream findInWorkingDirectory(String fileName) {
        try {
            return new FileInputStream(System.getProperty("user.dir") + fileName);
        } catch (FileNotFoundException e) {
            return null;
        }
    }

}

This works like that:

  1. Tries to locate property file in current working directory - that way the active profile could be set using a file lying around on the server
  2. If failed to found one, tries classpath - uses the file contained in WAR (you can generate spring.properties during the build process for different environments)
  3. If failed to found one, loads it from source directory - useful while running the application exploded i.e. via jetty-maven-plugin
  4. If failed to found one, panicks and returns empty Properties object

PropertySourcesPlaceholderConfigurer

Having active profile set is only one half of the story, now let's jump to reading property files. In Spring properties are accessed by using PropertySourcesPlaceholderConfigurer that resolves placeholders to property values. We need to configure it in such way, that it will load different different property files based on active profile.

@Configuration
public class PropertySourcesConfig {

    private static final Resource[] DEV_PROPERTIES = new ClassPathResource[]{
            new ClassPathResource("example-dev.properties"),
    };
    private static final Resource[] TEST_PROPERTIES = new ClassPathResource[]{
            new ClassPathResource("example-test.properties"),
    };
    private static final Resource[] PROD_PROPERTIES = new ClassPathResource[]{
            new ClassPathResource("example-prod.properties"),
    };

    @Profile("dev")
    public static class DevConfig {
        @Bean
        public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
            PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
            pspc.setLocations(DEV_PROPERTIES);
            return pspc;
        }
    }

    @Profile("test")
    public static class TestConfig {
        @Bean
        public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
            PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
            pspc.setLocations(TEST_PROPERTIES);
            return pspc;
        }
    }

    @Profile("prod")
    public static class ProdConfig {
        @Bean
        public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
            PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
            pspc.setLocations(PROD_PROPERTIES);
            return pspc;
        }
    }

}

This configuration class has a list of property files and static inner class for each environment. The static inner classes are annotated with @Profile, that indicates the profile they should be active in. Each of these classes defines PropertySourcesPlaceholderConfigurer bean that is loaded with proper list of property files. In this case it's just one file, for "dev" profile it will be `example-dev.properties' that contains one property:

example.message=Welcome to DEV environment

Accessing properties

Now the property can be accessed in every Spring component, may it be @Configuration, @Controller, @Service or whatever.

@Controller
public class IndexController {

    @Value("${example.message}")
    private String message;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    @ResponseBody
    public String showIndex() {
        return message;
    }

}

Here the @Controller will display property value, and it may come from either example-dev.properties, example-prod.properties, example-test.properties, depending on active profile set in spring.properties.

Closing remarks

I don't know if this approach is best, but it works for me. Of course I'm open to suggestions and ideas. And you can grab the source code and play with it.

Polite Notice - if you have a questions concerning implementation details in your own projects then you're much better off asking them on Stack Overflow. More people to help you this way.