본문 바로가기

WEB/Spring

@ComponentScan과 @Autowired

스프링은 설정 정보가 없어도

1) 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다.

2) 또 의존관계도 자동으로 주입하는 @Autowired 라는 기능도 제공한다.

 

✔️ 컴포넌트 스캔의 필요성 

스프링 빈을 등록할 때는 자바 코드의 @Bean이나 XML의 <bean> 등을 통해서 설정 정보에 직접 등록할 스프링 빈을 나열하는 방법이 있다. 하지만 등록해야 할 스프링 빈이 많아진다면 하나씩 다 입력하는 것은 한계가 있고 누락할 가능성도 있다. 

=>  스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을  제공한다. 

 

✔️ 컴포넌트 스캔 방법 

✏️[ 1 ] 다음과 같이 설정 정보 클래스에 @ComponentScan 어노테이션을 붙이면 된다. 

@Configuration
@ComponentScan//자동으로 스프링 빈으로 등록
public class AutoAppConfig {

}

- 기존의 AppConfig와는 다르게 @Bean으로 등록한 클래스가 하나도 없다!

2) 

 

컴포넌트 스캔은 이름 그대로 @Component 애노테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록한다.

✏️[ 2 ] 이제 각 클래스가 컴포넌트 스캔의 대상이 되도록 @Component 애노테이션을 붙여주자.

→ 클래스 위에 붙이면 된다.

빈으로 등록하고 싶은 것들을 따로 AppConfig에 다시 적는 것이 아니라 해당 클래스 파일로 가서 @Component 어노테이션 붙이기만 하면 된다

@Component
public class MemberServiceImpl implements MemberService{
}

참고: @Configuration 이 컴포넌트 스캔의 대상이 된 이유도 @Configuration 소스코드를 열어보면 @Component 애노테이션이 붙어있기 때문이다.

 

🔎 컴포넌트 스캔 동작 과정

빈 등록 과정

@ComponentScan 은 클래스를 다 뒤져서 @Component 가 붙은 모든 클래스스프링 빈으로 등록한다.

  • 이때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다.
    • 빈 이름 기본 전략: MemberServiceImpl 클래스 memberServiceImpl
    • 빈 이름 직접 지정: 만약 스프링 빈의 이름을 직접 지정하고 싶으면 @Component("memberService2") 이런식으로 이름을 부여하면 된다.

 


✔️ 자동 의존 관계 주입의 필요성 

하지만 이렇게 별다른 코드없이 클래스 위에만 어노테이션만 딱! 추가하기만 한다면 의존관계는 어떻게 설정하는 것인가?!

그냥 위에 어노테이션만 붙이면 이전에 수동으로 설정 파일에서 의존관계를 따로 하나씩 설정했지만 이 어노테이션을 쓰게 되면 설정 정보를 사용하지 않는다는 문제점이 있다. 

💡[ 해결 방법 ]

=> 의존관계 자동 주입을 사용해서 하면 된다.
생성자 위에 @Autowired를 붙이면 된다. (자동으로 연결하여 의존관계 주입)
@Autowired //ac.getBean(MemberRepository.class) 이것과 비슷하게 동작
public MemberServiceImpl(MemberRepository memberRepository) {
	this.memberRepository = memberRepository;
}

 

🔎 의존관계 자동 주입 동작 과정

의존관계 자동 주입 과정

생성자에 @Autowired 를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.

이때 기본 조회 전략은 타입이 같은 빈을 찾아서 주입한다는 것이다. 

 

예를 들어, 위의 사진에서 public MemberServiceImpl(MemberRepository memberRepository)

부분에서 MemberRepository 타입이 와야 하므로 MemberRepository 타입을 뒤진다. (같은 타입이 여러 개이면? 충돌이 날 것이다. 이를 해결하는 방법은 다음 글에서 소개 예정이다. )

" getBean(MemberRepository.class) " 와 동일하다고 이해하면 된다.

 

 

[ 완성 코드 ]

컨포넌트 스캔과 의존관계 자동 주입을 모두 적용한 모습은 다음과 같다. 

@Component
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;
    
    @Autowired
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

 

Test 코드로 확인

AutoAppConfig 클래스 : 앞서 작성했던 컴포넌트 스캔 방법과 자동 의존 관계 주입 적용한 코드 

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(
		excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}

 

 

AutoAppConfigTest.java

import hello.core.AutoAppConfig;
import hello.core.member.MemberService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import
org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.*;
public class AutoAppConfigTest {
   @Test
   void basicScan() {
          ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
          MemberService memberService = ac.getBean(MemberService.class);
          assertThat(memberService).isInstanceOf(MemberService.class);
   }
}

 

AppConfig 클래스 : 기존 설정 클래스 => 컴포넌트 스캔과 자동 의존 관계 주입하지 않고 직접 빈으로 등록하고 설정해준 것

AutoAppConfig 클래스 : 컴포넌트 스캔 방법과 자동 의존 관계 주입 적용

 

설정 정보로 AutoAppConfig 클래스(컴포넌트 스캔 방법과 자동 의존 관계 주입 적용)를 넘겨준다.

실행해보면 기존(컴포넌트 스캔과 자동 의존 관계 주입하지 않고 직접 빈으로 등록하고 설정해준 것)과 같이 잘 동작하는 것을 확인할 수 있다.

 

[결과]

로그를 잘 보면 컴포넌트 스캔이 잘 동작하는 것을 확인할 수 있다.

  ClassPathBeanDefinitionScanner - Identified candidate component class:
  .. RateDiscountPolicy.class
  .. MemberServiceImpl.class
  .. MemoryMemberRepository.class
  .. OrderServiceImpl.class

 

 

[참고]

옵션 : excludeFilters 에 대해서 

@ComponentScan(
	excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
		//자동 스캔 예외 대상 설정 (앞서 한 예시들과의 충돌을 피하기 위해)
)

컴포넌트 스캔을 사용하면 @Configuration 이 붙은 설정 정보도 자동으로 등록되기 때문에, AppConfig, TestConfig 등 앞서 만들어두었던 설정 정보도 함께 등록되고, 실행되어 버린다. 그래서 excludeFilters 를 이용해서 설정정보는 컴포넌트 스캔 대상에서 제외했다.

 

 

 

- 이 글은 김영한님의 스프링 기본편을 수강하고 정리한 글입니다. (인프런)