Mastering Spring Framework:1.0

Introduction
Problems with Traditional Java
Tight Coupling Explained
Why Spring Was Needed
What is a Bean?
IoC Container (ApplicationContext)
Dependency Injection (DI)
Types of Dependency Injection
Practical Example (Payment System)
Multiple Beans Problem & Solutions
Why Spring Is Better Than
newConclusion
What is Spring
- Spring is one of the most widely used Java frameworks for building high-quality, scalable enterprise applications.
Before understanding why Spring became so popular, let’s first look at the problems it was created to solve.
class OrderService {
private PaymentService paymentService = new UpiPaymentService();
}
What's wrong with this code?
You might already spot the problem here.
Problems with above code:
1. Tight Coupling
If you want to switch to WalletPaymentService or any other payment service you must edit and recompile OrderService.
This means:
No flexibility
Hard to replace implementations
Hard to extend
2.Hard to test
Testing requires replacing the real implementation with:
mock
dummy
fake
But since the object is created inside the class using new, it's nearly impossible to inject a mock.
3.Scattered Object Creation
Every class creates its own objects.
Object lifecycle & configuration logic ends up being scattered:
hard to maintain
hard to scale
hard to understand
And Tight Coupling Was Just the Beginning…
Inefficient JDBC & ORM integration
Scattered configurations
Deployment complexity
Verbose & repetitive code
Manual dependency wiring everywhere
Hard-to-test code
And that’s where Spring entered the scene
Spring Framework to the Rescue
Spring introduced:
Beans
Inversion of Control (IoC)
Dependency Injection (DI)
These concepts removed:
boilerplate
tight coupling
manual dependency management
Spring made Java development clean, modular, and testable.
what are Beans :
A Spring Bean is simply a Java object that is: Instantiated by Spring Configured by Spring Managed throughout its lifecycle by Spring Beans
Beans form the backbone of a Spring application. They are the core building blocks that Spring wires together to create the application.
In traditional java we do object creation like PaymentService Psobj = new UPIPaymentService();
like this but in spring we have to create it like this neither we need to use new keyword instead of this we use spring annotation(@component : we will see this in detail ) to tell that to compiler that this is the class and the management of the object of this class is managed by spring
so here’s the first doubt come :
whats the difference between creating object manually like Ps ps = new Ps() and via beans and if beans manages it how it manages and why the need of it
Imagine toys and a toy-box
You (the program) want a toy car (an object
Ps).One way: you walk to the shelf and say “I want a toy car” and make it yourself:
Ps ps = new Ps();— you built it, you own it, you decide everything about it.Another way: you ask Mom (Spring) to give you a toy car from the toy-box. Mom keeps all toys organized, gives you the right toy when you ask, and can swap a faster toy car later without you changing anything.
In Spring-land, those toys Mom gives are called beans.
Injecting a bean = Mom handing you the toy instead of you creating it yourself.
Why bother? — Short, kid-friendly reasons
Less hard work for you: you don’t need to build toys everywhere (
new Ps()everywhere).Swap toys easily: Mom can replace the toy car with a toy truck and you don’t change your code.
Share toys: Mom can give the same toy to everyone (singleton) or make a new one each time (prototype).
Easier to test: when you want to test, Mom can give a pretend toy (a mock) instead of the real one.
Mom handles rules: Mom can check the toy before giving (initialization), or take it back when done (destruction).
In technical terms, a Spring Bean is a Java object whose lifecycle is fully managed by the Spring IoC container. Spring is responsible for creating, configuring, injecting, and destroying these objects.
How does Spring know which class to make a Bean?
1.Using @Component (and Stereotype annotations)
1.@Bean
2. @component // Generic bean
3.@Service //buisness logic
4.@Repository //database access
5.@controller // web layer
6.@RestController //web endpoints
@Component
public class PaymentService {
}
2.Using @Bean in a @Configuration Class
@Configuration
public class AppConfig {
@Bean
public PaymentService paymentService() {
return new PaymentService();
}
}
ex-
@Component
public class OrderService {//<-- BEAN
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) { // injected automatically
this.paymentService = paymentService;
}
}
You never write:
new PaymentService();
Spring does everything.
Bean Lifecycle :

Lifecyble methods:
The @PostConstruct annotation is used to mark a method that should be invoked immediately after a bean has been constructed and all of its dependencies have been injected.
The @PreDestroy annotation is used to mark a method that should be invoked just before a bean is destroyed by the container. This method can perform any necessary cleanup or resource releasing tasks.

