Spring Core module
Inversion of Control
Spring IoC (Inversion of Control) Container is the core of Spring Framework. It creates the objects, configures and assembles their dependencies, manages their entire life cycle. The Container uses Dependency Injection(DI) to manage the components that make up the application.
It gets the information about the objects from a configuration file(XML) or Java Code or Java Annotations and Java POJO class. These objects are called Beans.
There are 2 types of IoC containers:
The followings are some of the main features of Spring IoC,
Creating Object for us,
Managing our objects,
Helping our application to be configurable,
Managing dependencies
Dependency Injection
Dependency injection is a pattern we can use to implement IoC. Dependency Injection is a core principle of the Spring Framework. It's a design pattern that promotes loose coupling between objects by removing the responsibility of creating and managing dependencies from the objects themselves. Instead, the Spring container takes on this responsibility.
Types of IoC Container
BeanFactory
BeanFactory interface is the simplest container providing an advanced configuration mechanism to instantiate, configure, and manage the life cycle of beans.
BeanFactory uses Beans and their dependencies metadata to create and configure them at run-time. BeanFactory loads the bean definitions and dependency amongst the beans based on a configuration file (XML).
BeanFactory does not support Annotation-based configuration whereas ApplicationContext does.
ApplicationContext
- ApplicationContext is the sub-interface of BeanFactory. It is used when we are creating an enterprise-level application or web application. ApplicationContext is the superset of BeanFactory, whatever features provided by BeanFactory are also provided by ApplicationContext.
Note: It is because of these additional features, developers prefer to use ApplicationContext over BeanFactory. Spring – BeanFactory : It provides basic functionalities and is recommended for use for lightweight applications like mobile and applets.
ApplicationContext Implementation Classes
There are different types of Application containers provided by Spring for different requirements as listed below which later onwards are described alongside with declaration, at lastly providing an example to get through the implementation part with the pictorial aids. Containers are as follows:
AnnotationConfigApplicationContext container
AnnotationConfigWebApplicationContext
XmlWebApplicationContext
FileSystemXmlApplicationContext
ClassPathXmlApplicationContext
Beans in Spring
In Spring Framework, a bean is simply an object that is managed by the Spring IoC (Inversion of Control) container. These beans are created, configured, and assembled by the container, which takes care of their lifecycle. Beans are the fundamental building blocks of a Spring application and are typically used to represent components or services that your application requires.
In Spring, beans are created (instantiated) when they are needed, but this creation process happens as part of the IoC container's lifecycle management. To clarify:
1. Bean Creation at Startup (Singleton Beans):
- By default, beans are created at the time the application context is initialized, meaning when the Spring container is started.
2. Lazy Initialization:
- For beans that are marked with
@Lazy
, Spring will delay the creation of the bean until it is actually required, i.e., when it is first injected or used.
3. Prototype Beans:
- Beans with the prototype scope are created every time they are injected or requested. They are not created during startup but are instantiated whenever a request for them is made
Bean Lifecycle
In Spring, @PostConstruct
and @PreDestroy
are annotations used to define lifecycle methods that are executed after the bean is initialized and before the bean is destroyed, respectively. These annotations provide a way to execute custom code at specific points in a bean’s lifecycle.
1. @PostConstruct
Purpose: The
@PostConstruct
annotation is used to mark a method that should be executed after the bean has been initialized, i.e., after the bean's dependencies have been injected and the bean is fully created.When it is called: It is called once all the dependencies of the bean are injected, but before the bean is made available for use (typically just after the bean’s initialization).
Typical use cases: You might use
@PostConstruct
to perform any additional setup or initialization logic, such as opening a connection, loading configuration, or performing resource allocation that needs to occur before the bean is used.
Example:
@Component
public class MyService {
@PostConstruct
public void init() {
// Initialization logic here
System.out.println("MyService bean is initialized.");
}
}
2. @PreDestroy
Purpose: The
@PreDestroy
annotation is used to mark a method that should be executed before the bean is destroyed, i.e., when the application context is being closed or the bean is being removed from the context.When it is called: It is called just before the bean is destroyed, typically when the application context is shutting down or when the bean is being explicitly removed.
Typical use cases: You might use
@PreDestroy
to release resources, close connections, or perform any necessary cleanup operations.
Example:
@Component
public class MyService {
@PreDestroy
public void cleanup() {
// Cleanup logic here
System.out.println("MyService bean is being destroyed.");
}
}
Beans Scope in Spring core
Singleton Scope
The default scope in Spring, singleton, ensures that a single instance of a bean is created per Spring IoC container. This instance is shared across the entire container.
@Component
@Scope("singleton")
public class SingletonBean {
public SingletonBean() {
System.out.println("Singleton instance created");
}
}
Prototype Scope
In the prototype scope, a new instance of the bean is created every time it is requested from the Spring container.
@Component
@Scope("prototype")
public class PrototypeBean {
public PrototypeBean() {
System.out.println("Prototype instance created");
}
}
Request Scope
The request scope is specific to web-aware Spring applications. A new bean instance is created for each HTTP request.
@Component
@Scope("request")
public class RequestBean {
public RequestBean() {
System.out.println("Request instance created");
}
}
Session Scope
Let’s understand what is session first
Request 1: User visits the website for the first time.
- The server creates a session and sends the Session ID back to the client in the response.
Request 2: User sends another request (e.g., navigating to a different page).
The client includes the Session ID in the request.
The server identifies the session using the Session ID and retrieves the stored data for that user.
Request N: User continues interacting with the website.
- Session data is maintained until the session expires or is explicitly terminated.
How Does HTTP Work?
HTTP is Stateless: Each HTTP request sent by a client (browser) to the server is independent of any previous request. The server does not retain information about the user between requests.
Problem: In a stateless protocol, there is no built-in way to remember a user's data (like login status or a shopping cart) across requests.
Session Scope in Spring
In Spring, session scope refers to the lifecycle of a bean being tied to an HTTP session. A new instance of a session-scoped bean is created for each HTTP session, and it is shared across all requests made within that session.
Once the session expires or the user logs out, the session-scoped bean is destroyed.
Application Scope
The application scope is also specific to web applications. It creates a single bean instance for the lifecycle of a ServletContext.
@Component
@Scope("application")
public class ApplicationBean {
public ApplicationBean() {
System.out.println("Application instance created");
}
}
Stereotype Annotation
Let’s understand Annotations first:
- Spring Annotations are a form of metadata that provides data about a program. Annotations are used to provide supplemental information about a program.
Stereotype Annotation:
Spring Framework provides us with some special annotations. These annotations are used to create Spring beans automatically in the application context. @Component annotation is the main Stereotype Annotation. There are some Stereotype meta-annotations which is derived from @Component those are
@Service: We specify a class with @Service to indicate that they’re holding the business logic. Besides being used in the service layer, there isn’t any other special use for this annotation. The utility classes can be marked as Service classes.
@Repository: We specify a class with @Repository to indicate that they’re dealing with CRUD operations, usually, it’s used with DAO (Data Access Object) or Repository implementations that deal with database tables.
@Controller: We specify a class with @Controller to and responsible to indicate that a class serves as a controller, handle user requests and return the appropriate response. It is mostly used with REST Web Services.
Examples of Bean Scope and Stereotype annotation
- Main Class
package com.codefreak;
import org.springframework.context.ApplicationContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.codefreak.config.AppConfig;
import com.codefreak.service.Item;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Item samsungItem1 = (Item) context.getBean("getItem1");
// printing value
System.out.println("Item 1 Name : " + samsungItem1.getItemName());
System.out.println("Item 2 Cost : " + samsungItem1.getItemCost());
Item samsungItem2 = (Item) context.getBean("getItem1");
// check both beans, output will be true as scope is singleton
if (samsungItem1 == samsungItem2) {
System.out.println("Both beans are same, as same instance is used acrossed the entire container");
}
// checking prototype
Item redmiItem1 = (Item) context.getBean("getItem1");
Item redmiItem2 = (Item) context.getBean("getItem2");
if (redmiItem1 != redmiItem2) {
System.out.println("Both beans are not same, as there are different instace created for this bean");
}
SpringApplication.run(MainApplication.class, args);
}
}
- App Config
package com.codefreak.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import com.codefreak.service.Item;
@Configuration
public class AppConfig {
@Bean
@Scope("singleton")
public Item getItem1() {
Item item = new Item("Samsung A12", 30000);
return item;
}
@Bean
@Scope("prototype")
public Item getItem2() {
Item item = new Item("Redmi", 40000);
return item;
}
@Bean
@Scope("request")
public Item getItem3() {
Item item = new Item("IQOO", 70000);
return item;
}
}
- Item service
package com.codefreak.service;
public class Item {
private String itemName;
private int itemCost;
public Item(String itemName, int itemCost) {
this.itemName = itemName;
this.itemCost = itemCost;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public int getItemCost() {
return this.itemCost;
}
public void setItemCost(int itemCost) {
this.itemCost = itemCost;
}
}
- Item Controller
package com.codefreak.controller;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.RestController;
import com.codefreak.service.Item;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@RestController
public class ItemsController {
private final ApplicationContext context;
public ItemsController(ApplicationContext context) {
this.context = context;
}
@GetMapping(value = "/")
public ResponseEntity<?> getItem() {
Item iqooItem = (Item) context.getBean("getItem3");
System.out.println(iqooItem.hashCode());
return new ResponseEntity<>(iqooItem, HttpStatus.OK);
}
}
Dependency Injection
Setter Dependency Injection
package com.codefreak.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.codefreak.models.Car;
// Setter dependency injection
@Component("CarService")
public class CarService {
private Car car;
@Autowired
public void setCar(Car car) {
this.car = car;
}
public Car getCarDetails() {
car.setCarName("Mercedes");
car.setCarCost(400000);
return car;
}
}
Field Dependency Injection
package com.codefreak.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.codefreak.models.Car;
// field level dependency injection
@Component("CarService")
public class CarService {
@Autowired
private Car car;
public Car getCarDetails() {
car.setCarName("Mercedes");
car.setCarCost(400000);
return car;
}
}
Constructor Dependency Injection
package com.codefreak.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.codefreak.models.Car;
// constructor dependency injection
@Component("CarService")
public class CarService {
private final Car car;
public CarService(Car car) {
this.car = car;
}
public Car getCarDetails() {
car.setCarName("Mercedes");
car.setCarCost(400000);
return car;
}
}
Some config files before implementing this dependency injection
- Appconfig
package com.codefreak.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import com.codefreak.service.Item;
@Configuration
@ComponentScan(basePackages = "com.codefreak")
public class AppConfig {
}
- Main class
package com.codefreak;
import org.springframework.context.ApplicationContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.codefreak.config.AppConfig;
import com.codefreak.service.CarService;
import com.codefreak.service.Item;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// setter dependecy injection demo
CarService carService = (CarService) context.getBean("CarService", CarService.class);
System.out.println(carService.getCarDetails().getCarName());
System.out.println(carService.getCarDetails().getCarCost());
SpringApplication.run(MainApplication.class, args);
}
}
What happens when Spring Boot application starts
Bootstrapping the Application:
Spring Boot's
main
method callsSpringApplication.run()
, which starts the Spring context. This method does several things:It loads the application context.
It sets up the environment, which includes loading configuration properties, system properties, etc.
It auto-configures beans based on available libraries and settings.
Creating the Application Context:
The IoC container is essentially a bean factory that manages the lifecycle and dependencies of beans in the application. Spring Boot uses the AnnotationConfigApplicationContext or GenericWebApplicationContext (for web applications) as its default context.
The container is responsible for managing the bean definitions. Beans can be defined by annotating classes with
@Component
,@Service
,@Repository
,@Controller
, or other annotations, or by explicitly registering beans in configuration classes with@Bean
methods.
Dependency Injection:
Spring’s IoC container scans the classpath for components marked with annotations like
@Component
,@Service
,@Repository
, etc.It automatically instantiates these classes and injects any required dependencies, either via constructor injection, setter injection, or field injection, based on how the beans are configured.