Beast
Beast

Reputation: 629

Constructor argument resolution

I have recently started working on spring 3.2. I am trying to understand constructor argument resolution in case of when dependencies are passed through constructor injection. I have created the below example.

package com.springinaction.springidol;

public interface Performer {
    void perform();
}
package com.springinaction.springidol;

public class Juggler implements Performer {

    private int beanBags=3;
    private String name;

    public Juggler(){
    }

    public Juggler(String name,int beanBags){
        System.out.println("First constructor gets called");
        this.beanBags=beanBags;
        this.name=name;
    }

    public Juggler(int beanBags,String name){
        System.out.println("Second constructor gets called");
        this.beanBags=beanBags;
        this.name=name;
    }

    public void perform(){
    System.out.println("JUGGLING "+beanBags+name+" BEANBAGS");
    }
}

Please find below instance of spring configuration file I have used.

<?xml version="1.0" encoding="UTF-8" ?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id="duke" class="com.springinaction.springidol.Juggler">
    <constructor-arg value="Jinesh" />
    <constructor-arg value="77" />
</bean>

In the above scenario the constructor invoked is the first constructor. But after that I slightly change the xml file and added the type attribute for both of the arguments.

<?xml version="1.0" encoding="UTF-8" ?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id="duke" class="com.springinaction.springidol.Juggler">

<constructor-arg type="java.lang.String" value="Jinesh" />
<constructor-arg type="int" value="77" />

</bean>

</beans>

In the above case the constructor invoked by spring is the second constructor. I don't understand why spring has decided to invoke the second constructor instead of the first one? In cases like above how spring decides which constructor to invoke when we pass the type attribute ?

Upvotes: 3

Views: 3874

Answers (2)

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 280179

Spring uses a ConstructorResolver instance to resolve which constructor to use to instantiate your class. It calls the autowireConstructor() method to determine that. You can find the source code online. An older version, here. If you have the source (use maven), you can debug and walk through it yourself.

Within the method it tries determine the difference between the specified arguments and the arguments in the controller with ArgumentsHolder#getTypeDifferenceWeight() method. In our case, it will return a value of 0 because the arguments match (even in a different order).

That value is compared to a minTypeDiffWeight value (originally Integer.MAX_VALUE). If it's smaller, the current constructor being evaluated gets priority and the value replaces minTypeDiffWeight. The method continues like this through all the Class' constructors, comparing again vs minTypeDiffWeight. Since both constructors will give a value of 0 (0 is not smaller than 0), the first one found is used.

It just so happens that

Juggler.class.getDeclaredConstructors();

returns an array like

[public Test.Juggler(int,java.lang.String), public Test.Juggler(java.lang.String,int), public Test.Juggler()]

where the second (declared) constructor appears first. The getDeclaredConstructors() method javadoc states

The elements in the array returned are not sorted and are not in any particular order.

So it's just coincidence. Because the argument types match, Spring chooses the first constructor it finds in that array.

Upvotes: 6

Michael Wiles
Michael Wiles

Reputation: 21194

You can explicitly specify the ordering of the constructor arguments by adding an index attribute.

<constructor-arg type="java.lang.String" index="0" value="Jinesh" />
<constructor-arg type="int" index="1" value="77" />

I assume you can include index and type together though the spring reference docs don't explicitly say you can.

With Spring 3 you can actually specify the name of the parameter you're referring to - to remove all ambiguity - so if you can't use type and index together, this is your solution.

Upvotes: 1

Related Questions