의존관계 자동 주입

땡글이·2023년 1월 15일
0

Spring Framework

목록 보기
2/8

의존관계를 주입하는 방법에는 총 4가지가 있다.

  • 생성자 주입
  • 수정자(setter) 주입
  • 필드 주입
  • 일반 메서드 주입

최근에는 의존관계를 주입할 때, 생성자 주입을 많이 사용하고, 수정자 주입, 필드 주입, 일반 메서드 주입은 많이 사용하지 않는다. 또한 스프링을 포함한 DI 프레임워크에서도 생성자 주입을 권장한다. 이유는 뭘까?

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

우선, 스프링 컨테이너에서는 싱글톤 객체로 빈들을 관리한다. 그리고 스프링 컨테이너에서 관리되는 빈 객체들은 여러 스레드에서 접근하고 사용할 수 있기에, 싱글톤 객체는 상태를 유지하고 있으면 안된다. 즉, 필드를 가지고 있으면 안되고 무상태(stateless)로 유지되어야 한다.

대부분의 의존관계 주입은 한 번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다. 즉, 불변해야 한다.

수정자 주입을 사용하면, setXXX 메서드를 public 으로 열어두어야 한다. 일반 메서드 주입도 마찬가지이다.
하지만, 이런 메서드를 열어두게 되면 누군가 실수로 변경하게 되는 불상사가 발생할 수 있다! 이런 설계는 좋은 설계가 아니다.
생성자 주입은 객체를 생성할 때 1번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계할 수 있게 된다.

final 키워드

생성자 주입을 사용하면, final 키워드를 사용할 수 있다. 그래서 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아줄 수 있다. 뿐만 아니라, 추후에 변경되지 않도록 하는 장점도 있다.

조회된 빈이 2개 이상일 때 해결방법

필드 이름 변경

@Autowired을 이용해 의존관계를 주입해줄 수 있다. @Autowired 어노테이션은 타입 매칭을 시도하고, 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가로 매칭한다.

// 의존관계 주입
@Autowired 
private DiscountPolicy discountPolicy;


// 빈 등록
@Component
public class FixDiscountPolicy implements DiscountPolicy {}

@Component
public class RateDiscountPolicy implements DiscountPolicy {}

위처럼 DiscountPolicy 타입의 빈이 2개가 등록되어 있다면, @Autowired DiscountPolicy discountPolicy 로 의존성을 주입받는다면, NonUniqueBeanDefinitionException 오류가 발생한다.

  • 이유는 @Autowired 어노테이션은 맨처음 타입으로 매칭을 시도하는데, 2개 이상의 빈이 조회되었기 때문이다.
  • 하지만, @Autowired는 해당 타입에 여러 빈이 있으면, 필드이름, 파라미터 이름으로 매칭을 시도하기 때문에 해당 문제를 해결할 수 있다.
// 기존코드
@Autowired
private DiscountPolicy discountPolicy;

// 새로운 코드
@Autowired
private DiscountPolicy rateDiscountPolicy;

위처럼 코드를 작성하면, 타입이 DiscountPolicy 이며, 스프링 컨테이너에 rateDiscountPolicy 이름으로 등록된 빈을 조회한다.

@Qualifier 어노테이션 사용

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

@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}

...

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

위처럼 명시해둔 뒤, 주입 시에 @Qualifier 어노테이션을 붙여주고 등록한 이름을 적어준다.

생성자 주입 예시

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

Setter 주입 예시

@Autowired
public DiscountPolicy setDiscountPolicy (@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
	this.discountPolicy = discountPolicy;
}

만약, @Qualifier 어노테이션으로 빈을 조회할 때 못찾게되면 어떻게 될까? 그렇게 될 경우, mainDiscountPolicy 라는 이름의 스프링 빈을 추가로 찾게 된다.

하지만 @Qualifier 어노테이션은 @Qualifier 를 찾는 용도로만 사용하는 것이 명확하고 좋다.

@Qualifier 정리

  1. @Qualifier 끼리 매칭
  2. 빈 이름 매칭
  3. NoSuchBeanDefinitionException 예외 발생

그런데, @Qualifier를 사용하게 되면, 문자(”mainDiscountPolicy”)로 매칭되기 때문에 컴파일시 타입 에러가 체크되지 않기 때문에, 꼭 필요한 경우에만 사용하는 것을 권장한다.

@Primary 사용

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

@Component
public class FixDiscountPolicy implements DiscountPolicy {}

...

@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}

