Learn / Frameworks / Spring Boot / Dependency Injection and Layers

Spring Boot · Lesson 3 of 5

Dependency Injection and Layers

Beans, services, repositories and constructor injection.

  • Intermediate
  • 14 min read
  • 3 objectives

Before this lessonLesson 2: REST Controllers and Validation

What you will learn

  • Explain inversion of control
  • Use @Service and constructor injection
  • Structure controller/service/repository

The heart of Spring is inversion of control. Instead of a class creating the objects it depends on (new EmailSender()), it declares what it needs and Spring supplies it. This makes code easier to test (swap in a fake) and to change (replace an implementation without touching users of it).

Beans

A bean is an object created and managed by Spring's container. Mark a class with a stereotype annotation and component scanning registers it.

  • @Component: a generic bean.
  • @Service: business logic.
  • @Repository: data access.
  • @RestController: web layer.
  • @Configuration with @Bean methods: create beans manually, for example for third-party classes.

Constructor injection

public interface NotificationSender {
    void send(String to, String message);
}

@Service
class EmailSender implements NotificationSender {
    public void send(String to, String message) {
        System.out.println("Email to " + to + ": " + message);
    }
}

@Service
public class OrderService {

    private final NotificationSender sender;      // depends on an interface

    public OrderService(NotificationSender sender) {   // Spring passes it in
        this.sender = sender;
    }

    public void placeOrder(String email) {
        // ...save order...
        sender.send(email, "Order received");
    }
}

Prefer constructor injection: dependencies are explicit, can be final, and the class cannot exist in a half-built state. With a single constructor, @Autowired is unnecessary.

Testing gets easy

class OrderServiceTest {
    @Test
    void sendsConfirmation() {
        var sent = new java.util.ArrayList<String>();
        NotificationSender fake = (to, msg) -> sent.add(to + ":" + msg);

        new OrderService(fake).placeOrder("a@b.com");

        assertEquals(1, sent.size());
    }
}

The classic layers

  • Controller: HTTP in and out; no business logic.
  • Service: the rules of your application; transactions live here.
  • Repository: talks to the database.

Dependencies point downward only: controllers call services, services call repositories. Keeping that direction makes each layer replaceable and testable.

Multiple implementations

If two beans implement the same interface, Spring cannot decide. Mark one @Primary, or select by name with @Qualifier("smsSender").

Bean scopes

Beans are singletons by default: one shared instance. So never keep per-request or per-user state in fields of a service.

// Write your solution here
Up next · Lesson 4Spring Data JPAEntities, repositories, relationships and derived queries.