본문 바로가기

Spring

[Spring] 컴포넌트 스캔(Component Scan)


준비


이전에 작성된 글인 싱글톤(Singleton)의 코드가 있어야 본 글의 코드 예제를 수행할 수 있습니다. 

 

 

컴포넌트 스캔(Component Scan)이란?


지금까지 스프링 빈을 등록할 때 @Bean을 사용하였습니다.

이는 관리할 빈이 많아진다면 관리하기 버거운 사태가 생기게 됩니다.

스프링은 굳이 빈 설정파일을 만들지 않거나, @Bean을 쓰지 않고도 빈을 등록하는 컴포넌트 스캔(Component Scan)이라는 기능을 제공합니다.

 

  • @ComponentScan을 사용하여 @Component가 붙은 모든 클래스를 스프링 빈으로 등록할 수 있습니다.
    • 의존관계도 자동으로 주입하는 @Autowired 기능도 제공합니다.

sample_Project 패키지에 AutoDependencyConfig.java 클래스를 생성합니다.

 

package developingman.sample_Project;

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

@Configuration
@ComponentScan
public class AutoDependencyConfig {
}

 

  • 기존의 DependencyConfig 소스 코드와 비교한다면 @Bean으로 등록한 클래스를 볼 수 없습니다.
  • 주의 >> ComponentScan을 사용하면 @Configuration이 붙은 설정 정보도 자동으로 등록됩니다.
    • @Configuration이 붙은 설정 정보가 자동으로 등록되는 이유는 @Configuration 코드에 @Component annotation이 있기 때문입니다.

 

  • 기존에 작성한 AppConfig가 있다면 정상적인 작동이 되지 않습니다.
  • DependencyConfig 등 @Configuration 설정이 된 파일이 있을 시 아래의 코드를 추가합니다 :
    @ComponentScan(excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class))
  • XML 방식의 component scan
<beans>
	<context:component-scan base-package="com.acme"/>
</beans>

 

예제 코드


기존에 작성된 코드를 아래의 코드 예제에 맞춰 수정합니다.

 

memberService.java 클래스 수정

 

package developingman.sample_Project.member;

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

@Component
public class MemberService {
    private final MemberRepository memberRepository;
    
    @Autowired
    public MemberService(MemberRepository memberRepository){
        this.memberRepository = memberRepository;
    }
    
    public void createMember(Member member){
        memberRepository.postMember(member);
    }

    public Member getMember(Long memberId){
        return memberRepository.getMember(memberId);
    }

    public void deleteMember(Long memberId){
        memberRepository.deleteMember(memberId);
    }
}

 

 

SnackService.java 클래스 수정

 

package developingman.sample_Project.snack;

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

@Component
public class SnackService {
    private static SnackRepository snackRepository;

    @Autowired
    public SnackService(SnackRepository snackRepository){
        this.snackRepository = snackRepository;
    }
    public void createSnack(Snack snack){
        snackRepository.postSnack(snack);
    }

    public Snack editSnack(Long snackId, String korName, int price){
        return snackRepository.patchSnack(snackId, korName, price);
    }

    public Snack getSnack(Long snackId){
        return snackRepository.getSnack(snackId);
    }
    public void deleteSnack(Long snackId){
        snackRepository.deleteSnack(snackId);
    }
}

 

 

MemberRepository.java 클래스 수정

 

package developingman.sample_Project.member;

import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class MemberRepository {
    private static Map<Long, Member> members = new HashMap<>();

    public void postMember(Member member) {
        members.put(member.getMemberId(), member);
    }

    public Member getMember(Long memberId) {
        return members.get(memberId);
    }

    public void deleteMember(Long memberId){
        members.remove(memberId);
    }
}

 

 

SnackRepository.java 클래스 수정

package developingman.sample_Project.snack;

import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class SnackRepository {
    private static Map<Long, Snack> snacks = new HashMap<>();

    public void postSnack(Snack snack){
        snacks.put(snack.getSnackId(),snack);
    }

    public Snack patchSnack(Long snackId, String korName, int price){
        Snack snack = snacks.get(snackId);
        snack.setKorName(korName);
        snack.setPrice(price);
        return snacks.put(snackId, snack);
    }

    public Snack getSnack(Long snackId){
        return snacks.get(snackId);
    }

    public void deleteSnack(Long snackId){
        snacks.remove(snackId);
    }
}

 

기존에 코드에서 @Component와 @Autowired를 추가하였습니다.

  • @ComponentScan: @ComponentScan이 등록된 곳에서 @Component를 가져오기 위해 사용됩니다.
  • @Autowired: 생성자 의존성 주입에 필요한 설정 정보 대신 의존관계 자동 주입을 해줍니다.

 

BasePackages

BasePackages는 탐색할 패키지의 시작 위치를 지정하고, 해당 패키지부터 하위 패키지 모두 탐색합니다.

  • @ComponentScan()의 매개변수로 basePackages = ""를 줄 수 있습니다.
  • 지정하지 않으면, @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 됩니다.
    • 설정 정보 클래스의 위치를 프로젝트 최상단에 두고 패키지 위치는 지정하지 않은 방법이 가장 편할 수 있습니다.
  • 스프링 부트를 사용하면 @SpringBootApplication을 이 프로젝트 시작 루트 위치에 두는 것을 추천합니다.
    • @SpringBootApplication에 @ComponentScan이 들어있습니다.

 

 

컴포넌트 스캔 기본 대상

  • @Component: 컴포넌트 스캔에서 사용됩니다.
  • @Controller & @RestController: 스프링 MVC 및 REST 전용 컨트롤러에서 사용됩니다.
  • @Service: 스프링 비즈니스 로직에서 사용됩니다.
  • @Repository: 스프링 데이터 접근 계층에서 사용됩니다.
    • 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해 줍니다.
  • @Configuration: 스프링 설정 정보에서 사용됩니다.
    • 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 합니다.
  • 해당 클래스의 소스 코드에는 @Component를 포함하고 있습니다.

 

필터

  • includeFilters: 컴포넌트 스캔 대상을 추가로 지정합니다.
  • excludeFilters: 컴포넌트 스캔에서 제외할 대상을 지정합니다.
  • FilterType 옵션
    • ANNOTATION: 에너테이션으로 인식해서 동작합니다.
    • ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작합니다.
    • ASPECTJ: AspectJ 패턴을 사용합니다.
    • REGEX: 정규 표현식을 나타냅니다.
    • CUSTOM: TypeFilter라는 인터페이스를 구현해서 처리합니다.

'Spring' 카테고리의 다른 글

[Spring] DI(Dependency Injection) 종합 실습  (0) 2023.04.10
[Spring] 다양한 의존 관계 주입 방법  (0) 2023.04.09
[Spring] 싱글톤(Singleton)  (0) 2023.04.06
[Spring] 빈 스코프(Bean Scope)  (0) 2023.04.05
[Spring] 빈(Bean)  (0) 2023.04.04