Spring - Don't create bean if a primary bean is present - java

Is it possible to prevent the creation of a bean of type A if it could be generated as a primary bean
Example:
I have two configuration classes and have two profiles.
AppConfig.java: (The generic configuration class having all beans)
#Configuration
public class AppConfig {
#Value("${host}")
private String host;
#Bean
public A getA() {
//uses the 'host' value to create an object of type A
// Involves database connections
}
#Bean
public B getB(A a) { //Others using bean A. This might come from either getA() or getOtherA()
...
}
}
SpecificConfig.java: (These beans will be created only if profile-a is active)
#Configuration
#Profile("profile-a")
public class SpecificConfig{
#Bean
#Primary
public A getOtherA() {
//return a bean of type A
}
}
Here when profile-a is chosen, the bean of type A will come from SpecificConfig.java. But the problem is when profile-a is active the parameter host in AppConfig.java is not available and hence getA method in AppConfig throws an exception.
Since bean of type A is already there or will be there (I'm not sure of the order of bean creation), I don't want the getA() in AppConfig to be executed.
(when profile-a is active)
Is there a way to achieve this?
Possible solutions:
Add #Profile({"!profile-a"}) to the top of getA method in AppConfig.
Add if checks to see if host param exists.
I don't want to do the above two as I would have to change in multiple places.
(There are a bunch of other beans like A and other params like host)
Thanks
Let me know if any clarification is required.

The Condition annotations of Spring Boot auto-configuration is a solution to constrain the bean creation.
#ConditionalOnBean : check specified bean classes and/or names are already contained in the BeanFactory.
#ConditionalOnProperty : check specified properties have a specific value
Example:
#Configuration
public class SpecificConfig{
#Bean
#ConditionalOnBean(A.class)
#Primary
public A getOtherA() {
//return a bean of type A
}
}

Related

Defining Spring Beans with same method name for different profiles

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.
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).
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.
I figured why changing the names of method allowed application context to load:
The problem is, spring container requires all it's beans to have a unique name as descried here https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-beanname
Every bean has one or more identifiers. These identifiers must be unique within the container that hosts the bean.
When not using XML configuration, I think the only way to have same bean method name is to give a unique name to beans within the #Bean(NAME) annotation, see this for details Spring bean with same method name but different qualifier fail to load

#Primary bean in one #Configuration class does not override a bean defined and used in another such class

Here's the class with the primary bean:
#Configuration
public class AppConfig {
#Bean
#Primary
public WeatherGauge weatherGauge() {
return () -> "40 F";
}
}
and here's the class defining and using the competing bean:
#Configuration
public class WeatherConfig {
#Bean
public WeatherGauge weatherGauge() {
return () -> "20 C";
}
#Bean
public StateReporter stateReporter(WeatherGauge weatherGauge) {
return new StateReporter(weatherGauge);
}
}
I'm using spring-boot-starter-parent:2.1.9.RELEASE. If I use StateReporter and print the weather gauged, I get 20 C, which does not come from the primary bean. The #Primary is ignored. Is this by design or a flaw? Just the way #Configuration works? If i define the primary implementation as a #Component class, the #Primary is in fact honored.
Edit: I forgot to say that AppConfig gets picked up if the other bean is not present. Everything is in the same package as the main class and I do use the allow-override=true property.
You're defining two beans with the same name and type: only one will be created, and the other will be overridden.
The #Primary annotation is for when two or more beans of the same type exist. It designates one of them as the primary bean used in dependency injection.
You can see how this works by making a small code change.
#Bean
#Primary
public WeatherGauge weatherGauge2() {
return new WeatherGauge("40 F");
}
#Bean
public WeatherGauge weatherGauge() {
return new WeatherGauge("20 C");
}
Now two beans are defined, with one of them weatherGauge2 being the primary.
I assumed that you used prop:
spring.main.allow-bean-definition-overriding=true
to run this code. This link propably will clear your problem for you.
If you dont want to read whole article:
Mechanism which caused you this problem is called bean overriding. It's almost impossible to predict which bean will override another with java based configs. When you use both (xml and java based) configurations, then java based is always loaded first and XML configuration always latest, so it will override everything else. That's why your #Component class with #Primary is honored - because is loaded after configuration.

Spring dependency injection with duplicate beans and #Order annotation

