다양한 의존관계 주입 방법

1. 생성자 주입

- 생성자 호출시점에 딱 한번만 호출되는 것이 보장이 된다. 한번 세팅하면 그 다음부터는 세팅할 수 없도록 할 수 있다는 것이다.
- 불변, 필수 의존관계에 사용된다.
- 생성자가 딱 1개만 있으면 @Autowired를 생략할수도 있다.

 


2. 수정자 주입 (setter주입)

- setter라고 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법이다.
- 선택, 변경 가능성이 있는 의존관계에 사용된다.
- @Autowired의 기본동작은 주입할 대상이 없으면 오류가 발생한다. 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false)로 지정하면 된다.

 


3. 필드 주입

- 필드 주입을 사용하면 외부에서 변경이 불가능해서 테스트하기 힘들다는 단점이 있어 최근에는 사용하지 않는다.
- 애플리케이션의 실제 코드와 관계없는 테스트코드에 사용한다.
- 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용한다.


4. 일반 메서드 주입

- 일반 메서드를 통해서 주입을 받을 수 있다.
- 한꺼번에 여러 필드를 주입 받을 수 있다. 일반적으로는 잘 사용하지 않는다.

 

 

 

 

 

 

 

 

@Autowired 옵션처리

1. @Autowired(required = false)

- 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출이 안된다.

 

 

@Autowired(required = false)의 예시

 

- 위의 setNoBean1 메서드의 파라메터에 있는 Member는 스프링 빈에 등록된 객체가 아니다. 그때 그냥 @Autowired를 한다면 스프링 빈이 아닌 객체를 의존관계 자동주입을 하려고 했으니 오류가 발생한다. 아마 NoSuchBeanDefinitionException이 나올 것이다. required = false 옵션을 넣어주면 호출 자체가 안되기 때문에 Exception이 터지지 않고 아예 print값이 찍히지 않는다.

 

 

 

 

 


2. @Nullable

- 자동 주입할 대상이 없으면 null이 입력된다.

 

@Nullable의 예시

- 스프링 빈으로 등록되지 않은 것을 아는데 찍어주어야 할 상황이 생길 수도 있을 것이다. 위의 setNoBean2처럼 파라메터가 한개라면 그럴일이 없지만 예를 들어 파라메터가 3개인데 마지막 변수는 스프링 빈이 아니지만 테스트를 위해서 null이라도 찍어보고 싶다. 그럴 때 사용할 수 있다.

 

 

- 결과는 이처럼 null로 찍힌다.

 

 

 

 

 

 


3. Optional<>

- 자동 주입할 대상이 없으면 Optional.empty가 입력된다.

 

Optional&amp;amp;lt;&amp;amp;gt;의 예시

- 자바 8 문법인 Optional을 사용할 수도 있다. Optional은 “존재할 수도 있지만 안 할 수도 있는 객체”, 즉, “null이 될 수도 있는 객체”를 감싸고 있는 래퍼 클래스이다. 

 

 

- 결과는 이처럼 Optional.empty로 얻을 수 있다.

 

 

 

 


@Nullable, Optional<>은 스프링 전반에 걸쳐서 지원이 된다. 예를 들어서 생성자 자동 주입에서 마지막 파라메터가 스프링빈에 없는데 생성은 하고 싶다? 그러면 넣을 수도 있는 것이다.

 

 

 

 

 

 

 

 

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

최근에는 스프링을 포함한 DI프레임워크 대부분이 생성자 주입을 권장한다.

 

1. 불변

- 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 오히려 대부분 의존관계는 애플리케이션 종료전까지 변하면 안된다.
- 수정자 주입을 사용한다고 하면 메서드를 public으로 열어두어야 하고 누군가가 실수로 변경할 수도 있기 때문에 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다.

 

2. 누락

- 프레임워크 없이 순수한 자바 코드 단위 테스트 하는 경우가 많은데 수정자 주입인 경우 setter를 통해 한개의 필드라도 값을 넣어주지 않으면 NullPointerException이 발생한다. 
생성자 주입을 사용하면 생성자를 통해서 값을 넣어야하는데 값이 누락되면 컴파일 에러로 자바 컴파일러단에서 해결할 수 있다.  또한, 생성자 주입을 사용하면 final 키워드를 사용할수 있다. 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에서 막아줄 수 있다.

 

 

- 생성자 주입은 프레임 워크에 유지하지 않고 순수한 자바 언어의 특징을 잘 살리는 방법이다.
- 기본으로 생성자 주입을 사용하고, 필수값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하면 된다. 생성자 주입과 수정자 주입을 동시에 사용할 수 있다.
- .. 그냥 항상 생성자 주입을 선택하자 !

 

 

 

 

 

 

 Lombok 롬복

 

