In the last part, we learned about the concepts of dependency injection and inversion of control (Part 1 Link). This part is going to be a deep dive into the Spring Core framework itself and understanding various annotations.

Edit: Part 3 is now available. Link

Quick Summary Of Last Part:

In Dependency Injection, instead of a component creating its own dependencies or objects it relies on, these dependencies are provided or “injected” from the outside. This decouples components and makes it easier to replace or modify individual parts of the system without affecting the entire application.

In traditional programming, the application controls the flow by calling various functions and methods. In IoC, the control over certain aspects of the application is shifted to a container or framework, which manages the creation and lifecycles of objects and their dependencies. DI is one of the mechanisms often used to implement IoC.

But how do we do this in code?

Before we start, Go ahead and create a new Spring Boot project just like we did in the last part from https://start.spring.io/ and open it in your IDE/Editor.

What Will We Be Building?

We will be building a todo-list application, that returns a list of tasks to do when you visit the /tasks route.

The TodoList will implement a List interfact which will be used in our Spring Boot app.

The List Interface:

Let’s start by defining the list interface. Create a new file called ListInterface.java in src\main\java\com\example\project_name and add the following code.

package com.benmeehan111.Springcore;

import java.util.ArrayList;

interface ListInterface {
public ArrayList<String> getTasks();
}

The TodoList Class:

Now, Let’s make a TodoList class that uses this interface. Again, create a new file called TodoList.java and add the following code.

package com.benmeehan111.Springcore;

import java.util.ArrayList;

public class TodoList implements ListInterface {
private ArrayList<String> tasks = new ArrayList<>();

TodoList() {
this.tasks.add("Finish homework");
this.tasks.add("workout");
}

public ArrayList<String> getTasks() {
return this.tasks;
}
}

It’s just a simple class that adds two tasks to an array list and returns it.

Creating The /tasks Route:

Now, let’s use this class and interface in a API route. Create a new file called Controller.java with the following code.

package com.benmeehan111.SpringCore.Demo;

import java.util.ArrayList;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Controller {
private ListInterface tasks;

Controller() {
this.tasks = new TodoList();
}

@GetMapping("/tasks")
public ArrayList<String> GetAllTasks() {
return this.tasks.getTasks();
}
}

This code defines a Java class named Controller and annotates it with @RestController, which indicates that this class will handle HTTP requests and return the response as JSON.

It defines a method named GetAllTasks and annotates it with @GetMapping("/tasks"). This means that when a GET request is made to the endpoint "/tasks," this method will be executed.

Now, if you run your application using ./mvnw spring-boot:run and go to http://localhost:8080/tasks, you should see both of the tasks listed.

Telling Spring To Take Control:

Well, what we just did was nothing special, we just created a class and instantiated it in a constructor and called its method.

What we want to do is instead of us creating the object of the class, we want Spring to do it instead.

To do this, we will go to our TodoList class and add the following

import org.springframework.stereotype.Component;

@Component

This will tell Spring to take control of creating and deleting instances of the TodoList class.

We will also modify our controller constructor to look like this

import org.springframework.beans.factory.annotation.Autowired;   
...

@Autowired
Controller(ListInterface l) {
this.tasks = l;
}

This will tell spring to automatically inject the object of TodoList class (which Spring took control of) into this constructor.

Now, the output of the /tasks route will not change but, we have just demonstrated dependency injection and inversion of control.

What Are Qualifiers?

Let’s say we have an another class like TodoList called ShoppingList which also implements the List interface. How will Spring know which one to inject into the constructor?

package com.benmeehan111.SpringCore.Demo;

import java.util.ArrayList;

import org.springframework.stereotype.Component;

@Component
public class ShoppingList implements ListInterface {
private ArrayList<String> tasks = new ArrayList<>();

ShoppingList() {
this.tasks.add("Vegetables");
this.tasks.add("Flour");
}

public ArrayList<String> getTasks() {
return this.tasks;
}
}

