Warm-up

Build both containers

10 minjunior16 yrs

One concept, guided. Near-impossible to fail.

What this teaches

  • Constructor injection is one recursive call; field injection is two passes
  • The second pass is the only reason a field-injected object can exist unwired
  • A constructor-injected object cannot be obtained in an invalid state
  • @Autowired is not needed on a single constructor since Spring 4.3

Starter

Starter.java
import java.lang.reflect.*;
import java.util.*;

/**
 * Warm-up: a container is fifteen lines, and writing both makes the argument
 * for you.
 *
 * Constructor injection is one recursive call: to build a bean, first build
 * what its constructor asks for. Field injection is two passes: instantiate
 * everything, then assign the fields. That second pass is the whole
 * difference, and everything the entry claims follows from it.
 */
public class Starter {

    interface Repository {
        String find(String id);
    }

    static class JdbcRepository implements Repository {
        @Override
        public String find(String id) {
            return "row:" + id;
        }
    }

    /** Field injection: no constructor declares anything. */
    static class FieldInjectedService {
        Repository repository;              // @Autowired

        String lookup(String id) {
            return repository.find(id);
        }
    }

    /** Constructor injection: the dependency is the signature. */
    static class ConstructorInjectedService {
        private final Repository repository;

        ConstructorInjectedService(Repository repository) {
            this.repository = repository;
        }

        String lookup(String id) {
            return repository.find(id);
        }
    }

    // TODO 1: write the constructor-injection container.
    //
    //     static Object byConstructor(Class<?> type) throws Exception
    //
    // Take the class's only constructor, resolve each parameter type by
    // calling yourself, then newInstance. Note what you did NOT have to
    // write: any concept of a partly built object.

    // TODO 2: write the field-injection container.
    //
    //     static Map<Class<?>, Object> byField(Class<?>... types) throws Exception
    //
    // Pass one: newInstance every type into a map. Pass two: for each bean,
    // walk getDeclaredFields() and set each one from the map.
    //
    // Mark the exact line that starts pass two. That line is why a
    // field-injected bean can exist before it is usable.

    public static void main(String[] args) throws Exception {
        // TODO 3: before you write either container — predict what this does.
        FieldInjectedService unwired = new FieldInjectedService();
        try {
            System.out.println(unwired.lookup("42"));
        } catch (NullPointerException e) {
            System.out.println("unwired field injection: " + e.getMessage());
        }

        // TODO 4: now try to write the equivalent mistake for
        // ConstructorInjectedService — an instance of it that has no
        // repository. You cannot. Say why in one sentence.

        // TODO 5: wire ConstructorInjectedService with your byConstructor
        // container and call lookup. Then wire FieldInjectedService with
        // byField and call lookup. Both should print row:42.

        // TODO 6: add a second dependency to BOTH classes — an Auditor with
        // one method. Notice which one stops compiling and which one keeps
        // compiling and starts failing at runtime. That contrast is the
        // entire interview answer.
    }
}

Run it locally:

cd exercises/java/spring-core/constructor-vs-field-injection/01-warmup
javac Starter.java -d /tmp/out && java -cp /tmp/out Starter

Done when

  • Both containers are written and wire the same two beans
  • You watched a field-injected bean throw before its second pass ran
  • You can say which line of your own code makes the cycle possible

← Back to Why is constructor injection preferred over field injection?