SPRING CONCEPTS

How Spring creates and connects objects

Spring Concepts / Detailed guide

Start with a small Java program: a TodoPrinter displays task titles, a TodoService decides what to return, and a TodoStore supplies the tasks. These are classes you write. Their methods need actual objects to work with. Spring can create and keep track of selected objects and supply them to other objects that need them.

Open the Spring Framework explanation in the third column. Follow the steps below before learning the annotation names.

2.1 A class defines a type; an object is one instance

A class describes fields, constructors and methods. Declaring a class does not automatically run its constructor. An expression such as new TodoStore() creates an instance and calls its constructor. The variable store holds a reference to that instance, rather than containing the whole object.

Illustrative Java statements — explained before the complete example
TodoStore store = new TodoStore();
TodoService service = new TodoService(store);
TodoPrinter printer = new TodoPrinter(service);

Read the second line as: create a service object and pass the existing store reference to its constructor. The service saves that reference in a field so its methods can call the store later. That required store is called a dependency.

2.2 Is the entire application one object? Does every class need an object?

QuestionAnswer
Is the application a single object?No. It is a running program with many collaborating objects, classes, resources and threads. A Spring ApplicationContext manages a selected group of those objects; it is not the entire application.
Is every class instantiated?No. A class can exist without any instances. A static method can run without an instance of its declaring class. An abstract class cannot be directly instantiated, and an interface has no constructor for creating an instance.
How many objects can one class have?Zero, one, or many. Two executions of new TodoStore() create two distinct instances. Several variables can also refer to the same instance.
Does Spring create every object?No. It manages objects registered through configuration, component scanning, or framework integration. A Todo, DTO, String or local List is not automatically a Spring bean simply because the application uses Spring.
What is usually managed?Controllers, services, repositories and shared configured helpers. These objects perform work. Individual Todo objects carry task data and are usually constructed by application code or data-mapping libraries.

2.3 First connect the objects yourself

Create store → Pass store to service constructor → Pass service to printer constructor

This is normal Java and already uses constructor dependency injection: the caller supplies the dependency through a constructor. Spring is not required for this Java pattern. In a small program, writing these three construction statements is straightforward. In a larger application, many services need shared collaborators, configuration and lifecycle handling.

2.4 Tell Spring how to build the selected objects

The runnable example uses explicit @Bean factory methods so you can see the construction code. @Configuration marks the configuration class. Each @Bean method tells Spring how to obtain one managed object. Spring supplies a method's parameters from the objects in its context; our method then calls the Java constructor using those references.

Illustrative factory method — included in Main.java below
@Bean
public TodoService todoService(TodoStore store) {
    return new TodoService(store);
}

The parameter store is supplied by Spring. The new expression still creates the Java object. Spring records the returned service as a bean and makes it available when the printer needs a service. In this example there is exactly one matching bean for each dependency type.

Factory methods: from ordinary Java to Spring

A factory method is a method whose job is to create or obtain an object and return its reference. In this example, it contains the instructions Spring uses to obtain a service object. Explore factory methods in the third column.

Step 1: create the service directly

Illustrative statements
TodoStore store = new TodoStore();
TodoService service = new TodoService(store);

new TodoService(store) constructs a service and calls its constructor. The existing store reference is passed as an argument. The service variable receives the reference to the resulting service object.

Step 2: put construction inside a method

Illustrative factory method declaration
public TodoService createTodoService(TodoStore store) {
    return new TodoService(store);
}

Read it as: “Give me a store reference, and I will construct and return a service that uses it.” This is an ordinary Java method. The word factory describes its purpose; Java has no special factory-method keyword.

Call inside an instance method of the same helper class
TodoStore store = new TodoStore();
TodoService service = createTodoService(store);

The caller supplies the store and receives the service. These excerpts explain the idea; the complete files and run command are in section 2.6 below.

Step 3: tell Spring to use the factory

Illustrative AppConfig.java excerpt using the demo classes
@Configuration(proxyBeanMethods = false)
public class AppConfig {
    @Bean
    public TodoStore todoStore() {
        return new TodoStore();
    }

