CS

[CS] 컴포넌트 스캔과 의존관계 주입

sson-coding 2026. 1. 27. 21:28

지금까지 스프링 빈을 등록할 때 자바 코드의 @Bean 이나 XML의 등을 통해서
설정 정보에 직접 등록한 스프링 빈을 나열했다.

이렇게 등록해야 할 스프링 빈이 수십,수백개가 되면 일일이 등록하기 귀찮고, 설정 정보가 커지고, 누락하는 문제도 발생한다.

그래서 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔을 제공한다.
또한 의존관계도 자동으로 주입하는 @Autowired 기능도 제공한다.


컴포넌트 스캔

컴포넌트 스캔이란?

스프링 컨테이너가 클래스패스를 탐색하면서 특정 애노테이션이 붙은 클래스를 자동으로 스프링 빈으로 등록하는 기능

즉, 개발자가 일일이 등록하지 않아도 스프링이 스프링 빈으로 등록해준다.


컴포넌트 스캔 장점

  1. 설정 코드 감소
    • 수십,수백 개의 빈을 일일이 등록할 필요 없음
    • 설정 파일이 단순해짐
  2. 유지보수 용이
    • 클래스 추가 → 애노테이션만 붙이면 자동 등록
  3. 계층 구조 명확
    • Controller/Service/Repository 역할이 드러남

컴포넌트 스캔 사용법

@Configuration
@ComponentScan
public class AppConfig {
}

컴포넌트 스캔을 사용하려면 먼저 @ComponentScan 을 설정 정보에 붙여주면 된다.

컴포넌트 스캔을 사용하면 @Configuration 이 붙은 설정 클래스도 자동으로 등록된다.
실제로 @Configuration 소스 코드를 열어보면 @Component 애노테이션이 붙어있다.

위와 같이 설정하면 스프링은 설정 클래스가 위치한 패키지부터 하위 패키지까지 모두 탐색한다.


컴포넌트 스캔 대상 애노테이션

컴포넌트 스캔은 다음 애노테이션이 붙은 클래스를 스프링 빈으로 등록한다.

  • @Component : 컴포넌트 스캔의 기본 대상
  • @Controller : 웹 컨트롤러
  • @Service : 비즈니스 로직
  • @Repository : 데이터 접근 계층
  • @Configuration : 설정 클래스

스캔 범위

  • @ComponentScan 어노테이션이 있는 파일의 패키지 아래를 탐색한다.
  • basePackages , basePackageClasses 로 지정도 가능하다.
  • 설정 클래스는 프로젝트 최상단에 두는 것이 일반적이다.
  • Spring Boot 사용시 @SpringBootApplication 에 포함되어 있어서 자동으로 최상단 기준으로 스캔된다.

옵션

  • 특정 어노테이션을 포함/ 제외
    • includeFilters : 컴포넌트 스캔 대상으로 추가
    • excludeFilters : 컴포넌트 스캔 대상에서 제외
  • FilterType
    • ANNOTATION : 기본값, 어노테이션을 인식해 동작
    • ASSIGNABLE_TYPE : 지정한 타입과 자식 타입을 인식해 동작
    • ASPECTJ : AspectJ 패턴 사용
    • REGEX : 정규 표현식
    • CUSTOM : TypeFilter 라는 인터페이스를 구현해서 처리

빈 중복

  • 수동 빈 등록 vs 자동 빈 등록
    • 수동 빈 등록이 우선권을 가짐
  • 자동 빈 등록 vs 자동 빈 등록
    • ConflictBeanDefinitionException 예외 발생

@ComponentScan 동작 방식

  1. 스프링 컨테이너 시작
  2. @ComponentScan 설정 확인
  3. 지정된 패키지 탐색
  4. @Component 계열 애노테이션 발견
  5. 스프링 빈으로 등록
    1. 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용
  6. @Autowired 의존관계 자동 주입
    1. 생성자에 @Autowired 를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입
    2. 기본 조회 전략은 타입이 같은 빈을 찾아서 주입


@Autowired 와 의존성 주입 방식 비교

앞에서 컴포넌트 스캔을 통해 스프링이 자동으로 빈을 등록하는 과정을 살펴봤다.

이제 등록된 빈을 어떻게 연결(DI)할 것인지 알아보자.

스프링은 이를 위해 의존성 주입을 제공하며,
의존관계 주입은 생성자 주입, Setter 주입, 필드 주입, 일반 메서드 주입 4가지 방법이 있다.

의존성 주입이란?

객체가 직접 의존 객체를 생성하지 않고, 외부에서 필요한 의존 객체를 주입받는 것

장점

  • 객체 생성 책임을 외부로 분리
  • 결합도 감소
  • 테스트 용이성 증가

생성자 주입

이름 그대로 생성자를 통해 의존 관계를 주입 받는 방법이다.

@Service
public class OrderService {

    private final MemberRepository memberRepository;

    @Autowired
    public OrderService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

특징

  • 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다.
  • 불변, 필수 의존관계에 사용한다.
  • 생성자가 1개만 있으면 @Autowired 를 생략해도 자동 주입 된다.(스프링 빈에만 해당)

장점

  • 테스트 코드 작성이 쉬움
  • 순환 참조를 컴파일 시점에 발견 가능

Setter 주입

setter 라 불리는 필드이 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법이다.

@Service
public class OrderService {

    private MemberRepository memberRepository;

    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

특징

  • 선택, 변경 가능성이 있는 의존관계에 사용

장점

  • 의존성을 나중에 변경 간능

단점

