Overview of This Lesson

Refresh this page because I am probably still making changes to it.

API Used in this Lesson

Review

Other Useful Resources

Dependency Injection and Inversion of Control

We've talked in a previous lesson about how one of the main features of Spring is that it uses dependency injection and inversion of control. But what does this actually mean?!

It's actually really cool, so let's try to understand why.

A lot of us, myself included, have been spending a lot of time lately doing jigsaw puzzles. Jigsaw puzzles are fun and relaxing, but what's interesting is that when you have a jigsaw puzzle, you can only use the jigsaw pieces for that puzzle. For example, if I have a puzzle of Diagon Alley from Harry Potter, I can't use any of those puzzle pieces in a flower collage puzzle. Puzzle pieces are tightly coupled to each other: you can only use puzzle pieces in one jigsaw puzzle.

On the other end of the spectrum, we have Lego. Lego pieces can be used to build anything! You can buy a Lego set for Hogwarts Castle, or you can get a Lego set for the Millenium Falcon. Because Lego pieces are created to fit to any other lego piece, you can use Lego pieces to build anything you want: you're not limited to what's on the box!

So you can use your Harry Potter and Star Wars Lego sets and use them to build anything! There's actually a web site called Rebrickable that shows you things you can build with mixed Lego sets!

Java classes should be independent from each other, like Lego pieces. Java classes should be designed in such a way that they can fit into any program.

This makes them more reusable - you can use a class in a project without having to edit it or bring other classes with it. This allows you to test them independently of other classes, also. Java classes should not be like a jigsaw puzzle.

In software design you learned about Robert C. Martin's SOLID principals. By following SOLID, your Java classes will be just like Lego pieces. If you didn't learn SOLID or need a review, I've added some links in the up above, and there are several videos where Uncle Bob lectures about his SOLID principles, like this one:

Dependency Injection

Dependency injection uses the principles of SOLID to keep classes independent from each other while still allowing you to use independent classes together if you need to. For example, say you are designing an application that manages various delivery drivers that can deliver packages and cargo, and can also pick up individual customers and deliver them to destinations.

public class Driver {
    private String name;
    private Vehicle vehicle;

    public Driver() {
        vehicle = new Vehicle("car49", true);
    }
}

public class Vehicle {
    private String id;
    private boolean active;
}

Here we've created a dependency between Driver and Vehicle. If we want to instantiate Driver, it's going to require that Vehicle is also instantiated. This creates a very tight level of coupling between the two classes that we don't want.

vehicle object inside a driver object
Driver has a Vehicle

And what if there are different kinds of vehicles? Maybe there are cars for transporting individual customers, and trucks for transporting packages and other cargo.

public abstract class Vehicle {
    private String id;
    private boolean active;
}

public class Car extends Vehicle {
    private int numPassengers;
}

public class Truck extends Vehicle {
    private double maxCargoVolume;
    private double maxCargoWeight;
}
hierarchy showing car and truck as child classes of vehicle
What if there were different Vehicle types?

A driver could be assigned any particular vehicle if they're licensed to drive it.

public class Driver {
    private String name;
    private Vehicle vehicle;

    public Driver() {
        vehicle = new Car("car49", true, 2);
        // or instead:
        // vehicle = new Truck("truck10", true, 128.5, 1500.0);
    }
}

Now what if we add a new vehicle Bicycle for carrying food deliveries in one or more carrying boxes?

public class Bicycle extends Vehicle {
    private int numBoxes;
}
vehicle now has 3 children including bicycle
Adding More Vehicle Types

Every time we want to start using a new vehicle type, we have to go in and modify our Driver class. This violates the Open-Closed principle of SOLID: A class should not have to be modified for it to be extended.

Inversion of Control

Inversion of control is a principle that reverses, or inverts, the process we've demonstrated above. Instead of the Driver class having control over the construction of the Vehicle class, we should instead reverse or invert the control so that Driver is not constructing instances of the Vehicle class at all.

We can accomplish this by creating an instance of Vehicle somewhere else and pass the instance of Vehicle into the Driver class. This is what dependency injection does.

