Google Developer Student Clubs 1기/GDSC 백엔드 스터디

[백엔드 스터디] 4주차 - 컴포넌트 스캔 그리고 의존관계 자동 주입

Razelo 2023. 1. 27. 17:28

이번 4주차 주제는 컴포넌트 스캔 그리고 의존관계 자동 주입이다. 


컴포넌트 스캔이란?

스프링 빈을 등록할 때 구성파일에 @Bean을 등록해서 사용할 수 있었다. 하지만 관리할 빈이 많아지면 이 방식이 관리하기 번거로워진다. 그래서 굳이 빈 설정파일을 만들지 않거나 @Bean을 안쓰고 빈 등록을 할 수 있는 방식을 쓰게 되었다. 그게 @Component를 써서 컴포넌트 스캔을 하는 방식이다. 

 

설정파일이 없다면 의존관계를 어떻게 주입할까?

@Autowired를 사용해서 자동 의존관계 주입을 한다. 자동 의존관계 주입은 아래서 살펴본다. 

 

컴포넌트 스캔이란 스프링이 스프링 빈으로 등록될 준비가 된 클래스들을 스캔해서 빈으로 등록해주는 과정을 말한다.

@Component 어노테이션이 붙어있는 클래스들은 모두 컴포넌트 스캔의 대상이 된다. 쉽게 말해서 @Component를 가진 모든 대상을 가져와서 빈에 등록하기 위해 찾는 과정이라고 보면 된다. 빈 설정파일을 안만들어도 되고 또한 @Bean을 통해서 빈을 하나하나 지정할 필요가 없기 때문에 간편하다. 

 

@Configuration, @Service, @Repository, @Controller 등의 어노테이션에도 전부 @Component가 포함되어 있기에 자동으로 컴포넌트 스캔의 대상이 된다. 

 

컴포넌트 스캔을 사용하려면 설정 정보 클래스에 @ComponentScan 어노테이션을 붙여줘야 한다. 이때 컴포넌트 스캔의 범위는 설정 정보 클래스의 패키지를 포함한 모든 하위 패키지가 된다. 그래서 모든 자바 클래스를 전부 컴포넌트 스캔하면 시간이 오래 걸릴 수 있기 때문에 필요한 위치부터 탐색할 수 있도록 지정해줘야 한다. 

 

@ComponentScan(
	basePackages = "org.core"
)

 

스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인 @SpringBootApplication을 프로젝트 시작 루트 위치에 두는 것이 관례라고 한다. @SpringBootApplication 어노테이션에 @ComponentScan이 들어있기 때문에 하위 패키지 모두 컴포넌트 스캔의 대상이 된다고 한다. 

 

컴포넌트 스캔의 특징 

빈 이름 기본 전략은가장 앞 문자를 소문자로 바꾼 것이 빈 이름이 된다. 즉 MemberServiceImpl -> memberServiceImpl이 되는 것이다. 또한 @Component("지정할 이름")을 통해 빈 이름을 수동으로 지정할 수 있다. 

 

컴포넌트 스캔 대상

@Component 컴포넌트 스캔에서 사용
@Controller 스프링 MVC 컨트롤러에서 사용
@Service 스프링 비즈니스 로직에서 사용
@Repository 스프링 데이터 접근 계층에서 사용
@Configuration 스프링 설정 정보에서 사용

 

컴포넌트 스캔 범위

@ComponentScan 어노테이션이 있는 파일의 패키지 아래를 찾는다. 

basePackages / basePackageClasses로 지정도 가능하다.

권장하는 방법은 구성파일에 등록 시에 프로젝트 최상단에 두는 것이다. 다만 SpringBoot 사용하는 중이라면 @SpringBootApplication에 포함되어있어서 자동으로 최상단으로 유지된다. 

 

컴포넌트 스캔에서 주의할 점

수동 빈 등록과 자동 빈 등록이 있는데 자동으로 빈을 등록한 상태에서 수동 등록 파일과 이름이 겹칠 경우에는 수동 빈 파일이 우선권을 가진다. 하지만 최근 SpringBoot의 경우는 오류를 내기에 주의해야 한다. 

특정 인터페이스를 상속받는 A와 B가 있을 때 둘 중 내가 사용할 것만 빈에 등록해야 하기 때문에 한 곳에만 @Component를 붙여야 한다. 그렇지 않다면 @Qulifier나 @Primary를 사용해야 한다. 

 

@Qulifier와 @Primary는 무엇일까? 

