Mark McLaren
Mark McLaren

Reputation: 11540

Spring 3.1 bean visibility using bean definition profiles

I have been experimenting with using Spring 3.1's bean definition profiles and nested beans. I had hoped that I could define different beans depending on the active profile. Consider the following heavily over simplified example such that my Spring context contains something like

<bean id="say" class="test.Say" p:hello-ref="hello"/>

<beans profile="prod">
    <bean id="hello" class="test.Hello" p:subject="Production!"/>
</beans>

<beans profile="dev">
    <bean id="hello" class="test.Hello" p:subject="Development!"/>
</beans>

I get the following error:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'say' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'hello' while setting bean property 'hello'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'hello' is defined at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:328) at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:106) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1360) aJava Result: 1

I was expecting that the hello bean would be defined according to the active Maven profile (in my case prod or dev). I'm starting to think that the Spring active profiles (spring.profiles.active) may be completely unrelated to Maven profiles.

Could somebody please explain where I am going wrong? (Is this even possible using profiles?).

Upvotes: 7

Views: 8246

Answers (2)

Mark McLaren
Mark McLaren

Reputation: 11540

Thanks to maba (whose answer I shall accept), I started thinking about this in a different way.

I've modified the parent bean "say" because it needs to be lazily initialized because when it is initially encountered the nested bean contexts do not yet exist. So the new version adds a new bean and changes the "say" definition such that it now looks like:

<bean class="test.InitProfile" p:profiles="dev"/>

<bean id="say" class="test.Say" lazy-init="true" p:hello-ref="hello"/>

The new InitProfile bean is an InitializingBean responsible for setting up the active profiles.

It contains:

package test;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.StringUtils;

public class InitProfile implements InitializingBean, ApplicationContextAware {

    private ConfigurableApplicationContext ctx;
    private String[] profiles;

    public void setApplicationContext(ApplicationContext ac) throws BeansException {
        ctx = (ConfigurableApplicationContext) ac;
    }

    public void setProfiles(String inprofiles) {
        if (inprofiles.contains(",")) {
            profiles = StringUtils.split(inprofiles, ",");
        } else {
            profiles = new String[]{inprofiles};
        }
    }

    public void afterPropertiesSet() throws Exception {
        String[] activeProfiles = ctx.getEnvironment().getActiveProfiles();
        if (profiles != null && activeProfiles.length == 0) {
            ctx.getEnvironment().setActiveProfiles(profiles);
            ctx.refresh();
        }
    }
}

Using this approach has the added advantage of being able to set the active spring profile using a classpath properties file (this can differ depending on my active Maven profile). I also like this approach because I can use it for both web application and command line applications.

Upvotes: 2

maba
maba

Reputation: 48065

I was expecting that the hello bean would be defined according to the active Maven profile (in my case prod or dev). I'm starting to think that the Spring active profiles (spring.profiles.active) may be completely unrelated to Maven profiles.

That's true, they are unrelated.

Here is how you can fix it:

Make sure that the web.xml that you have in src/main/webapp/WEB-INF/ folder has the following context setting:

<context-param>
    <param-name>spring.profile.active</param-name>
    <param-value>${profileName}</param-value>
</context-param>

And then make sure that the maven-war-plugin has filtering turned on for the web.xml:

<plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.3</version>
    <configuration>
        <filteringDeploymentDescriptors>true</filteringDeploymentDescriptors>
    </configuration>
</plugin>

And then lastly in your profiles:

<profiles>
    <profile>
        <id>dev</id>
        <properties>
            <profileName>dev</profileName>
        </properties>
    </profile>
    <profile>
        <id>prod</id>
        <properties>
            <profileName>prod</profileName>
        </properties>
    </profile>
</profiles>

You could also add a default value in the normal properties section:

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <profileName>dev</profileName>
</properties>

So if you run without the -P option the dev spring profile will be used.

When running mvn package the web.xml will have the correct value for the spring.profile.active.

Upvotes: 12

Related Questions