TIL

196일차(모험 105일차) - 컴포넌트 스캔과 의존관계 자동 주입 시작하기

haedal-uni 2022. 3. 29. 23:06
728x90

[add] 컴포넌트 스캔과 의존관계 자동 주입 시작하기 [#28] #29

지금까지 스프링 빈을 등록할 때는 자바 코드의 @Bean이나 XML의 등을 통해서

설정 정보에 직접 등록할 스프링 빈을 나열했다.

 

예제에서는 몇개가 안되었지만, 이렇게 등록해야 할 스프링 빈이 수십, 수백개가 되면

일일이 등록하기도 귀찮고, 설정 정보도 커지고, 누락하는 문제도 발생한다.

 

그래서 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는

컴포넌트 스캔이라는 기능을 제공한다.

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

 

코드로 컴포넌트 스캔과 의존관계 자동 주입을 알아본다.

기존 AppConfig.java는 과거 코드와 테스트를 유지하기 위해 남겨두고,

새로운 AutoAppConfig.java를 만들었다.

 

 


AutoAppConfig.java

컴포넌트 스캔을 사용하려면 먼저 @ComponentScan 을 설정 정보에 붙여주면 된다.

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

 

 

@ComponentScan 

컴포넌트 스캔은 이름 그대로 @Component 애노테이션이 붙은 클래스를 스캔해서

스프링 빈으로 등록한다

 

 

@ComponentScan(excludeFilters = @ComponentScan.Filter (type= FilterType.ANNOTATION, classes = Configuration.class))

스프링 bean으로 자동 등록하는데 그 중에서 뺄 것을 지정하는 것이다.

여기서는 Configuration.class를 뺄 것이다. → Configuration 어노테이션이 붙은 것은 뺄 것이다.

(필터 타입은 annotation)

 

 

 

 

 

Configuration을 제외 한 이유?

컴포넌트 스캔을 사용하면 @Configuration 이 붙은 설정 정보도 자동으로 등록되기 때문에,

AppConfig, TestConfig 등 앞서 만들어두었던 설정 정보도 함께 등록되고, 실행되어 버린다.

그래서 excludeFilters 를 이용해서 설정정보는 컴포넌트 스캔 대상에서 제외했다.

 

보통 설정 정보를 컴포넌트 스캔 대상에서 제외하지는 않지만,

기존 예제 코드를 최대한 남기고 유지하기 위해서 이 방법을 선택했다

 

 

AppConfig가 자동으로 등록이 되면 안된다.

수동으로 등록하는 건데 안그러면 충돌이 날 것이다. 

 

 

 

@Configuration을 클릭하면 @Component가 붙어있는 것을 볼 수 있다.

 

 

* @Configuration 이 컴포넌트 스캔의 대상이 된 이유도 @Configuration 소스코드를 열어보면

@Component 어노테이션이 붙어있기 때문이다.

 

 

 

* shift + shift : 검색 

 

 

 


 

❓ : 그런데 AutoAppConfig에 @ComponentScan에서 @Configuration부분을 대상에서 제외했는데

AutoAppConfig 코드에 @Configuration을 넣은건지 궁금해졌다.

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

 

 

🅰️ : 테스트에서는 AutoAppConfig.class를 ApplicationContext를 생성할 때

파라미터로 넘겨줘서 빈으로 등록된다.

 

그러나 테스트가 아니라 메인에서 애플리케이션을 실행시킨다면 얘기가 달라진다.

@Configuration이 붙어있어야 빈으로 등록된다.

 

@Configuration을 붙이기 싫다면 메인 메서드 내에서도 해당 configuration 클래스를

ApplicationContext로 넘겨주면 된다.

출처: https://www.inflearn.com/questions/455061

 

 


 

❓: 그렇다면 AutoAppConfig에서 excludeFilters로 제외를 해도

AutoAppConfig 클래스의 @Configuration 부분은 적용이 안되는건지 궁금해졌다.

 

 

🅰️ : AutoAppConfig는 다음과 같이 직접 등록한 부분이기 때문에 포함된다. 

컴포넌트 스캔과 무관하게 포함된다.

 

new AnnotationConfigApplicationContext(AutoAppConfig.class)

출처 : https://www.inflearn.com/questions/462112

 


 

 그런데  @Component 어노테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록한다면

@Configuration이 필요없는게 아닐까?

 

 

🅰️ 

@Configuration을 붙인 이유는 @Bean이 붙은 메서드를 통해 객체를 생성하려고 할 때 

(내부 의존관계를 위해 직접 메서드 호출을 하게 되면) proxy를 통해 내부적으로 빈 목록을 먼저 찾아서

이미 생성된 빈이 있다면 이미 생성된 빈을 반환하고자 함이다. 

 

ComponentScan을 하든

ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class) 를 하든

빈의 등록 방식은 싱글톤이지만

Configuration 어노테이션을 사용해야지만 중복된 객체를 생성하지 않고 등록한 빈을 주입해준다

출처: https://www.inflearn.com/questions/394925

 

 


 

