Monday, October 04, 2010

Spring: Ambiguous constructor argument types

This is not TFS related, but build related and this is my only public blog where I document build-like stuff for future reference if I was to ever need it.

In Spring for Java, if you’re going to use “names” for wiring up things like constructor arguments, the class files have to be built with javac –g, which is an entry-level debug mode for javac. This will compile the classes, keeping such things as variable names. If you don’t use javac –g, then Java takes the liberty to modify variable names. I’m guessing they do this for, among other things, keep the class sizes down.

Below is a poorly formatted, convoluted series of knowns and tests to come up with a hypothesis and eventual solution to Spring issue we were running into.

Knowns

1) The Spring configuration is using variable name for mapping constructer arguments in the spring configuration to the set of constructor parameters.

bean id="SetManager" class="com.mycompany.manager.SetManager" lazy-init="false"

constructor-arg name="courtSetCSV" type="org.springframework.core.io.Resource" value="classpath:..."

constructor-arg name="pubSetCSV" type="org.springframework.core.io.Resource" value="classpath:..."

constructor-arg name="jurSetCSV" type="org.springframework.core.io.Resource" value="classpath:..."

bean

2) When deploying the application out, the error we’re getting is this.

SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'SetManager' defined in class path resource [spring-manager.xml]: Unsatisfied dependency expressed through constructor argument with index 0 of type [org.springframework.core.io.Resource]: Ambiguous constructor argument types - did you specify the correct bean references as constructor arguments?

at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:684)

at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:192)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:984)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:886)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:479)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:450)

3) When creating a WAR via Eclipse’s Export feature, the bindings are wired up fine. When creating the WAR via Ant (or using javac from the command line, which is what Ant is doing), we get the error. When diff-ing the WAR files, they are the same with one exception: The class files created via Eclipse are larger.

Hypothesis Given all of this, our hypothesis is that running the build via Ant, the method names are getting truncated (or modified) such that using the name attribute in Spring won’t work.

Tests

1) To test the use of indexes, instead of names we used something like this: “index="0"”. Using this got us around the initial issue, but ran us into a very similar problem loading another bean. At this point, we knew we were on to something. Or on something. Here are the Spring configuration snippets of using names verses indexes.

Names

bean id="SetManager" class="com.mycompany.manager.SetManager" lazy-init="false"

constructor-arg name="courtSetCSV" type="org.springframework.core.io.Resource" value="classpath:..."

constructor-arg name="pubSetCSV" type="org.springframework.core.io.Resource" value="classpath:..."

constructor-arg name="jurSetCSV" type="org.springframework.core.io.Resource" value="classpath:..."

bean

Indexes

bean id="SetManager" class="com.mycompany.manager.SetManager" lazy-init="false"

constructor-arg index="0" type="org.springframework.core.io.Resource" value="classpath:..."

constructor-arg index="1" type="org.springframework.core.io.Resource" value="classpath:..."

constructor-arg index="2" type="org.springframework.core.io.Resource" value="classpath:..."

bean

We used JAD to decompile both classes. Bingo, the issue is highlighted! When using javac, the default settings truncate the variable names. If Spring is to use variable names for wiring up arguments or properties, this won’t work very well ;)

Ant

public SetManager(Resource resource, Resource resource1, Resource resource2)

{

loadCourtSetMaps(resource);

loadPub(resource1);

loadJur(resource2);

}

Eclipse

public CustomDigestCollectionSetManager(Resource courtSetCSV, Resource pubSetCSV, Resource jurnSetCSV)

{

loadCourtlineCollectionSetMaps(courtSetCSV);

loadPublicationCollectionSetMap(pubSetCSV);

loadJurisdictionCollectionSetMap(jurSetCSV);

}


Solution

1) What parameter change in Ant’s javac Task could we change to get the accurate parameter names?

a. Set debug = true. Turning debug on will make sure the class files are compiled with the same variable names (and other things) as what is in the source code. This will increase the class file size, but from what we’ve noticed so far, does not cause great performance degradation.