At runtime, java classes do not retain the name of the constructor or method parameters, unless classes are compiled with debug options on. This has some interesting implications for Spring Constructor Injection:
Consider the following simple class:
and a sample Spring bean configuration xml file:
Here I am using the c namespace for constructor injection. This fails with the exception that the argument types are ambiguous - this is because the first argument is a String and since its runtime representation does not have the argument name present, Spring cannot determine if it should be substituted for the first name or last.
There are a couple of fixes possible for this scenario:
1. To use index based constructor injection, the drawback though is that it is very verbose:
2. To compile with debug symbols on, this can be done by passing a -g or -g:var flag to the java compiler - this will ensure that the parameter names are preserved in the class file and the original concise bean configuration with c namespace will work.
3. A neat fix is to annotate the constructor with @ConstructorProperties which basically provides the argument names to Spring:
This works with or without debug options turned on.
4. Probably the best fix of all is to simply use @Configuration to define the beans:
Consider the following simple class:
package dbg;
public class Person {
private final String first;
private final String last;
private final Address address;
public Person(String first, String last, Address address){
this.first = first;
this.last = last;
this.address = address;
}
public String getFirst() {
return first;
}
public String getLast() {
return last;
}
public Address getAddress() {
return address;
}
}
and a sample Spring bean configuration xml file:
<bean name="address1" class="dbg.Address" p:street1="street1" p:street2="street1" p:state="state1"/>
<bean name="person1" class="dbg.Person" c:address-ref="address1" c:last="Last1" c:first="First1" ></bean>
<bean name="person2" class="dbg.Person" c:first="First2" c:address-ref="address1" c:last="Last2" ></bean>
Here I am using the c namespace for constructor injection. This fails with the exception that the argument types are ambiguous - this is because the first argument is a String and since its runtime representation does not have the argument name present, Spring cannot determine if it should be substituted for the first name or last.
There are a couple of fixes possible for this scenario:
1. To use index based constructor injection, the drawback though is that it is very verbose:
<bean name="person1" class="dbg.Person" >
<constructor-arg value="First1"></constructor-arg>
<constructor-arg value="Last1"></constructor-arg>
<constructor-arg ref="address1"></constructor-arg>
</bean>
<bean name="person2" class="dbg.Person" >
<constructor-arg value="First2"></constructor-arg>
<constructor-arg value="Last2"></constructor-arg>
<constructor-arg ref="address1"></constructor-arg>
</bean>
2. To compile with debug symbols on, this can be done by passing a -g or -g:var flag to the java compiler - this will ensure that the parameter names are preserved in the class file and the original concise bean configuration with c namespace will work.
3. A neat fix is to annotate the constructor with @ConstructorProperties which basically provides the argument names to Spring:
public class Person {
private final String first;
private final String last;
private final Address address;
@ConstructorProperties({"first","last","address"})
public Person(String first, String last, Address address){
this.first = first;
this.last = last;
this.address = address;
}
This works with or without debug options turned on.
4. Probably the best fix of all is to simply use @Configuration to define the beans:
@Configuration
public static class TestConfiguration{
@Bean
public Address address1(){
return new Address();
}
@Bean
public Person person1(){
return new Person("First1", "Last1", address1());
}
@Bean
public Person person2(){
return new Person("First2", "Last2", address1());
}
}