ExerciseWarm-up
Warm-up
Build both containers
10 minjunior1–6 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 StarterDone 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?