의존성 주입 (Dependency Injection)

서버란·2024년 11월 4일

스프링

목록 보기
2/4

Spring에서 @Autowired는 Spring 컨테이너가 관리하는 Bean을 자동으로 주입해주는 어노테이션입니다. Bean이 필요한 클래스에 @Autowired를 통해 객체를 주입할 수 있으며, 이때 Constructor Injection, Setter Injection, Field Injection 방식으로 주입할 수 있습니다. 또한 여러 Bean 중 특정 Bean을 주입할 때는 @Primary와 @Qualifier 어노테이션을 활용합니다.

1. 객체 주입 방법

1.1 Constructor Injection (생성자 주입)

  • 방식: 생성자를 통해 Bean을 주입합니다.

장점:

  • 불변성 보장: 생성자를 통해 객체가 생성되면서 필요한 의존성을 모두 주입받으므로, 이후 변경되지 않는 불변 객체를 생성할 수 있습니다.
  • 테스트 용이성: 주입할 객체를 명확히 전달하기 때문에 테스트 시 Mock 객체 주입이 용이합니다.
    사용 예:
@Service
public class MyService {
    private final MyRepository myRepository;

    @Autowired  // 생성자 주입
    public MyService(MyRepository myRepository) {
        this.myRepository = myRepository;
    }
}
  • 주의: 생성자가 하나뿐인 경우 @Autowired 어노테이션을 생략할 수 있으며, Spring이 자동으로 주입합니다.

1.2 Setter Injection (세터 메서드 주입)

  • 방식: 필드를 초기화할 때 사용하는 Setter 메서드를 통해 Bean을 주입합니다.

장점:

  • 선택적 의존성 주입: 필요할 때만 객체를 주입받도록 할 수 있어 객체의 의존성이 선택적일 때 적합합니다.
  • 유연성: 특정 상황에서만 다른 객체를 설정할 때 유용하게 사용됩니다.
    단점:
  • 불변성 약화: 객체 생성 후에도 주입된 Bean이 변경될 수 있어 불변성을 보장하지 않습니다.

사용 예:

@Service
public class MyService {
    private MyRepository myRepository;

    @Autowired  // Setter 주입
    public void setMyRepository(MyRepository myRepository) {
        this.myRepository = myRepository;
    }
}

1.3 Field Injection (필드 주입)

  • 방식: 직접 필드에 @Autowired를 붙여 Bean을 주입합니다.

장점:

  • 간결함: 코드가 간단해지고, 세터 메서드나 생성자를 만들 필요가 없어 직관적입니다.

단점:

  • 테스트 어려움: 직접적으로 주입이 불가능하므로 테스트에서 필드를 초기화하기 어려워지는 경우가 많습니다.
  • 캡슐화 약화: 필드가 외부로 드러나기 때문에 객체 지향의 원칙인 캡슐화를 약화시킵니다.

사용 예:

@Service
public class MyService {
    @Autowired  // 필드 주입
    private MyRepository myRepository;
}

추천: 일반적으로 생성자 주입이 가장 권장되는 방식입니다. 생성자 주입은 객체를 불변하게 유지하며 테스트에 용이하기 때문에 스프링에서 권장하는 주입 방법입니다. 필드 주입은 주로 테스트를 위해 간단하게 사용하거나, 기존 코드에 간단히 Bean을 추가할 때 유용하지만, 새로운 코드에서는 되도록 지양하는 것이 좋습니다.

2. @Primary와 @Qualifier를 이용한 Bean 선택

Spring 컨텍스트에 동일한 타입의 여러 Bean이 있는 경우, 주입 시점에 어떤 Bean을 사용할지 모호해질 수 있습니다. 이를 해결하기 위해 @Primary와 @Qualifier를 사용할 수 있습니다.

2.1 @Primary

  • 설명: 동일한 타입의 여러 Bean이 있을 때 우선순위를 정할 수 있도록 합니다. @Primary를 사용하면 기본적으로 우선 주입될 Bean을 설정할 수 있습니다.