    @Bean
    public TodoService todoService(TodoStore store) {
        return new TodoService(store);
    }
}

When AppConfig is registered with the context, Spring reads the @Bean methods as bean definitions. Spring supplies each method's required dependency arguments and manages its returned object. You write the factory body, including the constructor call.

Part of the service factoryMeaning
TodoService before the nameThe return type: what kind of object reference the method returns.
todoServiceThe method name; also the bean name with the default naming configuration.
TodoStore storeA required dependency that Spring supplies from the context.
new TodoService(store)Calls the constructor to construct the service.
returnPasses the service reference back to Spring.

Step 4: follow who calls what

Startup dependency sequence
Spring calls todoStore()
  -> receives and registers a TodoStore object
  -> supplies that store to todoService(store)
  -> the method constructs and returns a TodoService
  -> Spring manages the returned service
Context startup and lookup — statements using AppConfig
var context = new AnnotationConfigApplicationContext(AppConfig.class);
TodoService service = context.getBean(TodoService.class);
// Close the context when finished; the complete demo uses try-with-resources.

The context invokes the factories as needed to create the configured beans. In this default singleton example, subsequent lookups reuse the registered service. Getting a bean is different from directly calling a Java factory method to construct another object.

Step 5: distinguish the constructor from the factory

Constructor declared inside TodoService
// Constructor declared inside TodoService
public TodoService(TodoStore store) {
    this.store = store;
}

The constructor has the class name and no return type. It initializes the service during construction. The factory method has a return type, an ordinary method name and a return statement. Here the factory calls the constructor internally and returns its result.

Factories are useful when creation needs choices or setup: selecting an implementation, supplying configuration, or creating an object from a library. A factory need not be static and can return a new or existing object. Spring's bean scope separately determines how the managed object is reused.

For this configuration: proxyBeanMethods = false means direct calls to its methods are ordinary Java calls. Supply dependencies through factory parameters and use the context to retrieve managed beans.

Read “Spring calls the factory methods” as: Spring calls the configured @Bean methods, supplies their required arguments, and manages the objects those methods return.

Practice: find all three @Bean methods in Main.java below. Identify the return type, the dependency parameters and the constructor call in each one.

Reference: Spring: using @Bean.

2.5 What happens at startup, and what happens later?

  1. Our main method starts an AnnotationConfigApplicationContext with AppConfig.class.
  2. Spring reads the configuration and its bean definitions.
  3. To create the printer, Spring needs the service; to create the service, it needs the store. Spring obtains the dependencies before completing each dependent object.
  4. Spring calls the factory methods with the required references, registers their returned objects and completes their initialization. In this example the default singleton beans are created during context startup.
  5. Our code asks the context for the printer and calls its print method. The printer calls the service, which calls the store. These are ordinary Java method calls.
printer.print() → service.titles() → store.findAll()

The two diagrams show different things: construction supplies references; later method calls use those references. Spring does not invent the business logic inside these methods.

2.6 Run a complete small example

Download the Spring objects demo ZIP. Extract it and open a terminal in spring-objects-demo, where pom.xml is located. You need JDK 21+ and Maven 3.9+ on PATH, plus internet for dependencies on the first run. This console example needs no database or Tomcat installation.

Project structure
spring-objects-demo/
  pom.xml
  src/main/java/com/edupoly/demo/Main.java

To create it manually, save these two complete files at those paths. All helper classes are nested inside Main to keep this first exercise small; real projects normally use separate files.

pom.xml — complete file
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.edupoly</groupId>
  <artifactId>spring-objects-demo</artifactId>
  <version>1.0.0</version>
  <properties>
    <maven.compiler.release>21</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>7.0.9</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.14.1</version>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>3.5.0</version>
        <configuration>
          <mainClass>com.edupoly.demo.Main</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
src/main/java/com/edupoly/demo/Main.java — complete file
package com.edupoly.demo;

import java.util.List;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

public class Main {
    // A data class: individual Todo instances are not registered as beans here.
    public record Todo(String title) {}

