Post

스프링 핵심원리 - 기본편 정리3

스프링 핵심원리 - 기본편 정리3

들어가며

저번 글에서 수동 빈으로 등록하는 방법을 알아봤었다, 이번에는 자동으로 등록하는 방법과 의존관계주입에 대해서 알아볼 것이다.


자동 빈 등록

why?

수동으로 빈을 등록하면, 등록해야 할 빈의 수가 많아지면 번거롭고 누락이 있을 수 있기 때문에 자동으로 빈을 등록하는 것이 필요하다.

이를 도와주는 것이 @Component@ComponentScan 어노테이션이다.

@Component@ComponentScan

컴포넌트로 지정을 하면 컴포넌트 스캔이 컴포넌트를 읽어 빈으로 등록하는 것이다.

예제를 통해 봐보자. 다음 AutoAppConfig 클래스가 기존의 AppConfig를 대체한 클래스이다. AutoAppConfig

그리고 기존 빈에 등록되어있던 것들을 Component로 지정해 준다. MemberSerivceImpl

MemoryMemberRepository

OrderServiceImpl

RateDiscountPolicy

그러면 ComponentScan이 Component가 붙은 모든 클래스를 빈으로 등록한다.

이렇게 자동으로 등록된 빈은 스프링 컨테이너에 어떻게 저장될까?

수동으로 등록할 때는 _@Bean_이 붙은 메소드 명을 Key로 반환값을 Value로 등록했었다.

자동은 Component가 붙은 클래스명을 key값으로 하되, 첫글자를 소문자로 바꾼다. 그리고 그 클래스의 인스턴스를 Value 값으로 하여 등록한다. (만약 클래스명이 아닌 다른 이름을 key값으로 하려면 Component("직접지정") or Component(value="직접지정") 이렇게 하면 된다.)

컴포넌트 스캔은 기본적으로 @ComponentScan 어노테이션이 붙은 패키지 및 하위 패키지를 모두 탐색한다. 그리고 옵션을 통해서도 지정할 수 있다.

영한님이 추천하는 방법은 굳이 탐색 시작 위치를 건드릴 필요 없이 설정 정보를 프로젝트의 최상단에 두는 것이다. 그럼 알아서 프로젝트 내에서만 탐색을 할 것이다.

그리고 스프링 부트로 프로젝트를 생성할 때 메인이 되는_@SpringBootApplication_ 안에도 _@ComponentScan_이 있기 때문에 보통 _@SpringBootApplication_이 있는 coreApplication을 최상단(루트)에 둔다.

컴포넌트 스캔 옵션

컴포넌트 스캔의 옵션에는 크게 두 가지가 있다.

  1. basePackages - 컴포넌트 스캔 위치를 임의로 지정한다. basePackages = "hello.core" #스캔할 위치 지정
  2. 필터 기능: 1) excludeFilters - 컴포넌트 스캔에서 제외할 대상 지정, 지정된 어노테이션이 붙은 클래스를 제외시킨다.(옵션) excludeFilters = @ComponentScan.Filter(type=FilterType.ANNOTATION, classes = Configuration.class)

    2) includeFilters - 컴포넌트 스캔에서 추가할 대상 지정, 지정된 어노테이션이 붙은 클래스를 추가시킨다.(옵션) includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)

위 사용 예시를 보면 FilterTypeANNOTATION으로 지정해주었는데 이를 포함하여 총 5개의 옵션이 있다.

filterType 옵션

  • ANNOTATION: 기본값, 어노테이션 인식

  • ASSIGNABLE_TYPE: 지정한 타입과 하위 타입 인식(클래스 인식)

  • 그 외 ASPECTJ(AspectJ 표현식), REGEX(정규 표현식), CUSTOM(커스텀)..이 있다.

중복 등록 상황

자동 vs 자동

자동으로 등록될 때 그 이름이 같으면 _ConflictingBeanDefinitionException_이 발생 한다.

자동 vs 수동

수동으로 등록할 때에는 수동이 우선권을 가진다.

수동으로 등록할 때에는 생성자를 호출하면서 의존관계가 주입되었는데, 자동으로 등록하면 어떻게 생성자 코드를 직접 호출하지 않으면서 의존관계를 주입할 수 있을까?


의존관계 자동 주입

먼저 의존관계 주입이라는 개념에 대해서 다시 한번 복기해보자.

수동으로 빈을 등록했을 때와는 다르게 자동 빈 등록을 하면 의존관계가 바로 주입되지 않고, 빈 등록이 모두 다 끝난뒤에 등록된 빈 사이에 의존관계를 주입한다.

예제로 예시를 들기 위해서 예제 구조를 다시 가져왔다. 서비스 구조

예제를 예시로 들면 OrderService는 MemberRepository와 DiscountPolicy 인터페이스 역할에 의존하고 있는데 등록 되어있는 빈 중 인터페이스를 구현할 수 있는 빈을 찾아(자기 자신이나, 확장한 인터페이스도 찾겠지만..) 주입 하는 것이다.

