A comprehensive demonstration of Spring Framework's evolution from XML-based to modern annotation-based configuration. This repository showcases three different approaches to Inversion of Control (IoC) and Dependency Injection (DI), making it an excellent learning resource for understanding Spring's configuration methods.
- Overview
- Key Concepts Explained
- Three Approaches Demonstrated
- Getting Started
- Project Structure
- Approach Comparison
- Code Deep Dive
- Running the Applications
- Learning Outcomes
This project demonstrates the evolution of Spring IoC configuration through three distinct approaches:
- Pure XML Configuration - Traditional Spring approach (pre-Spring 3.0)
- Hybrid XML + Annotations - Transition phase combining both methods
- Pure Java Configuration - Modern Spring approach (Spring 3.0+)
Each approach achieves the same goal: letting Spring manage object creation and dependency injection, but with different configuration styles.
What is IoC?
- Traditional Programming: Your code controls object creation using
newkeyword - IoC Pattern: Framework (Spring) controls object creation and lifecycle
- Benefit: Loose coupling, easier testing, centralized configuration
Example:
// Without IoC
Customer customer = new Customer();
FullName fullName = new FullName();
customer.setFullName(fullName); // Manual wiring
// With IoC
Customer customer = context.getBean(Customer.class); // Spring creates and wires everythingWhat is DI?
- A design pattern where objects receive their dependencies from external sources
- Spring "injects" dependencies rather than objects creating them
- Makes code more testable and maintainable
Types Demonstrated:
- Constructor Injection - Dependencies passed through constructor (recommended)
- Setter Injection - Dependencies set through setter methods
- Field Injection - Dependencies injected directly into fields using
@Autowired
What is ApplicationContext?
- Advanced IoC container (extends BeanFactory)
- Manages complete lifecycle of beans
- Provides additional enterprise features (event propagation, declarative mechanisms)
Two implementations shown:
ClassPathXmlApplicationContext- Loads configuration from XML filesAnnotationConfigApplicationContext- Loads configuration from Java classes
Traditional approach where all beans are defined in XML files.
ApplicationContext context = new ClassPathXmlApplicationContext("banking-app.xml");
Customer customer = context.getBean("customer", Customer.class);Configuration: banking-app.xml
<bean id="customer" class="sh.surge.kunal.banking.models.Customer">
<property name="accountNo" value="6855858"/>
<property name="email" value="[email protected]"/>
</bean>Use Case: Legacy applications, XML-heavy environments
Transition approach combining XML for container setup with annotations for bean definition.
ApplicationContext context = new ClassPathXmlApplicationContext("banking-app.xml");
UserDataDao userDataDao = context.getBean(UserDataDao.class);XML enables component scanning:
<context:component-scan base-package="sh.surge.kunal.banking.*"/>Classes use annotations:
@Component
@PropertySource("classpath:application.properties")
public class UserDataDao {
@Autowired
private RestClient restClient;
@Value("${userUrl}")
private String userDataUrl;
}Use Case: Migrating from XML to annotations, mixed codebases
Modern approach with zero XML - everything configured using Java annotations.
ApplicationContext context = new AnnotationConfigApplicationContext(TransactionConfigurer.class);
TransactionService service = context.getBean(TransactionService.class);Configuration class:
@Configuration
@ComponentScan(basePackages = "sh.surge.kunal.banking")
@Import({DirectDebitConfigurer.class, ExternalDebitConfigurer.class})
public class TransactionConfigurer {
}Use Case: Modern Spring applications, Spring Boot (default approach)
- Java: JDK 8 or higher
- IDE: IntelliJ IDEA (Community or Ultimate)
- Build Tool: Maven
- Git: For cloning the repository
# Clone the repository
git clone https://github.com/Kunal70616c/spring-modern-ioc.git
# Navigate to project directory
cd spring-modern-iocMethod 1: From Welcome Screen
- Open IntelliJ IDEA
- Click "Open"
- Navigate to the cloned
spring-modern-iocfolder - Select the project root (containing
pom.xml) - Click "OK"
- IntelliJ will auto-detect Maven and import dependencies
Method 2: From File Menu
- File → Open
- Select the project folder
- Click "OK"
- Wait for Maven to sync (check progress bar at bottom)
- File → Project Structure (
Ctrl+Alt+Shift+S/Cmd+;on Mac) - Project Settings → Project
- Set Project SDK to Java 8+
- Set Language level to 8+
- Click "Apply" and "OK"
- Open Maven tab (right sidebar)
- Click Reload icon (circular arrows)
- Wait for dependency download to complete
Ensure these files exist:
src/main/resources/banking-app.xmlsrc/main/resources/application.properties
If the resources folder isn't marked as Resources Root:
- Right-click
src/main/resources - Mark Directory as → Resources Root
Your pom.xml should include:
<dependencies>
<!-- Spring Context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.x</version>
</dependency>
<!-- Spring WebFlux (for RestClient) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<version>6.1.x</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<!-- JavaFaker -->
<dependency>
<groupId>com.github.javafaker</groupId>
<artifactId>javafaker</artifactId>
<version>1.0.2</version>
</dependency>
<!-- JSON -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20231013</version>
</dependency>
</dependencies>spring-modern-ioc/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── sh/surge/kunal/banking/
│ │ │ ├── configurations/ # Configuration classes
│ │ │ │ ├── DirectDebitConfigurer.java
│ │ │ │ ├── ExternalDebitConfigurer.java
│ │ │ │ ├── RestConfigurer.java
│ │ │ │ └── TransactionConfigurer.java
│ │ │ ├── facades/ # Facade pattern interfaces
│ │ │ │ └── Account.java
│ │ │ ├── models/ # Domain models
│ │ │ │ ├── AbstractAccount.java
│ │ │ │ ├── Address.java
│ │ │ │ ├── CompanyAddress.java
│ │ │ │ ├── CompanyType.java (Enum)
│ │ │ │ ├── Corporate.java
│ │ │ │ ├── CurrentAccount.java
│ │ │ │ ├── Customer.java
│ │ │ │ ├── DirectDebitTransaction.java
│ │ │ │ ├── ExternalDebitTransaction.java
│ │ │ │ ├── FullName.java
│ │ │ │ ├── Gender.java (Enum)
│ │ │ │ ├── HomeAddress.java
│ │ │ │ ├── Individual.java
│ │ │ │ ├── SavingsAccount.java
│ │ │ │ └── Transaction.java (Abstract)
│ │ │ ├── services/ # Service layer
│ │ │ │ └── TransactionService.java
│ │ │ └── utils/ # Application entry points
│ │ │ ├── CustomerApp.java # Pure XML approach
│ │ │ ├── TransactionApp.java # Pure Java Config approach
│ │ │ ├── UserApp.java # Hybrid approach
│ │ │ └── UserDataDao.java # REST client demo
│ │ └── resources/
│ │ ├── application.properties # External configuration
│ │ └── banking-app.xml # XML bean definitions
│ └── test/
│ └── java/
├── pom.xml # Maven configuration
└── README.md
| Aspect | Pure XML | Hybrid (XML + Annotations) | Pure Java Config |
|---|---|---|---|
| Container Initialization | ClassPathXmlApplicationContext |
ClassPathXmlApplicationContext |
AnnotationConfigApplicationContext |
| Bean Definition | <bean> tags in XML |
@Component, @Service annotations |
@Bean methods + @Component |
| Configuration Location | banking-app.xml |
XML + annotated classes | @Configuration classes |
| Dependency Injection | <property> / <constructor-arg> |
@Autowired, @Qualifier |
@Autowired, Constructor injection |
| Component Scanning | Manual in XML | <context:component-scan> |
@ComponentScan |
| External Properties | Not shown | @PropertySource + @Value |
@PropertySource + @Value |
| Refactoring Support | ❌ Poor (no IDE support) | ✅ Excellent (type-safe) | |
| Verbosity | High (lots of XML) | Medium | Low (concise) |
| Learning Curve | Easy to start | Medium | Steeper initially |
| Modern Usage | Legacy only | Migration phase | ✅ Recommended |
| Example App | CustomerApp |
UserApp + UserDataDao |
TransactionApp |
public class CustomerApp {
public static void main(String[] args) {
Faker faker = new Faker();
Logger logger = Logger.getLogger("CustomerApp");
// Load context from XML file
ApplicationContext context = new ClassPathXmlApplicationContext("banking-app.xml");
// IoC: Spring creates the object
FullName fullName1 = context.getBean("fullName", FullName.class);
// DI: Set properties (Faker generates random data)
fullName1.setFirstName(faker.name().firstName());
fullName1.setLastName(faker.name().lastName());
logger.info("Full Name: " + fullName1);
// Get Customer bean (with dependencies already injected by Spring)
Customer customer1 = context.getBean("customer", Customer.class);
customer1.setAccountNo(faker.number().numberBetween(10000000, 99999999));
logger.info("Customer: " + customer1);
}
}Key Points:
- Uses
ClassPathXmlApplicationContextto load XML configuration - All bean definitions are in
banking-app.xml - Manual property setting after bean retrieval
- Good for understanding Spring basics
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Enable annotation processing -->
<context:annotation-config/>
<!-- Scan for components with annotations -->
<context:component-scan base-package="sh.surge.kunal.banking.*"/>
</beans>Configuration Elements:
<context:annotation-config>- Enables@Autowired,@Required, etc.<context:component-scan>- Scans packages for@Component,@Service, etc.
public class UserApp {
public static void main(String[] args) {
Logger logger = Logger.getLogger("UserApp");
// XML loads the context BUT enables component scanning
ApplicationContext context = new ClassPathXmlApplicationContext("banking-app.xml");
// Spring finds UserDataDao via @Component annotation
UserDataDao userDataDao = context.getBean(UserDataDao.class);
// Display user data fetched from REST API
userDataDao.getUserData().entrySet().stream()
.map(entry -> entry.getKey() + " : " + entry.getValue())
.forEach(logger::info);
}
}@Component // Marks this class as a Spring-managed bean
@PropertySource("classpath:application.properties") // Loads external properties
public class UserDataDao {
@Autowired // Spring injects RestClient automatically
private RestClient restClient;
@Value("${userUrl}") // Injects value from application.properties
private String userDataUrl;
private Map<String, String> userDataMap;
public Map<String, String> getUserData() {
userDataMap = new HashMap<>();
// Fetch data from external API using injected RestClient
String response = restClient.get()
.uri(userDataUrl) // URL from properties file
.retrieve()
.body(String.class);
// Parse JSON response
JSONArray usersArray = new JSONArray(response);
for (int i = 0; i < usersArray.length(); i++) {
String username = usersArray.getJSONObject(i).getString("name");
String email = usersArray.getJSONObject(i).getString("email");
userDataMap.put(username, email);
}
return userDataMap;
}
}Key Annotations Explained:
| Annotation | Purpose | Example |
|---|---|---|
@Component |
Marks class as Spring bean | Auto-discovered during component scan |
@Autowired |
Injects dependency | Spring finds RestClient bean and injects it |
@Value("${key}") |
Injects property value | Reads userUrl from application.properties |
@PropertySource |
Loads properties file | Makes application.properties available |
userUrl=https://jsonplaceholder.typicode.com/usersBenefits of this approach:
- Externalizes configuration (easier to change environments)
- Combines XML stability with annotation convenience
- Good migration path from pure XML
public class TransactionApp {
public static void main(String[] args) {
Logger logger = Logger.getLogger("TransactionApp");
// Zero XML! Load configuration from Java class
ApplicationContext context =
new AnnotationConfigApplicationContext(TransactionConfigurer.class);
// Get service with all dependencies injected
TransactionService transactionService = context.getBean(TransactionService.class);
// Process transactions
logger.info("Direct Debit: " + transactionService.processDirectDebit());
logger.info("External Debit: " + transactionService.processExternalDebit());
}
}@Configuration // Marks this as a configuration class (replaces XML)
@ComponentScan(basePackages = "sh.surge.kunal.banking") // Scans for components
@Import({DirectDebitConfigurer.class, ExternalDebitConfigurer.class}) // Imports other configs
public class TransactionConfigurer {
// This class doesn't need methods - it just enables component scanning
// Actual beans are defined in imported configurers or found via @Component
}Why @Import?
- Modularizes configuration
- Separates concerns (each configurer handles specific beans)
- Makes large applications manageable
@Configuration
public class DirectDebitConfigurer {
@Bean(name = "directDebitTransaction") // Creates a Spring-managed bean
public Transaction getDirectDebitTransaction() {
return new DirectDebitTransaction(); // Factory method
}
}@Configuration
public class ExternalDebitConfigurer {
@Bean(name = "externalDebitTransaction")
public Transaction getExternalDebitTransaction() {
return new ExternalDebitTransaction();
}
}@Configuration
public class RestConfigurer {
@Bean // Creates RestClient bean for HTTP calls
public RestClient restClient() {
return RestClient.create(); // Spring WebClient factory method
}
}@Service // Specialized @Component for service layer
@Data // Lombok: generates getters, setters, toString, etc.
public class TransactionService {
private final Transaction directDebitTransaction;
private final Transaction externalDebitTransaction;
private Faker faker;
// Constructor Injection (recommended over field injection)
public TransactionService(
@Qualifier("directDebitTransaction") Transaction directDebitTransaction,
@Qualifier("externalDebitTransaction") Transaction externalDebitTransaction
) {
this.directDebitTransaction = directDebitTransaction;
this.externalDebitTransaction = externalDebitTransaction;
this.faker = new Faker();
}
public Transaction processDirectDebit() {
DirectDebitTransaction transaction = (DirectDebitTransaction) this.directDebitTransaction;
// Use Faker to generate realistic test data
transaction.setSenderName(faker.name().fullName());
transaction.setReceiverName(faker.name().fullName());
transaction.setTransactionDate(faker.date().birthday()
.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
transaction.setAmount(faker.number().numberBetween(10000, 100000));
transaction.setPaymentDate(faker.date().birthday()
.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
return transaction;
}
public Transaction processExternalDebit() {
ExternalDebitTransaction transaction = (ExternalDebitTransaction) this.externalDebitTransaction;
transaction.setSenderName(faker.name().fullName());
transaction.setReceiverName(faker.name().fullName());
transaction.setTransactionDate(faker.date().birthday()
.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
transaction.setAmount(faker.number().numberBetween(10000, 100000));
transaction.setBranchAddress(faker.address().fullAddress());
transaction.setBranchCode(faker.number().digits(5).toString());
transaction.setBranchPostalCode(faker.address().zipCode());
transaction.setBranchName(faker.company().name());
return transaction;
}
}Key Points:
@Serviceis a specialized@Componentfor service layer- Constructor injection is preferred (immutable, testable)
@Qualifierdisambiguates when multiple beans of same type existfinalfields ensure immutability (dependencies can't be changed)
@Data // Generates getters, setters, toString, equals, hashCode
@AllArgsConstructor // Constructor with all fields
@NoArgsConstructor // No-arg constructor (required by Spring)
@Component // Makes this a Spring-managed bean
public class Customer {
protected long accountNo;
@Autowired
protected FullName fullName;
@Autowired
@Qualifier("homeAddress") // Specify which Address bean to inject
protected Address address;
@Autowired
@Qualifier("savingsAccount")
protected Account account;
protected long contactNo;
protected String email;
protected String password;
}Lombok Annotations:
@Data- Reduces boilerplate (no need to write getters/setters)@AllArgsConstructor/@NoArgsConstructor- Constructor generation- Makes code cleaner and more maintainable
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false) // Uses parent class fields in equals/hashCode
@ToString(callSuper = true) // Includes parent class toString
public class DirectDebitTransaction extends Transaction {
private LocalDate paymentDate;
}@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@ToString(callSuper = true)
public class ExternalDebitTransaction extends Transaction {
private String branchName;
private String branchAddress;
private String branchPostalCode;
private String branchCode;
}- Open
CustomerApp.java - Right-click in the editor
- Select "Run 'CustomerApp.main()'"
- View output in Run console
Expected Output:
INFO: Full Name: FullName(firstName=John, middleName=Michael, lastName=Doe)
INFO: Address 1: HomeAddress(doorNo=123, streetName=Main Street, city=New York, pinCode=100001)
INFO: Customer: Customer(accountNo=12345678, fullName=..., [email protected], ...)
- Open
UserApp.java - Right-click → "Run 'UserApp.main()'"
- Application fetches user data from REST API
Expected Output:
INFO: Leanne Graham : [email protected]
INFO: Ervin Howell : [email protected]
INFO: Clementine Bauch : [email protected]
...
- Open
TransactionApp.java - Right-click → "Run 'TransactionApp.main()'"
Expected Output:
INFO: Direct Debit: DirectDebitTransaction(paymentDate=2024-03-15, senderName=Alice Smith, receiverName=Bob Johnson, amount=45000)
INFO: External Debit: ExternalDebitTransaction(branchName=Chase Bank, branchAddress=123 Wall St, amount=75000)
Open Terminal in IntelliJ (Alt+F12 / Option+F12):
# Compile the project
mvn clean compile
# Run CustomerApp (Pure XML)
mvn exec:java -Dexec.mainClass="sh.surge.kunal.banking.utils.CustomerApp"
# Run UserApp (Hybrid)
mvn exec:java -Dexec.mainClass="sh.surge.kunal.banking.utils.UserApp"
# Run TransactionApp (Pure Java Config)
mvn exec:java -Dexec.mainClass="sh.surge.kunal.banking.utils.TransactionApp"For each application:
- Run → Edit Configurations
- Click + → Application
- Configure:
- Name:
CustomerApp(or relevant name) - Main class:
sh.surge.kunal.banking.utils.CustomerApp - Module: Select your project module
- Name:
- Apply → OK
- Select from dropdown and click Run (green play icon)
Create configurations for all three apps to easily switch between them!
Solution:
- Check internet connection
- File → Invalidate Caches → Invalidate and Restart
- Delete
.m2/repositorycache and re-sync
Solution:
- Verify file is in
src/main/resources/ - Right-click
resourcesfolder → Mark Directory as → Resources Root - Rebuild: Build → Rebuild Project
Solution:
- For XML approach: Check bean definition in
banking-app.xml - For annotation approach: Ensure
@Componentor@Beanis present - Verify component scan package matches your package structure
Solution:
- Ensure
RestConfigureris in a scanned package - Verify Spring WebFlux dependency is in
pom.xml - Check
@Configurationannotation is present onRestConfigurer
Solution:
- Check for infinite loops in bean creation
- Verify circular dependencies don't exist
- Enable debug logging: Add
-Dlogging.level.org.springframework=DEBUGto VM options
After studying this repository, you will understand:
✅ Inversion of Control (IoC) - How Spring manages object lifecycle
✅ Dependency Injection (DI) - Constructor, setter, and field injection
✅ ApplicationContext - Spring's advanced IoC container
✅ Bean Lifecycle - Creation, initialization, and destruction
✅ Bean Scopes - Singleton (default), prototype, etc.
✅ XML Configuration - Traditional <bean> definitions
✅ Annotation-based Config - @Component, @Service, @Repository, @Controller
✅ Java-based Config - @Configuration and @Bean methods
✅ Hybrid Approach - Combining XML and annotations
✅ Component Scanning - @ComponentScan and <context:component-scan>
✅ Property Injection - @Value and @PropertySource
✅ Qualifier Usage - @Qualifier for disambiguation
✅ Configuration Import - @Import for modular configs
✅ REST Integration - Using RestClient in Spring
✅ Lombok Integration - Reducing boilerplate code
✅ JavaFaker Integration - Generating test data
✅ Constructor Injection - Preferred over field injection
✅ Immutability - Using final fields with constructor injection
✅ Separation of Concerns - Configuration, services, and models
✅ External Configuration - Using application.properties
✅ Type Safety - Java config provides compile-time checking
- Working with legacy Spring applications (pre-3.0)
- Team requires centralized, visual configuration
- Integrating with XML-heavy enterprise systems
- Migrating from XML to modern Spring
- Need gradual transition with minimal risk
- Some configuration must remain in XML (legacy constraints)
- Starting new Spring projects ✅ RECOMMENDED
- Using Spring Boot (this is the default)
- Want type-safe, refactoring-friendly configuration
- Need IDE support (autocomplete, navigation)
Spring 1.x (2004) Spring 2.5 (2008) Spring 3.0 (2009) Spring 5.x+ (2017+)
│ │ │ │
│ │ │ │
Pure XML XML + Annotations Java Config Spring Boot
<bean> @Component @Configuration Auto-configuration
<property> @Autowired @Bean Convention over config
@Service @ComponentScan Zero XML by default
- Type Safety - Compile-time checking, refactoring support
- Less Verbose - No need to repeat class names in XML
- Better IDE Support - Navigate to bean definitions easily
- Testability - Constructor injection makes unit testing easier
- Maintainability - Configuration close to the code it configures
- Project Lombok - Reduces Java boilerplate
- JavaFaker - Generates fake data for testing
- Spring WebFlux - Reactive REST client
After mastering this repository:
- Spring Boot - Modern, opinionated framework built on Spring
- Spring Data JPA - Database access with repositories
- Spring MVC - Build web applications
- Spring Security - Authentication and authorization
- Spring AOP - Aspect-oriented programming for cross-cutting concerns
If this guide helped you understand the evolution of Spring IoC, please consider giving it a Star! It helps others discover the repository and stay updated with new examples.
- Star this Repo: Click the ⭐ button at the top right of this page.
- Follow for More: Follow me on GitHub to keep up with my latest Java and Spring projects.
- Share: If you know someone learning Spring, feel free to share this resource!
Happy Coding! 🚀