Spring partial auto-wire prototype bean with runtime-defined constructor arguments

The javadoc before ConstructorResolver.autowireConstructor(...)

says

Also applies if explicit constructor argument values ​​are specified, mapping all other arguments from beans to bean factory.

but I cannot get it to work. I am getting BeanCreationException

:

Could not resolve matching constructor (hint: specify index / type / name arguments for simple parameters to avoid type ambiguity)

In this example, I have a bean with a constructor that accepts Spring beans as well as String

and int

that will only be known at runtime.

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class BeanWithRuntimeDependencies {

    public final DependencyA dependencyA;
    public final DependencyB dependencyB;
    public final String myString;
    public final int myInt;

    public BeanWithRuntimeDependencies(
            DependencyA dependencyA, DependencyB dependencyB, 
            String myString, int myInt) {
        this.dependencyA = dependencyA;
        this.dependencyB = dependencyB;
        this.myString = myString;
        this.myInt = myInt;
    }

}

@Component
public class DependencyA { /* ... */ }

@Component
public class DependencyB { /* ... */ }

      

and my test:

@RunWith(SpringRunner.class)
@SpringBootTest
public class PrototypeBeanConstructorsApplicationTests {

    @Autowired private ApplicationContext context;

    @Autowired private DependencyA dependencyA;

    @Autowired private DependencyB dependencyB;

    @Test
    public void getBeanFromContext() {
        BeanWithRuntimeDependencies bean = 
            context.getBean(BeanWithRuntimeDependencies.class, "runtime string", 10);
        assertNotNull(bean);
        assertEquals(dependencyA, bean.dependencyA);
        assertEquals(dependencyB, bean.dependencyB);
        assertEquals("runtime string", bean.myString);
        assertEquals(10, bean.myInt);
    }

}

      

The source code ConstructorResolver.autowireConstructor(...)

has a comment:

// Explicit arguments given -> arguments length must match exactly.

      

which seems to contradict his javadoc.

Can this be done? What am I doing wrong?

+3


source to share


1 answer


It looks like it can't be done the way you are doing.

This is actually a strange situation. According to the following snippet at ConstructorResolver.autowireConstructor(...)

(source line # 207) Spring will not consider your constructor as a candidate for invocation:

...    
// Explicit arguments given -> arguments length must match exactly.
if (paramTypes.length != explicitArgs.length) {
    continue;
}

      

And as you correctly pointed out, this is indeed contrary to the javadoc statement:

... mapping all remaining arguments to beans with bean factory

But in any case, implementation means that by default Spring cannot resolve a constructor for an instance of such beans. And you need to create a factory method manually. Something like:



@Configuration
public class Config{

    @Bean
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public BeanWithRuntimeDependencies beanWithRuntimeDependencies(String myString, int myInt){
        return new BeanWithRuntimeDependencies(dependencyA(), dependencyB(), myString, myInt);
    }

    @Bean
    public DependencyA dependencyA(){
        return new dependencyA();
    }

    @Bean
    public DependencyB dependencyB(){
        return new dependencyB();
    }
}

      

Then you can get the bean from the context as you want:

BeanWithRuntimeDependencies bean = 
    context.getBean(BeanWithRuntimeDependencies.class, "runtime string", 10);

      


If you don't want to deal with the configuration class and factory, you can simply pass the beans you need to context.getBean()

. You must of course get these beans from the context:

BeanWithRuntimeDependencies bean = 
    context.getBean(BeanWithRuntimeDependencies.class, 
        context.getBean(DependencyA.class), 
        context.getBean(DependencyB.class),
        "runtime string", 10);

      

+3


source







All Articles