Mastering SpringBoot Concepts: A Deep dive into Core Concepts

“Nothing in life is to be feared, it is only to be understood.” ~Marie Curie

Watching a tutorial on Spring Boot, I kept hearing the word “container” mentioned repeatedly. While I was somewhat familiar with the term, my understanding was vague. So, I decided to dig deeper to gain a solid grasp of these concepts. What I found was a world of interconnected ideas, each playing a crucial role in how Spring Boot applications function. Let’s break them down in a structured way to help you become a top-tier Spring Boot developer!

How a Spring Boot Application Works

A Spring Boot application simplifies Java development by following an opinionated approach with minimal configuration. When the application runs, several key processes take place:

  1. Application Startup
  • The SpringApplication.run() method initializes the application context and starts the embedded server (Tomcat, Jetty, or Undertow).

2. Spring Container Initialization

  • The Spring Container, managed by the ApplicationContext, is created to handle the lifecycle of beans (components, services, repositories, and controllers).

  • It scans for beans, registers them, and ensures proper dependency injection.

3. Dependency Injection (DI)

  • Spring Boot automatically injects dependencies, allowing different components to interact without being tightly coupled.

4. Auto-Configuration

  • Spring Boot detects and configures necessary components automatically, reducing the need for manual setup.

5. Request Handling (for Web Applications)

  • If the application is a web service, it listens for incoming HTTP requests.

  • Requests are processed through the MVC layers, and responses are sent back to the client.

With these processes working seamlessly together, Spring Boot ensures an efficient, scalable, and easy-to-manage development experience, making it widely used for modern Java applications.

Understanding Dependency Injection vs. Autowiring

Dependency Injection (DI)

Dependency Injection is a design pattern where an object receives its dependencies from an external source rather than creating them itself. In Spring Boot, DI is essential for creating loosely coupled components and improving testability.

Autowiring

Autowiring is one of the ways to achieve dependency injection in Spring Boot. It allows the framework to automatically resolve and inject dependencies based on type or qualifiers, reducing manual bean management.

So, while all autowiring is dependency injection, not all dependency injection is done through autowiring. Spring provides multiple ways to achieve dependency injection, and autowiring is just one of them.

Ways to Achieve Dependency Injection (and Autowiring)

There are three primary ways to achieve dependency injection in Spring Boot:

  1. Constructor Injection (Recommended)
@Service
public class UserService {
    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}
  • Ensures immutability and easy testing.

  • Recommended for required dependencies.

2. Setter Injection

@Service
public class UserService {
    private UserRepository userRepository;

    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}
  • Used when dependencies are optional or configurable.

  • Allows modifying dependencies at runtime.

3. Field Injection (Not Recommended)

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
}
  • Harder to test and maintain.

Preferred Approach: Constructor injection is generally preferred because it ensures required dependencies are provided and makes unit testing easier.

Loose Coupling

Loose coupling is a software design principle where components are minimally dependent on each other. In Spring Boot, dependency injection enables loose coupling by ensuring that objects do not create their dependencies but instead receive them from the Spring Container.

For example, instead of this tightly coupled code:

public class MyService {
    private MyRepository myRepository = new MyRepository();
}

We use DI to achieve loose coupling:

@Service
public class MyService {
    private final MyRepository myRepository;

    @Autowired
    public MyService(MyRepository myRepository) {
        this.myRepository = myRepository;
    }
}

This way, if we want to switch MyRepository with another implementation, we can do so easily without modifying MyService.

Spring MVC Layers

Spring Boot applications often follow the Spring MVC architecture, which divides the application into multiple layers for better separation of concerns and maintainability. These layers are:

1. Model Layer

The model layer represents the application’s data. This includes the entities or POJOs (Plain Old Java Objects) that map to database tables.

Example:

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
}

This class defines a User entity that will be stored in the database.

2. Repository Layer

The repository layer is responsible for interacting with the database. It uses Spring Data JPA to abstract away SQL queries and CRUD operations.

Example:

@Repository
public interface UserRepository extends JpaRepository<User, Long> {}

With just one line of code, this interface provides full database operations for the User entity.

3. Service Layer

The service layer contains business logic and acts as a bridge between the repository and controller layers. This ensures a separation of concerns, making the application modular.

Example:

@Service
public class UserService {
    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
}

Here, the UserService fetches user data from the database and passes it to the controller.

4. Controller Layer

The controller layer handles incoming HTTP requests and interacts with the service layer to process data before returning responses to the client.

Example:

@RestController
@RequestMapping("/users")
public class UserController {
    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }
    @GetMapping
    public List<User> getUsers() {
        return userService.getAllUsers();
    }
}

The UserController exposes an API endpoint (/users) to retrieve all users from the database.

Autowiring: @Qualifier and @Primary

When multiple beans of the same type exist, Spring needs to know which one to inject. You can specify which bean to use with:

  • @Qualifier: Used to specify a particular bean when multiple beans exist.
@Service
public class MyService {
    private final MyRepository myRepository;

    @Autowired
    public MyService(@Qualifier("mySpecialRepository") MyRepository myRepository) {
        this.myRepository = myRepository;
    }
}
  • @Primary: Used to mark a default bean when multiple beans of the same type exist.
@Primary
@Repository
public class DefaultRepository implements MyRepository {}
  • If no @Qualifier is specified, the @Primary bean is used by default.

Lombok in Spring Boot

Lombok is a Java library that helps eliminate boilerplate code like getters, setters, and constructors using annotations.

  • @Getter and @Setter – Generates getters and setters.

  • @NoArgsConstructor – Creates a no-args constructor.

  • @AllArgsConstructor – Creates a constructor with all fields.

  • @Data – Combines @Getter, @Setter, @ToString, and @EqualsAndHashCode.

import lombok.Data;

@Data
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
}

That’s it for now guys, Follow me for more.