컴포넌트 스캔과 의존관계 자동주입
스프링을 공부하면서 가장 신기했던, 어디서나 편리하게 사용하고 있는 의존관계 자동주입에 대해 공부한다.
처음에 회사에 갔을때, 스프링을 들어봤지만 제대로 공부하지 않았을때 이 개념을 봤을때는 정말 이해가 가지 않았다.
사실 스프링은 편리하다고 하지만 개념적인 내용이 어려워서 '그냥 그렇구나' 하고 사용했던 기억이 난다.
그때 바로 공부하고, 이해하고 적용하면서 업무를 진행했으면 달라졌을까...? 하는 후회가 많이 되곤한다.
그렇지만, 지금이라도 이렇게 공부하여 정리할 수 있어서 다행이다.
강의는 기존시간에 배웠던 @Configuration, @Bean을 활용한 스프링 컨테이너에 직접 등록하는 방법을 이어서
이로인해 발생하는 문제점(불편한점)을 제시하고, 그에 따른 해결책으로 '컴포넌트 스캔'과 의존관계 자동주입을 설명한다.
스프링 빈 등록 시 Java 코드의 '@Bean' 을 사용하여 직접 스프링 빈을 등록 할 수 있다.
그렇지만, 직접 등록할 빈이 많아지면 관리가 힘들어지는 문제가 발생한다.(누락/반복...)
스프링은 설정정보가 없어도 자동으로 스프링 빈을 등록할 수 있는 기능이 있는데 그것이 바로 컴포넌트 스캔 / 의존관계 자동주입이다.
컴포넌트 스캔
'@ComponentScan' 이라는 애노테이션을 설정 정보 클래스에 붙여주면 된다.
기존에 '@Configuration'을 붙인 AppConfig의 경우에는 그 안에 메소드를 작성하고, 해당 메소드마다 @Bean 애노테이션을 붙여서 해당 메소드가 스프링 빈으로 관리되게끔 설정했었다.
그렇지만, 컴포넌트 스캔은 '@Component' 애노테이션이 붙은 모든 클래스를 스캔해서 스프링 빈으로 등록한다
각 클래스가 스캔의 대상이 되도록 애노테이션을 붙어주어야 한다.
스프링 빈 직접등록 설정
- @Configuration 애노테이션을 AppConfig 클래스에 붙여준다( 설정정보 )
- AppConfig 안에 각 서비스(MemberService, OrderService, MemberRepository, discountPolicy 등등)를 생성해주는 메소드를 작성하고, 해당 메소드에 @Bean을 붙여서 스프링 컨테이너가 관리하는 객체임을 체크한다
- 비즈니스 로직 작성 시 스프링 컨테이너에서 사용하고자 하는 서비스를 꺼내서 활용한다
스프링 빈 자동등록 설정
- @ComponentScan 애노테이션을 AutoAppConfig 클래스에 붙여준다( 설정정보 )
- 각 서비스(MemberService, OrderService, MemberRepository, discountPolicy 등등)에 @Component 애노테이션을 붙여준다
- ( @Bean으로 등록한 메소드가 없기 때문에 ) 각 서비스안에서 호출이 되도록 @Autowired 애노테이션을 붙여 의존관계 주입을 완성한다
두 단계 모두 목적은 스프링 컨테이너에 스프링 빈을 담아 의존관계를 만들어 주는 것 이다.
얼핏보면 큰 차이가 없어보이지만, 첫번째는 스프링 빈에 등록하기 위해 모든 메소드를 직접 작성해야한다.
하지만, 두번째 방법은 새로운 서비스를 만들때, 해당 클래스에 @Component만 붙여주고 그 안에서 의존관계를 설정할 부분에 @Autowired만 지정해주면 되기 때문에 굉장히 편리하게 개발할 수 있다.
기본 스캔 대상
하지만, 회사에 들어가보면 @Component 애노테이션이 붙은 서비스를 보지 못했을 것 이다.
대부분 컨트롤러에는 @Controller, 서비스에는 @Service, 레포지토리에는 @Repository가 붙은 것을 본적이 있을 것 이다.
이는 해당 애노테이션을 따라가보면 답이 나오는데, 그 안에 보면 @Component가 포함되어 있는것을 알 수 있다.
이러한 이유로, 각 역할에 맞는 애노테이션을 붙이면 컴포넌트 스캔에 대상이 된다.
탐색위치 / 필터
@ComponentScan 애노테이션에 basePackages 옵션을 주어서 꼭 필요한 위치부터 탐색할 수 있도록 설정할 수 있다.
이를 설정하지 않으면, 컴포넌트 스캔 애노테이션이 붙어있는 설정 정보 클래스의 패키지가 시작 위치가 된다.
설정 정보 클래스의 위치를 프로젝트 최상단에 두어서 모든 파일이 대상이 되도록 설정하는 것을 권장한다
( 이는 스프링부트에서 기본으로 제공하는 방법이다 )
( 스프링부트 처음에 @SpringBootApplication 애노테이션 안에 @ComponentScan이 들어있다 )
includeFilters / excludeFilters 옵션으로 컴포넌트 스캔 대상에서 제외할 대상을 지정하거나 스캔 대상을 추가할 수 있다.
그렇지만, 추가는 @Component면 충분하고, 제외도 많이 사용되지 않기 때문에 최대한 스프링의 기본 설정에 맞추어 사용하는 것이 권장된다.
의존관계 자동주입
컴포넌트 스캔과 같이 사용하는 의존관계 자동주입은 스프링 빈 등록의 편리함을 완성시켜준다.
의존관계 주입은 4가지 방법이 있다
- 생성자 주입
- 수정자 주입(Setter 주입)
- 필드 주입
- 일반 메서드 주입
생성자주입
생성자를 통해서 의존관계를 주입받는 방법이다.
이 방법은 생성자 호출 시점에 딱 1번만 호출되는 것이 보장된다.
그렇기 때문에 불변, 필수 의존관계에 사용한다.
또한 생성자가 딱 1개만 있으면 @Autowired를 생략해도 자동 주입된다.
수정자주입( Setter 주입 )
setter라 불리는 필드의 값을 변경하는 수정자 메소드를 통해서 의존관계를 주입받는 방법이다.
선택, 변경의 가능성이 있는 의존관계에 사용한다
필드주입
필드에 바로 주입하여 의존관계를 주입받는 방법이다.
코드가 간결하지만, 외부에서 변경이 불가능하여 테스트 하기 힘들다는 치명적인 단점이 있다.
DI 프레임워크가 없으면 쓸 수 없어서 되도록 사용하지 않는것이 좋다.
일반 메서드주입
일반적인 메소드(init과 같은 이름으로)를 통해서 의존관계를 주입받는 방법이다.
한번에 여러 필드를 주입 받을 수 있지만, 일반적으로 메소드를 통한 주입 방법을 잘 사용하지 않는다.
생성자주입을 사용하자
사실, 이 부분은 항상 이해가 되지 않았던 부분이다.
전 회사에서 필드주입을 왕창 넣어놨는데, 강의를 들으면서 보니 필드주입은 사용하지 말라고 한 것 이다.
심지어, 인텔리제이에서는 필드주입을 추천하지 않는다는 경고 표시까지 되어있다.
( 전 회사에서는 이클립스를 사용하였기 때문에 전혀 알지 못했다. )
생성자 주입을 사용해야 하는 이유중 하나는 불변이다.
대부분의 의존관계 주입은 한번 일어나면, 애플리케이션 종료시점까지 변경할 일이 없다.
변경하면 안되는 메소드를 열어두는 것은 좋은 설계 방법이 아니기 때문에,
객체 생성시 딱 1번 호출되는 생성자 주입을 통한 의존관계 주입이 추천된다.
다른 이유는 누락이다.
프레임워크 없이 순수한 자바 코드를 통한 테스트 진행 시 다른 주입방법을 선택하면,
OrderServiceImpl에 대한 테스트를 진행한다고 가정하면, NPE이 발생한다.
( 해당 서비스는 MemberRepository, discountPolicy를 필요로 함 )
이는 해당 서비스가 필요로하는 Repository들이 생성되어 있지 않기 때문인데,
테스트 코드를 작성할때, 그것을 바로 파악하기 어렵다.
오류가 발생하면, 그 후에 내용을 파악해야하고, 그 다음 해당 Repository들에 대한 생성을 따로 해주어야한다.
생성자 주입을 선택한다면, 의존관계 주입이 아니더라도 생성자에서 해당 서비스에 필요로 하는 Repository들을 요구하기 때문에, 순수 자바 코드를 통한 테스트에서도 바로 해당 Repository들을 생성해주면 되기 때문에 문제가 발생하지 않는다.
그리고 혹 입력하지 않고 실행한다면 컴파일 오류가 발생한다.
( IDE에서 어떤 값을 넣어야하는지 바로 알 수 있고, 컴파일 시점에 오류이기 때문에 바로 잡을 수 있다. )
생성자 주입 방식은 프레임워크에 의존하지 않고, 순수한 자바의 특징을 잘 살리는 방법이라 이 방법을 추천한다.
롬복과 최신트렌드
대부분 서비스에 final을 붙여 불변으로 만들어주고, 생성자 주입을 통해 의존관계를 설정한다.
매번 이렇게 설정하기 번거로운 면이 있어서, 롬복에서는 @RequiredArgsConstructor 애노테이션을 제공한다.
이 애노테이션이 붙은 서비스는 final이 붙어있는 필드를 모아서 생성자를 자동으로 만들어 준다.
그리고, 딱 1개의 생성자만 있을때는 @Autowired를 생략할 수 있어서 간편한 사용이 가능하다.
Service에 @Component, @RequiredArgsConstructor 만 입력해 주면 의존관계 주입 끝!
조회 대상 빈이 2개 이상일때
- @Autowired 필드 명 매칭 : 필드 명을 빈 이름으로 변경한다
- @Qualifier : 추가 구분자 설정( @Qualifier("mainDiscountPolicy") )
- @Primary : 해당 애노테이션이 붙으면 우선순위를 가진다
@Qualifier와 @Primary 가 동시에 사용되면 @Qualifier 가 우선순위를 가진다
( 스프링은 자세한걸 우선순위로 )
조회한 빈이 모두 필요할 때 - List, Map
의도적으로 모든 타입이 다 필요할때 - 고객이 할인의 종류를 선택하다면? ( rate, fix )
Map<String, DiscountPolicy>: map의 키에 스프링 빈의 이름을, 값에는 DiscountPolicy 타입으로 조회한 모든 스프링빈을 담아준다
List<DiscountPolicy>: DiscounyPolicy 타입으로 조회한 모든 스프링 빈을 담아준다
'Spring > 핵심원리' 카테고리의 다른 글
[스프링 핵심원리 - 기본편] 내용 정리7 (0) | 2023.09.04 |
---|---|
[스프링 핵심원리 - 기본편] 내용 정리6 (0) | 2023.08.30 |
[스프링 핵심원리 - 기본편] 내용 정리4 (0) | 2023.08.27 |
[스프링 핵심원리 - 기본편] 내용 정리3 (0) | 2023.08.22 |
[스프링 핵심원리 - 기본편] 내용 정리2 (0) | 2022.02.21 |