IoC · DI · Bean — The *Heart* of Spring
IoC · DI · Bean — The *Heart* of Spring
🎯 What You'll Be Able to Do After This Lesson
After finishing this lesson, you'll be able to confidently handle the following three things.
- ▸✅ How @Autowired injects Beans (3 approaches)
- ▸✅ Resolving Bean conflicts of the same type with @Qualifier / @Primary
- ▸✅ Registering external library classes via @Configuration + @Bean
Keep these learning goals as a checklist — once you can answer all of them, close the lesson.
IoC (Inversion of Control) — *Receive, Don't Create*
Core Idea
IoC (Inversion of Control) = instead of creating objects yourself, you let the framework create them and hand them to you. The name reflects that the flow of control is inverted.
The Old Way — Creating Everything Yourself
It looks clean on the surface, but there are serious problems:
- ▸Hard to test: You only want to test
OrderService, but the realEmailServiceruns with it — actual emails get sent - ▸Hard to change: If
EmailServiceis swapped for SmsService, every call site must be updated - ▸Hard to trace dependencies: No way to track who creates whom
The New Way — Receive and Use
Now OrderService doesn't care where its dependencies came from — it just receives and uses them. Who creates them is Spring's responsibility.
That's IoC. The control over object creation has been handed to the framework.
Why This Is Better
1. Easier to test:
2. Easy to swap implementations: To replace EmailService with KakaoMessageService, change one line in the Spring configuration. Calling code stays the same.
3. Dependencies are explicit: Just reading the constructor signature tells you exactly what the class needs.
The IoC Container — Spring's Factory
The core component of Spring that creates, stores, and wires objects together is called the IoC container, or ApplicationContext.
How it works:
1. On startup, Spring scans all classes annotated with @Component, @Service, @Repository, and @Controller
2. It creates one instance of each discovered class and stores it in the container
3. When another component needs one of those objects, Spring injects it via constructor or @Autowired
> 💡 Objects stored in the container are called Beans — a metaphor for small unit objects, like beans.
Quick Summary
- ▸IoC = delegating object creation to the framework
- ▸Benefits = testing, swapping, and tracing all become easier
- ▸Spring container = the factory that creates and connects Beans
This is the most fundamental idea in Spring. DI, AOP, and transactions all operate on top of IoC.
DI (Dependency Injection) — *Three Approaches*
The Relationship Between DI and IoC
IoC is the broader concept, and DI (Dependency Injection) is one of its concrete implementations. It is the specific technique of injecting the dependencies an object needs from the outside.
3 Injection Approaches
1. Constructor Injection (Recommended)
Advantages:
- ▸Allows
final→ guarantees immutability - ▸All dependencies are guaranteed at object creation time
- ▸Circular dependencies are caught at compile time
- ▸Easy to inject mock objects in tests
As of Spring 4.3+, @Autowired can be omitted. Combined with Lombok's @RequiredArgsConstructor, it becomes even more concise:
2. Setter Injection
Advantage: Can express optional dependencies (may or may not be present)
Disadvantage: Cannot use final. Null if setter is never called. Rarely used.
3. Field Injection (Discouraged)
Looks concise but:
- ▸Hard to test (injection only possible via Reflection)
- ▸Circular dependencies unknown until runtime
- ▸Dependencies can be added carelessly because it's too convenient
> 💡 Industry consensus: Always use constructor injection. Field injection is only seen in legacy code.
When There Are Multiple Beans of the Same Type
Spring doesn't know which one to inject. Three solutions:
1. @Primary — designate a default:
2. @Qualifier — explicit selection:
3. Receive as a List — get all implementations:
Common Pitfalls
Circular dependency: A injects B, and B injects A. With constructor injection, this causes an error at startup — a signal that your design is wrong. The fix is to extract the shared part into a new class.
@Autowired vs @Resource vs @Inject: Knowing Spring's @Autowired is sufficient. The other two are Java standards but are rarely used in practice.
Quick Summary
- ▸Constructor injection is the standard. Combine with Lombok's
@RequiredArgsConstructor - ▸When multiple Beans of the same type exist, use
@Primaryor@Qualifier - ▸Circular dependencies are a design signal — refactoring is needed
Bean — *Objects Managed by Spring*
What Is a Bean?
A Bean is an object registered and managed by the Spring IoC container. Think of it like a small-unit object among a collection of beans. What distinguishes it from an ordinary Java object is that Spring manages its creation, lifecycle, and injection entirely.
Ways to Register a Bean
1. @Component Family (Most Common)
The names differ, but they all register a Bean at their core. They serve as semantic distinctions with some behavioral differences (@Repository provides exception translation).
2. @Bean (Method-Level)
The return value of a method in a configuration class becomes a Bean:
Used to register classes from external libraries (RestTemplate, ObjectMapper, etc.) as Beans — classes you can't annotate yourself.
3. Auto-configuration — Spring Boot Starters
Starter dependencies like spring-boot-starter-data-jpa automatically register many Beans (DataSource, EntityManager, TransactionManager, etc.).
Bean Scope — When Is a New Instance Created?
The default is singleton — only one instance per container, shared by all. But other options exist.
Most common pitfall: Keeping mutable fields in a singleton Bean. In a multi-threaded environment, this causes data corruption. Always use immutable state or an external store (DB, Redis).
Bean Lifecycle
A Bean goes through the following stages:
1. Instantiation (constructor call)
2. Dependency injection (@Autowired, etc.)
3. Initialization (@PostConstruct or InitializingBean.afterPropertiesSet())
4. In use
5. Destruction (@PreDestroy or DisposableBean.destroy())
@PostConstruct is used frequently — for preparing resources, warming caches, etc. @PreDestroy is important for graceful shutdown.
@Configuration vs @Component
Both are registered as Beans, but they serve different roles.
- ▸@Configuration — a configuration class. Its
@Beanmethods are wrapped in a proxy to guarantee the same instance is returned - ▸@Component — a regular Bean
When @Bean methods inside a @Configuration class call each other, they return the same instance. With @Component, there's a risk of a new object being created each time.
Quick Summary
- ▸Bean = an object managed by Spring
- ▸Register via
@Serviceand similar annotations, or a@Beanmethod - ▸Default scope is singleton (appropriate in most cases)
- ▸Lifecycle hooks are available via
@PostConstructand@PreDestroy
@Bean · @Configuration · Resolving @Qualifier · @Primary Conflicts
@Component vs @Bean — When to Use Which
- ▸@Component (including @Service, @Repository, @Controller) — for classes you write yourself. Spring scans them automatically.
- ▸@Bean — when you need to register external library objects or conditionally.
@Configuration + @Bean Example
External library classes (RestTemplate, ObjectMapper) can't be annotated directly, so they're registered with @Bean.
Two Beans of the Same Type — Conflict
Multiple Beans of the same type → Spring doesn't know which one to inject.
Solution 1 — Specify a Name with @Qualifier
The Bean name defaults to the method name (cardPayment, kakaoPayment).
Solution 2 — Set a Default with @Primary
Which One to Use?
- ▸A clear default/primary implementation →
@Primary - ▸Inject different ones depending on context →
@Qualifier - ▸Auto-matching by name without either is also possible (
@Autowired PaymentService cardPayment;)
Conditional Registration — @ConditionalOnProperty
The Bean is only registered when payment.provider=kakao is set in application.yml. This cleanly separates different implementations per profile.
🤖 Try Asking AI This Way
Knowing the concepts from this lesson lets you give AI specific, precise instructions. Instead of a vague "fix this," you can make requests with vocabulary — that's the starting point for saving tokens.
- ▸"There are two implementations of this PaymentService — branch them using @Qualifier"
- ▸"Register this RestTemplate as a Bean using @Configuration + @Bean"
- ▸"Conditionally activate the Kakao payment module using @ConditionalOnProperty"
Why This Saves Tokens
Without understanding the concepts, you have to follow up every AI answer with "what does that mean?" — and that follow-up costs tokens. Learn the concept once, and the conversation wraps up in a single exchange.