💡 

@Configuration은 단순히 설정 클래스임을 명시적으로 나타내는 것 이외에도

@Component와 조금 다르게 동작한다.

 

먼저, @Component, @Configuration 내 @Bean 메서드는 최초 호출시 그 리턴 객체가 빈으로 등록된다.

이후 다시 @Bean 메서드를 호출하게 되면 @Configuration의 경우 프록시 객체가 호출되며

스프링 컨테이너로부터 빈을 찾아서 최초에 등록된 빈을 반환해주므로 싱글톤이 보장된다.

 

그러나 @Component의 경우 스프링 컨테이너로부터 빈을 찾아 반환해주지 않고

new()를 통해 새롭게 만들어진 객체를 반환해준다.

 

이는 큰 차이이며 @Component 내 @Bean 메서드를 사용하게 될 때는 이 차이를 알고 사용해야 한다.

 

 

+) 

강의 내에서 @Configuration과 @ComponentScan을 함께 사용하게 된 것은 excludeFilter 때문이다.

 

일반적인 상황에서는 @SpringBootApplication 내 @ComponentScan이 존재하므로

따로 @ComponentScan을 사용하진 않는다.

 

물론 수동 빈 등록을 위해 @Configuration을 사용하여 설정 클래스를 별도로 만들어주는 경우는 많다.

출처 : https://www.inflearn.com/questions/348474

 

 

 

처음에는 😵‍💫 이런 느낌이었는데 위 글을 보고 ❗ 느낌을 받았다.

 

 



@Component를 붙여준다.

 

MemoryMemberRepository 

 

 

RateDiscountPolicy

 

 

MemberServiceImpl

 

 

 

 

의존관계 주입

AppConfig에서는

memberService는 memberRepository 의존관계 주입할 것이라고 명시를 할 수 있었다.

// AppConfig
@Bean // 스프링 컨테이너에 등록
public MemberService memberService() {
    System.out.println("call AppConfig.memberService"); 
    return new MemberServiceImpl(memberRepository());
}

 

 

 

그런데 AutoAppConfig에는 아무것도 적혀있지 않다.

 

그냥 MemberServiceImpl이 스프링 빈으로 등록이 되어버린다. 

의존관계 주입을 해줄 수 있는 방법이 없다.

 

그래서 자동의존관계 주입이 필요하다. → @Autowired

@Autowired를 생성자에 붙여주면 스프링이 memberRepository 타입에 맞는 것을 찾아와서

의존관계 주입을 자동으로 연결해서 주입해준다.

 

MemberServiceImpl

 

그래서 Component를 쓰면 Autowired를 쓰게 된다.

의존관계를 설정할 수 있는 방법이 없으므로 

 

마치 ac.getBean(MemberRepository.class)와 같이 동작을 한다라고 이해하면 된다.

 

 

 

정리

이전에 AppConfig에서는 @Bean 으로 직접 설정 정보를 작성했고, 의존관계도 직접 명시했다.

이제는 이런 설정 정보 자체가 없기 때문에, 의존관계 주입도 이 클래스 안에서 해결해야 한다.

@Autowired 는 의존관계를 자동으로 주입해준다

 

 

 

 

OrderServiceImpl

@Component 추가

 

 

생성자 위에 @Autowired 추가

 

 

@Autowired 를 사용하면 생성자에서 여러 의존관계도 한번에 주입받을 수 있다.

 

 

 


Test 코드 작성하기

@Test
void basicScan(){
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);

    MemberService memberService = ac.getBean(MemberService.class);
    assertThat(memberService).isInstanceOf(MemberService.class);
}

AnnotationConfigApplicationContext 를 사용하는 것은 기존과 동일하다.

설정 정보로 AutoAppConfig 클래스를 넘겨준다.

실행해보면 기존과 같이 잘 동작하는 것을 확인할 수 있다.

 

 

 

 

ClassPathBeanDefinitionScanner - Identified candidate component class:

 

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

 

 

싱글톤 빈이 생성된 것을 알 수 있다.

 

 

 

참고로 아래 코드에서 

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);

AnnotationConfigApplicationContext ac 부분을 ApplicationContext ac 으로 변경해도 된다. 

 

참고 >> https://www.inflearn.com/questions/47449

 

 

 

 

 

그림으로 보기

@ComponentScan

 

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

이때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다.

 

빈 이름 기본 전략: MemberServiceImpl 클래스 → memberServiceImpl

빈 이름 직접 지정: 만약 스프링 빈의 이름을 직접 지정하고 싶으면

@Component("memberService2") 이런식으로 이름을 부여하면 된다.

단, 구현체에 해줘야 한다. ex) MemberServiceImpl

 

 

 

@Autowired 의존관계 자동 주입

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

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

memberRepository와 memoryMemberRepository는 타입이 맞다.

(자식 타입이기 때문에 타입이 맞다.)

 

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

 

* 생성자에 파라미터가 많아도 모두 찾아서 자동으로 주입해준다. (pdf 참고)

 

 

728x90