    public static class TodoStore {
        public List<Todo> findAll() {
            return List.of(new Todo("Learn Java"), new Todo("Learn Spring"));
        }
    }

    public static class TodoService {
        private final TodoStore store;

        public TodoService(TodoStore store) {
            this.store = store;
        }

        public List<String> titles() {
            return store.findAll().stream().map(Todo::title).toList();
        }
    }

    public static class TodoPrinter {
        private final TodoService service;

        public TodoPrinter(TodoService service) {
            this.service = service;
        }

        public void print(String label) {
            System.out.println(label + ": " + service.titles());
        }
    }

    @Configuration(proxyBeanMethods = false)
    public static class AppConfig {
        @Bean
        public TodoStore todoStore() {
            return new TodoStore();
        }

        @Bean
        public TodoService todoService(TodoStore store) {
            return new TodoService(store);
        }

        @Bean
        public TodoPrinter todoPrinter(TodoService service) {
            return new TodoPrinter(service);
        }
    }

    public static void main(String[] args) {
        // First: our code creates and connects all three objects.
        TodoStore store = new TodoStore();
        TodoService service = new TodoService(store);
        TodoPrinter printer = new TodoPrinter(service);
        printer.print("Manual Java");

        // Second: Spring calls the configured factory methods and supplies
        // each method's arguments from objects registered in the context.
        try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
            TodoPrinter first = context.getBean(TodoPrinter.class);
            TodoPrinter second = context.getBean(TodoPrinter.class);
            first.print("Spring");
            System.out.println("Same printer bean: " + (first == second));
            System.out.println("Store passed to service: "
                + (context.getBean(TodoService.class).store
                   == context.getBean(TodoStore.class)));
        }
    }
}
Command — Windows CMD, PowerShell, macOS or Linux
mvn -q compile exec:java
Expected application output
Manual Java: [Learn Java, Learn Spring]
Spring: [Learn Java, Learn Spring]
Same printer bean: true
Store passed to service: true

Maven or the JDK may also print diagnostic messages. Both approaches produce the same titles because they call the same classes. The first true shows that two lookups return the same singleton printer. The second true confirms that Spring supplied the same store reference to the service. The manual objects and Spring-managed objects are separate sets.

2.7 Connect this example to the todo web application

TodoPrinter is a console helper, not a web controller. In the web project, a TodoController takes the entry-point role: Spring MVC receives an HTTP request and invokes a mapped method on the controller bean. The controller uses its injected service, which uses its repository. Your code still implements the task operations.

Later, @Service and @Controller, together with enabled component scanning, let Spring find classes without an explicit factory method for each one. With one constructor, Spring supplies its required arguments without needing @Autowired. Spring Data separately registers implementations for configured repository interfaces. A plain interface cannot be instantiated using new.

The default singleton scope means one bean instance per definition per application context, not one object for the entire program and not one new controller for each request. Do not store a current user's request data in shared controller or service fields. DTOs and Todo records are normally separate data objects.

2.8 What else does Spring Framework help with?

NeedSpring's supportYou still write
Connect collaborating classesA container that manages configured objects and supplies dependencies.Classes, constructors, configuration and operations.
Handle a web requestSpring MVC maps routes, binds inputs and renders views or JSON.Controller methods and their intended responses.
Validate inputIntegration that invokes configured validation rules.Constraints and useful error responses.
Coordinate database transactionsTransaction support when configured with a suitable data source or database setup.Transaction boundaries and business behavior.

Spring Boot builds on these facilities and reduces startup and configuration work. Start with the objects and their relationships; the next section explains Boot.

2.9 Practice and check your understanding

  1. Add a third Todo in findAll(), rerun the command and predict which output lines change.
  2. Explain why both bean lookups return the same printer, while two new TodoPrinter(service) expressions would create separate printers.
  3. Point to the exact parameter where Spring supplies the store. Then point to the line where Java constructs the service.
  4. Explain why new Todo("Learn Java") creates a data object but does not register a Spring bean.

Further reading: Spring's container and beans.

← All Spring Concepts · Learning path

Java 21 · Spring Boot 4.1.1 · Edupoly