성장일기

내가 보려고 정리하는 공부기록

백엔드/스프링

[Spring] 4. 자동으로 의존관계 주입하기 - 스프링 핵심원리 기본편

와나나나 2024. 2. 7. 22:23
728x90

인프런 김영한 강사님의 스프링 핵심원리 강의를 듣고 정리하고자 한다

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

 

스프링 핵심 원리 - 기본편 강의 - 인프런

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢

www.inflearn.com

 

오늘의 주제는 자동으로 의존관계 주입하기이다. 지난 게시글에서 정리했던 내용은 자바 코드의 @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는 여러 이유로 가끔 사용한다고 한다.

 

 

✅ 중복 등록과 충돌

컴포넌트 스캔 시 같은 빈 이름을 등록하게 될 수도 있다. 이럴 때에는 어떻게 될지 알아보자. 상황은 다음 두가지이다.

  1. 자동 빈 등록 vs 자동 빈 등록
  2. 수동 빈 등록 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는 그럴 필요가 없어 좋다. 

 

 

이렇게 자동과 수동 기능을 알아보았는데 실무에서는 편리한 자동 기능을 기본으로 하는 게 좋다. 다만 애플리케이션에 광범위하게 영향을 미치는 기술 지원 객체는 수동 빈으로 등록해 설정 정보에 바로 나타나게 하는 것이 유지보수에 좋다.

 


이렇게 이번 게시글 정리가 끝났다. 다음 게시글은 빈 생명주기 콜백을 주제로 마지막 게시글을 쓸 예정이다.