그중 MemoryMemberRepository와 RateDiscountPolicy가 빈으로 등록되어있기 때문에 이들을 찾아 주입한다.

의존관계 주입은 @Autowired 어노테이션을 통해서 이루어진다. 이를 예제에 적용한 코드를 보자.

생성자 주입 예시 생성자에 @Autowired 어노테이션을 붙혀 생성시점에 의존관계를 주입받도록 하였다. 참고로 생성자가 1개만 있을 때는 Autowired를 붙히지 않아도 자동으로 의존관계를 주입한다.

Autowired를 붙히는 위치에 따라서 크게 4가지로 구분되는데 여기서는 생성자에 붙히는 생성자 주입방식을 사용하였다.

4가지 예시

의존관계 주입 방식 4가지

  • 생성자 주입 방식
  • 수정자 주입 방식
  • 필드 주입 방식
  • 일반 메서드 주입 방식

일반적으로는 생성자 주입 방식을 추천한다. 생성자 방식을 이용하면 생성 시점에 한번만 의존관계가 주입되기 때문에 불변의 특징을 가질 수 있고 이는 곧 좋은 객체지향프로그래밍의 장점을 살리는 것이기도 하다.

또한 생성 시점에 의존관계를 추가하기 때문에 단위 테스트를 할 때 의존관계를 명시하지 않으면 컴파일 오류가 발생하고, final 키워드를 사용할 수 있어 안전성을 더해준다

가끔 불변이 아닌 변경이나, 지연이 필요한 경우에만 setter주입을 사용하고 그외에는 생성자 주입방식을 사용하도록 하자.

그리고 필드 주입방식은 * 외부에서 의존관계주입이 아예 안되기 때문에 사용하지 않도록 하자


의존관계 주입 중 다양한 경우

_@Autowired_로 주입을 받을 때 앞에서 getBean 메서드를 통해 빈을 조회하는 것과 마찬가지로 중복되는 등 여러가지 경우가 생길 수 있다. 어떤 경우가 있고 각각 어떻게 해결하는지 알아보자.

주입할 스프링 빈이 없는 경우

Autowired에는 required라는 옵션이 있는데 디폴트가 true여서 빈이 없는 경우 오류가 난다.

이런 경우 해결할 수 있는 방법이 3가지가 있다.

  • @Autowired 어노테이션의 required를 false로 설정한다. -> 수정자 메서드 호출자체가 안된다.
  • 수정자 주입 메서드의 파라미터에 _@Nullable_을 사용한다. ->null 값을 주입한다.
  • Optional을 활용한다. ->Optional.empty 값을 주입한다.

2개이상 조회되는 경우

_@Autowired_는 기본적으로 타입을 통해서 조회를 한다. 그래서 두 빈의 타입이 중복되는 경우 타입이외에 구별할 수 있는 방법이 필요하다.

특정 하위 타입으로 명시하는 경우 해결할 수 있겠지만 이렇게 하면 DIP를 위반하게 되고 유연성이 떨어지게 된다.

이를 해결할 수 있는 3가지 방법이 있다.

  • @Autowired 필드 명, 파라미터 명 매칭 -> 타입이 중복되면 필드 명/파라미터 명 매칭을 시도한다.
  • @Qualifier 매칭 -> 구분자를 추가하여 매칭한다.
  • @Primary 지정 -> 우선 선택될 빈을 지정한다.

@Primary vs @Qualifier

Primary는 기본값 처럼 동작, Qualifier는 매우 상세히 동작한다. 따라서 Qualifier를 명시하는 경우 Qualifier가 우선순위를 가진다.

모두 조회하는 경우

타입이 같은 모든 빈이 필요한 경우가 있을 것이다. 그럴 경우에는 Map과 List를 사용하여 여러 빈 정보를 받아올 수 있다.

컬렉션 프레임워크를 통해 타입을 지정하여 Map과 List를 필드로 하면 각 타입에 맞게 빈들이 주입된다.

모두 불러오기


롬복 라이브러리

  1. 개발을 할 때 대부분의 필드는 final을 사용함
  2. 보통 생성자 주입방식을 선택하고 생성자를 만들어야함

-> 이를 자동화 해주는 것이 Lombok라이브러리

롬복 사용

위와 같이 @RequiredArgsConstructor 어노테이션 하나면 주석 처리한 부분 만큼의 코드를 생략할 수 있다.(롬복 짱짱맨 롬복을 애용하자)


자동 등록 vs 수동 등록

  • 편리한 자동기능기본으로 사용
  • 광범위하게 영향을 미치는 직접 등록하는 기술 지원 객체수동
  • 다형성을 적극 활용하는 비즈니스 로직은 수동 등록 고민, 자동으로 한다면 특정 패키지에 묶기 -> 핵심은 딱 보고 이해 될 수 있게!
This post is licensed under CC BY 4.0 by the author.