개발/spring

스프링 빈 주입 시 우선 순위

방푸린 2024. 11. 12. 17:35
반응형

스프링에서 "빈(Bean)"이란?

  • 스프링 IoC(Inversion of Control) 컨테이너에 의해 관리되는 객체
  • 빈은 스프링 애플리케이션의 핵심 구성 요소로, 애플리케이션에서 필요한 객체를 컨테이너가 생성하고 관리
  • 빈은 컨테이너에 의해 자동으로 주입됨
 
 

빈 주입 시 우선순위와 규칙

 

  1. 빈 이름 우선순위:
    • @Bean(name="beanName")을 사용해 명시적으로 이름을 지정하면, 이 이름이 가장 높은 우선순위를 갖음
    • 이름이 지정되지 않은 경우, 메서드 이름이 기본적으로 빈 이름으로 사용
    • 같은 이름을 가진 빈이 여러 개 등록되면, 스프링은 중복으로 인식해 오류를 발생시키므로 빈 이름은 고유해야
  2. 타입 우선순위:
    • 빈 주입 시 이름을 명시하지 않고 타입을 사용하여 주입하면, 스프링은 해당 타입에 맞는 빈을 찾음
    • 스프링 컨텍스트에 같은 타입의 빈이 여러 개 있을 경우, @Primary 어노테이션을 사용하여 우선순위를 지정할 수 있음. @Primary로 지정된 빈이 기본적으로 선택됨
    • @Qualifier("beanName")을 사용하여 특정 이름의 빈을 지정하면, @Primary와 관계없이 해당 이름의 빈이 주입
  3. @Primary와 @Qualifier 우선순위:
    • @Primary는 같은 타입의 빈이 여러 개 있을 때 기본 빈을 지정하는 데 사용됨
    • @Qualifier가 지정되면 @Primary보다 우선하여 지정된 이름의 빈을 주입. 즉, @Qualifier가 있으면 @Primary가 무시됨!
  4. 함수명과 빈 등록 순서:
    • @Bean을 사용하는 경우, 메서드 이름이 빈 이름이 됨. 따라서 @Bean(name="beanName")을 명시하지 않으면 메서드명이 빈 이름으로 사용됨.
    • XML 설정을 사용할 경우, <bean> 태그에서 id 속성으로 이름을 지정하지 않으면 클래스 이름의 첫 글자를 소문자로 변환한 이름이 빈 이름으로 사용됨.
    • 빈 등록 순서가 중요할 때는 @DependsOn을 사용하여 특정 빈이 먼저 초기화되도록 순서를 지정할 수 있음.

 

 

사용 예시

@Configuration
public class AppConfig {

    @Bean
    public Service defaultService() {
        return new ServiceImpl();
    }

    @Bean
    @Primary
    public Service primaryService() {
        return new PrimaryServiceImpl();
    }

    @Bean(name = "customService")
    public Service customNamedService() {
        return new CustomServiceImpl();
    }
}

이렇게 등록된 빈들을 사용할 때는 아래와 같이 다양한 방법으로 사용할 수 있다.

주의해야 할 점은 이름만 같게 해 준다고 자동으로 주입되는 게 아니라는 사실...

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class ServiceConsumer {

    private final Service defaultService;
    private final Service primaryService;
    private final Service customService;

    // 기본 주입: @Primary가 지정된 빈이 주입됨
    @Autowired
    public ServiceConsumer(Service primaryService) {
        this.primaryService = primaryService;
    }

    // @Qualifier를 사용하여 이름으로 주입
    @Autowired //setter 방식으로 주입 시 필수
	public void setDefaultService(@Qualifier("defaultService") Service defaultService) {
    this.defaultService = defaultService;
}


    // @Qualifier를 사용하여 특정 이름의 빈 주입
    @Autowired
    @Qualifier("customService") //메서드에 붙여도 되지만 인자에 붙이는게 더 읽기 쉬움
    public void setCustomService(Service customService) {
        this.customService = customService;
    }

    public void useServices() {
        primaryService.execute();    // PrimaryServiceImpl이 실행됨
        defaultService.execute();    // ServiceImpl이 실행됨
        customService.execute();     // CustomServiceImpl이 실행됨
    }
}

하지만 내가 선호하는 방식은 아니다. 주입하는 코드가 너무 난잡하고 길다.. 나라면 아래처럼 생성자 방식으로 주입할 것이다...

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class ServiceConsumer {

    private final Service defaultService;
    private final Service primaryService;
    private final Service customService;

    // 필드에 특정 빈을 주입할 수 있도록 생성자 파라미터에 @Qualifier 추가
    public ServiceConsumer(
        @Qualifier("defaultService") Service defaultService,
        @Qualifier("primaryService") Service primaryService, //@Primary라서 @Qualifier 없어도 됨
        @Qualifier("customService") Service customService
    ) {
        this.defaultService = defaultService;
        this.primaryService = primaryService;
        this.customService = customService;
    }

    // 메서드 예시
    public void useServices() {
        defaultService.execute();
        primaryService.execute();
        customService.execute();
    }
}

위처럼 해도 되지만 사실 진짜 원하는건 아래와 같이 롬복을 사용한 방식이다.

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class ServiceConsumer {

    private final Service primaryService;
    @Qualifier("defaultService")
    private final Service defaultService;
    @Qualifier("customService")
    private final Service customService;

...
}

하지만 이러면 에러가 난다. 

Lombok does not copy the annotation 'org.springframework.beans.factory.annotation.Qualifier' into the constructor

 

꼭 써야한다면..

src/main/java 하위에(com.xx~과 같은 위치) lombok.config 파일을 생성하고 아래 내용을 작성한다.

# lombok.config
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier

이렇게 설정하면 Qualifier를 복사하여 어노테이션을 롬복이 컴파일할 때 만드는 파일에 추가해 준다.

728x90
반응형