rateDiscountPolicy 가 우선권을 가지도록 설정해두면, discountPolicy 로 접근해도 문제 없이 rateDiscountPolicy 빈이 의존성 주입이 된다.

하지만, 요즘 트렌드는 스프링에 내장된 자동 빈 등록 기능을 사용한다. 스프링은 @Component 뿐만 아니라, @Controller,@Service, @Repository 처럼 계층에 맞추어 일반적인 애플리케이션 로직을 자동으로 스캔할 수 있도록 지원한다. 거기에 더해 스프링 부트는 컴포넌트 스캔을 기본으로 사용하고, 스프링 부트의 다양한 스프링 빈들도 조건이 맞으면 등록하도록 설계했다.

설정정보를 기반으로 애플리케이션을 구성하는 부분이나 실제 동작하는 부분을 명확하게 나누는 것이 이상적이지만, 개발자 입장에서 스프링 빈을 하나 등록할 때 @Component 만 넣어주면 끝나는 일을 @Configuration 설정정보에 가서 @Bean 을 적고, 객체를 생성하고, 주입할 대상을 일일이 적어주는 과정은 상당히 번거롭다.

@Component@Bean은 언뜻 보면 같은 기능을 하지만, 조금 다르다. @Component클래스를 대상으로 빈을 등록하는 어노테이션이고, @Bean메서드를 대상으로 빈을 등록하는 어노테이션이다.

또 관리할 빈이 많아서 설정정보가 커지면 설정정보를 관리하는 것 자체가 부담이 된다. 그리고 결정적으로 자동 빈 등록을 사용해도 OCP, DIP 원칙을 지킬 수 있다.

수동 빈 등록은 언제 사용하면 좋을까?

애플리케이션 로직은 업무 로직기술 지원 로직으로 나눌 수 있다.

  • 업무 로직 : 웹을 지원하는 컨트롤러, 핵심 비즈니스 로직이 있는 서비스, 데이터 계층의 로직을 처리하는 리포지토리 등이 모두 업무 로직이다. 보통 비즈니스 요구사항을 개발할 때 추가되거나 변경된다.
  • 기술 지원 로직 : 기술적인 문제나 공통관심사(AOP)를 처리할 때 주로 사용된다. 데이터베이스 연결이나, 공통 로그 처리 처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술들이다.

업무 로직은 숫자도 매우 많고, 한 번 개발해야 하면 컨트롤러, 서비스, 리포지토리 처럼 어느정도 유사한 패턴이 있다. 이런 경우 자동 기능을 적극 사용하는 것이 좋다. 문제가 발생해도 어떤 곳에서 문제가 발생했는지 파악하기 쉽다.

하지만, 기술 지원 로직은 업무 로직과 비교해서 그 수가 매우 적고, 보통 애플리케이션 전반에 걸쳐서 광범위하게 영향을 미친다. 그리고 업무로직은 문제가 발생했을 때, 어디가 문제인지 명확하게 잘 드러나지만, 기술 지원 로직은 적용이 잘 되고 있는지 아닌지 조차 파악하기 어려운 경우가 많다. 그래서 이런 기술 지원 로직들은 가급적 수동 빈 등록을 사용해서 명확하게 드러내는 것이 좋다.

업무로직에서도 수동 빈 등록이 유용한 순간은 있다! 조회해야하는 빈이 여러 개 일 경우에는 수동 빈 등록을 해두는 것이 좋다.

@Configuration
public class DiscountPolicyConfig {
	@Bean
	public DiscountPolicy rateDiscountPolicy {
		return new RateDiscountPolicy();
	}
	
	@Bean
	public DiscountPolicy fixDiscountPolicy {
		return new FixDiscountPolicy();
	}
}

위처럼 설정클래스에 빈을 모아두면, "DiscountPolicy 관련 빈이 rateDiscountPolicy와 fixDiscountPolicy 이구나" 라고 이해할 수 있을 것이다.

개인적으로는 같은 패키지 내에 RateDiscountPolicy, FixDiscountPolicy 클래스 파일을 DiscountPolicy 클래스와 같은 패키지 내에 두는 것만으로도 이해하기 쉬울 것 같다.

Reference

https://www.baeldung.com/spring-component-annotation
https://velog.io/@albaneo0724/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-Bean%EA%B3%BC-Component%EC%9D%98-%EC%B0%A8%EC%9D%B4
https://jojoldu.tistory.com/27

profile
꾸벅 🙇‍♂️ 매일매일 한발씩 나아가자잇!

0개의 댓글