Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?).

like image 229
Mark McLaren Avatar asked Nov 08 '12 12:11

Mark McLaren


People also ask

Can you use @component together with profile?

Yes, @Profile annotation can be used together with @Component on top of the class representing spring bean.

Can a bean have multiple ids?

Multiple Names For a Bean Using XML Even before the advent of Java configurations, Spring allows a bean to have multiple names using XML configurations. In fact, we can give a bean multiple names by using the name attribute of the bean element.

How do I make multiple profiles in spring boot?

Activating a Profile active is a standard property that Spring Boot will pick up automatically to activate a profile. Pass the profile name to this property value to activate that profile. If we want to activate multiple profiles then we can pass comma-separated names of those profiles.

What are the main ways to define a bean in the application context?

To declare a bean, simply annotate a method with the @Bean annotation. When JavaConfig encounters such a method, it will execute that method and register the return value as a bean within a BeanFactory .


2 Answers

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.

like image 177
maba Avatar answered Sep 27 '22 20:09

maba


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.

like image 37
Mark McLaren Avatar answered Sep 27 '22 20:09

Mark McLaren