Spring and named constructor arguments

I’m a big fan of Spring and I’ve been using various parts of it now for the last two and a half years. Using the Spring dependency injection container has indeed made my life (as a programmer, of course Eye-wink) a lot easier. In this post I will demonstrate a technique which could be incorporated into Spring to add support for constructor injection using named parameters. The same technique could also be useful for scripting languages like Jython and Groovy to add support for calling Java methods using named parameters.

The problem

With Spring I have the option of using either constructor injection or setter injection. I prefer constructor injection over setter injection because I won’t have to break basic OO principles like encapsulation (by providing a setter for a required dependency) and I don’t have to tie my code to Spring (by implementing InitializingBean) just to be able to check if all dependencies have been set up properly.

Many times however I fall back to setter injection just because it makes my Spring configuration files much more readable. Consider the following Spring configuration file snippets:

<!-- Set up ConnectionPoolImpl instance using constructor injection -->
<bean id="connectionPool" class="ConnectionPoolImpl">
  <constructor-arg index="0" value="gbg5"/>
  <constructor-arg index="1" value="3066"/>
  <constructor-arg index="2" value="bob"/>
  <constructor-arg index="3" value="Ns7Ysh"/>
  <property name="minConnections" value="1"/>
  <property name="maxConnections" value="10"/>
  <property name="maxIdleTime" value="10000"/>
</bean>

<!-- And now using setter injection -->
<bean id="connectionPool" class="ConnectionPoolImpl">
  <property name="host" value="gbg5"/>
  <property name="port" value="3066"/>
  <property name="username" value="bob"/>
  <property name="password" value="Ns7Ysh"/>
  <property name="minConnections" value="1"/>
  <property name="maxConnections" value="10"/>
  <property name="maxIdleTime" value="10000"/>
</bean>

The second one is clearly much more readable. I could of course have added comments to the first one to make it more readable but comments will have to be maintained and neither Spring nor any other tool I know of would be able to warn me when the comments have to be updated. Also note that sometimes there are sensible defaults for some properties and in those cases there’s nothing wrong with setter injection (that’s why I use setter injection for the last three integer values).

In the above example there are no sensible defaults for host, port, username and password and all of them are required. If I would go with the second alternative my ConnectionPoolImpl class would have to implement Spring’s InitializingBean to be able to enforce that these properties get set. This clearly ties my code to Spring. Not only in the sense that the InitializingBean interface must be available at runtime regardless of whether Spring is used to construct the ConnectionPoolImpl instance or not. But also when not using Spring one would have to make sure to call afterPropertiesSet() on ConnectionPoolImpl to determine whether the object has been set up properly.

The solution

So, what would the solution be? Well, I’d like to be able to do the following in Spring:

<!-- Set up ConnectionPoolImpl instance using constructor injection -->
<bean id="connectionPool" class="ConnectionPoolImpl">
  <constructor-arg name="host" value="gbg5"/>
  <constructor-arg name="port" value="3066"/>
  <constructor-arg name="username" value="bob"/>
  <constructor-arg name="password" value="Ns7Ysh"/>
  <property name="minConnections" value="1"/>
  <property name="maxConnections" value="10"/>
  <property name="maxIdleTime" value="10000"/>
</bean>

Here I’m assuming that the ConnectionPoolImpl class has a constructor with the signature:

ConnectionPoolImpl(String host, String port, String username, String password)

Using the name attribute with the constructor-arg element would let me match an argument with a named parameter in the constructor’s signature instead of using the index. Note that the order of the constructor-arg elements should be insignificant. Even if I change the constructor signature to

ConnectionPoolImpl(String username, String password, String host, String port)

the above Spring configuration file snippet should still do the right thing.

Now you might think that there’s no way to do this in Java. After all, it’s simple enough to obtain the parameter types from a Java Constructor object, but there’s no way to get the list of parameter names. That may be true, but it’s also true that if you compile your Java classes with local variable debugging information (i.e. using javac -g or javac -g:vars) all the information about constructor (and method) parameter names will be readily available inside the resulting .class file. All we need is a way of obtaining that piece of debug info.

It turns out that ASM makes this very easy. The following code dumps out the constructor and method signatures including parameter names (if available) of a class you specify on the command line:

import java.io.InputStream;
import java.util.ArrayList;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.EmptyVisitor;

public class ParameterNamesTest {

    public ParameterNamesTest(String a, Object[] b) {
    }

    public ParameterNamesTest(long x, double y, int z) {
    }

    public void doSomething(byte a, short b, String c) {
    }

