상세 컨텐츠

본문 제목

Bean 수동 등록, Bean 주입 우선순위 - @Qualifier와 @Primary

Spring

by aeongiii 2024. 12. 10. 22:07

본문

 

1. Bean 자동 등록

  • @Component를 사용하면 @ComponentScan에 의해 자동으로 Bean에 등록된다.
  • 일반적으로 @Component를 사용하여 자동으로 등록하는 것이 좋다.
    • 프로젝트의 규모가 커지면 등록할 Bean이 매우 많아지기 때문이다.
    • 특히, 비즈니스 로직 관련 클래스는 @Controller, @Service 와 같은 애너테이션을 사용해야 개발생산성에 유리하다.

 

 

2. Bean 수동 등록

  • 기술적인 문제공통적인 관심사를 처리할 때 사용한다.
  • 비즈니스 로직보다는 Bean의 수가 적기 때문에 수동으로 등록할 만 하다.
  • 기술 지원 Bean : 비즈니스 로직을 지원하는 부가적, 공통적 기능
    • Ex) 공통 로그 처리
  • 수동등록 방법
    • Bean으로 등록하고자 하는 객체를 반환하는 메서드를 선언하고 @Bean 붙이기
    • 해당 메서드가 속한 클래스에 @Configuration
    • 스프링 서버가 뜰 때 Spring IoC Container에 저장된다.
    • 빈의 이름은 passwordEncoder이다. (첫글자 소문자로 변환되어 저장됨)
@Configuration
public class PasswordConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
  • 수동 등록한 passwordEncoder 빈을 사용해보자.
    • 아래 코드를 보면, 진짜 비밀번호(문자열)를 암호화한 뒤, 로그인을 시도하는 비밀번호(문자열)과 비교한다.
    • 비교 결과에 따라 True / False를 반환한다.
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;

@SpringBootTest
public class PasswordEncoderTest {

    @Autowired
    PasswordEncoder passwordEncoder;

    @Test
    @DisplayName("수동 등록한 passwordEncoder를 주입 받아와 문자열 암호화")
    void test1() {
        String password = "Robbie's password";

        // 암호화
        String encodePassword = passwordEncoder.encode(password);
        System.out.println("encodePassword = " + encodePassword);

        String inputPassword = "Robbie";

        // 해시된 비밀번호와 사용자가 입력한 비밀번호를 해싱한 값을 비교
        boolean matches = passwordEncoder.matches(inputPassword, encodePassword);
        System.out.println("matches = " + matches); // 암호화할 때 사용된 값과 다른 문자열과 비교했기 때문에 false
    }
}

 

 

1) 같은 타입의 Bean이 여러 개일 경우 - 주입 우선순위

  • Food라는 인터페이스를 만들고, Food의 구현체인 Chicken과 Pizza를 만들어보자.
  • 그리고 테스트를 실행해보자.
package com.sparta.springauth.food;

public interface Food {
    void eat();
}
package com.sparta.springauth.food;

import org.springframework.stereotype.Component;

@Component
public class Chicken implements Food {
    @Override
    public void eat() {
        System.out.println("치킨을 먹습니다.");
    }
}
package com.sparta.springauth.food;

import org.springframework.stereotype.Component;

@Component
public class Pizza implements Food {
    @Override
    public void eat() {
        System.out.println("피자를 먹습니다.");
    }
}
  • 이때 테스트에는 어떻게 주입받아와야 할까?
  • 3가지 방법이 있다.

(1) 등록된 Bean이름을 직접 명시한다.

  • @SpringBootTest
    public class BeanTest {
    
        @Autowired
        Food pizza; // ===> 직접 Pizza라고 명시함
        
        @Autowired
        Food chicken;
        
    }
    

    • @Autowired가 기본적으로는 Bean의 타입(=Food)로 주입한다.
    • 그러나 Food로 연결되지 않을 경우 Bean 이름 (= pizza, chicken)으로 찾아 주입한다.

(2) @Primary를 사용한다.

  • 같은 타입의 Bean이 여러개 있더라도 우선적으로 주입된다.
@Component
@Primary  // ====> 우선순위로 지정하고싶은 빈에 붙인다.
public class Chicken implements Food {
    @Override
    public void eat() {
        System.out.println("치킨을 먹습니다.");
    }
}
@SpringBootTest
public class BeanTest {
    @Autowired
    Food food;  // 우선순위를 지정했으므로, food로 써도 된다.
}

 

 

(3) @Qualifier를 사용한다.

  • @Qualifier("pizza") 에서 이름이 똑같아야 주입에 성공한다.
@Component
@Qualifier("pizza")  // ====> Pizza 클래스에 @Qualifier 추가
public class Pizza implements Food {
    @Override
    public void eat() {
        System.out.println("피자를 먹습니다.");
    }
}
@SpringBootTest
public class BeanTest {

    @Autowired
    @Qualifier("pizza")  // ====> 주입하고자 하는 필드에도 똑같이 추가.
    Food food;
}

 

 

그렇다면, 같은 타입에 Bean에 @Primary와 @Qualifier가 동시에 적용되어 있다면?

 

 

3. @Primary와 @Qualifier 의 우선순위

  • @Qualifier의 우선순위가 더 높다.
  • 그러나 @Qualifier는 양쪽에 모두 코드를 추가해야해서 번거롭다.

따라서, 같은 타입의 Bean이 여러개 있을 때는

  • 범용적으로 사용하는 Bean 객체 → @Primary를 사용
  • 지역적으로만 사용하는 Bean 객체 → @Qualifier를 사용 (번거로움 최소화)

 

 

참고 : 스프링에서는 주로 좁은 범위의 설정이 더 우선순위가 높다.

관련글 더보기