Overview

In a previous article, I showed how to refactor a controller and move the business logic into its own service class. We based that service class on an interface.

NOTE: The code for the previous article can be found here. If you did not work through the samples in that article, you can use this code as a starting point for this article.

When we called that service, we explicitly instantiated an instance of the class we created. That works, but it hard-codes a dependency to a specific implementation, which limits our flexibility.

In this article, I will show you how to use Dependency Injection to remove that hard-coded dependency. Spring explicitly supports Dependency Injection.

Declaring a class as a Service

The MathServiceImpl class contains our business logic. To use Spring's Dependency Injection, we must explicitly declare it as a Service. To do this, we decorate the class with the @Service attribute. If we have multiple implementations of the same interface, we distinguish them by also decorating the class with the @Qualifier attribute and providing a unique string to identify this implementation. I typically use the class name as this string, but you may use whatever conventions you like to enforce uniqueness.

An example is shown in the listing below:

@Qualifier("MathServiceImpl")
@Service
public class MathServiceImpl implements MathService {
    @Override
    public Integer AddNumbers(Integer firstNumber, Integer secondNumber) {
        Integer sum = firstNumber + secondNumber;
        return sum;
    }

    @Override
    public Integer SubtractNumbers(Integer firstNumber, Integer secondNumber) {
        Integer difference = firstNumber - secondNumber;
        return difference;
    }
}

We can create a second Service class that implements this same interface and provide a different Qualifier, as shown below:

@Qualifier("MathServiceMockImpl")
@Service
public class MathServiceMockImpl implements MathService {
    @Override
    public Integer AddNumbers(Integer firstNumber, Integer secondNumber) {
        return 10;
    }

    @Override
    public Integer SubtractNumbers(Integer firstNumber, Integer secondNumber) {
        return 5;
    }
}

In this example, we heard coded the return values of the add and subtract methods to 10 and 5, respectively.

This is a common practice if we want to use one class for production and a different class for testing. The production class may contain dependencies to other services, databases, and files; while the testing class may hard code return values to avoid hitting those dependencies.

Instantiating the service class

Now that we have declared the MathService classes as services, we no longer need to explicitly instantiate them: we can now use Dependency Injection to automatically instantiate a class whenever it is used.

In the MathController class, delete the two lines that instantiate MathServiceImpl:

MathService mathService = new MathServiceImpl();

Instead, at the top of the class, add a private variable named "mathService" of type MathService and decorate this with the @Autowired attribute and the @Qualifier attribute. Pass "MathServiceImpl" as the argument to @Qualifier, as shown below:

@Autowired
@Qualifier("MathServiceImpl")
private MathService mathService;

The @Autowired attribute tells Spring to use Dependency injection to create an instance of an object whenever the mathService variable is used.

The @Qualifier attribute tells Spring which class to instantiate.

Here is the new full listing of the MathController class:

@RequestMapping("math")
@RestController
public class MathController {

    @Autowired
    @Qualifier("MathServiceImpl")
    private MathService mathService;

    @GetMapping("add/{firstNumber}/{secondNumber}")
    public ResponseEntity Add(
        @PathVariable("firstNumber") Integer firstNumber, 
        @PathVariable("secondNumber") Integer secondNumber) {
            Integer sum = mathService.AddNumbers(firstNumber, secondNumber);
            return new ResponseEntity(sum, HttpStatus.OK);
    }

    @GetMapping("subtract/{firstNumber}/{secondNumber}")
    public ResponseEntity subtract(
        @PathVariable("firstNumber") Integer firstNumber, 
        @PathVariable("secondNumber") Integer secondNumber) {
            Integer difference = mathService.SubtractNumbers(firstNumber, secondNumber);
            return new ResponseEntity(difference, HttpStatus.OK);
    }
}

When we run this and call the add method, we get the same results as in the last article, but we have fewer hard-coded dependencies.

AddNumbers
Fig. 1

Using a Different Qualifier

We can tell Spring to use a different implementation of MathService by changing the @Qualifier argument.

Replace the @Qualifier line in MathController with the "MathServiceMockImpl", as shown below:

@Autowired
@Qualifier("MathServiceImpl")
private MathService mathService;

Now, when we run this and call the add method, it always returns 10, regardless which parameters we pass, as shown in Fig. 2.

AddNumbers Mock Implementation
Fig. 2

Conclusion

In this article, I showed you how to use the built-in Dependency Injection features of Spring Boot to avoid hard-coded dependencies in your code.

You can find the sample code here.