We have two options. The first way is to add an another annotation called as @Primary to one of our classes.

By adding the primary annotation, Spring will give priority to ShoppingList over TodoList class.

Note: There can be only one primary for an interface.

The alternative way is to use the @Qualifier annotation.

@Autowired
Controller(@Qualifier("shoppingList") ListInterface l) {
this.tasks = l;
}

You need to provide the ‘bean ID’ to the qualifier. It is simply the name of the class but with the first letter in lower case.

Reminder: Qualifier annotation takes precedence over Primary.

What Happened Internally?

So, what went on internally was Spring created new instances of Shopping List and TodoList classes and injected them into our Controller class constructor where we have @Autowired. This is called Constructor Injection.

Where Else Can You Inject?

We just injected an object into a constructor. But there are other places you can inject a object as well using the same @Autowired annotation.

Setter Methods:

If you have setter methods that need to get an object as an argument, you can use autowired. This is known as Setter Injection. For example,

@Autowired
public void setTasks(ListInterface tasks) {
this.tasks = tasks;
}

This will again create an object of class which implements ListInterface and inject it into our code.

Fields:

You can also use @Autowired in individual fields. This is known as Field Injection.

@Autowired
private ListInterface tasks;
Note: Field Injection makes testing hard and is no longer recommended to use.

Lazy Initialization:

You might have noticed in the above diagram that Spring created new instances of both the TodoList and ShoppingList classes even though only ShoppingList was used in our Controller class.

This is because Spring by default automatically creates a new instance of any class marked with @Component annotation at the start. To prevent this behaviour, we can add @Lazy annotation to our class for which we don’t want the instance to be created unless it is used somewhere in the code.

For example, in our TodoList class,

import org.springframework.context.annotation.Lazy;

@Component
@Lazy
public class TodoList implements ListInterface {
... rest of the code
}

This is cause the instance of the TodoList class to be not created at the start if its not used.

Word of advice, do not go ahead an start adding @Lazy to all the components that are not used. Pre-mature optimisation will make your code so hard to debug later.

Bean Scope:

So, far we have been learning about objects managed by Spring itself. The correct word for these is Bean.

A bean is simply a Java object that is created, maintained and deleted by the Spring container instead of your app at runtime.

“bean scope” refers to the lifecycle and visibility of a bean. The choice of bean scope determines how and when the container creates and manages instances of the bean.

Singleton (default scope):

Spring container creates only one instance of the bean for the entire application context.

@Autowired
Controller(@Qualifier("shoppingList") ListInterface l, @Qualifier("shoppingList") ListInterface m) {
this.tasks = l;
this.newTasks = m;
}

In the above code, ‘l’ and ‘m’ point to the same object in the memory. You can even verify this by adding a print line to the ShoppingList class constructor.

It’s suitable for stateless and thread-safe beans, like service classes, that can be shared across the application. But sometimes, you might want to override this behaviour. In this case, we would use Prototype scope instead.

Prototype Scope:

In the prototype scope, a new instance of the bean is created each time it is requested from the container. These instances are not shared and are independent of each other. It’s useful for stateful beans that should have a unique instance per request.

You make a bean Prototype scope by adding the following annotation to a class

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

Other Scopes:

There are other scopes that we will be going over later in the course when we explore APIs and REST services.

  • Request Scope
  • Session Scope
  • Application Scope
  • WebSocket Scope
  • Custom Scopes

Bean Lifecycle:

In Spring, the lifecycle of a bean typically involves the following stages:

  • Instantiation: The Spring container creates a bean instance.
  • Populating Properties: Spring sets the properties and dependencies of the bean.
  • Initialization: You can perform custom initialization tasks during this phase.
  • Bean in Use: The bean is now ready for use.
  • Destruction: You can perform custom cleanup tasks before the bean is destroyed.
@Component
public class MyBean {

@PostConstruct
public void customInit() {
// Custom initialization logic
}

@PreDestroy
public void customDestroy() {
// Custom destruction logic
}
}