The basic premise of Dependency injection is that if ClassA depends on ClassB, ClassA is not responsible for instantiating ClassB. This can solve several problems.

For example, instead of instantiating vehicle inside of Driver, we can instantiate it elsewhere and simply pass the instance of vehicle to driver. And this is dependency injection! We are injecting the dependency (which is the vehicle object) into the Driver class by passing the vehicle object into the Driver's constructor.

public class Driver {
    private String name;
    private Vehicle vehicle;

    public Driver(Vehicle vehicle) {
        this.vehicle = vehicle;
    }
}

This decreases the level of coupling between the two classes and thanks to polymorphism, means that we can now add as many new types of vehicles as we want without having to modify the Driver class:

// somewhere else, external to Driver class:
Vehicle v1 = new Car("car49", true, 2);
Vehicle v2 = new Truck("truck10", true, 128.5, 1500.0);

// vehicle is injected into Driver via the constructor
Driver d1 = new Driver(v1);
Driver d2 = new Driver(v2);

Now Driver doesn't have to worry about the implementation of Vehicles at all because Driver no longer has control over the instantiation of Vehicles. Vehicle is created outside of Driver, and the reference passed to Driver. In other words, the vehicle is injected into the driver constructor.

Dependency Injection is a type of Inversion of Control. The flow of control has been inverted by Dependency Injection: the dependency is being created externally instead of internally.

Dependency Injection can also be done via setter methods in the Driver class. For example, a vehicle can be passed into a setVehicle() method of the Driver class, and the setVehicle() method assigns the vehicle parameter to an instance variable:

public class Driver {
    private String name;
    private Vehicle vehicle;

    public Driver(Vehicle vehicle) {
        this.vehicle = vehicle;
    }
    public void setVehicle(Vehicle v) {
        this.vehicle = v;  // or v.clone()
    }
}

Dependency Injection in Spring

Spring uses dependency injection to make a lot of tasks much easier for us when we develop enterprise applications.

Spring is instantiating objects for you behind the scenes. when you start your application class with @SpringBootApplication, one of the things it does is a component scan: it looks for classes with annotations and it instantiates each one of those and puts them in a container called the Inversion of Control Container.

inversion of control container
Spring's Inversion of Control Container (IOCC)

The Inversion of Control container is where all those instantiated classes are kept until they are called upon by something else in the application. For example, when Spring starts up and sees your class with the @Controller annotation, it instantiates it and keeps it in the IoC container so that it can give it to you when your code needs it. When some code in your application needs the controller, Spring goes and finds it in the IOC container, and then injects it where it's needed.

Spring does this with other classes and objects, too. When Spring sees certain annotations when it scans for components, or when you ask for an instance of certain classes, Spring locates it for you in the inversion of control container and gets it for you, all set up and ready to go.

For example, in an earlier video, I showed you how to use the HttpServletResponse class to modify the body of the response before it's sent to the client. Your handler did not instantiate the HttpServletResponse object, it was a parameter in your method: spring had one already constructed and filled with all the information it needed and it was put in the inversion of control container just waiting for someone to ask to use it.

@GetMapping("/addBook")
public void addNewBook(HttpServletResponse response, @RequestParam String firstName,
@RequestParam String lastName) {

}

When you created the parameter, Spring said, "Oh, I have one of those in my Inversion of Control container.." so it retrieved the instance of the response object and injected it into the method by using the parameter you added to your handler method.

This is also where the request params come from: they are injected into the handler method when we create parameters for them. When Spring sees @ResponseParam("firstName") String firstName Spring says, "Oh, I've got a String here that came from the query string and it's called "firstName", here you go."

One of Spring's jobs is to watch for parameters and classes with certain annotations and references to certain objects and classes: when it sees them, it looks for something in the inversion of control container that fits the shape of what you asked for and injects it into the parameter.

It's like going to an auto parts shop to look for a part for a car - you can ask the shop owner, "Do you have a fuel door for a Jeep Patriot?" and the owner says, "Sure, I have one right here!" The owner doesn't have to tell you to wait so he can go and build one, he's got one right in the shop.

Let's do a quick example of Dependency Injection so you can get a better idea of how it works in Spring.

