Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Defining Spring Beans with same method name for different profiles

Tags:

java

spring

I have a configuration class defining two beans depending on the selected profile, with overridden configuration methods:

@Configuration
class MyConfig {
    @Profile("profile")
    @Bean
    MyBean myBean(MyBeanProperties properties) {
        return new MyBean(properties);
    }

    @Profile("!profile")
    @Bean
    MyBean myBean(MyBeanProperties properties, AdditionalProperties addProps) {
        MyBean result = new MyBean(properties);
        result.addAdditionalProperties(addProps);
        return result;
    }
}

and a class which autowires the MyBean into it

@Service
class MyService {
     MyBean autowiredBean;
     private MyService(MyBean bean) { this.autowiredBean = bean; }
}

Now, when I start the Spring context, it fails with the message

Parameter 0 of constructor in com.example.MyServce required a bean of type 'com.example.MyBean' that could not be found.

How is that possible? I clearly define the Spring bean so it should be present when the context is created.

like image 700
daniu Avatar asked Dec 23 '22 20:12

daniu


2 Answers

The reason for this is that Spring considers these beans to be of the same name because of the configuration method name, so it fails to instantiate them (although only one should be created in any given active Profile). This will work fine:

@Configuration
class MyConfig {
    @Profile("profile")
    @Bean
    MyBean myBean(MyBeanProperties properties) {
        return new MyBean(properties);
    }

    @Profile("!profile")
    @Bean
    // note different method name
    MyBean otherBean(MyBeanProperties properties, AdditionalProperties addProps) {
        MyBean result = new MyBean(properties);
        result.addAdditionalProperties(addProps);
        return result;
    }
}

I have not found this behavior explained anywhere so I posted this self-answered question to share.

The real-life case this occurred for me what a WebClient which was instantiated with a client registration in one profile, and without one in the other (because none was needed for creating an exchange filter).

like image 185
daniu Avatar answered Jan 19 '23 00:01

daniu


This is caused when two beans are defined with the same method name and one of them is expected to be skipped based on some condition(in this case based on profile). In this case, "myBean" is defined twice with different profiles.

The way a config class gets parsed is by iterating through all the beanMethods in that class and adding the corresponding bean definition. The iteration is in order of how the beanMethods are defined in the config class. Here is the link to the code.

Depending on the order in which these beans are defined in the config class, if the first bean defined is expected to be skipped based on the profile annotation, the beanMethod name gets added to a list of "to-be-skipped" beanMethods. Here is the link to the code.

Now, when it encounters the second bean with the same name, it sees that this beanMethod name is already present in the "to-be-skipped" methods list and hence skips the bean even though there is no inherent condition (like the profile) that would cause it to be skipped. Here is the link to the code.

You will notice that if you swap the order of the beans and use the same profile to run which was failing earlier, the bean would get picked up.

Using unique beanMethod names within the config class would be the best way to avoid this scenario.

like image 30
blazarprime Avatar answered Jan 18 '23 23:01

blazarprime