How to set active spring 3.1 environment profile via a properites file and not via an env variable or system property
Asked Answered
M

4

50

We use the new environment profiles feature of spring 3.1. We currently set the active profile by setting the environment variable spring.profiles.active=xxxxx on the server to which we deploy the application.

We think this is a suboptimal solution as the war file we want to deploy should just have an additional properties file which sets the environment in which the spring app context should load so the deployment is not dependent on some env var set on the server.

I tried to figure out how to do that and found:

ConfigurableEnvironment.setActiveProfiles()

which I can use to programmatically set the profile but then I still don't know where and when to execute this code. Somewhere where the spring context loads up? Can I load the parameter I want to pass to the method from a properties file?

UPDATE: I just found at docs which I might be able to implement to set the active profile?

Morelli answered 21/12, 2011 at 9:21 Comment(6)
Having the active profile as a property of the server is quite often the right approach. Since you might have different datasources for the pre-production, development and production servers. If the server can supply the active profile you can deploy the same code to multiple environments with no changes. This of course may not suit your use case but is worth pointing out.Musket
You want to have the environment coded into the WAR file? Isn't that defeating the purpose? You should have one war file for all environments. Set the active profile per server or with a system property or environment variable.Hippocras
Thanks Alex for pointing that out and I'm aware of it though this is not an option for us, as we want to control the profile on deploy.Morelli
Also @ericacm: We do want to provide the profile to set on deploy on our own to be not dependent on an environment set on a server which we don't control. I think it is right that one should just have one war and not have an environment coded into it. In my answer below you see how we achieve this by loading a env.properties.Morelli
@ShadowWizard Woh!!, it was not on purpose, it was a mistake, i edit a lot, sometimes it gets wrongDiphyllous
@ShadowWizard 1 Cross, i get your pointDiphyllous
M
40

The answer from Thomasz is valid as long as the profile name can be provided statically in the web.xml or one uses the new XML-less configuration type where one could programmatically load the profile to set from a properties file.

As we still use the XML version I investigated further and found the following nice solution where you implement your own ApplicationContextInitializer where you just add a new PropertySource with a properties file to the list of sources to search for environment specific configuration settings. in the example below one could set the spring.profiles.active property in the env.properties file.

public class P13nApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private static Logger LOG = LoggerFactory.getLogger(P13nApplicationContextInitializer.class);

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        try {
            environment.getPropertySources().addFirst(new ResourcePropertySource("classpath:env.properties"));
            LOG.info("env.properties loaded");
        } catch (IOException e) {
            // it's ok if the file is not there. we will just log that info.
            LOG.info("didn't find env.properties in classpath so not loading it in the AppContextInitialized");
        }
    }

}

You then need to add that initializer as a parameter to the ContextLoaderListener of spring as follows to your web.xml:

<context-param>
    <param-name>contextInitializerClasses</param-name>
    <param-value>somepackage.P13nApplicationContextInitializer</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

You can also apply it to DispatcherServlet:

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextInitializerClasses</param-name>
        <param-value>somepackage.P13nApplicationContextInitializer</param-value>
    </init-param>
</servlet>
Morelli answered 22/12, 2011 at 8:49 Comment(4)
I'm just curious @Morelli why your calling it P13n. I wrote some personalization code a while back for a company and I used the term P13N and it was written in Java and used Spring. Its probably just a coincidence but just curious.Absher
How to add the initializer outside web.xml, e.g. in myapplication.xml context?Longwinded
Is it possible to do this with DispatcherServlet, without ContextLoaderListener?Mcalpin
I was not able to inject configuration into bean using @Value(${propName}), but i was able to inject using @Value(#{environment[propName]}). Why is it so ?Zacheryzack
P
51

In web.xml

<context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>profileName</param-value>
</context-param>

Using WebApplicationInitializer

This approach is used when you don't have a web.xml file in Servlet 3.0 environment and are bootstrapping the Spring completely from Java:

class SpringInitializer extends WebApplicationInitializer {

    void onStartup(ServletContext container) {
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.getEnvironment().setActiveProfiles("profileName");
        rootContext.register(SpringConfiguration.class);
        container.addListener(new ContextLoaderListener(rootContext));
    }
}

Where SpringConfiguration class is annotated with @Configuration.

Prostatectomy answered 21/12, 2011 at 9:40 Comment(4)
Thank you very much for the answer. I do like the solution with the xml less WebApplicationInitializer but we do use XML.Morelli
-Dspring.profiles.active=someprofile i think is more manageable. As it doesnt have to be on in some cases and the default will be applied.Duval
You may want to use spring.profiles.default instead. This way the active profiles can be overridden using system property -Dspring.profiles.activePanamerican
Java config: If you use AbstractAnnotationConfigDispatcherServletInitializer you can override the createServletApplicationContext and configure the context additionally. ExampleTantalate
M
40

The answer from Thomasz is valid as long as the profile name can be provided statically in the web.xml or one uses the new XML-less configuration type where one could programmatically load the profile to set from a properties file.

As we still use the XML version I investigated further and found the following nice solution where you implement your own ApplicationContextInitializer where you just add a new PropertySource with a properties file to the list of sources to search for environment specific configuration settings. in the example below one could set the spring.profiles.active property in the env.properties file.

public class P13nApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private static Logger LOG = LoggerFactory.getLogger(P13nApplicationContextInitializer.class);

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        try {
            environment.getPropertySources().addFirst(new ResourcePropertySource("classpath:env.properties"));
            LOG.info("env.properties loaded");
        } catch (IOException e) {
            // it's ok if the file is not there. we will just log that info.
            LOG.info("didn't find env.properties in classpath so not loading it in the AppContextInitialized");
        }
    }

}