    public static void main(String[] args) throws Exception {

        Class clazz = ParameterNamesTest.class;
        if (args.length > 0) {
            clazz = Class.forName(args[0]);
        }

        /*
         * Load the .class file from the class path.
         */
        String clazzName = clazz.getName();
        int lastDot = clazzName.lastIndexOf('.');
        clazzName = clazzName.substring(lastDot + 1);
        InputStream is = clazz.getResourceAsStream(clazzName
                                                + ".class");

        /*
         * Now use ASM to extract the method and constructor
         * parameter names from the class file.
         */
        ClassReader cr = new ClassReader(is);
        cr.accept(new MyClassVisitor(), false);
        is.close();
    }

    private static class MyClassVisitor extends EmptyVisitor {
        public MethodVisitor visitMethod(int access, String name,
                String desc, String signature, String[] exceptions) {
            /*
             * This is called by ASM for every method and
             * constructor in the class file. Create a new
             * MyMethodVisitor which will receive callbacks when ASM
             * reads local variable debug info in the class file.
             */
            boolean ztatic = (access & Opcodes.ACC_STATIC) > 0;
            return new MyMethodVisitor(ztatic, name, desc);
        }
    }

    private static class MyMethodVisitor extends EmptyVisitor {
        private String methodName;
        private Type[] paramTypes;
        private ArrayList paramNames;
        private boolean ztatic;

        public MyMethodVisitor(boolean ztatic, String methodName,
                               String desc) {

            this.methodName = methodName;
            this.ztatic = ztatic;
            /*
             * Determine the parameter types of the visited method
             * or constructor.
             */
            paramTypes = Type.getArgumentTypes(desc);
            /*
             * paramNames will be used to collect the actual
             * parameter names.
             */
            paramNames = new ArrayList(paramTypes.length);
        }

        public void visitLocalVariable(String variableName,
                String desc, String sig, Label start,
                Label end, int index) {

            if (!ztatic) {
                /*
                 * For non static methods the first local variable is
                 * always 'this'.
                 */
                index--;
            }
            if (index >= 0 && paramNames.size() < paramTypes.length) {
                /*
                 * The first paramTypes.length local variables are
                 * always the local variables which hold the method
                 * arguments.
                 */
                paramNames.add(variableName);
            }
        }

        public void visitEnd() {
            /*
             * Now paramNames contains the parameter names (if the
             * class was compiled with local variable debug info).
             * Dump the method or constructor signature including
             * the parameter names to System.out.
             */
            if (ztatic) {
                System.out.print("static ");
            }
            System.out.print(methodName + "(");
            for (int i = 0; i < paramTypes.length; i++) {
                String paramName = paramNames.isEmpty() ? "?"
                                 : (String) paramNames.get(i);
                System.out.print(paramTypes[i].getClassName());
                System.out.print(" " + paramName);
                if (i < paramTypes.length - 1) {
                    System.out.print(", ");
                }
            }
            System.out.println(")");
        }

    }
}

asm-2.2.2.jar and asm-commons-2.2.2.jar are needed
to compile and run this example. You will find them on the
ASM web site.

Limitations

It’s important to note that this technique has a few limitations:

  • As I’ve already mentioned the .class files must be compiled with local
    variable debugging information. This is a problem if you’re using third party
    classes which haven’t been compiled with the required debug info. E.g. Sun’s
    classes included in the JRE (like java.lang.String) won’t work with this
    technique.
  • If you’re using an obfuscator to protect your classes from reverse engineering
    the obfuscator will most probably remove any debugging information from the class
    files.
  • The .class files must be available for reading. There may be
    security restrictions in the environment your code is running in
    which limits the access to the actual .class files, e.g. when
    running in an application server. Also, the technique will most likely not
    work for dynamically generated classes (generated using
    CGLIB or similar).

Neither of these are big issues for me. I’d use the technique mainly with my
own classes which I have full control over and the projects I work on don’t
require the use of an obfuscator. I’ve also verified that this technique will
work in plain Tomcat which is the environment I deploy to most of the time.

I must admit that it’s unfortunate that this won’t work with Sun’s classes.
But the good news is that it will work with a lot of open source projects out
there, even if you don’t want to recompile from source. Out of the 60000 classes
currently in my local Maven repository, about 40000 have local variable
debugging information.

Conclusions

It is my belief that adding this feature to Spring would make the decision
whether to use setter or constructor injection a lot easier. I would get
nicer looking and more self documenting Spring configuration files without
having to sacrifice anything in the design of my classes.

Of course, the Java code presented above was only a very simple example to
demonstrate the technique. Currently, I’m working on a patch for Spring which
adds the name attribute to the constructor-arg element
in the Spring configuration XML schema. Hopefully I will have it ready and
uploaded to the Spring issue tracker in a few days. Then it will, of course, be
up to the Spring people to decide whether this should be added to Spring or not.
Keep your fingers crossed!

One thought on “Spring and named constructor arguments”

Comments are closed.