Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Partial auto-wire Spring prototype bean with runtime determined constructor arguments

The javadoc to ConstructorResolver.autowireConstructor(...) says

Also applied if explicit constructor argument values are specified, matching all remaining arguments with beans from the bean factory.

but I can't get it to work. I get a BeanCreationException:

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

In this example, I have a bean with a constructor that takes Spring beans as well as a String and an 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 of ConstructorResolver.autowireConstructor(...) has a comment:

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

which seems to contradict its javadoc.

Is it possible to do this? What I am I doing wrong?

like image 971
whistling_marmot Avatar asked Mar 23 '17 21:03

whistling_marmot


People also ask

What is constructor mode of Autowiring?

constructorIt works similar to the “byType” mode but it looks for the class type of the constructor arguments. If none or more than one bean are detected, then it throws an error, otherwise, it autowires the “byType” on all constructor arguments. XML.

What is the difference between the Spring container's handling of a prototype bean and a singleton bean when the bean is destroyed?

That means that a singleton bean will remain in your JVM memory unless the application context is shutdown or if the bean is manually destroyed. On the other hand, prototype beans will not be cleaned up by Spring and the bean instances will be left up to the garbage collector for cleaning.

How to use constructor based Dependency injection?

Constructor arguments resolution y; public class Foo { public Foo(int year, String name) { // ... } } A final note, in case you are passing a reference to an object, you need to use ref attribute of <constructor-arg> tag and if you are passing a value directly then you should use value attribute as shown above.

Can we Autowire a constructor?

3) constructor autowiring modeIn case of constructor autowiring mode, spring container injects the dependency by highest parameterized constructor. If you have 3 constructors in a class, zero-arg, one-arg and two-arg then injection will be performed by calling the two-arg constructor.


1 Answers

Looks like it is not possible to do it the way you're doing.

Actually it is strange situation. According next snippet in ConstructorResolver.autowireConstructor(...)(source code line #207) Spring will not consider your constructor as candidate for invocation:

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

And as you correctly noted it is really contradict to javadoc statement:

...matching all remaining arguments with beans from the bean factory

But anyway implementation means that by default Spring can't resolve constructor for instantiate such beans. And you have to create 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 bean from context as you want to do:

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

If you don't want to have a deal with configuration class and factory method, you can simply pass needed beans into the context.getBean(). Sure you have to get this beans from context:

BeanWithRuntimeDependencies bean = 
    context.getBean(BeanWithRuntimeDependencies.class, 
        context.getBean(DependencyA.class), 
        context.getBean(DependencyB.class),
        "runtime string", 10);
like image 163
Ken Bekov Avatar answered Sep 28 '22 03:09

Ken Bekov