You then need to add that initializer as a parameter to the ContextLoaderListener of spring as follows to your web.xml:

<context-param>
    <param-name>contextInitializerClasses</param-name>
    <param-value>somepackage.P13nApplicationContextInitializer</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

You can also apply it to DispatcherServlet:

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextInitializerClasses</param-name>
        <param-value>somepackage.P13nApplicationContextInitializer</param-value>
    </init-param>
</servlet>
Morelli answered 22/12, 2011 at 8:49 Comment(4)
I'm just curious @Morelli why your calling it P13n. I wrote some personalization code a while back for a company and I used the term P13N and it was written in Java and used Spring. Its probably just a coincidence but just curious.Absher
How to add the initializer outside web.xml, e.g. in myapplication.xml context?Longwinded
Is it possible to do this with DispatcherServlet, without ContextLoaderListener?Mcalpin
I was not able to inject configuration into bean using @Value(${propName}), but i was able to inject using @Value(#{environment[propName]}). Why is it so ?Zacheryzack
L
6

For some reason only one way works for me

public class ActiveProfileConfiguration implements ServletContextListener {   
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.setProperty(AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, "dev");
        System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "dev");
    }

....

 <listener>
     <listener-class>somepackahe.ActiveProfileConfiguration</listener-class>
 </listener>
 <listener>
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
Levitical answered 26/12, 2012 at 9:49 Comment(1)
I was using AbstractAnnotationConfigDispatcherServletInitializer and not ApplicationContextInitializer or WebApplicationInitializer. This solution worked for this case, without needing the xml.Spiraea
S
0

Here is a variation on the P13nApplicationContextInitializer approach. However, this time we obtain the path to env properties from JNDI. In my case I set a JNDI global environment variable as coacorrect/spring-profile = file:/tmp/env.properties

  1. In tomcat/tomee server.xml add this: <Environment name="coacorrect/spring-profile" type="java.lang.String" value="/opt/WebSphere/props"/>
  2. Further, in tomcat/tomee, add to the WAR's META-INF/context.xml <ResourceLink global="coacorrect/spring-profile" name="coacorrect/spring-profile" type="java.lang.String"/>
  3. In any container, add appropriate in web.xml

    public class SpringProfileApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>{
    
    public static final Logger log = LoggerFactory.getLogger(SpringProfileApplicationContextInitializer.class);
    private static final String profileJNDIName="coacorrect/spring-profile";
    private static final String failsafeProfile="remote-coac-dbserver";
    
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
    
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
    
        try {
            InitialContext ic = new InitialContext();
            Object r1 = ic.lookup(profileJNDIName);
            if (r1 == null) {
                // try the tomcat variant of JNDI lookups in case we are on tomcat/tomee
                r1 = ic.lookup("java:comp/env/"+profileJNDIName);
            }
            if (r1 == null) {
                log.error("Unable to locate JNDI environment variable {}", profileJNDIName);
                return;
            }
    
            String profilePath=(String)r1;
            log.debug("Found JNDI env variable {} = {}",r1);
            environment.getPropertySources().addFirst(new ResourcePropertySource(profilePath.trim()));
            log.debug("Loaded COAC dbprofile path. Profiles defined {} ", Arrays.asList(environment.getDefaultProfiles()));
    
        } catch (IOException e) {
            // it's ok if the file is not there. we will just log that info.
            log.warn("Could not load spring-profile, defaulting to {} spring profile",failsafeProfile);
            environment.setDefaultProfiles(failsafeProfile);
        } catch (NamingException ne) {
            log.error("Could not locate JNDI variable {}, defaulting to {} spring profile.",profileJNDIName,failsafeProfile);
            environment.setDefaultProfiles(failsafeProfile);
        }
    }
    

    }

Southpaw answered 13/6, 2015 at 3:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.