Dependency Injection lässt sich am besten durch "Einbringen von Abhängigkeiten" übersetzen und ist in der Software-Entwicklung ein bekannter Begriff, bei dem es wie bei der Fabrikmethode um die Auslagerung von Konstruktoraufrufen zur Objekterzeugung geht. Die Ziel der Dependency Injection ist es, die Abhängigkeiten zwischen Klassen, die durch import-Anweisungen entstehen, zu reduzieren, und die Klassen dadurch zu entkoppeln. Lose Kopplung von Klassen (oder Komponenten im Allgemeinen) hat den Vorteil, dass sich Änderungen einfacher durchführen lassen, da diese sich nur lokal auswirken.

Dependency Injection ist kein GoF-Muster. Den Begriff hat Martin Fowler in seinem Artikel "Inversion of Control Containers and the Dependency Injection pattern" geprägt [Fow04]. Er suchte nach einem Begriff für die Entkopplung bei der Objekterzeugung, der sich von dem allgemeinen Begriff Inversion of Control (IoC) (s. Kapitel Spring) abgrenzen lässt. Dependency Injection folgt dem Prinzip der eindeutigen Verantwortlichkeit (engl. Single-Responsibility-Prinzip) [Mar18], das nach Robert Martin besagt, dass jede Klasse in der objekt-orientierten Programmierung nur eine wesentliche Aufgabe erfüllen soll und entsprechend dieser Aufgabe weiterentwickelt wird:

"There should never be more than one reason for a class to change." (Robert Martin)

Demzufolge ist die Situation im folgenden Code-Beispiel zu vermeiden: Die Klasse MyClass verwendet intensiv das Interface MyLogger, ist aber aufgrund eines direkten Konstruktoraufrufs auch von der konkreten Klasse ConsoleLogger abhängig, die dieses Interface implementiert. Wenn nun die konkrete Klasse ausgetauscht werden soll, muss die Klasse MyClass geändert werden, obwohl sich an ihrer eigentlichen Funktionalität nichts geändert hat.

import MyLogger;
import ConsoleLogger; // this dependency should be avoided

class MyClass {
    MyLogger logger = new ConsoleLogger();
	
	// ...
}

Dependency Injection hat zum Ziel diese Abhängigkeit aufzulösen. Dies erfolgt im einfachsten Fall über einen Konstruktor oder über eine Setter-Methode.

import MyLogger; // no import of concrete implementation required

class MyClass {
    MyLogger logger;
	
	MyClass(MyLogger logger) { this.logger = logger; } // constructor dependency injection
	
	void setLogger(MyLogger logger) { this.logger = logger; } // setter dependency injection
	
	// ...
}

Nun stellt sich die Frage, wer das Objekt erzeugt und von außen der Klasse MyClass injiziert. Allgemein ist anzustreben, die Konfiguration darüber, welche konkrete Implementierungsklasse je Interface genutzt werden soll, an einer zentralen Stelle zusammenzuführen, z.B. in einer Konfigurationsklasse oder in einer Konfigurationsdatei (z.B. XML, YAML). Eine derartige Konfiguration wird i.d.R. von einem Framework in seiner Funktion als IoC-Container vorgesehen und verwaltet. Die Ansätze je Framework können sich dabei leicht unterscheiden. Im Folgenden werden die Dependency Injection-Ansätze von Spring und Google Guice vorgestellt. Beide nutzen die Java Reflection API, um zu ermitteln, welche konkrete Klasse an ein Interface gebunden werden kann. Guice spricht hier von Binding, während der entsprechende Begriff in Spring Wiring heißt.

Spring

Damit das Spring Framework die Objekte einer Klasse in Konstruktoren, in Methoden oder direkt in Attribute injizieren kann, sind diese dem Framework als sogenannte Beans bekanntzumachen. Spring definiert Beans wie folgt, wobei das Framework selbst die Funktion als IoC-Container übernimmt:
"In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container." (Spring Docs)

Alle Klassen, die mit der Annotation @Component (aus dem Package org.springframework.stereotype) oder einer ihrer Spezialisierungen wie @Service, @Repository oder @Controller versehen sind, werden von Spring automatisch als Beans erkannt und registriert. Die registrierten Beans können zur Laufzeit über den Anwendungskontext erreicht werden. In dem folgenden Code-Beispiel soll ein Objekt der Klasse CapsLockConsoleLogger als Bean an anderen Stellen eingebunden werden können. Daher ist die Klasse als @Component annotiert (Zeile 1).

@Component
class CapsLockConsoleLogger implements MyLogger {

    @Override
    public void log(String message) { System.out.println(message.toUpperCase()); }
}

