Affects: 6.1.5

I was trying to override a @Bean method from the parent class but with a different bean name, I expected the here would only be one bean generated by method in child, but the result was weird and has something to do whether you are using @Import or @ComponentScan.

I know this is not a practical usage, but it's still an interesting and intriguing problem.


Example

package com.ai.demo.configtest;

public class MyTestBean {
    public MyTestBean(String msg) {
        this.msg = msg;
    }

    String msg;

    public String getMsg() {
        return msg;
    }
}
package com.ai.demo.configtest;

import org.springframework.context.annotation.Bean;

public class BeanInOverriddenParent {
    @Bean("ParentBean")
    MyTestBean getBeanOverridden() {
        return new MyTestBean("parent");
    }
}
package com.ai.demo.configtest;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanOverridingChild extends BeanInOverriddenParent {

    @Bean("ChildBean")
    @Override
    MyTestBean getBeanOverridden() {
        return new MyTestBean("beanMethodOverriddenInChildBean");
    }
}
package com.ai.demo.configtest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(BeanOverridingChild.class)
@ComponentScan(basePackageClasses = {TestMain.class})
public class TestMain {

    @Autowired
    MyTestBean[] myTestBeans;

    public TestMain() {

    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestMain.class);
        TestMain bean = applicationContext.getBean(TestMain.class);
        for (MyTestBean myTestBean : bean.myTestBeans) {
            System.out.println(myTestBean.getMsg());
        }
    }
}

Case 1: only using @Import(BeanOverridingChild.class)

It produced two beans; the console output are like:

beanMethodOverriddenInChildBean
beanMethodOverriddenInChildBean
````

###  Case 2:  only using `@ComponentScan(basePackageClasses = {TestMain.class})`
It thrown an `BeanCreationException` in following output:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'ParentBean' defined in class path resource [com/ai/demo/configtest/BeanOverridingChild.class]: No matching factory method found on class [com.ai.demo.configtest.BeanOverridingChild]: factory bean 'beanOverridingChild'; factory method 'getBeanOverridden()'. Check that a method with the specified name exists and that it is non-static. at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:616) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1335) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1165) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:962) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624) at org.springframework.context.annotation.AnnotationConfigApplicationContext.(AnnotationConfigApplicationContext.java:93) at com.ai.demo.configtest.TestMain.main(TestMain.java:22) ```

Comment From: Aimini

The case 1 isn't hard to understand since I found that the bean definitions is registered into DefaultListableBeanFactory.beanDefinitionMap by name, And the ConfigurationCLassParser generates two bean methods within ConfigurationClass object, which has different name.

But the case 2 is more interesting since it prefer to throw exception and it actually says here is no proper 'getBeanOverridden()'. to construct it.


After some code-digging I found it's has something to do with the code below at line 460

https://github.com/spring-projects/spring-framework/blob/c04290e9ab0b587fe53dec0590aeb45fa55a870e/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java#L459-L461

In the first case it gets a Method Object and it's com.ai.demo.configtest.MyTestBean com.ai.demo.configtest.BeanInOverriddenParent.getBeanOverridden(),

In the second case it gets a null, so the program continues to the

https://github.com/spring-projects/spring-framework/blob/c04290e9ab0b587fe53dec0590aeb45fa55a870e/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java#L466-L474

At below line, it gets all candidates to generate that bean but ignore the methods in super class if there already found a method with the same signature, so it'll only keeps class com.ai.demo.configtest.BeanOverridingChild.

https://github.com/spring-projects/spring-framework/blob/c04290e9ab0b587fe53dec0590aeb45fa55a870e/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java#L468

But the mbd.isFactoryMethod at below have to verify if the bean name is equal to the one from the method, and it fails.

https://github.com/spring-projects/spring-framework/blob/c04290e9ab0b587fe53dec0590aeb45fa55a870e/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java#L470

Then after various checks it decides to throw an BeanCreationException. https://github.com/spring-projects/spring-framework/blob/c04290e9ab0b587fe53dec0590aeb45fa55a870e/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java#L611


That mbd object in code mbd.getResolvedFactoryMethod() is getting factoryMethodToIntrospect field inside itself, the difference is from https://github.com/spring-projects/spring-framework/blob/c04290e9ab0b587fe53dec0590aeb45fa55a870e/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java#L232-L234

here can have SimpleAnnotationMetadata when use ScanComponent and StandardAnnotationComponent when use Import, that explains everything.


I personally think it is a problem and that only keeping bean in sub class is optimal. but on the opposite side I also think this can rarely become a realistic situation, so if it worthy to change the code takes me into dilemma.

Comment From: snicoll

Duplicate of #28286