---
title: 'Dependency Injection'
source: 'https://academia.sh/en/courses/enterprise-application-framework/dependency-injection'
course: 'The Enterprise Application Framework'
language: en
updated: '2026-10-21T05:03:16+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Shows with reflection that constructor-based injection supplies a dependency without any assignment at all in the source'
    - 'Proves by running it that both the container-built and the manually built form of the same class work'
    - 'Measures when and how an unbound interface produces an exception in the container'
---

# Dependency Injection

When an IoC container supplies a dependency through the constructor, it is born from an annotation; the same dependency given by hand is born from a call standing in the source — and the same class works with both. When no implementation is bound to an interface, the container throws an exception immediately, but not while the container is being built — at the first request.

The previous lesson closed by saying that the `new Service(manual)` line was hand-imitating a
mechanism inside the object container: giving a dependency to a constructor. The `Service`
class's single constructor takes a `Store` parameter, and the container resolves this parameter
on its own while constructing `Service` — without ever writing `new InMemoryStore()` in the
source. This lesson's question is: is this resolution really an **injection**, or is it just
another name for construction? And more importantly: does the same `Service` class also work
when its dependency is given by hand?

**Dependency injection** itself is not a new idea in this course — the Design Patterns course
established it as a pattern that provides the separation between principle and detail, the
cornerstone of testable design. This lesson does not re-grind that pattern; it measures **which
mechanism** the container applies it with and what it leaves in the source. The question asked
is the same: did this behavior come from a call in the source, an annotation, or neither?

## The Container Fills the Constructor's Inside

The container's `get` method, when a type is requested, first finds that type's constructor,
then walks through the constructor's parameter types one by one and, for each one, **calls
itself again**: `arg[i] = get(constructor.getParameterTypes()[i]);`. Since `Service`'s only
parameter is `Store`, this loop runs once and calls `get(Store.class)`; since `Store` is an
interface, the container looks it up in the `bindings` map, constructs the `InMemoryStore` it
finds, and returns it. In the end, when `constructor.newInstance(arg)` is called, `arg[0]` is
already a ready `InMemoryStore` instance — there is no line in the source that fills this value
at all, filling it happens entirely inside this recursive loop.

This is the definition of constructor-based injection: a dependency is resolved **before** the
object is constructed and passed to the constructor as an argument. Injection through a field
(the object first being constructed and then a value being written to its field through
reflection) is not built at all in this topic — the container only fills constructor parameters,
because `Service`'s `store` field is `final` and is assigned in the constructor. This constraint
is deliberate: a `final` field cannot be changed after it is constructed, so it is impossible for
`Service` to exist without its dependency — the compiler itself enforces this, not the
container's courtesy.

The `constructor.setAccessible(true)` line is worth a separate observation. `Service`'s
constructor carries no access modifier at all, meaning it has package visibility; an ordinary
`new Service(store)` call would work with this visibility anyway, since it would come from the
same package too. But the container itself constructs `Service` through a general-purpose
`Class<?>` object, without knowing in advance which package it came from; reflection's normal
access check steps in in this case, and without the `setAccessible(true)` call it would throw an
`IllegalAccessException`. This line is the source of the container's guarantee that it can
"construct every managed component regardless of its visibility" — and this guarantee, too, is a
decision that never shows up in the source, it only stands in the container's own code.

The container's constructor selection also carries an assumption of its own: the `get` method
makes no selection at all, it says directly `type.getDeclaredConstructors()[0]` — it assumes from
the start that the class has **exactly one** constructor, and that even if there is more than
one, the first is correct. Since both `Service` and `InMemoryStore` have a single constructor,
this measurement never stresses this assumption, but the container itself never checks it
anywhere. If a second constructor had been added — an overload like
`Service(Store store, String label)` — the order of the array returned by
`getDeclaredConstructors()` is **not guaranteed** by the Java language specification; the
container could not know in advance which constructor it would choose, and if the chosen
constructor's parameter carried a type that could not be resolved as a managed component (like
`String`), the recursive `get` call would throw an exception. This is a fragility this lesson
does not measure but the model carries: constructor-based injection rests on the assumption that
a class has a **single** way to be constructed, and this assumption is not written explicitly in
the source at all — it can only be understood by looking at how many constructors the class
declares.

The injection measured by this topic is also, at the same time, a narrow injection: the
constructor can only request another managed component or an interface, not a bare value like
`String` or `int`. Filling a value-typed dependency requires a separate mechanism — the `Value`
annotation — and this measurement does not build that at all; this is exactly why `InMemoryStore`
returns its own data as a fixed value.

- **CC6.** Constructor parameter resolution is depth-first: if a parameter has its own
  dependency, that too is resolved first, through the same `get` call.