사용 예:

@Repository
@Primary  // 우선적으로 선택될 Bean
public class PrimaryRepository implements MyRepository {
    // 구현 코드
}

@Repository
public class SecondaryRepository implements MyRepository {
    // 구현 코드
}
  • 작동 방식: @Primary가 붙은 Bean이 동일한 타입의 다른 Bean보다 우선하여 주입됩니다. 그러나 @Qualifier가 지정된 경우, @Primary보다 @Qualifier가 우선됩니다.

2.2 @Qualifier

  • 설명: @Qualifier는 특정 Bean의 이름을 지정하여 주입할 Bean을 명확하게 지정할 수 있습니다. 이를 통해 @Primary보다 더 세밀하게 Bean을 구분할 수 있습니다.

사용 예:

@Repository("primaryRepository")
public class PrimaryRepository implements MyRepository {
    // 구현 코드
}

@Repository("secondaryRepository")
public class SecondaryRepository implements MyRepository {
    // 구현 코드
}

@Service
public class MyService {
    private final MyRepository myRepository;

    @Autowired
    public MyService(@Qualifier("secondaryRepository") MyRepository myRepository) {
        this.myRepository = myRepository;
    }
}
  • 작동 방식: @Qualifier를 통해 특정 Bean 이름을 명시적으로 지정하여 Bean을 주입합니다. @Primary 설정이 있더라도, @Qualifier가 우선 적용되므로 원하는 Bean을 정확히 지정할 수 있습니다.

요약

주입 방식설명장점단점
Constructor Injection생성자를 통해 주입 (가장 권장되는 방식)불변성 보장, 테스트 용이의존성이 많아질 경우 생성자 코드가 길어질 수 있음
Setter InjectionSetter 메서드를 통해 주입선택적 주입 가능, 유연성불변성 약화, 테스트 시 추가 설정 필요
Field Injection필드에 직접 주입 (간편하지만 비권장)코드 간결화테스트 어려움, 캡슐화 약화

@Primary와 @Qualifier

  • @Primary: 동일한 타입의 여러 Bean 중 기본적으로 우선 주입할 Bean을 지정.
  • @Qualifier: Bean 이름을 명시하여 특정 Bean을 주입할 때 사용, @Primary보다 우선 순위가 높음.

이러한 주입 방식과 어노테이션을 활용하면 다양한 상황에서 유연하게 의존성을 주입할 수 있으며, 코드의 가독성과 유지보수성을 높일 수 있습니다.


Q1: Constructor Injection이 권장되는 이유는 무엇이며, 어떤 경우에 Field Injection을 사용할 수 있을까요?

Constructor Injection이 권장되는 이유:

  • 불변성 보장: 생성자를 통해 필요한 모든 의존성을 주입받아 객체가 생성되므로, 생성 이후에 의존성이 변경되지 않아 객체의 불변성을 유지할 수 있습니다.
  • 테스트 용이성: 생성자 주입을 사용하면 주입할 객체를 명시적으로 전달하므로, 테스트 시 Mock 객체를 쉽게 주입할 수 있습니다.
  • 의존성 확인 용이성: 객체의 의존성을 생성자에서 모두 주입받기 때문에, 어떤 의존성이 필요한지 한눈에 파악할 수 있어 유지보수에 용이합니다.

Field Injection을 사용할 수 있는 경우:

  • 간단한 테스트 클래스나 임시 데모 코드에서 빠르게 Bean을 주입해야 할 때 간단하게 사용할 수 있습니다.

  • 기존 코드에 작은 수정이 필요하거나, 주입할 Bean이 많지 않아 생성자나 세터 주입이 불필요하게 복잡해지는 경우에 사용하기도 합니다.

  • 단, 실제 애플리케이션 코드에서는 생성자 주입을 사용하는 것이 더 권장되며, 필드 주입은 주로 테스트 코드에서 간단히 사용됩니다.

