기술과 산업/언어 및 프레임워크

Spring Framework 시리즈 2화 – @Component와 @Autowired로 의존성 주입 구현하기

B컷개발자 2025. 5. 20. 12:49
728x90

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를 지원합니다:

  1. 생성자 주입 (추천)
    • 불변성 보장
    • 테스트 용이
    • 필드가 final이어야 함
  2. 필드 주입
@Autowired
private Engine engine;
  • 가장 간단하지만, 테스트에서 Mock 주입이 어려워 비권장
  1. 세터 주입
private Engine engine;

@Autowired
public void setEngine(Engine engine) {
    this.engine = engine;
}
  • 선택적인 의존성에 사용 가능
  • 주로 Configuration 클래스에서 활용

@Autowired 사용 시 주의사항

  1. 빈이 없으면 오류 발생
    → 기본적으로 필수 빈으로 간주되어, 주입 대상이 없으면 NoSuchBeanDefinitionException이 발생합니다.
  2. ✅ 해결 방법: @Autowired(required = false) 또는 Optional<Engine>
  3. 순환 참조 주의
    → 두 객체가 서로를 주입할 경우 UnsatisfiedDependencyException 발생
  4. 스프링이 관리하지 않는 객체에는 작동하지 않음
    → 반드시 @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 기반으로 설정 클래스를 구성하고 빈을 등록하는 방법을 실습해보겠습니다.

728x90