본문 바로가기
코딩log/Spring Framework

[Spring Framework]Autowired와 자동 의존관계 주입의 옵션들

by 벨크 2023. 4. 17.
반응형

  @ComponentScan과 @Component로 원하는 객체들을 자동으로 스프링 Bean으로 등록할 수 있습니다. 그럼 자동으로 등록된 Bean에 자동으로 의존관계를 주입하는 것에 대해 조금 더 디테일하게 알아보도록 하겠습니다.


Autowired와 자동 의존관계 주입의 옵션들

Autowired와 자동 의존관계 주입의 옵션들

 

Autowired가 붙어있으나 스프링으로 등록된 Bean type이 없을 때

 

  먼저 Autowired가 붙어서 자동 의존관계 주입 대상이나 의존관계 주입을 하기 위해 대상 type을 조회했을 때 없는 경우입니다. 일반적으로 @Autowired가 붙어 있는데 스프링빈에 의존관계를 주입할 Bean이 없으면 에러가 발생합니다. 하지만 때때로 특정 필드에만 의존관계 주입을 해야 하는 경우도 발생합니다. 그런 경우에는 아래의 3가지 방식으로 optional 하게 의존관계를 주입할 수 있습니다.

 

@Autowired(required = false)
public void noBeanTest(Car car) {
    System.out.println("car: " + car);
}

@Autowired
public void noBeanTest2(@Nullable Car car) {
    System.out.println("car: " + car);
}

@Autowired
public void noBeanTest3(Optional<Car> car) {
    System.out.println("car: " + car);
}

 

 위 3가지 방법은 각각 다음과 같이 동작합니다.

  • Autowired(required = false): 의존관계가 없으면 method 자체가 호출되지 않는다.
  • @Nullable : 의존관계가 없으면(해당 타입의 Bean이 없으면), null이 변수에 들어간다.
  • Optional < > : Java 8 이상에서만 사용 가능하다. Bean이 없으면 Optional.empty 가 변수에 들어간다.

 

생성자 주입을 선택해야 하는 이유

 

  의존 관계 자동 주입에는 3가지 방법이 있습니다. "생성자 주입, Setter(수정자) 주입, 필드(변수 직접) 주입" 이렇게 3가지입니다. 스프링은 이 3가지 방법 중에 생성자 주입을 권장하는데요. 그 이유는 생성자 주입 외의 다른 방법들은 의존관계가 변경될 수 있기 때문입니다. 대부분의 의존관계는 애플리케이션이 동작하는 동안 불변해야 합니다. 따라서 다른 개발자가 의존관계를 임의로 변경하는 경우가 발생하는 것은 좋지 않습니다.

 

  추가로  final을 사용한 필드는 생성자에서만 값을 세팅할 수 있습니다. 따라서 final이 붙은 필드에 의존관계를 주입하고 싶은 경우에는 생성자 주입만을 사용해야 합니다. final을 사용했는데 생성자에서 값을 세팅하지 않거나 의존관계를 주입하지 않은 경우에는 컴파일 오류가 발생합니다. 장애 예방 차원에서 final을 사용한 필드를 선언하고, 생성자 의존관계 주입을 하는 것이 좋습니다.

 

같은 type의 Bean이 2개 이상 존재 할 때 자동의존관계 주입

 

  @Autowired는 type으로 Bean을 조회하여 의존관계를 주입합니다. 그런데 필요에 따라 같은 type의 Bean이 두 개 이상 존재하는 경우가 있습니다. 로컬에서 개발할 때는 DB를 메모리에 구현하고 서버 환경에서 테스트 시에는 실제 DBMS에 연결하는 식으로 Bean을 사용할 수도 있고, Client의 필요에 따라 각각 다른 구현체가 필요할 수도 있습니다. 앞선 예를 사용하자면, 슈퍼카와 SUV를 모두 가지고 있는 운전자가 필요에 따라 각각 다른 자동차를 사용할 수 있는 거처럼 말이죠.

 

  하지만 같은 type의 Bean이 두 개 이상 등록되어 있는 경우 @Autowired에서 빌드 오류가 발생합니다. 그렇다면 이런 경우에는 어떻게 해야 할까요?

 

//1.필드명을 빈 이름으로 변경한다.
@Autowired
public Driver(Car superCar){
...
}

//2.@Qualifier를 사용한다.
@Component
@Qualifier("mainCar")
public class SuperCar implements Car {
..
}

@Autowired
public Dirver(@Qualifier("mainCar") Car car){
....
}

//3.@Primary를 사용한다.
@Component
@Primary
public class SuperCar implements Car {
...
}

 

  보통은 @Primary를 사용하여 우선순위가 1순위인 Bean을 등록하여 사용하고, Qualifier를 보조로 (2순위 이하) 사용합니다. @Qualifier의 경우에는 Bean 타입이나 이름을 비교하는 것이 아니고 @Qualifier 이름만 가지고 Bean을 찾습니다. 따라서 Qualifier를 찾는 용도로만 사용하는 게 좋습니다.

 

참고: @Qualifier를 조금 더 간단하게 사용하고 싶을 때는 @Qualifier를 사용하고 싶은 이름의 Annotation을 만들어서 사용하면 좋습니다. 오타 등의 실수를 방지할 수 있습니다.

 

같은 type의 다양한 Bean을 사용해야 하는 경우

 

  서비스를 개발하다 보면 같은 type의 여러 구현체를 사용해야 하는 경우가 있습니다. 대표적으로 클라이언트가 특정 구현체 중에 하나를 선택할 수 있는 서비스들 말이죠. 수동 의존관계 주입을 할 경우에는 클라이언트가 필요한 Bean을 선택하여 의존관계를 주입하면 되지만, 자동으로 Bean을 등록하고 의존관계 주입을 하는 경우에는 어떻게 해야 할까요?

 

  Spring은 그래서 Map과 List 형식의 의존관계 주입을 제공하고 있습니다. 같은 타입의 Bean 전체를 Map이나 List로 주입받아서 필요한 구현체를 선택해서 사용할 수 있습니다.

 

public class SelectCar {

    private final Map<String, Car> carMap;
    private final List<Car> carList;
    
    @Autowired
    public selectCar(Map<Car, String> carMap, List<Car> carList){
        this.carMap = carMap;
        this.carList = carList;
    }
    
    public void dirveSelectedCar(String carCode){
        Car car = carMap.get(carCode);
        
        car.drive();
    }
 }
 
 public static void main(String[] args) {
     ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, SelectCar.class);
     
     SelectCar selectCar = ac.getBean(SelectCar.class);
     selectCar.driveSelectedCar("superCar");
 }

 

  위처럼 Bean으로 등록된 같은 타입의 구현체들을 Map 형식으로 주입받아서 클라이언트에서 구현체를 선택하는 전략 패턴들을 구현할 수 있습니다.

 


  @Autowired를 이용한 자동 의존관계 주입에는 다양한 규칙이 있습니다. 기본적으로 편리한 자동 의존관계 주입을 사용하는 게 좋습니다. 다형성을 활용하는 업무 로직 같은 경우에는 수동 등록을 고민하여 적절히 사용하시면 됩니다.

반응형

댓글