You can use the @PostConstruct and @PreDestroy annotations to implement custom initialization and destruction methods.

Prototype Bean Lifecycle:

Unlike singleton beans, prototype beans are not entirely managed by the Spring container. Spring only manages the instantiation and property injection phases for prototype beans. Here’s the lifecycle for a prototype bean:

  1. Instantiation: The Spring container creates a new prototype bean instance whenever it is requested.
  2. Populating Properties: Spring injects any required properties or dependencies.
  3. Bean in Use: The prototype bean is now available for use.
  4. Destruction: It is the responsibility of the application to destroy the prototype bean when it’s no longer needed and to release any resources acquired by the bean.

Because Spring does not manage prototype bean destruction, it’s essential to manually clean up any resources, such as closing database connections or file handles, when you’re done using a prototype bean.

@Bean and @Component Annotations:

What if we have a class or a third party library class that is not a Bean? i.e, it is not annotated with @Component ? How do we still ask Spring to manage this class?

This is where the @Bean annotation comes in. It is used with on a method instead of a class. It basically tells Spring, “Hey Spring, take control of whatever object this function returns.”

It bascially says that the method is responsible for instantiating, configuring, and initializing a new object.

On the other hand, when you annotate a class with @Configuration, you indicate that this class is a source of bean definitions for the Spring IoC container. You are basically saying, “Okay, this class is going to have methods that will be annotated with @Bean annotation.”

These classes are often referred to as “Java configuration classes” and are an alternative to XML-based or annotation-based configuration. They can define beans using @Bean methods and provide a structured, programmatic way to configure your Spring application.

Java configuration classes annotated with @Configuration allow you to define inter-bean dependencies by calling other @Bean methods within the same class. This makes it easy to establish relationships between beans and build a complex configuration in a structured and programmatic way. Dependencies between beans are resolved by the Spring container, ensuring that beans are created and wired together appropriately.

Go ahead and create a new file in our project called ThirdParty.java with the following code

package com.benmeehan111.SpringCore.Demo;

public class ThirdParty {
public void performAction() {
System.out.println("Third-party service is performing an action.");
}
}

It’s just a simple class simulating a third party library.

Let’s use it in our Controller class

package com.benmeehan111.SpringCore.Demo;

import java.util.ArrayList;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Controller {
private ListInterface tasks;

private ThirdParty thirdParty;

@Autowired
Controller(@Qualifier("shoppingList") ListInterface l, ThirdParty thirdParty) {
this.tasks = l;
this.thirdParty = thirdParty; // setting thirdparty
}

@GetMapping("/tasks")
public ArrayList<String> GetAllTasks() {
thirdParty.performAction(); // using thirdparty
return this.tasks.getTasks();
}
}

Now, if you run this Spring will throw an error like

Parameter 1 of constructor in com.benmeehan111.SpringCore.Demo.Controller required a bean of type ‘com.benmeehan111.SpringCore.Demo.ThirdParty’ that could not be found.

This is because we did not annotate ThirdParty with @Component . So Spring does not know what to inject into the constructor.

Let’s create an another file called Configure.java and add the following code

package com.benmeehan111.SpringCore.Demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Configure {

@Bean
public ThirdParty thirdPartyService() {
return new ThirdParty();
}

}

Now, the application should run without any errors.

We have just turned a ThirdParty class into a bean managed by Spring itself. We will see plenty of real-world examples of this once we start using databases and external services.

Wrapping Up:

That concludes our deep dive into Spring core and some of the annotations. It was a lot and at this point you might be probably thinking, “Creating and deleting objects? That’s it? I would have done this without any framework”. but, this concept of Spring taking control is fundamental in building a lot of other things that will make our development work a lot easier.

In the next part, we will go over databases and how to access them in a spring boot application.

“Time is a great teacher, but unfortunately it kills all its pupils.” — Hector Berlioz