Java dependency injection example

Philip 73 Published: 10/05/2024

Java dependency injection example

Here is a detailed example of Java dependency injection using the Spring Framework.

Suppose we have a simple banking application that has several components: Account, Transaction, and Bank. The Bank class uses an instance of the Transaction class to process transactions. We want to decouple the Bank from the concrete implementation of the Transaction class.

Using Dependency Injection (DI), we can define the dependency between these classes in a separate configuration file (e.g., Spring XML or Java Configuration) rather than having the Bank class create its own instance of the Transaction class.

Here's an example using Spring Java Configuration:

@Configuration

public class BankingConfig {

@Bean

public Transaction transaction() {

return new SimpleTransaction(); // or some other implementation

}

@Bean

public Bank bank(Transaction transaction) {

return new Bank(transaction);

}

}

In this example, we define two beans: transaction and bank. The transaction bean is a simple instance of the Transaction class. The bank bean takes an instance of the Transaction class as its constructor argument.

Now, let's implement the Account, Transaction, and Bank classes:

public class Account {

private int balance;

public Account(int initialBalance) {

this.balance = initialBalance;

}

public void deposit(int amount) {

balance += amount;

}

}

public abstract class Transaction {

public abstract void processTransaction(Account account, int amount);

}

public class SimpleTransaction extends Transaction {

@Override

public void processTransaction(Account account, int amount) {

// Simulate some processing logic...

System.out.println("Processing transaction...");

}

}

public class Bank {

private Transaction transaction;

public Bank(Transaction transaction) {

this.transaction = transaction;

}

public void makeTransaction(Account account, int amount) {

transaction.processTransaction(account, amount);

}

}

In the Bank class, we simply inject an instance of the Transaction interface through its constructor. We can then use this instance to process transactions.

To use these classes, we create a Spring application context and define our beans:

public class Main {

public static void main(String[] args) {

AnnotationConfigApplicationContext context =

new AnnotationConfigApplicationContext(BankingConfig.class);

Bank bank = context.getBean(Bank.class);

Account account = new Account(100);

bank.makeTransaction(account, 50); // Process a transaction

}

}

In this example, we create an instance of the Bank class using the Spring application context. We then use the makeTransaction method to process a transaction.

By injecting dependencies through the configuration file (Spring Java Configuration), we decouple the Bank class from the concrete implementation of the Transaction class. This makes it easy to swap out different implementations of the Transaction class without modifying the Bank class itself.

This is just one example of how Dependency Injection can be used in Java using the Spring Framework. There are many more powerful and complex scenarios where DI can be applied, but this should give you a basic understanding of the concept.

Why does Java need dependency injection?

I'd be happy to explain why Java (or any object-oriented programming language) needs dependency injection!

Dependency injection is a software design pattern that allows components to be loosely coupled, making it easier to test, maintain, and extend the codebase. In Java, this concept becomes even more crucial due to its robust ecosystem, which encourages developers to write modular, reusable, and scalable code.

Here's why Java needs dependency injection:

Loose Coupling: When classes are tightly coupled, changes in one part of the system can have unintended consequences elsewhere. Dependency injection helps decouple these dependencies, making it easier to modify or replace individual components without affecting the entire application.

Reusability: By injecting dependencies rather than hardcoding them, you can reuse code across different contexts and applications. This promotes a more modular architecture, where components can be easily swapped out or combined in new ways.

Testability: With dependency injection, it's easier to write unit tests for individual classes because they're not reliant on external systems or databases. This makes testing faster, more reliable, and less prone to errors. Scalability: As applications grow, dependencies can become a significant bottleneck. By injecting dependencies, you can isolate these bottlenecks and make it easier to scale specific components without affecting the rest of the system. Flexibility: Dependency injection enables you to switch between different implementations of the same interface or abstract class. This is particularly useful when working with APIs, databases, or other external systems that may change over time. Reduced Coupling to Frameworks: When using frameworks like Spring or Java EE, dependency injection helps decouple your business logic from the framework itself. This makes it easier to switch between different frameworks or integrate them with other technologies. Improved Code Organization: By separating concerns and injecting dependencies, you can organize your code more effectively. This leads to a cleaner, more maintainable architecture that's easier to understand and extend.

To achieve these benefits, Java developers typically use inversion of control (IoC) containers or frameworks like Spring or CDI (Contexts and Dependency Injection). These tools help manage the dependencies between classes, making it easier to write robust, scalable, and maintainable code.

In summary, dependency injection is essential in Java because it enables loose coupling, reusability, testability, scalability, flexibility, reduced coupling to frameworks, and improved code organization. By injecting dependencies rather than hardcoding them, developers can create more robust, maintainable, and scalable applications that are easier to test, extend, and evolve over time.