In der folgenden Klasse SpringClient wird diese Bean in das Attribut MyLogger logger injiziert (Zeilen 4-5), d.h. genau an dieser Stelle findet die Dependency Injection statt. Obwohl kein Konstruktoraufruf zu sehen ist, kann der Logger später verwendet werden (Zeile 13). Die Annotation @Autowired sorgt dafür, dass das Framework per Reflection nach einer passenden Bean für das Interface MyLogger sucht und diese Bean an das Attribut bindet. Wichtig ist, dass genau eine passende Bean gefunden wird – und nicht keine oder mehrere.

Die Klasse SpringClient ist hier eine einfache Spring Boot-Anwendung, die als solche annotiert ist (Zeile 1) und wie üblich gestartet wird (Zeile 8). Durch den Aufruf von SpringApplication.run() wird ein ApplicationContext-Objekt erzeugt, das den Spring IoC-Container repräsentiert und über das als zentraler Anwendungskontext alle Beans verwaltet werden und erreichbar sind.

@SpringBootApplication
class SpringClient { // this example is a Spring Boot application

    @Autowired
    MyLogger logger;

    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(SpringClient.class, args);
    }

    @Bean
    CommandLineRunner run(ApplicationContext ctx) {
        return args -> logger.log("Hello World!");
    }
}

Wenn mehrere Beans dieselbe Schnittstelle erfüllen, muss über einen qualifizierenden Namen bestimmt werden, welche Bean das Framework injizieren soll. Dazu kann eine Konfigurationsklasse angelegt werden – wie z.B. die folgende Klasse MyConfiguration, in der die 3 Beans definiert sind (2 vom Typ MyLogger, 1 vom Typ String).

@Configuration
class MyConfiguration {

    @Bean
    MyLogger loggerA() { return new CapsLockConsoleLogger(); }

    @Bean
    MyLogger loggerB() { return new TimestampConsoleLogger(dateFormat()); }

    @Bean 
	String dateFormat() { return "yyyy-MM-dd HH:mm:ss"; }
}
@Component
class TimestampConsoleLogger implements MyLogger {

    SimpleDateFormat dateFormat;

    TimestampConsoleLogger(String pattern) { dateFormat = new SimpleDateFormat(pattern); }

    @Override
    public void log(String message) { System.out.println(dateFormat.format(new Date()) + "\t" + message); }
}

Die Beans können über eine @Qualifier-Annotation und ihren Methodennamen wie folgt injiziert werden.

@Autowired @Qualifier("loggerA")
MyLogger logger;

Alternativ kann eine Bean über ihren Methodennamen beim Anwendungskontext abgerufen werden.

logger = ctx.getBean("loggerA", MyLogger.class); // request bean from application context
logger.log("Hello World!");

Anstatt in der Konfigurationsklasse MyConfiguration können die Beans auch in einer entsprechenden XML-Konfigurationsdatei definiert werden, welche dann als Anwendungskontext zu laden oder diesem hinzuzufügen ist.

class SpringXMLConfigClient {

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        MyLogger logger = ctx.getBean("loggerA", MyLogger.class);
        logger.log("Hello World!");
    }
}

Google Guice

Google Guice bietet eine bewährte Alternative für Dependency Injection, falls in einem Projekt bewusst ohne Spring gearbeitet werden soll. Als Guice in 2008 durch Google veröffentlicht wurde, war es das erste Framework, das Dependency Injection in Java mittels Annotationen möglich machte. In Guice wird die Konfigurationsklasse, die Interfaces an konkrete Implementierungsklassen bindet als Modul bezeichnet.

"Guice uses bindings to map types to their implementations. A module is a collection of bindings specified using fluent, English-like method calls." (Guice Docs)

Der folgende Beispiel-Code zeigt das Modul MyModule, das das Interface MyLogger an seine Implementierung CapsLockConsoleLogger bindet.

class MyModule extends AbstractModule { // a Guice configuration module

    @Override
    protected void configure() {
        bind(MyLogger.class).to(CapsLockConsoleLogger.class);
		// more bindings follow here ...
    }
}

Diese Konfigurationsklasse wird in dem folgenden Code-Beispiel verwendet, um ein Injector-Objekt zu erzeugen (Zeile 9). Den Objekten, die später über die Methode getInstance dieses Injector-Objekts erzeugt werden (Zeile 12), können ihre Abhängigkeiten über die Annotation @Inject injiziert werden – wie z.B. bei dem Attribut MyLogger logger (Zeilen 3-4).

class GuiceClient {

    @Inject
    MyLogger logger;

    public static void main(String[] args){

        // create an injector based on module configuration
        Injector injector = Guice.createInjector(new MyModule());

        // create objects using the injector
        GuiceClient client = injector.getInstance(GuiceClient.class);
        client.logger.log("Hello World!");
    }
}

Die gezeigten Code-Beispiele bezüglich Dependency Injection finden sich im Verzeichnis /patterns/dependency-injection des Modul-Repository.