인프런 김영한 강사님의 스프링 핵심원리 강의를 듣고 정리하고자 한다
오늘의 주제는 자동으로 의존관계 주입하기이다. 지난 게시글에서 정리했던 내용은 자바 코드의 @Bean으로 설정정보에 직접 등록할 스프링 빈을 나열했다. 그러나 스프링빈의 수가 많아지면 관리가 힘들어질 수 있다.
그래서 스프링은 설정 정보가 없어도 자동으로 스프링빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다.
그래서 이번 게시글에서는 다음과 같은 내용을 다루어보고자 한다.
- 컴포넌트 스캔
- 다양한 의존관계 주입방법
- 조회 빈이 2개 이상일 때
1. 컴포넌트 스캔
위에서 말했듯이 컴포넌트 스캔은 설정정보 없이 자동으로 스프링빈을 등록하는 기능이다. 아래 코드와 같이 사용한다.
@Configuration
@ComponentScan(
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class))
public class AutoAppConfig {
...
}
위처럼 설정정보(AppConfig)에 @ComponentScan을 써주면 된다. 그리고 내부 메소드들에는 @Bean을 붙이지 않아도 된다!
컴포넌트 스캔은 @Component 애노테이션이 붙은 클래스를 스캔해 스프링 빈으로 등록한다!!
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
...
}
위 코드처럼 @AutoWired를 생성자에 붙여 의존관계를 자동으로 주입할 수 있다. 여러 의존관계 또한 한 번에 주입받을 수 있다.
✅ 컴포넌트 스캔과 자동 의존관계 주입 순서
1. @ComponentScan (컴포넌트 스캔)
- @ComponentScan이 @Component가 붙은 모든 클래스를 스프링 빈으로 등록한다.
- 스프링 빈의 기본 이름은 클래스 명을 사용한다. (맨 앞의 대문자는 소문자로 사용한다.)
- 직접 지정도 가능하다. EX | @Component("memberService")
2. @AutoWired (의존관계 자동 주입)
- 생성자에 @AutoWired 지정하면 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아 주입한다.
- 기본 조회 방법은 타입이 같은 빈을 찾아 주입한다.
- 생성자에 파라미터가 많아도 다 찾아서 자동으로 주입한다.
✅ 탐색 위치와 기본 스캔 대상
# 탐색 위치
모든 클래스를 순차적으로 탐색하면 정말 많은 시간이 걸릴 것이다. 그래서 필요한 위치부터 탐색을 시작할 수 있게 시작 위치를 설정할 수 있다.
@ComponentScan(
basePackages = "hello.core"
)
- basePackages : 탐색 패키지의 시작 위치 지정
- 이 패키지를 시작으로 하위 패키지를 모드 탐색한다.
- 지정하지 않으면 @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.
강사님이 추천하는 방법은 basePackages 지정 없이 프로젝트 시작 루트와 같은 패키지에 메인 설정 정보를 두고 @ComponentScan을 붙이는 것이었다!
# 스캔 대상
컴포넌트 스캔은 @Component 뿐만 아니라 아래 애노테이션도 대상으로 둔다.
- @Controller : 스프링 MVC 컨트롤러에서 사용, 스프링 MVC 컨트롤러로 인식
- @Service : 스프링 비즈니스 로직에서 사용, 특별한 처리 X 지만 핵심 비즈니스 계층을 인식하는데 도움됨
- @Repository : 스프링 데이터 접근 계층에서 사용, 스프링 데이터 접근 계층으로 인식하고 데이터 계층의 에외를 스프링 예외로 변환
- @Configuration : 스프링 설정 정보에서 사용, 스프링 빈이 싱글톤을 유지하도록 추가 처리
애노테이션 인식은 자바가 지원하는 기능이 아닌 스프링이 지원하는 기능이다.
✅ 필터
컴포넌트 스캔 대상을 필터링 하는 것으로 두 가지 종류가 있다.
- includeFilters : 컴포넌트 스캔 대상을 추가로 지정
- excludeFilters : 컴포넌트 스캔 대상에서 제외할 대상 지정
@ComponentScan(
includeFilters = @Filter(type = FilterType.ANNOTATION, classes = AnnotationName.class))
이런식으로 작성해주면 된다. 작성할 때 type을 명시하는데, 이에는 5가지 옵션이 있다.
- ANNOTATION : 기본값, 애노테이션을 인식해서 동작
- ASSIGNABLE_TYPE : 지정한 타입과 자식 타입을 인식해 동작
- ASPECTJ : AspectJ 패턴 사용
- REGEX : 정규표현식
- CUSTOM : TypeFilter라는 인터페이스를 구현해서 처리
@Component면 충분하므로 includeFilters를 사용할 일이 거의 없고, excludeFilters는 여러 이유로 가끔 사용한다고 한다.
✅ 중복 등록과 충돌
컴포넌트 스캔 시 같은 빈 이름을 등록하게 될 수도 있다. 이럴 때에는 어떻게 될지 알아보자. 상황은 다음 두가지이다.
- 자동 빈 등록 vs 자동 빈 등록
- 수동 빈 등록 vs 자동 빈 등록
자동 빈 등록 vs 자동 빈 등록 : 컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록될 수 있다.
=> 이 경우에는 ConflictingBeanDefinitionException 예외 발생
수동 빈 등록 vs 자동 빈 등록 : 수동 빈 등록이 우선권을 가진다. 즉, 수동 빈이 자동 빈을 오버라이딩 한다.
2. 다양한 의존관계 주입 방법
의존관계 주입 방법으로는 크게 4가지 방법이 있다.
- 생성자 주입
- 수정자 주입 (setter주입)
- 필드 주입
- 일반 메소드 주입
이 중에서 우리가 써야하는 주입은 생성자 주입이고, 경우에 따라 수정자 주입을 옵션으로 부여하면 된다. 필드주입은 사용하지 않는게 좋다고 한다.
# 생성자 주입
생성자를 통해서 의존 관계를 주입 받는 방법을 말한다.
- 생성자 호출 시점에 딱 1번만 호출되는 것이 보장된다. 제약이 있어야 좋은데 이 점을 만족시킨다.
- 불변, 필수 의존관계에 주로 사용한다.
생성자가 클래스에 1개 뿐이라면, @Autowired를 생략해도 자동 주입된다.
# 수정자 주입
setter라 불리는 필드 값을 변경하는 수정자 메소드를 통해 의존관계를 주입하는 방법을 말한다.
- 선택, 변경 가능성이 있는 의존관계에 사용한다.
참고로 @Autowired 의 기본 동작은 주입할 대상이 없으면 오류가 발생한다. 주입 대상 없어도 동작하게 하려면 @Autowired(required = false) 로 지정하면 된다.
이 방법 외에도 org.springframework.lang.@Nullable 을 쓸 수도 있다. 이걸 쓰면 주입 대상이 없을 때 null로 대체할 수 있다.
위에서 주로 생성자 주입을 사용하면서 경우에 따라 수정자 주입을 부여하면 된다고 했는데 어떤 경우에 수정자 주입을 쓸까 ? 그리고 왜 생성자 주입을 써야할까 ?
✅ 생성자 주입을 쓰는 이유
불변
- 대부분의 의존관계 주입은 한 번 일어나면 애플리케이션이 종료될 때까지 의존관계를 변경할 일이 없어서 불변해야한다.
- 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출될 일이 없어 누군가 실수로 변경할 걱정을 하지 않아도 된다.
final 키워드 사용
- 생성자 주입을 쓰는 경우에만 final 키워드를 사용할 수 있다.
- 이를 사용함으로써 생성자에 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.
개발을 하다보면 대부분 불변이라 final 키워드를 쓴다고 한다. 이때 생성자를 생성하고, 주입 받은 값을 대입하는 일을 간단하게 할 수 있게 도와주는 기능이 있는데 바로 롬복이다.
롬복은 getter, setter를 자동 구현해주는 느낌이라고 생각하면 된다. 롬복 라이브러리가 제공하는 @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드를 모야 생성자를 자동으로 만들어준다고 한다.
여기에 생성자가 1개일 때 @Autowired를 생략할 수 있다는 점을 이용해 기능을 다 제공하면서 코드는 깔끔하게 쓸 수 있다고 한다.
📌 롬복 라이브러리 적용하기
1. build.gradle 에 라이브러리와 환경 추가
```
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
```
2. Settings (윈도우 기준) -> plugin -> lombok 검색 설치 실행
3. Settings -> Annotation Processors 검색 -> Enable annotation processing 체크
3. 조회 빈이 2개 이상
Autowired는 타입으로 조회하기 때문에, 하위 빈이 2개 이상일 때 NoUniqueBeanDefinitionException 오류가 발생한다.
이때 3개의 해결방법이 있다.
- @Autowired 필드 명 매칭
- @Qualifier -> @Qualifier 끼리 매칭 -> 빈 이름 매칭
- @Primary 사용
@Autowired 필드명 매칭
@Autowired 는 타입 매칭을 시도하고, 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.
// 기존 코드
@Autowired
private DiscountPolicy discountPolicy
// 필드 명을 빈 이름으로 변경
@Autowired
private DiscountPolicy rateDiscountPolicy
@Qualifier 사용
@Qualifier 는 추가 구분자를 붙여주는 방법이다. (빈 이름을 변경하는 건 아니다.) 별명 붙여주는 느낌이다.
아래와 같이 사용해주면 된다.
@Component
@Qualifier("mainDiscount")
public class RateDiscountPolicy implements DiscountPolicy {}
@Primary 사용
@Primary 는 우선순위를 정하는 방법이다. 이 애노테이션이 붙어있는 것이 우선권을 가진다.
@Qualifier은 주입받을 때 모든 코드에 @Qualifier를 붙여야 하지만 @Primary는 그럴 필요가 없어 좋다.
이렇게 자동과 수동 기능을 알아보았는데 실무에서는 편리한 자동 기능을 기본으로 하는 게 좋다. 다만 애플리케이션에 광범위하게 영향을 미치는 기술 지원 객체는 수동 빈으로 등록해 설정 정보에 바로 나타나게 하는 것이 유지보수에 좋다.
이렇게 이번 게시글 정리가 끝났다. 다음 게시글은 빈 생명주기 콜백을 주제로 마지막 게시글을 쓸 예정이다.
'백엔드 > 스프링' 카테고리의 다른 글
[Spring] JAVA 로깅 알아보기 (로그레벨, @Slf4j) (0) | 2024.04.03 |
---|---|
[Spring] 5. 빈 생명주기 콜백- 스프링 핵심원리 기본편 (1) | 2024.02.09 |
[Spring] 3. 싱글톤 컨테이너 - 스프링 핵심원리 기본편 (1) | 2024.02.05 |
[Spring] 2. 스프링으로 전환하기 (스프링 빈, 스프링 컨테이너) - 스프링 핵심원리 기본편 (1) | 2024.02.05 |
[Spring] 1. 순수 자바로 설계하기 - 스프링 핵심원리 기본편 (1) | 2024.01.29 |