@Qulifier는 같은 클래스의 빈이 여러개일 경우 @Autowired 의 DI가 애매해지기 때문에 @Qulifier 어노테이션에 id를 입력함으로써 주입될 빈을 직접 지정할 수 있다고 한다. @Qulifier("test")는 XML의 id가 test인 빈을 주입하라는 뜻이다. 

그리고 @Primary는 우선순위로 있는 클래스가 여러개가 있을 시에 그 중 가장 우선순위로 주입할 클래스 타입을 선택할 수 있는 어노테이션이다. 우선으로 사용할 클래스에 적어주기만 하면 된다. 

 

컴포넌트 스캔의 옵션

특정 어노테이션을 포함 혹은 제외시킬 수 있다. 

includeFilters를 통해 컴포넌트 스캔 대상으로 추가하고 excludeFilters를 통해 컴포넌트 스캔 대상에서 제외시킨다. 

 

@ComponentScan(
	includeFilters = @ComponentScan.Filter(type=FilterType.ANNOTATION, classes = TestIncludeComponent.class),
	excludeFilters = @ComponentScan.Filter(type=FilterType.ANNOTATION, classes = TestExcludeComponent.class)
)

 

FilterType옵션은 다음과 같다. 

ANNOTATION 기본값, 어노테이션을 인식해 동작
ASSIGNABLE_TYPE 지정한 타입과 자식 타입을 인식해 동작
ASPECTJ AspectJ 패턴 사용
REGEX 정규 표현식
CUSTOM TypeFilter라는 인터페이스를 구현해서 처리

 

의존관계 자동 주입이란? 

의존관계 자동 주입에는 생성자 주입, 수정자 주입, 자바 빈 프로퍼티, 필드 주입, 일반 메서드 주입이 있다. 

 

생성자 주입

생성자가 호출되는 시점에 단 한번만 호출되는 것을 보장한다.

불변, 필수 의존 관계에 사용한다.

생성자가 하나만 존재한다면 @Autowired를 생략해도 자동으로 의존 관계가 주입된다. 

 

생성자 주입을 사용해야 하는 이유 

1. 불변 

대부분의 의존 관계는 한번 주입되면 종료 시점까지 변경되지 않아야 한다. 수정자 주입의 경우 setter 메서드가 public으로 접근이 열려 있기 때문에 변경에 취약하다. 

2. 누락 방지 

생성자 주입을 사용하는 경우 주입되어야 하는 데이터가 누락되었을 때 컴파일 오류가 발생하므로 누락을 방지할 수 있다. 따라서 컴파일 시점에 값 설정이 누락되는 경우를 방지할 수 있다. 

3. final

생성자 주입을 사용할 때 필드는 final로 지정될 수 있다. 필드를 final로 지정하는 경우 값이 필수로 할당되어야 한다. 따라서 컴파일 시점에 값 설정이 누락되는 경우를 방지할 수 있다. 

 

수정자 주입

setter 메서드를 사용해 의존 관계를 주입한다.

선택, 변경 가능성이 있는 의존 관계에 사용한다. 

 

필드 주입

필드에 바로 의존 관계를 주입한다. 

외부에서 변경이 불가능해 테스트가 힘들다.

테스트 코드나 스프링 설정을 위한 @Configuration을 제외하고는 사용이 권장되지 않는다. 

 

일반 메서드 주입 

일반 메서드를 통해 의존 관계를 주입한다. 

한 번에 여러 필드를 주입 받는 것이 가능하다. 

일반적으로 잘 사용되지 않는다. 

 

자동 주입 대상을 옵션으로 처리하기

주입할 스프링 빈이 없는 경우 동작을 위해 @Autowired의 옵션을 설정해주어야 한다. 

 

@Autowired(required=false)

자동 주입할 대상이 없는 경우 수정자 메서드 자체를 호출하지 않는다. 

@Autowired(required = false)
public void setNoBean1(Member member) {...}

 

@Nullable

자동 주입할 대상이 없는 경우 null이 입력된다. 

@Autowired
public void setNoBean2(@Nullable Member member) {...}

 

Optional<>

자동 주입할 대상이 없는 경우 Optional.empty가 입력된다. 

@Autowired(required=false)
public void setNoBean3(Optional<Member> member) {...}

 

면접 예상 질문 

1. 컴포넌트 스캔에서 주의해야할 점을 한 가지 말씀해보세요.

-> 수동 빈 등록과 자동 빈 등록에서 이름이 겹치면 최근 SpringBoot의 경우 오류를 내기 때문에 주의해야 합니다. 

 

2. 수동 빈 등록방식보다 자동 등록 방식이 더 나은 이유가 무엇인가요? 

