Question 1 · Section 5

What is Dependency Injection?

BeanDefinition is a "recipe" by which Spring creates a bean: full class name, scope, dependencies, lazy/eager, etc.

Language versions: English Russian Ukrainian

Junior Level

Dependency Injection (DI) is a pattern where objects receive their dependencies from the outside, rather than creating them via new.

Why: Without DI, a class is tightly coupled to its dependencies. It cannot be tested in isolation (mocks cannot be injected), and swapping implementations requires code changes. DI solves this by having dependencies provided from outside.

Simple analogy: Instead of building a car yourself, it gets delivered to you on order.

Without DI:

// Class creates dependencies itself
public class OrderService {
    private OrderRepository repo = new OrderRepository();
}

With DI:

// Dependency comes from outside
public class OrderService {
    private final OrderRepository repo;

    public OrderService(OrderRepository repo) {
        this.repo = repo;  // Spring will pass the right repository itself
    }
}

Why:

  • Easy to test (swap repository for a mock)
  • Classes are not directly dependent on each other
  • Spring manages all objects itself

When DI is NOT needed

  1. Trivial scripts - one class, one dependency, no tests - DI adds complexity without benefit
  2. Value Objects / DTO - simple data objects, they have no dependencies
  3. Performance-critical systems with microsecond latency - DI container adds startup overhead (usually negligible)

Middle Level

DI Lifecycle in Spring

1. Registration: Scanning (@Component, @Bean) -> BeanDefinition
2. Instantiation: Object creation (reflection)
3. Population: Dependency injection (@Autowired)
4. Initialization: @PostConstruct, Proxy creation (@Transactional)
5. Ready: Bean is ready to work

BeanDefinition is a “recipe” by which Spring creates a bean: full class name, scope, dependencies, lazy/eager, etc.

Proxy creation - Spring creates a substitute object (proxy) that intercepts method calls and adds behavior (transactions, security). Details in file [[13. What is a proxy in Spring]].

Injection Types

Type Example Pros Cons
Constructor public Service(Repo r) {} Immutable, tests, fail-fast Many parameters
Setter @Autowired void setRepo(Repo r) {} Optional dependencies Can forget to call
Field @Autowired Repo repo; Less code Hidden dependencies, hard to test

Circular Dependencies

// A depends on B, B depends on A
// Constructor Injection -> startup error
// @Lazy -> Spring creates a proxy, real dependency injected later

Prototype in Singleton

// Prototype created ONCE when injected into Singleton
@Autowired PrototypeBean proto;  // Stuck with one instance

// ObjectProvider - new each time
@Autowired ObjectProvider<PrototypeBean> provider;
PrototypeBean bean = provider.getObject();  // New!

Senior Level

BeanDefinition: Meta-information

BeanDefinition stores:
  -> Class name
  -> Scope (Singleton, Prototype)
  -> Constructor arguments
  -> Autowire mode
  -> Init/Destroy methods

-> Can be modified via BeanFactoryPostProcessor BEFORE bean creation

Proxy and DI

DI happens BEFORE proxy creation!
  1. Spring creates the object
  2. Injects dependencies
  3. BeanPostProcessor.postProcessAfterInitialization -> creates Proxy

-> That's why @Transactional doesn't work with self-invocation!
-> Calling this.method() goes around the proxy

Constructor Injection and Circular Dependencies

Constructor Injection requires dependencies IMMEDIATELY.
  -> Spring cannot create A without B
  -> Spring cannot create B without A
  -> BeanCurrentlyInCreationException

Field Injection:
  -> Spring creates A and B via default constructor
  -> Then fills fields
  -> Hidden bug: NPE in @PostConstruct

Production Experience

Real scenario: Field Injection hid the problem

  • 50 services, circular dependencies via Field Injection
  • Application started but crashed on first request
  • Solution: switched to Constructor Injection
  • Result: error on startup -> fixed quickly

Best Practices

  1. Constructor Injection - always by default
  2. @Lazy - only for cycles (refactoring is better)
  3. ObjectProvider - for Prototype in Singleton
  4. Field Injection - DO NOT use
  5. > 5 dependencies -> violation of SRP, split the class
  6. DI != Service Locator -> do not pull context into code

Summary for Senior

  • DI = implementation of Inversion of Control. IoC is the general principle (“the framework calls your code, not the other way around”). DI is a specific implementation of IoC, where dependencies are provided from outside. Not all IoC is DI, but all DI is IoC.
  • Constructor - immutability, tests, fail-fast
  • BeanFactoryPostProcessor -> modifying metadata before creation
  • Proxy created AFTER DI -> self-invocation does not work
  • Circular deps -> Constructor breaks immediately, Field masks
  • Prototype in Singleton -> ObjectProvider or @Lookup
  • Field Injection - technical debt

Interview Cheat Sheet

Must know:

  • DI is a pattern where objects receive dependencies from outside, not by creating them via new
  • DI implements Inversion of Control (IoC): the framework calls your code, not the other way around
  • Three injection types: constructor, setter, field
  • DI makes testing easier (mocks), reduces coupling, simplifies implementation swapping
  • Spring manages DI lifecycle: Registration -> Instantiation -> Population -> Initialization -> Ready
  • Circular dependencies: constructor breaks immediately, field masks the problem
  • Prototype in Singleton requires ObjectProvider, otherwise there will be one instance
  • 5 dependencies -> violation of SRP, split the class

Common follow-up questions:

  • How does DI differ from IoC? IoC is the general principle, DI is a specific implementation of IoC.
  • What is BeanDefinition? A bean “recipe”: class name, scope, dependencies, init/destroy methods.
  • Why doesn’t @Transactional work with self-invocation? The this.method() call goes around the proxy, which is created AFTER DI.
  • How to resolve circular dependencies? Refactoring (extract a common service), @Lazy as a workaround, or setter injection.

Red flags (DO NOT say):

  • “Field Injection is a fine choice for production” (it is an anti-pattern, hides dependencies)
  • “DI adds too much overhead, better to do it manually via new” (overhead is negligible, benefits outweigh)
  • “Circular dependencies are fine, Spring resolves them itself” (Spring masks an architectural problem)
  • “All objects should be beans with DI” (DTOs, Value Objects, Entities are regular objects)

Related topics:

  • [[02. Difference between constructor setter and field injection]]
  • [[03. Which injection type is recommended and why]]
  • [[04. What is a Bean in Spring]]
  • [[06. What is Bean Lifecycle]]
  • [[08. What is BeanPostProcessor]]