Spring Framework 시리즈 2화 – @Component와 @Autowired로 의존성 주입 구현하기
Spring의 의존성 주입(DI)을 구성하는 기본 애노테이션인 @Component와 @Autowired의 사용법과 주의사항을 실제 코드 예제와 함께 알아봅니다.
의존성 주입(DI)은 왜 중요한가?
Spring에서 **DI(Dependency Injection)**는 객체 간 의존 관계를 외부에서 주입받는 방식으로 구성해 유연성과 테스트 용이성을 확보합니다.
직접 new로 객체를 생성하는 방식은 강한 결합을 유발하며, 테스트 시 Mock 객체를 주입하기도 어렵습니다.
Spring은 이를 해결하기 위해 주요 애노테이션들을 제공합니다:
- @Component: 스프링이 관리할 객체(빈)를 선언
- @Autowired: 스프링 컨테이너에 등록된 빈을 자동으로 주입
실습 예제 – 자동차와 엔진
1. 의존 관계 정의
// Engine.java
package com.example.springcore;
import org.springframework.stereotype.Component;
@Component
public class Engine {
public void start() {
System.out.println("Engine started");
}
}
// Car.java
package com.example.springcore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Car {
private final Engine engine;
@Autowired // 생성자 주입
public Car(Engine engine) {
this.engine = engine;
}
public void drive() {
engine.start();
System.out.println("Car is moving");
}
}
2. 실행 클래스
// SpringCoreApplication.java
package com.example.springcore;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
public class SpringCoreApplication implements CommandLineRunner {
private final Car car;
public SpringCoreApplication(Car car) {
this.car = car;
}
public static void main(String[] args) {
SpringApplication.run(SpringCoreApplication.class, args);
}
@Override
public void run(String... args) {
car.drive();
}
}
이렇게 하면 스프링이 Engine을 자동으로 생성하고, Car 객체 생성 시 의존성을 주입해줍니다. 개발자는 직접 new 키워드를 사용하지 않아도 됩니다.
@Component의 핵심 정리
항목 설명
역할 | 해당 클래스를 빈으로 등록함 |
기본 이름 | 클래스명을 소문자로 시작 (engine, car) |
스캔 범위 | @ComponentScan이 선언된 위치부터 하위 패키지만 스캔 |
※ 클래스가 하위 패키지에 없다면, 명시적으로 @ComponentScan("패키지명") 설정이 필요합니다.
@Autowired의 3가지 방식
Spring은 다음 3가지 방식으로 DI를 지원합니다:
- 생성자 주입 (추천)
- 불변성 보장
- 테스트 용이
- 필드가 final이어야 함
- 필드 주입
@Autowired
private Engine engine;
- 가장 간단하지만, 테스트에서 Mock 주입이 어려워 비권장
- 세터 주입
private Engine engine;
@Autowired
public void setEngine(Engine engine) {
this.engine = engine;
}
- 선택적인 의존성에 사용 가능
- 주로 Configuration 클래스에서 활용
@Autowired 사용 시 주의사항
- 빈이 없으면 오류 발생
→ 기본적으로 필수 빈으로 간주되어, 주입 대상이 없으면 NoSuchBeanDefinitionException이 발생합니다. - ✅ 해결 방법: @Autowired(required = false) 또는 Optional<Engine>
- 순환 참조 주의
→ 두 객체가 서로를 주입할 경우 UnsatisfiedDependencyException 발생 - 스프링이 관리하지 않는 객체에는 작동하지 않음
→ 반드시 @Component, @Configuration, 혹은 Bean으로 등록된 객체에서만 동작
빈 수동 등록 – @Bean
클래스를 수정할 수 없거나 외부 라이브러리인 경우에는 @Bean을 활용해 수동 등록이 가능합니다.
@Configuration
public class AppConfig {
@Bean
public Engine engine() {
return new Engine();
}
}
마무리 – @Component와 @Autowired는 Spring Core의 출발점
Spring을 처음 학습하거나, 기존 Spring Boot 프로젝트에서 내부 구조를 이해하고 싶은 개발자라면 @Component와 @Autowired의 정확한 작동 원리를 아는 것이 중요합니다.
이 두 애노테이션은 단순한 편의 기능이 아니라 스프링 컨테이너의 DI 구조를 이루는 핵심 구성요소입니다. 앞으로 나올 @Value, @Profile, @Configuration 등도 이 기반 위에 쌓인 기능들이죠.
다음 3화에서는 @Configuration과 @Bean을 활용해 Java 기반으로 설정 클래스를 구성하고 빈을 등록하는 방법을 실습해보겠습니다.