- @Getter, @Setter

: POJO클래스에 이 어노테이션들은 붙여주면 알아서 게터 세터를 만들어준다.

 


- @ToString

: toString메서드를 Override해서 필드들을 출력해준다. 

클래스명(필드1명=필드1값,필드2명=필드2값,...)  와 같이 출력이 된다.

 


- @RequiredArgsConstructor
: final 키워드가 붙은 필드(Required한)를 파라메터로 하는 생성자를 자동으로 생성해준다.

 

@RequiredArgsConstructor 의 예시

 

 

 



최근에는 생성자 1개를 두고 @Autowired를 생략하는 방법을 주로 사용한다. 여기에 롬복 라이브러리에서 제공하는 @RequiredArgsConstructor 를 함께 사용하면 기능은 다 제공하면서 코드를 깔끔하게 사용할 수 있다.

 

 

 

 

 

 

 

 

조회 빈이 2개 이상일 때

1. @Autowired 필드 명 매칭

- @Autowired는 타입기반으로 DI를 한다. 만약에 타입이 같은 스프링 빈이 여러개 있다면, 필드명을 찾거나 또는 생성자의 파라메터의 변수 이름과 빈의 이름이 같을 경우 그 클래스를 주입해준다.

 

 

 

@Autowired 필드 명 매칭예시

- DiscountPolicy에는 rateDiscountPolicy와 fixDiscountPolicy 2개의 구현체가 있다. 같은 타입을 @Autowired하기 때문에 변수명을 discountPolicy로 하면 오류가 나지만 구현체의 이름과 맞춰서 rateDiscountPolicy로 하니까 오류가 나지 않는다.

 

 

 

 

 

2. @Qulifier -> @Qulifier끼리 매칭 -> 빈 이름 매칭

- @Qulifier는 빈 이름을 변경하는 것이 아니라 추가적인 구분자를 붙여주는 방법이다.
@Qulifier("name")으로 구분해준 빈은 생성자, 수정자, 필드 주입 시 앞에 @Qulifier("name")를 붙여주는 방식으로 빈을 찾아준다.
만약, @Qulifier("name") 에서 name이라는 구분자로 등록된 빈이 없다면, name이라는 이름의 스프링빈을 추가로 찾는다. 
헷갈리지 않아야 하는 것은 빈 이름을 @Qulifier로 바꾸는게 아니라 구분자를 붙여주는 것이고 그것으로 찾지 못했을 때 추가적으로 @Qulifier로 와 같은 이름의 빈을 검색한다는 것이다.

 

 

RateDiscountPolicy

 

RateDiscountPolicy에 @Qualifier("mainPolicy")라고 설정해주었다.

 

 

 

 

@Qualifier 예시

 

조회빈이 2개이상인 파라메터 앞에 @Qualifier("mainPolicy")를 붙여줌으로서 DiscountPolicy는 RateDiscountPolicy가 되었다.

 

 

 

 

 

 


3. @Primary 사용

- 우선순위를 정하는 방법이다. 여러개의 빈이 매칭이 되면 @Primary가 우선권을 가진다.

 



RateDiscountPolicy

 

RateDiscountPolicy에 @Primary 라고 설정해주었다.

 

 

 

@Primary 예시

 

조회 빈이 2개 이상이지만 @Primary 애노테이션 덕분에 오류가 나지 않고 DiscountPolicy의 구현체는 RateDiscountPolicy가 되었다.

 

 

 

 

 

 

 

그런데.

뭔가 이상하지 않은가..

 

 

우리는 지금까지 SOILD 원칙을 최대한 지키면서 객체지향적인 설계와 프로그램을 짜왔다. 

그런데, DiscountPolicy에 두 개의 빈이 찾아지기 때문에 우리는 특정 빈을 찾을 수 있도록 인자의 파라미터 이름을 수정한다던지(@Autowired 필드명 방식), @Qualifier이나 @Primary 애노테이션을 붙이기 위해서 클라이언트 코드를 직접 수정하였다. 이는 개방에는 열려있고 수정에는 닫혀있어야하는 OCP원칙을 어긋났다. 

또 한, DiscountPolicy를 각각의 방법으로 인해 RateDiscountPolicy라는 구체화에 의존하도록 명시하였기 때문에 추상화에 의존해야지 구체화에 의존하면 안된다DIP원칙을 어긋났다.

 

이는 김영한 님의 해당 문제에 대한 답변.

기존 구현 클래스의 애노테이션도 변경하지 않으면 더 좋겠지만, 이 부분까지는 컴포넌트 스캔의 한계입니다. @Bean을 사용하면 확실하게 되지만 약간은 불편하지요. 따라서 둘의 트레이드 오프로 이해하시면 됩니다.

 

 

 

 

 

 


@Primary, @Qualifier 활용

- 코드에서 자주사용하는 메인 데이터베이스의 커넥션을 획득하는 스프링 빈이 있고, 코드에서 특별한 기능으로 가끔 사용하는 서브 데이터베이스의 커넥션을 획득하는 스프링빈이 있다고 생각해보자. 메인 데이터베이스의 커넥션을 획득하는 스프링빈은@Primary를 적용해서 조회하는 곳에서 @Qualifier 지정없이 편리하게 조회하고, 서브 데이터베이스 커넥션 빈을 획득할때는 @Qualifier를 지정해서 명시적으로 획득하는 방식으로 사용하면 코드를 깔끔하게 유지할 수 있다. 물론 이때 메인 데이터베이스의 스프링빈을 등록할때 @Qualifier를 지정해주는 것은 상관없다.

 

@Primary, @Qualifier 우선순위

- 스프링은 항상 더 자세한 것이 우선순위를 가져간다. 자동보다는 수동이, 넓은 범위의 선택권보다는 좁은 범위의 선택권이 우선권이 높다. 따라서 @Qualifier가 우선권이 높다.

 

 

 

 

 

 

 

 조회한 빈이 모두 필요할 때

 

조회한 빈이 모두 필요할 때는 어떻게 각각의 빈들을 모두 조회할 수 있을까? 이는 스프링에서 특별하게 제공하는 Map의 기능을 이용하면 된다. 

 

 

 

DiscountService

 

예를 들어 DiscountService라는 클래스가 있다고 하자. 이는 Map의 key값으로 String(DiscountPolicy의 이름이 될 것이다.), value값으로 DiscountPolicy인터페이스를 받고 있다.

 

이 클래스를 스프링 빈으로 등록하고 생성자 주입을 해주었다.

 

 

 

 

 

 

조금 알아보기 힘들지만, 위에 생성자에서 찍은 map과 list의 결과이다.

map에는 key로 클래스의 이름이 들어있고 value에는 해당 클래스의 객체참조값이 들어있다. DiscountPolicy 라는 빈은 RateDiscountPolicy와 FixDiscountPolicy라는 2개의 빈이 의존하고 있었기 때문에 자동 의존관계가 주입되어 이 2개가 map에는 key, value값으로 list에는 배열로서 나타내 진것을 확인할 수 있었다.

 

 

 

 

DicountService 의 Test

 

위의 DiscountService의 생성자 들이 자동주입이 될 수 있도록 AnnotaionConfigApplicationContext를 사용하여 스프링 빈을 등록할 때 DiscountService와 AutoAppconfig를 등록하였다. AutoAppConfig는 위에 예제들에서 사용한 모든 빈들을 ComponentScan해주는 설정파일이다. 물론 RateDiscountPolicy와 FixDiscountPolicy가 포함되어 있다. 

 

ac.getBean하여 빈을 받아온 후, discount라는 메서드를 먼저 TDD방식으로 만들었다. 이는 member와 price, disCountCode를 넘겨 어떤 할인 정책이냐에 따라 price가 얼마가 반환되는지 테스트하기 위한 메서드 이다.

 

2번의 테스트는 각각 rateDiscountPolicy와 fixDiscountPolicy를 넘겨서 해당 할인정책에 따라 가격이 잘 return이 되는지 확인하였다.

 

 

DicountService에 discount메서드를 추가함

 

discount메서드를 추가하였다. 

map에는 DiscountPolicy 빈을 의존하고 있는 RateDiscountPolicy와 FixDiscountPolicy라는 빈이 들어있기 때문에 get하여 해당 빈을 꺼내고 그 빈에 맞는 discount값을 return해주었다. 

 

 

 

테스트가 정상적으로 통과하였다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'JAVA > Spring' 카테고리의 다른 글

빈 스코프  (0) 2021.12.23
빈 생명주기 콜백  (0) 2021.12.18
컴포넌트 스캔 (@ComponentScan)  (0) 2021.12.17
싱글톤 패턴과 스프링 컨테이너  (0) 2021.12.15
Spring Socket에 대해 알아보자! - 2. WebSocket 만들어보자  (0) 2020.06.23

+ Recent posts