I am moderately confused about the DI injection mechanism in Spring when having multiple beans with the same name/type.
According to the exam slides from the Pivotal's "Core Spring" Course, Spring's behaviour with identical beans can be boiled down to:
One can define same bean more than once
Spring injects the bean defined last
Using #Order, the loading mechanism (and thus, which bean is loaded last) can be modified
However, in the following example, Spring will ignore any #Order annotations and inject the bean from the Config class last mentioned in the #Import statement.
I'm therefore wondering whether the order of config classes in the #Import annotation overrides any #Order annotations. Or do I miss another important point?
Any hints are highly appreciated. Thanks Stack Overflow!
Main Configuration class
#Configuration
#Import({RogueConfig.class,RewardsConfig.class})
public class TestInfrastructureConfig {
// nothing interesting here, just importing configs
}
RewardsConfig
#Configuration
#Order(1)
public class RewardsConfig {
#Bean
public RewardNetwork rewardNetwork() {
System.out.println("This Bean has been loaded from: " + this.getClass().getName());
return new RewardNetworkImpl(null, null, null);
}
}
RogueConfig
#Configuration
#Order(2)
public class RogueConfig {
#Bean
public RewardNetwork rewardNetwork() {
System.out.println("This Bean has been loaded from: " + this.getClass().getName());
return new RewardNetworkImpl(null, null, null);
}
}
Test class
public class RewardNetworkTests {
ApplicationContext applicationContext;
#BeforeEach
void setUp() {
applicationContext = SpringApplication.run(TestInfrastructureConfig.class);
}
#Test
void injectingRewardNetworkBeanWithOrdering() {
RewardNetwork rewardNetwork = applicationContext.getBean(RewardNetwork.class);
assertNotNull(rewardNetwork);
}
}
No matter what values I assign #Order, or if I use ordering at all, the result will always be:
This Bean has been loaded from: config.RewardsConfig$$EnhancerBySpringCGLIB$$62461c55
The only way to change this is to modify the Import annotation in my TestInfrastructureConfig like so:
#Import({RewardsConfig.class,RogueConfig.class}), which yields:
This Bean has been loaded from: config.RogueConfig$$EnhancerBySpringCGLIB$$6ca7bc89
I am wondering what needs to be done to allow the values defined in #Order to take any effect.
I've been able to get Spring to use the #Order annotations by loading the configurations directly ( i.e. without the detour through a #Configuration class using #Import):
#SpringJUnitConfig({RogueConfig.class, RewardsConfig.class})
public class CdiTest {
#Test
public void testCdiWithIdenticalBeans(#Autowired RewardNetwork rewardNetwork) {
assertThat(rewardNetwork).isNotNull();
}
}
With the #Order(2) annotation on the RogueConfig class, this bean got loaded last, as shown in stdout:
This Bean has been loaded from: config.RogueConfig$$EnhancerBySpringCGLIB$$552b937f
It seems that when using #Import in config classes it will load bean definitions in the order provided in the annotation, thus making any #Order annotations on the respective config classes useless.

Spring -- allow overriding THAT particular bean only

So I need to override a bean provided by a dependency in order to customize some configuration.
Is there any way to allow overriding just for THAT particular name?
I don't want to set
spring.main.allow-bean-definition-overriding=true
That's scary. I just want to override one particular named bean and disallow overriding in all other instances.
** EDIT **
#Bean
#Primary
fun vaadinAuthenticationSuccessHandler(
httpService: HttpService,
vaadinRedirectStrategy: VaadinRedirectStrategy
): VaadinAuthenticationSuccessHandler {
return VaadinUrlAuthenticationSuccessHandler(httpService, vaadinRedirectStrategy, "/")
}
results in
The bean 'vaadinAuthenticationSuccessHandler', defined in class path resource [n/c/s/config/security/VaadinAwareSecurityConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [org/vaadin/spring/security/config/VaadinSharedSecurityConfiguration.class] and overriding is disabled.
It's worth noting that similar code I've seen actually uses
#Bean(name = VaadinSharedSecurityConfiguration.VAADIN_AUTHENTICATION_SUCCESS_HANDLER_BEAN)
(which doesn't make a difference, but it's worth noting all the same)
In one of your #Configuration class, you can declare a #Bean with the same class as the one coming from your dependency library and mark it as #Primary to override the bean.
#Configuration
public class MyConfiguration {
#Bean
#Primary
public BeanClassFromDependency mrBean() {
return new YourOwnImplementationForBeanClassFromDependency();
}
}
Subsequently, you can autowire as usual.
#Autowired
private BeanClassFromDependency theBeanThatGotAway;

Why can I create another bean if a #Primary bean exists?

I don't understand what problem #Primary resolves.
The documentation says:
[#Primary] Indicates that a bean should be given preference when multiple
candidates are qualified to autowire a single-valued dependency. If
exactly one 'primary' bean exists among the candidates, it will be the
autowired value.
Example code:
#Configuration
class Configuration {
#Bean
#Primary
MyType bean1() {
return new MyType(1);
}
#Bean
MyType bean2() {
return new MyType(2);
}
}
Example:
I have 2 beans, bean1 and bean2, both with the type MyType. bean1 has a #Primary annotation, so when I autowire an object of type MyType to some constructor, bean1 will be chosen.
Why is it useful to have two beans of the same type if the primary bean will always be chosen? When and how could I use bean2 which isn't annotated as primary? The example shows that bean2 is redundant and unused.
You can still always qualify which bean you actually want, meaning the primary one will not always be chosen.
#Component
class MyComponent
{
public MyComponent(#Qualifier("bean2") MyType foo) { /*...*/ }
}
#Primary just tells Spring which bean to give precedence to if there are two or more possible candidates. You can always be explicit.
Also, another constructor might take a list of all MyTypes. In which case, both beans would be autowired.
#Component
class AnotherComponent
{
public AnotherComponent(List<MyType> allFoos) { /*...*/ }
}
So why could I have two beans of the same type if primary bean will be injected?
Actually the primary bean will be only injected if you didn't specify which one of your beans you want to inject, and #Primary is used to specify which bean will be injected when the type is not specified.
And to answer your question, having two beans of the same type is a common way of giving different implementations, there are many cases when we want to use two beans of the same bean, the most common situation is when we want to specify two data sources for the same application.
And to specify which one of our beans we want to go with, we use the #Resource annotation like this:
#Resource(name="bean2")
MyType bean;
For futher details you can check the discussed differences between #Resource and #Autowired.
Say you have multiple instances of beans which have some differences among them. In many cases (say > 90% for example) you will need one of them, and you will rarely use the other ones. In this case it makes sense to annotate as #Primary the most used one and in this way it will be directly injected by the framework when no further specification is provided. In the other case you will specify the exact bean to use using the #Qualifier annotation.
An example can be initializing beans of RestTemplate, say you will define a global one which will have generic settings and will be used accross all application, and another one with some specific retry policy for a small set of use-cases.
One of possible usages of #Primary you can override your Bean in tests by set #Primary there. Still your secondary Bean is used when you run an application.

Categories