Demonstration of Dependency Injection

Here is what your Message class might look like so far:

import org.springframework.stereotype.Component;

@Component
public class Message impolements Serializable {

    private String message = "Demonstrating Dependency Injection!";
    private long serialVersionUID = 1L;
    
    public Message() {} 

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "Message [message=" + message + "]";
    }

}

Next, add a controller to your project and add a private data member that will eventually hold a reference to a Message instance:

private Message msg;

Make sure that when you're prompted to import Message, you import your own!

Now we'll add another annotation here: @Autowired.

@Autowired
private Message msg;

@Autowired tells Spring to go look inside the Inversion of Control Container and see if there's anything in there that will fit into this variable. Of course, Spring will find the Message instance it created when it did the component scan at application startup!

Now let's map to root with a handler method. We'll print our message object to the response body so let's add the code to do that.

@GetMapping("/")
public void index(HttpServletResponse response) {
    try {
        PrintWriter out = response.getWriter();
        out.println(msg);
        out.close();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}

Note that we haven't instantiated our Message class! Will this work? I guess we'll find out.

Go ahead and run your program, then use your browser to go to http://localhost:8080

seeing the output of our message object in the browser
Output from running the application

We are able to see our message object's toString(), even though we didn't explicitly say Message whatever = new Message() !

That's because Spring did that part for us when it saw the @Component annotation in the Message class, then it stored the instance in the inversion of control container. And recall that it also does this with the HttpServletResponse object, too!

spring stores Message in IOCC on application startup, then retrieves it in the controller
Dependency Injection in Spring

When we @Autowired the message instance variable, Spring went to the container to look for somethign that would fit into that variable, and it found the message instance it created on startup.

We'll play a bit more with @Autowired throughout the term so we can access lots of other stuff.

Exercise

This interesting exercise demonstrates dependency injection with your own classes. We're going to pretend we work at a vet clinic that has patients (animals) that are the pets of our various clients.

Now we're going to create a Repository, which is just a class to store data, in this case it's going to be a basic collection or list.

The first part of our repository is an interface. Why an interface? This follows the rules of SOLID! We will make an interface that contains abstract methods that represent the functionality of the data store we want to create.

Now we add a collection class that implements PetParent. The Client class is a concrete class that represents a client that has one or more pets (a collection of Animals!)

Now you can add an index.html to your /templates folder and add the form code I wrote for you: https://pastebin.com/Qa0uZsJW

In your controller, map the index.html to the root "/".

Now the interesting bits!

In your controller, add a private data member that's of type PetParent. Remember, PetParent is an interface, but we can use interfaces as declared types. Remember to import the correct package, when prompted.

private PetParent petParent;

Now we don't usually need these, but add a constructor method to your controller and give it a parameter of type PetParent.

public MainController(PetParent petParent) { ... }

In the constructor, call super() and then assign the parameter to your petParent data member.

Now add one more handler method that's mapped to "/adopt" (or if you changed your form's action attribute, use that. If you want to output to the response body, make it a void method. If you want to use System.out.println(), make it return a String and just have it load the index page when it's done. Add code to the handler method to do the following:

To test out the program, run it. Then enter some data into the form and submit it. You should see the pet parent name followed by the one pet you just added.

Now this is super cool: hit the Back button and go back to the form, enter a new set of data and submit it. You'll see 2 pets in your list!

the adoption form
The Adoption Form
output showing adopter name and the pet info
Output after submitting the Adoption Form
same as above but with info for 2 pets
Output after submitting Adoption Form a second time

How did this work?? We didn't instantiate anything and we didn't even need @Autowire! And we didn't even use the Client class, yet somehow we did!

Here's what happened:

If you want to explore, try commenting-out the constructor and adding @Autowired to the petParent member, instead. It should still work!

Another way you can inject is via a set-method. Why not try that, too? Keep the constructor commented out, comment out the @Autowired above the data member. Now add a setPetParent(PetParent petParent) that does the same thing as the constructor. Don't forget to add @Autowired. Still works the same!

So now you know 3 ways to do dependency injection. Using the constructor is by far the best way and what most developers prefer, but it's good to know the other two ways, also!