본문 바로가기
Develop/Spring (이론)

@ComponentScan & 의존성 주입(@Autowired) 딥다이브

by 보보트레인 2023. 8. 18.

<탄생배경>

 

지금까지 스프링 빈을 등록할 때는 자바코드의 @Bean이나 XML의 <bean>을 통해서 설정 정보에 직접 등록할 스프링 빈을 나열했다.

 

이렇게 등록해야할 스프링 빈이 수십, 수백개가 되면 일일이 등록하기 어려워진다.

 

→ 스프링은 설정정보가 없어도 자동으로 스프링 빈을 등록하는 @ComponentScan이라는 기능을 제공한다.

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

 

 

 

@ComponentScan이란?

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

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

 


※예제를 시행할 때, 주의할 점

'excludeFilters'를 통해 컴포넌트 스캔 대상에서 따로  @Configuration 정보를 제거하기.

(안그러면 예제코드 다 다시짜야함... ) - 최대한 @Configuration으로 짜뒀던 예제코드를 지키기 위해...

package hello.core;


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

@Configuration
@ComponentScan(
        //@ComponentScan으로 다 자동으로 bean을 등록하는데 제외할것을 설정하는 코드
        //@Configuration은 우리가 수동으로 bean을 등록하는 곳인데 자동으로 등록되면 큰일남을 이해해야함.
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {

}

다음 예제코드에서

@Configuration을 타고 들어가보면 

다음과 같이 @Component 어노테이션을 보유한 것을 확인할 수 있다.

→ @ComponentScan을 사용하면 @Configuration이 붙은 설정 정보도 자동으로 등록되기 때문에, @Configuration에서 우리가 수동으로 설정한 @Bean 설정정보들도 함께 등록되는 대참사가 벌어진다. 

 

결론 : 'excludeFilters'를 통해 컴포넌트 스캔 대상에서 따로  @Configuration 정보를 제거해주는 세심함이 필요하다.


@Autowired

 

자동의존관계주입 = ac.getBean(MemberRepository memberRepository) 와 같은 효과

 

설명 : 이전에 AppConfig에서는 `@Bean` 으로 직접 설정 정보를 작성했고, 의존관계도 직접 명시했다. 이제는 이런 설
정 정보 자체가 없기 때문에, 의존관계 주입도 이 클래스 안에서 해결해야 한다.

 

의존관계 주입(Dependency Injection)은 객체 간의 의존 관계를 코드 내에서 직접 설정하는 것이 아닌 외부에서 객체를 생성하고 주입하는 방식이다. 이는 객체 간의 결합도를 낮추고 유연한 코드를 작성하는 데 도움이 된다.

 

@Autowired 어노테이션은 스프링 프레임워크에서 제공하는 의존성 주입 기능을 사용할 때 사용하는 어노테이션이다. @Autowired 어노테이션은 생성자, 필드, 메서드 등 다양한 위치에 붙일 수 있다.

 

따라서 @Autowired 어노테이션은 의존 객체를 주입받을 대상에 붙여주시면 된다.

 

 

<예제코드>

package hello.core.member;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;

    //자동의존관계주입(Autowired) = 마치 ac.getBean(MemberRepository memberRepository) 와 같다.
    @Autowired
    //생성자 -> 여기서는 추상화에만 의존하는 껍데기만 설정 (just 생성자 주입) -> config에서 자세히 설정할 것.
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Override
    public void join(Member member) {
        memberRepository.save(member);
    }

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }

    // 테스트 용도
    public MemberRepository getMemberRepository() {
        return memberRepository;
    }
}

이렇게 @Component가 달린 클래스를 스캔해서 스프링 빈으로 등록하는데

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

※생성자에 파라미터가 많아도 다 자동으로 찾아서 주입한다.


<예제>

1. AutoAppConfig라는 이름으로 클래스를 만들고  @ComponentScan어노테이션을 붙혀준다.

package hello.core;


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