IOC Container aka Application Context
At the center of everything is the ApplicationContext — Spring’s IoC (Inversion of Control) container.
Think of it as a big organized shelf that stores objects.
When your app starts:
Spring scans packages
Finds classes with
@Component,@Service, etc.Creates objects (Beans)
Stores them in the Spring Container
The container is like a box full of ready-to-use objects.
Dependency Injection :How do you use a Bean?
Dependancy Injection is a design pattern in which an object receives its dependencies from an external source (Spring), instead of creating them itself.
This leads to loose coupling, better testability, and cleaner code.
@Autowired
private PaymentService paymentService;
Spring looks in its container:
Finds the
PaymentServiceBeanInjects it here
You did NOT use new.
Quick Analogy
Imagine you go to a restaurant:
You don’t cook (don’t use new)
The chef (Spring) prepares everything
You just order what you need (use
@Autowired)
The chef-managed food = Beans
The kitchen = Spring Container
1. BEAN = The Object
A bean in Spring is simply:
An object created and managed by Spring.
Examples:
UPIPaymentobjectWalletPaymentobjectPaymentServiceobjectDatabaseServiceobject
If a class is marked with @Component, @Service, @Repository, or created using @Bean,
Spring adds it to its IoC container (big object box).
Think of it as:
Bean = Toy kept in the toy-box.
2. DI (Dependency Injection) = Giving That Object to Another Class
DI means:
👉 Spring gives the required beans TO YOUR CLASS automatically.
You don’t create the object; Spring gives it to you.
Example:
public PaymentService(PaymentInterface payment) {
this.payment = payment; // this is DI
}
Spring looks in its container and says:
“Oh, this class needs a PaymentInterface bean.
I already have UPIPayment or WalletPayment.
Let me inject it.”
Think of it as:
DI = Mom giving you the toy from the toy-box.
Injecting beans means giving one class the object of another class that Spring has already created.
Spring does this using Dependency Injection (DI).
There are 3 ways to inject beans:
1. Constructor Injection (recommended)
2.Field Injection
3.Setter Injection
Let’s understand each one clearly
1. Constructor Injection (BEST way)
Why best?
Immutable
Easy for testing
Avoids null issues
Works smoothly with @Autowired (optional)
Example:
@Service
public class OrderService {
private final PaymentService paymentService;
@Autowired // not required in latest Spring versions
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
Here:
Spring creates PaymentService bean
Spring creates OrderService bean
Spring injects PaymentService into OrderService via constructor
2. Field Injection (not recommended but simplest)
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
}
This works, but:
Harder to test
Not good for clean design
Hides dependencies
3. Setter Injection
Used when the dependency is optional or changeable.
@Service
public class OrderService {
private PaymentService paymentService;
@Autowired
public void setPaymentService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
As we previously seen on creating objects manually we faced problems like tight coupling , hard to test code , scatterd object creation
why to use DI
As you have OrderService class in that you wrote PaymentService Psobj = new UPIPaymentService(); now it you want other payments like wallet payment thats where loose coupling comes into the picture , with loose coupling u get independant testing , application is also much more scalable,you can integrate more and more features without changing or modyfying the previous code that much we will see below how to acheive that using beans and dependancy injection
enough theory lets learn pratically now using the same example
1. Main Application
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
private final PaymentService paymentService;
public DemoApplication(PaymentService paymentService) {
this.paymentService = paymentService;
}
@Override
public void run(String... args) {
paymentService.pay();
}
}
2. PaymentInterface
package com.example.demo;
public interface PaymentInterface {
void pay();
}
3. UPIPayment Implementation
package com.example.demo;
import org.springframework.stereotype.Component;
@Component // <-- Spring creates an object of this class automatically
public class UPIPayment implements PaymentInterface {
@Override
public void pay() {
System.out.println("Paying through UPI...");
}
}
4. PaymentService (Dependency is Injected)
package com.example.demo;
import org.springframework.stereotype.Component;
@Component // Spring creates PaymentService object too
public class PaymentService {
private final PaymentInterface payment;
// Constructor Injection
public PaymentService(PaymentInterface payment) {
this.payment = payment;
}
public void doPayment() {
payment.pay();
}
}
NOW — How does Spring actually do all this?
1.Spring scans your project
Because of:
@SpringBootApplication
Spring searches the folder for:
@Component@Service@Repository@Controller
and identifies classes it should manage as beans.
Spring says:
“Oh! I found
UPIPaymentandPaymentService. I will create objects for both.”
Spring creates an IoC Container
IoC Container (Bean Box)
├── UPIPayment object
└── PaymentService object
Spring looks for dependencies
When creating PaymentService, Spring sees:
public PaymentService(PaymentInterface payment)
Spring asks:
“Do I have a bean that implements
PaymentInterface?”
Yes → UPIPayment
So Spring:
payment = new UPIPayment();
service = new PaymentService(payment);
and stores them.
When app runs, Spring hands objects to you
You ask for PaymentService
Spring gives you the ready object:
With all dependencies filled
Already created
Ready to use
You never create anything manually
WHY THIS IS BETTER THAN new PaymentService() ?
1. Loose Coupling
If tomorrow you change:
@Component
public class CardPayment implements PaymentInterface {}
Spring will switch implementations with zero code change.
2. Automatic Wiring
You don’t write new anywhere.
3. Test friendly
You can easily write:
new PaymentService(new FakePayment());
4. Lifecycle management
Spring can:
initialize beans
destroy beans
keep one instance
create new on each request
5. Perfect for big apps
In real apps you have:
DB services
Logging
Payment Gateways
Email Senders
Security
All these need dependency injection.
Now if there are multiple beans and how spring decides which to inject then like we have UPIPaymentService and CardPaymentService how spring chooses which to inject for that
If you define two beans:
@Component
class UpiPaymentService implements PaymentInterface {}
@Component
class CardPaymentService implements PaymentInterface {}
Spring finds two beans implementing the same interface and cannot decide which one to inject, resulting in a NoUniqueBeanDefinitionException.
You must tell Spring which one you want:
Option 1: @Primary
@Primary
@Component
class WalletPaymentService implements PaymentInterface {}
Now Spring injects WalletPaymentService everywhere by default.
Option 2: @Qualifier
@Component
class PaymentService {
private final PaymentInterface payment;
public PaymentService(@Qualifier("walletPaymentService") PaymentInterface payment) {
this.payment = payment;
}
}
Now Spring knows exactly which bean you want.
Now still if you want fully spring to decide we can add
@ConditionalOnProperty(name="Payment.type" havingvalue="UPI")
and inside application.properties you need to mention
Payment.type=UPI
so now we dont have to change the code to change paymentservice we just have to change in properties or in Env variables
Conclusion
Spring solves the fundamental problems of traditional Java development by introducing Beans, IoC, and Dependency Injection. By removing tight coupling and manual object creation, Spring enables scalable, testable, and maintainable applications.
This is why Spring remains the backbone of modern Java backend development.