Q2: @Primary와 @Qualifier의 우선순위를 실제 코드에서 확인해보고 싶습니다. 코드 작성 방법은 어떻게 되나요?

  • @Primary와 @Qualifier를 활용해 Bean 우선순위를 설정해 보겠습니다.
  • @Qualifier는 @Primary보다 우선 적용됩니다.

Repository 인터페이스 정의:

public interface MyRepository {
    String getData();
}

PrimaryRepository와 SecondaryRepository 구현 클래스 생성:

@Repository
@Primary  // 기본 우선순위 설정
public class PrimaryRepository implements MyRepository {
    @Override
    public String getData() {
        return "Primary Repository Data";
    }
}

@Repository("secondaryRepository")  // @Qualifier에 사용할 Bean 이름 설정
public class SecondaryRepository implements MyRepository {
    @Override
    public String getData() {
        return "Secondary Repository Data";
    }
}

@Qualifier 사용 예제:

@Service
public class MyService {
    private final MyRepository myRepository;

    // @Qualifier를 사용하여 secondaryRepository를 명시적으로 주입
    @Autowired
    public MyService(@Qualifier("secondaryRepository") MyRepository myRepository) {
        this.myRepository = myRepository;
    }

    public String getData() {
        return myRepository.getData();
    }
}

테스트 실행:

  • 이 예제에서 @Qualifier("secondaryRepository")가 적용된 SecondaryRepository가 주입됩니다.
  • 만약 @Qualifier를 제거하면, @Primary가 붙은 PrimaryRepository가 기본으로 주입됩니다.
  • 이처럼 @Qualifier는 명시적으로 Bean을 지정할 때 사용되고, @Primary는 기본적으로 우선순위를 지정할 때 사용됩니다.

Q3: 여러 Bean이 생성되었을 때 특정 상황에 따라 동적으로 Bean을 선택하여 주입하려면 어떤 방법이 있을까요?

  • 여러 Bean 중 특정 상황에 따라 동적으로 Bean을 선택하는 방법으로 @Qualifier와 @Autowired를 조합하여 Map으로 주입하거나, Spring의 ApplicationContext를 활용하여 Bean을 조회하는 방법을 사용할 수 있습니다.

@Qualifier와 Map을 활용한 동적 주입:

  • 여러 Bean을 Map으로 주입하여 필요할 때 키 값으로 특정 Bean을 선택하는 방법입니다.
@Service
public class MyService {
    private final Map<String, MyRepository> repositoryMap;

    @Autowired
    public MyService(Map<String, MyRepository> repositoryMap) {
        this.repositoryMap = repositoryMap;
    }

    public MyRepository getRepository(String beanName) {
        return repositoryMap.get(beanName);
    }
}

이 코드에서 repositoryMap.get("beanName")을 통해 동적으로 Bean을 선택할 수 있습니다. 예를 들어, "primaryRepository"나 "secondaryRepository"를 키로 전달하여 해당 Bean을 얻을 수 있습니다.

ApplicationContext 활용:

  • Spring의 ApplicationContext를 주입받아 특정 상황에서 Bean을 동적으로 조회할 수 있습니다.
@Service
public class MyService {
    private final ApplicationContext applicationContext;

    @Autowired
    public MyService(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public MyRepository getRepository(String beanName) {
        return applicationContext.getBean(beanName, MyRepository.class);
    }
}
  • applicationContext.getBean("beanName", MyRepository.class)를 통해 이름으로 Bean을 조회하며, 이를 통해 특정 상황에 따라 동적으로 Bean을 선택할 수 있습니다.

이 두 방법 모두 유연하게 Bean을 선택할 수 있어 상황에 따라 알맞은 Bean을 주입하여 사용할 수 있습니다.

profile
백엔드에서 서버엔지니어가 된 사람

0개의 댓글