-> 수동 빈 등록방식은 빈이 많아질 수록 점점 관리가 복잡해지는데 반면 자동 등록 방식은 설정파일도 유지하지 않아도 되기 때문에 관리가 편리합니다. 

 

3. @ComponentScan에서 컴포넌트 스캔 대상을 추가하고 제외하는 옵션의 이름은 각각 무엇인가요? 

-> includeFilters와 excludeFilter이다. 

 

4. 특정 인터페이스를 구현하는 A, B가 있다고 가정할 때 둘 중 본인이 사용할 것만 빈에 등록해야 하기에 한곳에만 @Component를 선언해야 합니다. 하지만 그럼에도 불구하고 둘 모두 사용하고 싶다면 무슨 방법을 사용해야하나요?

-> @Qualifier 혹은 @Primary를 사용합니다. @Qualifier는 별칭을 지정해주는 기능 정도로 보면 되고 @Primary는 우선순위를 정해준다고 볼 수 있음.

 

5. 컴포넌트 스캔을 사용하면 자동으로 빈이름이 지정됩니다. 이때 기본 빈 이름 지정 원칙이 어떻게 되는지 말해보세요. 

-> 가장 앞 문자를 소문자로 바꾼 것이 빈 이름이 됩니다. 또한 수동으로 빈 이름을 지정해주고 싶다면 @Component 어노테이션 안에 지정할 이름을 선언할 수도 있습니다. 

 

6. @Autowired는 타입으로 Bean을 조회하기 때문에 2개 이상의 빈이 등록되는 경우 예외가 발생합니다. 이때 발생하는 예외는 무엇이고 해결 방법은 어떤 것이 있는지 아는 대로 말해보세요

-> NoUniqueBeanDefinitionException이 발생합니다. 해결방법은 크게 세 가지가 있습니다. @Autowired가 붙은 필드명을 변경해줄 수 있습니다. 또한 @Qualifier를 사용하면 빈에 별칭을 부여하는 것과 같은 효과를 얻을 수 있습니다. 여러 빈 객체의 충돌이 우려되는 부분에 @Qualifier("별칭명")을 주어서 등록된 빈의 별칭을 추가로 찾아서 매칭시켜줄 수 있습니다. @Primary를 사용할 수도 있습니다. @Primary는 최우선 순위를 정해주는 어노테이션입니다. 여러 빈이 매칭되면 @Primary가 명시된 빈이 최우선적으로 매칭됩니다. @Qualifier보다는 @Primary가 사용에 편의성이 있습니다. 

 

7. 수동 빈 등록은 주로 언제 사용하는 것이 좋나요? 

-> 기술 지원 로직을 구현할 때 좋다고 합니다. 애플리케이션 전반에 걸쳐서 광범위하게 영향을 미치는 기술 지원 로직은 예를 들어 AOP, DB연결, 공통 로그 처리처럼 비즈니스 요구사항에 해당되는 서비스가 아닌 공통 하위 기술들을 가급적 수동 빈 등록을 사용해서 따로 관리해주는 것이 유지보수에 좋다고 합니다. 또한 다형성을 적극 활용할 때 수동 빈 등록이 좋다고 합니다. 쉽게 연결을 구성하고 변경하기 좋기 때문이라고 합니다. (예시: 컴포넌트 스캔 5번)

 

8. @Qualifier와 @Primary를 둘 다 명시한다면 스프링은 어떻게 판단할까요? 

-> @Qualifier와 @Primary가 둘 다 명시된다면 스프링은 자동보다 수동 설정에 우선권을 가지기 때문에 @Qualifier가 우선적으로 매핑된다고 합니다. 

 

9. @Nullable을 선언한 파라미터에는 자동 주입할 대상이 없는 경우 무엇이 입력될까요? 

-> null이 입력된다. 


아래 출처에서 많은 도움을 받았습니다. 

 

감사합니다. 

 

컴포넌트 스캔 

- https://code-lab1.tistory.com/170

- https://velog.io/@neity16/Spring-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8-6-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%8A%A4%EC%BA%94Component-Scan-DI

- https://cjw-awdsd.tistory.com/12

- https://engkimbs.tistory.com/683

- https://bangu4.tistory.com/297

 

의존관계 자동 주입 

- https://dodeon.gitbook.io/study/kimyounghan-spring-core-principle/07-autowire

- https://romcanrom.tistory.com/92 

 

AspectJ 패턴이란?

- https://jiwondev.tistory.com/152

 

 

 

 

 

반응형