Spring Boot Development Essentials
Spring Foundations
The Spring Way of Building
In standard Java, you are in complete control. When an object needs another object to do its job, you create it. You might write new Car() or new DatabaseConnection(). You manage the entire lifecycle of these objects. This approach is direct, but it can lead to tightly coupled code. If MyService creates its own MyRepository, they are stuck together. Testing MyService in isolation becomes difficult because it always brings MyRepository along with it.
The Spring Framework introduces a different philosophy: Inversion of Control. Instead of your objects controlling their dependencies, a framework container takes over that responsibility. You declare what an object needs, and the framework provides it at runtime.
This is the core of IoC: Don't call us, we'll call you. Your components don't actively fetch dependencies; the Spring container 'calls' them with the dependencies they need.
Dependency Injection and the Container
Inversion of Control is the principle, and Dependency Injection (DI) is the design pattern that implements it. With DI, dependencies are passed into an object from an external source rather than being created internally. The external source in Spring is the IoC container, which is represented by the ApplicationContext interface.
Think of the ApplicationContext as a sophisticated factory and registry. At startup, it scans your application, creates instances of your components, and wires them together based on their declared dependencies. These framework-managed objects are called 'Beans'.
Spring supports three main types of dependency injection:
- Constructor Injection: Dependencies are provided as arguments to the class constructor. This is the preferred method. It ensures an object is created in a valid state with all its required dependencies present. It also makes dependencies explicit and easy to spot.
- Setter Injection: The container calls setter methods on your bean after it has been instantiated with a no-argument constructor.
- Field Injection: Dependencies are injected directly into the fields of a class, often using the
@Autowiredannotation. While concise, this method is generally discouraged for required dependencies because it can obscure the object's true needs and makes testing harder.
Finding Your Components
How does the ApplicationContext know which classes to manage? It starts with component scanning. When you launch a Spring Boot application, the @SpringBootApplication annotation kicks off this process.
This single annotation is actually a combination of three important ones:
@Configuration: Tags the class as a source of bean definitions.@EnableAutoConfiguration: Tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings.@ComponentScan: Tells Spring to scan the current package and all its sub-packages for other components, configurations, and services.
package com.example.myapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // Scans com.example.myapp and its children
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
For the scanner to recognize your classes as Spring Beans, you must mark them with a stereotype annotation. The most basic one is @Component. However, Spring provides more specific annotations for different layers of an application.
| Annotation | Purpose |
|---|---|
@Component | A generic stereotype for any Spring-managed component. |
@Service | Indicates that a class holds business logic. Sits in the service layer. |
@Repository | Used for classes that access a database. It enables special exception translation. |
@Controller | Marks a class that handles web requests. Used in the presentation layer. |
Using these more specific annotations helps both the developer and the framework understand the role of a particular class. A class marked with @Service is clearly intended to perform business operations, while one marked with @Repository deals with data persistence.
Wiring It All Together
Let's see a practical example of DI. Imagine we have a ProductService that needs to fetch products from a database via a ProductRepository.
// Data access layer
@Repository
public class ProductRepository {
// ... database access logic here ...
public Product findById(Long id) {
// Pretend this fetches from a DB
return new Product(id, "Sample Product");
}
}
// Business logic layer
@Service
public class ProductService {
private final ProductRepository productRepository;
// Constructor Injection: Spring will provide a ProductRepository bean.
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public Product getProduct(Long id) {
return productRepository.findById(id);
}
}
Notice that ProductService does not write new ProductRepository(). It simply declares a ProductRepository as a dependency in its constructor. At startup, the Spring ApplicationContext finds the @Repository bean and the @Service bean. It sees that ProductService requires a ProductRepository and automatically provides the instance.
This container-managed approach decouples your components, making your application more modular, easier to test, and simpler to maintain. You can focus on business logic instead of the plumbing of object creation and wiring.
What is the core principle behind the Spring Framework that dictates how objects and their dependencies are managed?
In Spring, the objects managed by the IoC container are called ______.
This shift from manual object management to a container-driven approach is the essential first step in thinking the 'Spring way'. It lays the foundation for all the powerful features the framework offers.