@Configuration
@ComponentScan(
        //@ComponentScan으로 다 자동으로 bean을 등록하는데 제외할것을 설정하는 코드
        //@Configuration은 우리가 수동으로 bean을 등록하는 곳인데 자동으로 등록되면 큰일남을 이해해야함.
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {

}

2. @Component를 대상 class에 모두 달아서 @ComponentScan대상임을 명시해준다.

3. @Autowired를 통해 의존관계를 자동으로 주입한다.

(예시)

4. 테스트 코드를 실행하여 스프링 컨테이너가 잘 스프링빈을 인식했는지 확인한다.

<실행코드>

package hello.core.scan;

import hello.core.AutoAppConfig;
import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AutoAppConfigTest {

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

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

<실행결과>

설명하자면 @Component를 달아줬던 autoAppConfig / rateDiscountPolicy / memberServiceImpl / memberMemberRepository 4개의 클래스를  @ComponentScan이 인식하여

→ AutoAppConfig라는 클래스에 싱글톤 bean으로 등록하였다. 

 

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

 

또한 @Autowired를 달아주었던 MemberServiceImpl과 OrderServiceImpl은 반드시 의존관계를 주입받아야한다.

스프링 컨테이너가 MemberRepository와 DiscountPolicy를 뒤질 것이고 일차적으로 같은 '타입'을 찾아 조회한다.

따라서 MemberRepository의 자식 타입인 memoryMemberRepository와   DiscountPolicy의 자식 타입인 rateDiscountPolicy가 @Autowired에 의해 주입된다.

 


@ComponentScan의 basePackages 설정

 

우리는 basePackages를 통해 @ComponentScan범위를 제한할 수 있다.

→ 정말 많은 자바 코드와 라이브러리를 다 뒤지면 속도와 효율성 측면에 위배되기 때문에 적절한 제한은 프로세스에 큰 도움이 된다.

다음과 같이 설정하고 Test코드를 돌리면 탐색할 패키지를 hello.core.member로 지정하고 그 하위패키지를 탐색한다.

다음과 같이 core.member 아래 패키지만 자동스캔이 되는 것을 확인할 수 있다.

 

@ComponentScan의 basePackageClasses 추가 설정

다음과 같이 추가 코드를 작성하고 테스트 코드를 실행하면 지정한 클래스의 패키지를 탐색 시작 위치로 지정한다.

만약 지정하지 않으면 `@ComponentScan` 이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.

 

적절히 사용하도록 하자.

 

※ 참고로 스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인 `@SpringBootApplication` 를 이 프로젝트 시작
루트 위치에 두는 것이 관례이다. (그리고 이 설정안에 바로 `@ComponentScan` 이 들어있다!)

 

<증명>

스프링부트를 처음 생성하면 CoreApplication 클래스가 생기는데 여기에 붙은 @SpringBootApplication 어노테이션을 파고들어가보면

@ComponentScan이 디폴트로 담겨있다. (스프링 부트는 다 계획이 있다... 관례가 괜히 생긴 것이 아님.)


컴포넌트 스캔 대상

 

컴포넌트 스캔은 @Component뿐만 아니라 다음의 내용도 포함한다.

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

얘들을 붙혀놓으면 자동으로 @ComponentScan대상이 된다. 

@Configuration과 @Component는 이미 이전에 확인했으니 나머지를 확인해보자

 

(Ctrl + N을 누르면 인텔리제이 內 클래스 검색이 가능하다.)

  

1. @Controller : Spring MVC컨트롤러임을 확인가능

2. @Service : 특별한 처리없음. 다만 개발자들이 핵심 비즈니스 로직임을 암묵적으로 명시함.

3. @Repository : 데이터 계층 예외를 스프링 예외로 변환해준다.

(ex: oracle쓰다가 mysql로 변경 시 데이터 계층 자체가 흔들릴 수 있는데, 스프링이 이를 막기위해 추상화해서 반환해줌)

 

반응형