Skip to main content

Command Palette

Search for a command to run...

1. Spring Core

Updated
6 min readView as Markdown
A

Associate System Engineer | Currently learning Java & building fun side projects | Blogging my dev journey to revise, reflect, and grow 👨‍💻🚀

Inversion of Control (IoC) and Dependency Injection (DI) in Spring

Understanding Spring Core Using XML Configuration

When developers begin their Spring journey, many jump straight into Spring Boot. While Spring Boot undeniably speeds up development, it also hides the very concepts that make Spring powerful. As a result, Spring often feels magical rather than logical.

Spring Core is where these fundamentals live. Once you understand Spring Core, everything else in the Spring ecosystem starts making sense.

At the heart of Spring Core lie two ideas that completely change how Java applications are built: Inversion of Control (IoC) and Dependency Injection (DI). In this article, we’ll explore these ideas step by step using XML-based configuration, without skipping what happens behind the scenes.


Why Do We Even Need Maven in a Spring Project?

Spring is not part of the Java Development Kit. It is an independent framework distributed as a collection of JAR files.

A JAR file is simply a packaged bundle of compiled .class files meant to be reused across applications. Before tools like Maven, developers had to manually download these JARs, place them into projects, and ensure compatible versions were used. This quickly became painful as applications grew.

Maven solves this problem by acting as a dependency manager. Instead of downloading JARs manually, we declare what we need, and Maven takes care of the rest.

In a Spring Core project, adding the following dependency is enough to unlock the IoC container and DI features:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.1.21</version>
</dependency>

This single dependency gives us access to ApplicationContext, XML configuration support, and the dependency injection engine.


The Core Problem: Manual Object Creation

In a traditional Java application, object creation is done explicitly using the new keyword.

JavaCourse course = new JavaCourse();

At first glance, this seems harmless. But notice what’s really happening here. The developer decides which class to instantiate, when to create it, and how it should be wired with other objects. This tightly couples the code to concrete implementations.

If tomorrow JavaCourse needs to be replaced with SpringBootCourse, the code itself must change. As applications grow, this kind of coupling spreads everywhere, making systems rigid and difficult to maintain.

Spring solves this by changing who controls object creation.


Inversion of Control (IoC): Reversing Responsibility

Inversion of Control means that control over object creation is inverted.

Instead of the developer creating objects, Spring does it.

Instead of the developer managing lifecycles, Spring manages them.

Any object created and managed by Spring is called a Spring Bean.

This shift may feel subtle at first, but it fundamentally changes application design. Your classes now focus on what they do, not how they are created.


The IoC Container and ApplicationContext

Spring provides an IoC container to manage beans. In XML-based configuration, this container is usually ApplicationContext.

ApplicationContext container =
        new ClassPathXmlApplicationContext("applicationconfig.xml");

When this line executes, Spring performs several actions internally. It reads the XML file, identifies bean definitions, creates objects, injects dependencies, and prepares everything for use.

An important detail here is that ApplicationContext uses eager initialization by default. This means all singleton beans are created at startup, even if they are never explicitly requested.


What Exactly Is a Spring Bean?

A Spring Bean is simply an object whose entire lifecycle is managed by Spring.

To verify this, consider a simple class:

public class JavaCourse {

    public JavaCourse() {
        System.out.println("Java course bean created");
    }
}

If this class is declared as a bean in XML and the application runs, the message appears in the console — even though we never used the new keyword. This confirms that Spring, not the developer, created the object.


Loose Coupling Through Interfaces

Loose coupling is achieved by depending on abstractions, not concrete classes.

Let’s define an interface:

public interface Icourse {
    Boolean getTheCourse(Double price);
}

Now we can have multiple implementations:

public class JavaCourse implements Icourse {
    @Override
    public Boolean getTheCourse(Double price) {
        System.out.println("Java Course price: " + price);
        return true;
    }
}
public class SpringBootCourse implements Icourse {
    @Override
    public Boolean getTheCourse(Double price) {
        System.out.println("Spring Boot Course price: " + price);
        return true;
    }
}

The key point is that the client class depends only on the interface:

public class TshapedSkill {

    private Icourse course;

    public boolean buyTheCourse(Double amount) {
        return course.getTheCourse(amount);
    }
}

Because the dependency is expressed in terms of an interface, implementations can be swapped without changing the code. Only configuration changes — and that is where Dependency Injection comes in.


Dependency Injection Using Setter Injection

With setter injection, Spring creates the object first and then injects dependencies using setter methods.

public class TshapedSkill {

    private Icourse course;

    public void setCourse(Icourse course) {
        this.course = course;
    }

    public boolean buyTheCourse(Double amount) {
        return course.getTheCourse(amount);
    }
}

XML configuration:

<bean id="java" class="services.JavaCourse"/>

<bean id="tshapedSkill" class="services.TshapedSkill">
    <property name="course" ref="java"/>
</bean>

Here, Spring creates the TshapedSkill object, calls the setCourse() method, and injects the JavaCourse bean. Setter injection works well when dependencies are optional or expected to change.


Dependency Injection Using Constructor Injection

Constructor injection pushes this idea further by ensuring dependencies are available at creation time.

public class TshapedSkill {

    private Icourse course;

    public TshapedSkill(Icourse course) {
        this.course = course;
        System.out.println("TshapedSkill bean created using constructor injection");
    }

    public boolean buyTheCourse(Double amount) {
        return course.getTheCourse(amount);
    }
}

XML configuration:

<bean id="java" class="services.JavaCourse"/>

<bean id="tshapedSkill" class="services.TshapedSkill">
    <constructor-arg ref="java"/>
</bean>

Spring identifies the constructor, injects the dependency while creating the object, and guarantees that the class is never partially initialized. This is why constructor injection is generally preferred in real-world applications.


Running the Application Without new

TshapedSkill skill = container.getBean(TshapedSkill.class);
Boolean status = skill.buyTheCourse(5999.99);

At runtime, Spring provides a fully wired object. The business logic executes through interfaces, and the application remains loosely coupled.


What Happens Internally Inside Spring?

https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/images/container-magic.png

https://www.springboottutorial.com/images/spring-features.png

https://miro.medium.com/1%2A5MShKzJV3ClbCRHHWxIWIw.png

When the application starts, Spring follows a well-defined internal flow. The XML configuration is read first, bean definitions are identified, objects are instantiated, dependencies are injected, constructors are executed, and finally control is handed back to the application.

This predictable lifecycle is what makes Spring reliable and extensible.


Why Learn Spring Core Before Spring Boot?

Spring Boot removes boilerplate and automates configuration, but it also hides these internal mechanics. Spring Core teaches you how objects are created, how dependencies are injected, and how the container manages lifecycles.

Once these fundamentals are clear, Spring Boot stops being confusing and starts feeling like a natural evolution.


Final Takeaway

Inversion of Control hands object creation to Spring. Dependency Injection removes hardcoded dependencies. Interfaces enable loose coupling. Setter injection provides flexibility, while constructor injection enforces correctness. Spring Core is the foundation upon which the entire Spring ecosystem is built.

Mastering these ideas doesn’t just help you write Spring applications — it helps you understand them.