  • 객체 생성 후에도 값 변경 가능
  • 불변성 보장하지 않음

필드 주입

이름 그대로 필드에 바로 주입하는 방법이다.

@Service
public class OrderService {

    @Autowired
    private MemberRepository memberRepository;
}

특징

  • 코드가 간결

단점

  • 외부에서 변경이 불가능해 테스트 하기 어려움
  • DI 컨테이너에 강하게 의존

일반 메서드 주입

일반 메서드를 통해서 주입 받을 수 있다.

@Component
public class OrderServiceImpl implements OrderService {
     private MemberRepository memberRepository;
     private DiscountPolicy discountPolicy;

     @Autowired
     public void init(MemberRepository memberRepository, DiscountPolicydiscountPolicy) {
         this.memberRepository = memberRepository;
         this.discountPolicy = discountPolicy;
     }
}

특징

  • 한번에 여러 의존성을 주입 받을 수 있다.
  • 일반적으로 잘 사용하지 않는다.

@Autowired 옵션

@Autowired 만 사용하면 required 옵션의 기본값이 true 로 되어 있어서 자동 주입 대상이 없으면 오류가 발생한다.

  • @Autowired(required=false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안됨
  • org.springframework.lang.@Nullable : 자동 주입 대상이 없으면 null 이 입력
  • Optional<> : 자동 주입할 대상이 없으면 Optional.empty 가 입력

주입 선택 - 생성자 주입 권장

과거에는 수정자 주입,필드 주입을 많이 사용했지만
최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다.

이유는 다음과 같다.

  • 대부분의 의존관계 주입은 한 번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다.
  • 수정자 주입을 사용하면 public 으로 열어두어야 한다.
  • 생성자 주입은 객체 생성 시 1회만 호출되므로 이후에 호출되는 일이 없어 불변하게 설계할 수 있다.
  • 단위테스트시 주입 데이터를 누락 했을 때 컴파일 오류가 발생해 어떤 값을 주입해야 하는지 알 수 있다.
  • final 키워드를 통해 안정성 확보 가능하다.

@RequiredArgsConstructor

개발을 해보면, 대부분이 다 불변이고 필드에 final 을 사용하게 된다.
생성자, 주입 받은 값을 대입하는 코드를 만들어야 한다.

의존성이 많아질수록 코드가 길어진다는 단점이 있다.

그럴때 롬복에서 @RequiredArgsConstructor 을 사용하면 된다.

  • final 이 붙은 필드를 모아서 생성자를 자동 생성
  • 생성자 주입 코드 간결화

조회 빈이 2개 이상일 때

@Autowired 는 기본적으로 타입 기준 조회를 한다.
같은 타입의 빈이 2개 이상일 때 문제가 발생한다.

해결방법은 아래와 같이 3가지가 있다.

@Autowired 필드 명 매칭

@Autowired 는 타입 매칭을 시도하고, 이때 여러 빈이 있으면 필드 이름, 파라미터 이름으로 추가 매칭한다.

@Autowired
private DiscountPolicy rateDiscountPolicy

필드 명 매칭은 먼저 타입 매칭을 시도 하고 그 결과에 여러 빈이 있을 때 추가로 동작하는 기능이다.

@Qualifier

@Qualifier 는 추가 구분자를 붙여주는 방법이다.
주입시 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아니다.

@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}

@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
 @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
 this.memberRepository = memberRepository;
 this.discountPolicy = discountPolicy;
}

@Qualifier 끼리 매칭 → 빈 이름 매칭 하고 없으면 NoSuchBeanDefinitionException 예외가 발생한다.

@Primary

@Primary는 우선순위를 정하는 방법이다.
여러 빈이 매칭되면 우선순위를 가진다.

@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}

우선순위

@Primary 는 기본값 처럼 동작하는 것이고, @Qualifier 는 매우 상세하게 동작한다.
따라서 @Qualifier 가 우선권이 높다.

애노테이션 직접 만들기

@Qualifier(”~”) 이렇게 문자를 적으면 컴파일시 타입 체크가 안된다.
이런 문제는 애노테이션을 만들어서 해결할 수 있다.

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, 
ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}

@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {}

//생성자 자동 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) { 
     this.memberRepository = memberRepository;
     this.discountPolicy = discountPolicy;
}
//수정자 자동 주입
@Autowired
public DiscountPolicy setDiscountPolicy(@MainDiscountPolicy DiscountPolicy discountPolicy) {
     this.discountPolicy = discountPolicy;
}

자동, 수동의 올바른 실무 운영 기준

  • 기본은 자동 등록
  • 기술 지원 객체는 수동 등록
    • 기술적인 문제나 공통 관심사(AOP) 를 처리할 때 사용한다.
    • 데이터베이스 연결, 공통 로그 처리 처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술
  • 다형성을 적극 활용하는 핵심 비즈니스 로직은 수동 등록 고려

참고자료

https://ittrue.tistory.com/229

https://velog.io/@neity16/Spring-핵심-원리-기본편-6-컴포넌트-스캔Component-Scan-DI

https://engineerinsight.tistory.com/46

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8?partners=5e1d25b4a61a4d7fa60f5029ad186263&utm_campaign=1233541&utm_medium=referral&utm_source=partners&utm_user=1233541

'CS' 카테고리의 다른 글

[CS] 싱글톤과 싱글톤 컨테이너  (0) 2026.01.27
[CS] 스프링 컨테이너, 스프링 빈  (0) 2026.01.26
[CS] - Spring vs Spring Boot  (1) 2026.01.19
[JWT] - Access Token , Refresh Token 만료  (0) 2026.01.11