- **CC7.** `Service`'s `store` field is `final`; this guarantees at compile time that the
  dependency cannot be assigned through any path in the source other than the constructor.
- **CC8.** The same `InMemoryStore` instance is constructed both through the container and by
  hand, and called through the same interface; the measured difference is the construction
  style, not the call contract.
- **CC9.** The `bindings` map holds only interface–implementation pairs; the decision of which
  implementation is selected sits not in the source, but in the `bind` call itself.
- **CC10.** When an unbound interface is requested, the container throws an exception naming
  which type is missing; this exception is not caught, it surfaces to the party making the
  first request.

```java
// Container.java — did a call in the source produce this dependency, or an annotation
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Container {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Component {}

    static int call = 0, annotation = 0;

    static void observe(String behavior, String source) {
        if (source.equals("call")) call++; else annotation++;
        System.out.printf("%-38s %s%n", behavior, source);
    }

    static void summary() {
        System.out.printf("behavior: %d call, %d annotation%n", call, annotation);
    }

    final Map<Class<?>, Class<?>> bindings = new HashMap<>();

    Container bind(Class<?> iface, Class<?> implementation) { bindings.put(iface, implementation); return this; }

    Object get(Class<?> type) throws Exception {
        if (type.isInterface()) {
            Class<?> implementation = bindings.get(type);
            if (implementation == null)
                throw new IllegalStateException("no implementation bound to interface: " + type.getSimpleName());
            return get(implementation);
        }
        Component c = type.getAnnotation(Component.class);
        if (c == null) throw new IllegalStateException("no component annotation: " + type.getSimpleName());

        Constructor<?> constructor = type.getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        Object[] arg = new Object[constructor.getParameterCount()];
        for (int i = 0; i < arg.length; i++) arg[i] = get(constructor.getParameterTypes()[i]);
        return constructor.newInstance(arg);
    }

    String tryGet(Class<?> type) {
        try { get(type); return "ok"; }
        catch (Exception e) { return e.getClass().getSimpleName(); }
    }

    public static void main(String[] args) throws Exception {
        Container container = new Container().bind(Store.class, InMemoryStore.class);

        Service service = (Service) container.get(Service.class);
        Container.observe("dependency supplied", service.store != null ? "annotation" : "call");

        InMemoryStore manual = new InMemoryStore();
        Container.observe("direct method call", "call");
        Container.observe("dependency given by hand", "call");

        Service manualService = new Service(manual);
        System.out.println("value returned through the container : " + service.report());
        System.out.println("value returned through manual wiring  : " + manualService.report());

        Container.summary();
    }
}

interface Store { String read(); }

@Container.Component
class InMemoryStore implements Store {
    public String read() { return "data"; }
}

@Container.Component
class Service {
    final Store store;
    Service(Store store) { this.store = store; }
    String report() { return store.read(); }
}
```

```
dependency supplied                    annotation
direct method call                     call
dependency given by hand               call
value returned through the container : data
value returned through manual wiring  : data
behavior: 2 call, 1 annotation
```

This topic's first lesson measured this same question across twelve behaviors and four defects,
and arrived at a total of `3 call, 6 annotation, 3 default` and `1 error, 2 silent, 1
ineffective`; this lesson stays only in that core's dependency-supplying slice.

## Two Paths, the Same Contract

The `dependency supplied` line comes out `annotation` because the `service.store` field fills
with no source line at all — the container gave it to the constructor. The `dependency given by
hand` line comes out `call` because the same field fills through an argument that really stands
in the source, in the `new Service(manual)` call. The only difference is *where* it came from;
`Service` itself does not know which path constructed it, and it does not need to — the `store`
field looks at the same `Store` interface in both cases.

This connects directly to testability: `service.report()` and `manualService.report()` both
work, both return the same `"data"` string, and the `direct method call` line proves this
separately by calling `manual.read()` directly, with the container never stepping in at all —
`Service` can be built by hand and tested. The principle the Design Patterns course established
becomes concrete right here: `Service` depends on an interface, not on a specific
implementation, which is why passing a fake `Store` implementation in place of the real
`InMemoryStore` during a test is possible without changing a single line of `Service`. The
container does not **apply** this principle — `Service` itself was already written according to
this principle; the container only records which implementation corresponds to which interface,
through the `bind` call, and hands it back at the right moment.

The `bindings` map itself is also a measurement point: the method
`Container bind(Class<?> iface, Class<?> implementation)` is explicitly called in the source —
`new Container().bind(Store.class, InMemoryStore.class)` is a **call**, not an annotation. The
decision of which implementation is chosen sits not on the classes, but inside the code that
builds the container. This is the lesson's second observation: even though injection itself is
born from an annotation, the decision of **which** implementation gets injected comes from a
line that really stands in the source, one that writes it.

