cdi

Dependency Injection

Introduction#

CDI’s flagship feature is a declarative API for dependency injection. Classes can have dependencies flagged with the @Inject annotation, which will indicate to the CDI manager that it needs to provide those dependencies when constructing an instance of the class.

Constructor Injection

The common case for injecting dependencies into a class is with constructor injection. This involves annotating a constructor on the class with @Inject. The CDI manager will look for a constructor with the @Inject annotation when creating an instance of the class. When it finds an @Inject-annotated constructor, it will use reflection to find which parameters are required by the constructor, construct or obtain instances of those dependencies, then call the constructor with those dependencies.

public class Spaceship {

    private final PropulsionSystem propulsionSystem;
    private final NavigationSystem navigationSystem;

    @Inject
    public Spaceship(PropulsionSystem propulsionSystem, NavigationSystem navigationSystem) {
        this.propulsionSystem = propulsionSystem;
        this.navigationSystem = navigationSystem;
    }

    public void launch() throws FlightUnavailableException {
        if (propulsionSystem.hasFuel()) {
            propulsionSystem.engageThrust();
        } else {
            throw new FlightUnavailableException("Launch requirements not met. Ship needs fuel.");
        }
    }

}

Any time a Spaceship instance defined in this manner is created by CDI, it will receive a PropulsionSystem and a NavigationSystem as arguments to its constructor. Because these dependencies are added via the constructor, we have an easy way to provide alternate dependencies in our test harness when unit testing:

public class SpaceshipTest {

    private Spaceship systemUnderTest;
    private TestPropulsionSystem testPropulsionSystem;
    private TestNavigationSystem testNavigationSystem;

    @Before
    public void setup() {
        setupCollaborators();
        systemUnderTest = new Spaceship(testPropulsionSystem, testNavigationSystem);
    }

    @Test
    public void launchSequenceEngagesThrustIfFueled() {
        //given
        testPropulsionSystem.simulateHavingFuel();

        //when
        systemUnderTest.launch();

        //then
        testPropulsionSystem.ensureThrustWasEngaged();
    }

}

Field Injection

The same example from above can also be done using what is known as field injection. Instead of annotating the constructor with @Inject, we annotate the fields we wish to have injected

public class Spaceship {

    @Inject
    private PropulsionSystem propulsionSystem;
    @Inject
    private NavigationSystem navigationSystem;

    public void launch() throws FlightUnavailableException {
        if (propulsionSystem.hasFuel()) {
            propulsionSystem.engageThrust();
        } else {
            throw new FlightUnavailableException("Launch requirements not met. Ship needs fuel.");
        }
    }

}

Note that getter/setter methods are not required for field injection to work. Also note that the field does not need to be public and can in fact be private, though it cannot be final. Making the field private will, however, make writing tests for the code more difficult, as the tests will have to use reflection to modify the field if it does not have a setter.

Also note that constructor injection and field injection can be used in tandem if desired, though it should be very carefully evaluated whether or not it makes sense to do so on a case by case basis. Perhaps it is necessary when working with legacy code to add a dependency to the class without modifying the constructor signature for some peculiar reason.

public class Spaceship {

    private PropulsionSystem propulsionSystem;
    @Inject
    private NavigationSystem navigationSystem;

    @Inject
    public Spaceship(PropulsionSystem propulsionSystem) {
        this.propulsionSystem = propulsionSystem;
    }

    public void launch() throws FlightUnavailableException {
        if (propulsionSystem.hasFuel()) {
            propulsionSystem.engageThrust();
        } else {
            throw new FlightUnavailableException("Launch requirements not met. Ship needs fuel.");
        }
    }

}

This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow