당니의 개발자 스토리

다양한 의존관계 주입 방법 본문

스프링/스프링 핵심 원리 - 기본편

다양한 의존관계 주입 방법

clainy 2024. 1. 24. 21:44

7. 의존관계 자동 주입

이번 시간부터는 의존관계 자동 주입에 대해서 알아보겠습니다.

먼저 다양한 의존관계 주입부터 시작해서, 어떻게 옵션 처리를 하는지, 그리고 다양한 방법 중에 어떤 방법이 좋은 건지 설명을 드리고요. 그 다음에 최신 트렌드도 말씀드릴게요. 그리고 문제점들을 해결하고 디테일하게 설명을 드리고요.

마지막에는 컴포넌트 스캔이나 자동 의존관계를 주입하는 거랑 수동으로 직접 @Bean으로 등록하는 거랑 실무에서는 하나의 애플리케이션이 두 개가 보통 섞여서 써지거든요.

그래서 어느 경우에는 자동으로 하고 어느 경우에는 수동으로 하는 게 좋은지 정리를 해드리겠습니다.


그래서 이번 시간에는 먼저 다양한 의존관계 주입 방법에 대해서 설명을 해드리겠습니다.

우선 의존관계 주입 방법은 크게 4가지가 있어요. 스프링 공부하신 분들은 다 들어보셨을텐데 생성자 주입, 수정자 주입 또는 setter 주입, 그리고 필드 주입, 일반 메서드 주입이 있습니다.

생성자 주입은 이름 그대로 생성자를 통해서 의존관계를 주입하는 거고, 수정자 주입은 setter 라는 메소드를 통해서 의존관계를 주입하는 거고, 필드 주입은 필드를 통해서 주입하는 겁니다. 그래서 필드 주입은 필드에서 바로 주입하는 거고, 마지막으로 아무 일반 메소드에다가 대고 의존관계를 주입할 수가 있어요.


먼저 생성자에다가 의존관계를 자동으로 주입하는 방법에 대해서 설명을 해드리겠습니다.

이게 우리가 지금까지 했던 거예요.

생성자@Autowired 라고 돼있죠.

얘가 컴포넌트 스캔을 하면, 스프링이 OrderServiceImpl스프링 빈으로 등록될 때 생성자를 호출할 겁니다. 그러면 생성자를 호출할 때,

'생성자에 @Autowired가 붙어있네.' 라고 하면, 스프링 컨테이너에서 memberRepositorydiscountPolicy라는 타입의 스프링 빈을 꺼내서 여기다가 딱 주입해줍니다.

이름 그대로 생성자를 통해서 의존 관계를 주입 받는 방법이고, 지금까지 우리가 진행했던 방법이 바로 생성자 주입입니다.

생성자 주입은 특징이 중요한데, 생성자 주입의 특징생성자를 호출하는 시점에 딱 한 번만 호출되는 게 보장이 돼요. 딱 한 번 호출 된다는 것은 그때 값을 세팅하고, 그 다음부터는 값을 세팅 못하게 막을 수 있다는 거예요.

그래서 이러한 특징 때문에 불변이고 필수 의존관계에서 주로 사용합니다.

불변인 것은 생성자는 두 번 호출이 안 되잖아요. 딱 한 번만 호출이 되기 때문에, 강제로 수정하는 메서드를 만들지 않는 이상, 얘는 어쨌든 불변인 거죠.

코드 잘 보시면,

개발에서 불변이라는게 진짜 중요해요. 좋은 아키텍처의 개발 습관은 제약이 있는 한계점이랑 제약이 있어야 돼요. 다 열어두면 뭐가 어디서 수정됐는지 알 수가 없잖아요.

그런데 memberRepository랑 discountPolicy는 딱 생성자를 통해서만 의존관계가 주입이 되고, 그 다음에는 어느 누구도 외부에서는 OrderServiceImplmemberRepository수정할 수가 없어요. discountPolicy도 딱 애플리케이션이 조립된 시점에만 한번 들어오고, 이 discountPolicy에 대한 인스턴스 자체를 변경할 수 있는 방법이 OrderServiceImpl 안에는 없어요.

왜냐면 나의 의도가 뭐냐면, 처음에 애플리케이션 Configuration으로 딱 스프링 컨테이너에 빌딩돼서 올라갈 때, 이 연관 관계의 그림을 다 만들고 끝내고 싶은 거에요. 공연을 띄우기 전에 배우들을 다 정하고 끝내고 싶은 거에요. 그러니까 공연하는 중간에는 배우를 바꿀 일이 없는 거에요.

그렇다고 하면, 임의로 수정할 수 있는 메서드를 만들면 될까요?

이렇게 수정할 수 있는 메서드를 public으로 열어두면 안되겠죠.

그래서 한 번 딱 생성되고 나면 얘는 불변인 거예요. 개발 할 때는 불변이라는 특징이 정말 중요해요.

이렇게 해두면 누군가는 반드시 바꿉니다.

내가 값을 세팅하고 나서, '더 이상 값을 바꾸면 안돼!' 라고 하면 가급적이면 생성자에 값을 넣고, 수정자 메서드 또는 setter 메서드라고 하는데 그런 걸 안 만들면 됩니다. 그러면 버그가 생길 확률이 확 줄어듭니다. 불변에 대해서 중요한 걸 말씀드렸고요.

그 다음에 생성자 주입은 필수 의존관계에 많이 사용해요.

이걸 보면,

private final로 해놨죠. 이렇게 해두면, 무조건 값이 있어야 된다는 뜻이에요. 물론 외부에서 NULL로 들어갈 수는 있지만, 어쨌든 이렇게 해두면 생성자로 들어오는 것들은 값이 꼭 있어야 된다고 내가 지정을 한 거예요.

만약 생성자에서 this.memberRepository = memberRepository; 를 빼면, 컴파일 오류가 납니다.

이렇게 언어적으로 내가 잡은 거에요. '얘는 웬만하면 무조건 세팅을 해줘!' 라는 게 지금 목표인 거예요.

그리고 외부에서 호출을 할 때도,

생성자에 보통 이렇게 값이 있으면 웬만하면 NULL을 호출합니다. 생성자 있는 거는 웬만하면 값을 다 넣어야 돼요. 문서에 얘는 NULL을 허용합니다. 라고 하지 않는 이상, 웬만하면 생성자에는 값을 다 채워 넣어야 된다고 보통 생각을 해요.

그래서 필수 값에 사용을 합니다. 그래서 생성자 주입불변이면서 필수인 의존관계에 보통 사용을 합니다. 그런데 항상 그런 건 아니고, 주로 그렇게 사용한다고 이해하시면 됩니다.

그런데 중요한 게 있어요. 생성자가 딱 1개만 있으면 @Autowired를 생략해도 자동 주입이 됩니다.

만약 이렇게 생성자가 2개 있으면, @Autowired를 지정해줘야 돼요. 왜냐면 예를 들어서, 스프링 컨테이너가 OrderServiceImpl를 생성할 때 뭘 생성자로 호출하면서 @Autowired를 호출해야 될지 모르잖아요. 그래서 생성자가 2개일 때는 안되고, 생성자가 딱 하나일 때는 스프링 컨테이너가 '아이고 이거는 내가 관리하는 스프링 빈이네' 합니다. @Bean으로 등록해도 되고 컴포넌트 스캔으로 등록해도 마찬가지에요.

그래서 생성자가 딱 하나 있으면, 자동으로 의존 관계 주입이 일어납니다. @Autowired가 없어도 있다고 생각하시면 됩니다.

간단하게 테스트 해보겠습니다.

이렇게 @Autowired가 없어도 생성자 호출이 될 때, 스프링 컨테이너에서 스프링 빈이 들어오면 NULL이 아닌 값이 있겠죠.

이걸 돌려보면,

NULL이 아닌 값이 들어있는 걸 확인하실 수 있습니다. 요즘에는 생성자가 한 개 일 때, 이렇게 생략해서 많이 사용합니다.


그 다음은 수정자 주입에 대해서 말씀드리겠습니다.

수정자 주입은 필드 있죠? 수정을 하려면 final빼야돼서 뺄게요.

이러한 필드 값을 수정할 때, 관례상 어떻게 사용하냐면,

이런 식으로, setXXX(XXX는 필드명)으로 메서드를 만들어서 많이 써요. 이게 java been property 규약입니다.

필드값을 직접 수정하기 좀 그러니까 메서드를 통해서 수정하는데 이걸 수정자라고 합니다.

여기에다가 @Autowired를 넣으면,

이렇게도 주입이 됩니다. 이것도 찍어보겠습니다.

여기에 값이 들어오면, 호출이 됐다고 이해하시면 되겠죠. 돌려보면,

값이 들어왔습니다.

만약에 @Autowired 빼면,

아무것도 출력이 안됩니다. 그러니까 OrderServiceImpl을 스프링 컨테이너에 딱 등록을 해요. 스프링 컨테이너는 사실 크게 두가지 라이프사이클이 있죠.

첫 번째는 스프링 빈을 다 등록을 해요. 그 다음에 뭘 하냐? 두 번째는 연관관계를 자동으로 주입합니다.

바로 이 @Autowired가 걸린 애들을 자동으로 주입을 합니다. 그런데 여기서는 사실 생성자도 의존관계 주입이 일어나요.

생성자 주입은 언제 일어나냐면, Java 스프링에 OrderServiceImpl 객체를 생성을 해야 돼요. 그러면 객체를 생성하려면 어쩔 수 없이 생성자를 불러야 되잖아요. 그래서 이 생성자 주입은 빈을 등록하면서 의존관계 주입도 같이 일어나요. 원래는 두 개는 다른 단계인 거죠.

그런데 어쨌든 생성자 주입은 java 코드기 때문에 스프링이 용 빼는 재주가 있는 게 아니거든요.

그래서 생성자 주입은 어쩔 수 없이 스프링 라이프 사이클에서 빈 등록할 때 같이 자동주입이 일어납니다.

반면에 수정자나 이런 것들은 의존관계 주입은 두 번째 단계에서 일어나게 됩니다.

그래서 실제 호출을 해보면, 순서가 생성자가 1번, setter 메서드가 2번, 3번이 되겠죠.

그래도 어쨌든 스프링이 싱글톤을 보장하기 때문에 memberRepository랑 discountPolicy에는 똑같은 값이 주입됩니다.

그렇기 때문에 여기선 수정자 주입을 쓰게 되면, 생성자가 필요없겠죠.

이렇게 해서 쓰시면 됩니다. 물론 지금 돌리면 기존 코드들 때문에 컴파일 오류가 날텐데, 일단 이런식으로 쓰시면 됩니다.

아무튼 그래서 setter 라고 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법이고요.

수정자 주입의 특징은 선택적인 것, 그리고 변경 가능성이 있는 의존관계사용하면 됩니다.

무슨 얘기냐면, 생성자에서 주입할 때는 가급적 필수 값이라고 했죠. 그런데 만약에 memberRepository는 스프링 빈에 등록이 안될 수도 있잖아요. 그럴 때도 수정자 주입을 사용할 수가 있습니다. 그렇게 선택적으로 의존관계를 주입을 하는 거에요.

그리고 참고로 선택적으로 하려면, required = false를 주시면 됩니다.

그러면 선택적으로 '아 memberRepository는 필수 값이 아니니까, 얘는 있어도 되고 없어도 돼' 이런 식으로 할 수가 있습니다.

그 다음에 변경 가능성이 있는 의존관계에 사용한대요. 거의 그런 일은 없는데 중간에 내가 배역을 바꾸고 싶어하면, 즉 중간에 인스턴스를 바꾸고 싶으면,

외부에서 이걸 강제 호출하면 되겠죠. 그런 식으로 활용할 수도 있습니다. 물론 그런 일은 거의 없어요.

하다보면 A 데이터베이스에 붙어 있던 데이터베이스 커넥션을 B 데이터베이스로 바꾼다는 등 있을 수는 있지만, 다른 좋은 방법들이 있어 가지고 굳이? 이긴 합니다.

참고로 @Autowired의 기본 동작은 주입할 대상이 없으면 오류발생합니다. 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false)로 지정하면 됩니다.

그리고 Java Bean Property에 대해서 말씀드릴게요. 코드를 보고 말씀드리겠습니다.

age 라는 필드가 있어요. 이 필드에 있는 값을 바꿀 때setXXX 이렇게, XXX필드명을 넣고, 값을 조회할 때는 getXXX 이 방식으로 만들어라. 라는 규약이 있습니다.

그러니까 Java 객체가 있는데, 이 객체 안에 있는 필드에 접근할 때는 직접 접근하는 게 아니라, 이렇게 메서드를 통해서 접근하자. 라는 게 규약입니다.

자바에서는 과거부터 필드의 값을 직접 변경하지 않고, setXxx, getXxx 라는 메서드를 통해서 값을 읽거나 수정하는 규칙을 만든 거에요. 여러 군데서 사용하려고 만든거죠. 이 정도만 알면 되고, 더 자세한 내용이 궁금하면 자바빈 프로퍼티로 검색해보면 됩니다.

그래서 요즘에 제일 많이 쓰는 건 생성자 주입을 거의 쓰고, 수정자 주입도 가끔 선택적이거나 변경 가능할 때 쓴다. 정도로 이해하시면 됩니다.


그 다음에 필드 주입에 대해서 넘어가 보겠습니다.

필드 주입은 이름 그대로 필드에다가 값을 그대로 빡 넣어버리는 거예요.

코드로 보면,

필드 주입을 하면 이렇게 할 수 있어요. 아주 심플하죠?

basicScan()을 이용해서 확인해볼 거예요.

그전에 getMemberRepository() 메서드를 통해서 값이 있는지 꺼내보겠습니다.

값을 찍어서 돌려보면,

잘 나옵니다.

필드 주입은 의존 관계를 필드에다가 바로 빡 넣어주는 거예요. private 인데도 가능합니다. 코드가 간결하고 좋아서 옛날에 많이 썼어요.

그런데 인텔리제이에 노란줄 하고 떴죠.

필드 주입을 권장하지 않아요. 라고 뜨죠. 사람들이 개발하다 보니까 이게 별로 안 좋은 안티 패턴이구나. 라는 것을 알게 된 거에요.

코드가 간결해서 많은 개발자들을 유혹합니다. 그런데 외부에서 변경이 불가능해서 테스트 하기가 굉장히 어려워요.

예를 들어서 스프링이 없는 상태에서 내가 OrderServiceImpl 테스트를 만들고 싶단 말이에요. 그러면 내가 테스트 할 때는 memberRepository를 가짜 memberRepository로 객체로 바꾸고 싶은 거예요.

왜냐면 지금은 직접 DB에 접근이 안되니까, 내가 그냥 Dummy 데이터를 넘기는 memberRepository로 바꿔서 테스트할 때가 많거든요. 그런데 필드 주입을 쓰면, 값을 바꿀 수 있는 방법이 없어요. discountPolicy도 지금 바꿀 수 있는 방법이 없어요.

스프링 컨테이너가 아닌, 순수한 자바로 테스트 할 수 있는 방법이 없고 결국 제대로 테스트할 수 있는 방법이 없어요.

예를 들어서 테스트를 하나 만들어보면,

여기서 OrderServiceImpl에 있는 memberRepository를 내가 바꾸고 싶어요. 그런데 값을 넣을 수 있는 방법이 없죠. 생성자 주입이나 setter 주입은 값을 새로 넣어주면 될텐데, 필드 주입은 그런게 없어요.

여기서 createOrder()호출하면,

여기서 그냥 NullPointException이 터지는 거에요. 값을 넣어줄 방법이 없으니까요

스프링에서 돌리는 게 아니라, 순수한 java 코드로 돌리는 거죠.

돌려보면 이렇습니다. 예외가 터지니까 내가 값을 넣어주고 싶은데, 넣을 수 있는 방법이 없죠.

테스트를 하기가 어렵습니다. 그럼 어떻게 해야되냐면, OrderServiceImpl 안에서 Setter를 열어야 돼요.

이렇게 하고,

테스트에서 set 메서드를 불러서 이런 식으로 해결할 수 있겠죠.

이렇게 해두고,

이렇게 테스트할 수 있습니다. 그래서 결과적으로 필드 인젝션을 하면, 테스트하기 위해서 또 setter를 만들어야 돼요. 그럴 바에는 그냥 setter에서 @Autowired 하는 게 낫죠. 그럼 스프링 컨테이너에서 쓸 수 있고, 내가 임의로 테스트 할 때도 할 수 있죠.

@Autowired스프링 컨테이너가 관리하는 걸 가져와야 @Autowired가 제대로 먹는거지, 내가 임의로 new 해서 생성하는 건 당연히 @Autowired가 안됩니다.

필드 주입은 최근에는 안쓰는 게 좋아요.

결론적으로 DI 프레임워크가 없으면 아무것도 할 수가 없어요. 원래는 순수한 Java 코드특정 클래스만 테스트하는 경우가 진짜 많거든요.

필드 주입을 쓰면,

얘를 테스트 하려면, 무조건 스프링 컨테이너를 다 띄우고, 스프링을 통해서 가져와야 되니까 DI 프레임워크가 없으면 아무것도 할 수가 없죠.

이런 순수한 java 테스트를 만들 수 있는 방법이 없습니다.

그래서 결론은 사용하지 말자! 인데 사용해도 되는 곳이 있어요. 애플리케이션의 실제 코드와 관계 없는 테스트 코드는 스프링 컨테이너에서도 할 수가 있거든요.

이런 스프링 부트 테스트는 필드에다가 @Autowired 해서 하는게 더 편해요.

@SpringBootTest 라고 되어있으면, 스프링 컨테이너에다가 스프링 빈을 다 올린 다음에, orderService를 바로 테스트 할 수 있거든요. 이런 테스트 코드에서는 필드에다가 바로 @Autowired 해도 됩니다. 왜냐면 이 테스트 코드는 누가 가져다 쓸 일이 없잖아요.

그 다음에 또 하나, @Configuration이 붙은 AutoAppConfig 같은 경우에는

이런 식으로 @Bean으로 등록하려고 orderService 객체를 생성할 때, 이미 빈으로 등록되어 있는 애들을 필드 주입을 통해서 생성자에다가 넣어줄 수 있습니다.

결론은 필드 인젝션은 딱 애플리케이션의 실제 코드와 관계 없는 테스트 코드 정도에서만 쓰거나 굉장히 특수한 목적으로만 쓰고, 애플리케이션 내 코드에서는 가급적이면 안 쓰시는 것을 권장 드립니다.


그 다음에 일반 메서드 주입에 대해서 말씀드리겠습니다.

일반 메서드 주입은 아무 메소드에다가 대고 @Autowired를 쓸 수 있어요.

그냥 이런 식으로 할 수 있습니다. 마치 수정자 주입할 때랑 비슷한 타이밍에 들어와서 다 주입을 해줍니다.

사실 수정자 주입도 이거랑 똑같죠. 그냥 메서드 위에 @Autowired 되어있는 겁니다.

일반 메서드 주입의 특징은 일반 메서드를 통해서 주입을 받을 수 있어요. 그래서 한 번에 여러 필드를 주입 받을 수 있는게 장점인데요. 사실 생성자 주입이랑 수정자 주입 안에서 다 해결하기 때문에 얘를 사용하는 일은 거의 없습니다. 일반적으로 잘 사용하지 않아요.

어쩌면 당연한 이야기이지만 의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동작하는 거예요. 일반 자바 객체는 안됩니다. 따라서, 스프링 빈이 아닌 Member 같은 클래스에서 @Autowired 코드를 적용해도 Member는 스프링 빈이 아니기 때문에 아무 기능도 동작하지 않습니다.

'스프링 > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글

생성자 주입을 선택해라!  (0) 2024.01.24
옵션 처리  (0) 2024.01.24
중복 등록과 충돌  (0) 2024.01.24
필터  (0) 2024.01.24
탐색 위치와 기본 스캔 대상  (0) 2024.01.24