The importance of this distinction becomes visible once a second `Store` implementation is put
in `InMemoryStore`'s place. The `Service` class only mentions the `Store` interface — in the
constructor signature, in the `report()` body, `InMemoryStore`'s name appears nowhere at all. If
the line `bind(Store.class, AnotherStore.class)` replaced `bind(Store.class,
InMemoryStore.class)`, not a single character in `Service`'s source file would change; the only
thing that changes is the line that builds the container. The **principle** and the **detail**
the Design Patterns course separated stand exactly at this boundary: the principle is the `Store`
interface, the detail is the `InMemoryStore` class, and the decision that connects them is
written not inside `Service` but outside the container. What this topic measures is that this
decision, too, leaves a trace in the source — just not inside `Service`.

A small side note: since `bindings` is a map, calling `bind` twice for the same interface
silently overwrites the first with the second — no exception, no warning. This lesson does not
measure this separately, but it is an observation from the same family as the third lesson's
scope measurement: writing to a map carries a different rule than adding to a list.

## Limiting Measurement: An Unbound Interface

In the setup above, `Store` was always bound to `InMemoryStore`. What happens if this binding is
skipped?

```java
// InterfaceError.java — the container throws an error if no implementation is bound to the interface, and exactly at the first request
public class InterfaceError {
    public static void main(String[] args) throws Exception {
        Container unbound = new Container();
        System.out.println("container built, no request made yet");
        System.out.println("number of bound implementations       : " + unbound.bindings.size());
        String result = unbound.tryGet(Service.class);
        System.out.println("exception thrown when Service requested : " + result);
    }
}
```

```
container built, no request made yet
number of bound implementations       : 0
exception thrown when Service requested : IllegalStateException
```

The `new Container()` line performs no check at all — even if `bind` was never called, the
container is constructed without a problem, the `bindings` map stays empty, and no warning
comes out. The defect falls when `Service` is really requested: `get(Service.class)`, while
trying to resolve its constructor parameter, enters `get(Store.class)`, sees that `Store` is an
interface, `bindings.get(Store.class)` returns `null`, and the container throws an
`IllegalStateException` explicitly naming the type. This is the only **noisy** one of this
course's four defects — the remaining three (a misspelled setting name, an unrecognized scope
name, a wrap marker placed on an interfaceless class) stay silent or ineffective, but an unbound
interface does not leave the container silent.

Timing matters here: the error falls **not while the container is built, but at the first
request**. A program can execute `new Container().bind(...)` lines and move on without getting
any error, then crash minutes later the first time `Service` is requested. The moment the
container is built and the moment the dependency graph is actually validated are not the same
moment — this distinction determines when you will find out if there is a missing link between
managed components.

The `Container` in this measurement was deliberately left in its simplest form: the `get`
method notices a missing binding only when that binding is actually requested, it does not scan
and validate the entire dependency graph in advance the moment the container is built. As a
result, a missing binding can go unnoticed for as long as the program never runs that path —
unlike a type mismatch the compiler could catch, this defect only surfaces once the relevant
code path is actually run.

This choice carries a trade-off: early validation (scanning the whole graph while the container
is built and finding missing bindings in advance) risks crashing right at the program's start
but catches the error before it reaches production; late validation (the path this measurement
chose) keeps the container's construction fast and simple, but a missing link can go unnoticed
for as long as that code path is never run. This lesson models the second path, because the
container itself is nothing more than a decision that does not show up in the source either —
when the `get` method validates is, just like where a dependency comes from, a choice that can
only be understood by reading the container's own code.

## Summary

- Constructor-based injection is the mechanism that resolves a dependency before the object is
  constructed and gives it to the constructor as an argument; there is no assignment anywhere in
  the source that fills this value.
- The same class can be constructed both through the container and by hand, and both work — as
  long as the dependency is bound to an interface, the container steps in without breaking
  testability.
- The decision of which implementation is injected into which interface comes from a `bind` call
  that really stands in the source; this is a behavior born from a call, not an annotation.
- When an unbound interface is requested, the container throws an exception immediately and by
  name — this is the only noisy one of the four defects.
- This error falls not on the line where the container is built, but at the first moment the
  dependency is really requested; construction and validation do not happen at the same time.

## Next Step

This lesson measured where `Service`'s dependency came from, but it never asked when `Service`
itself is reconstructed by the container — does `container.get(Service.class)` return the same
instance every time it is called, or does it construct a new one each time? The `Component`
annotation has a `scope` field, and this lesson never used it at all; the first lesson's `Service`
staying a singleton, and `Request`'s reconstruction on every call, both came from exactly this
field's value. The next lesson opens this field: when does a managed component stay the same,
when is it reconstructed, and at exactly what moment is the init method called?
