Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strange behavior when trying to override @Bean method from parent class and rewrite the bean name #32522

Closed
Aimini opened this issue Mar 23, 2024 · 2 comments
Labels
status: duplicate A duplicate of another issue

Comments

@Aimini
Copy link

Aimini commented Mar 23, 2024

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.<init>(AnnotationConfigApplicationContext.java:93)
	at com.ai.demo.configtest.TestMain.main(TestMain.java:22)
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Mar 23, 2024
@Aimini
Copy link
Author

Aimini commented Mar 23, 2024

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

if (factoryMethodToUse == null) {
factoryMethodToUse = mbd.getResolvedFactoryMethod();
}

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

if (candidates == null) {
candidates = new ArrayList<>();
Method[] rawCandidates = getCandidateMethods(factoryClass, mbd);
for (Method candidate : rawCandidates) {
if ((!isStatic || isStaticCandidate(candidate, factoryClass)) && mbd.isFactoryMethod(candidate)) {
candidates.add(candidate);
}
}
}

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.

Method[] rawCandidates = getCandidateMethods(factoryClass, mbd);

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

if ((!isStatic || isStaticCandidate(candidate, factoryClass)) && mbd.isFactoryMethod(candidate)) {

Then after various checks it decides to throw an BeanCreationException.

throw new BeanCreationException(mbd.getResourceDescription(), beanName,


That mbd object in code mbd.getResolvedFactoryMethod() is getting factoryMethodToIntrospect field inside itself, the difference is from

if (metadata instanceof StandardMethodMetadata sam) {
beanDef.setResolvedFactoryMethod(sam.getIntrospectedMethod());
}

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.

@snicoll
Copy link
Member

snicoll commented Mar 25, 2024

Duplicate of #28286

@snicoll snicoll marked this as a duplicate of #28286 Mar 25, 2024
@snicoll snicoll closed this as not planned Won't fix, can't repro, duplicate, stale Mar 25, 2024
@snicoll snicoll added status: duplicate A duplicate of another issue and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Mar 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: duplicate A duplicate of another issue
Projects
None yet
Development

No branches or pull requests

3 participants