관리 메뉴

제뉴어리의 모든것

[Section2] [Spring Core] Spring Framework의 핵심 개념 - Component Scan 본문

코드스테이츠/정리 블로깅

[Section2] [Spring Core] Spring Framework의 핵심 개념 - Component Scan

제뉴어리맨 2022. 8. 12. 23:48

@Component Scan이란?

스프링컨테이너의 구성정보가 없어도 @Component라는 애노테이션이 붙은 클래스들을 찾아서 빈으로 등록하는 스프링의 기능이다.

 

여기서 말하는 구성정보란,

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);

에서 AutoAppConfig.class 내부에 빈으로 등록할 객체들을 말하고,

 

구성정보가 없어도 된다는것은,

AutoAppConfig 클래스의 내부에 빈으로 등록할 객체들이 정의되어 있지 않아도 된다는 의미이다.

@Component Scan의 필요성

  • 구성정보 클래스에 등록할 빈들을 적어 놓는것은 한눈에 빈들의 존재를 알 수 있다는 장점도 있지만, 관리하는 빈들이 한없이 많아지게 되면 구성정보 클래스가 너무 비대해지고 그럼에따라 누락의 위험성도 있다.

 

@Component Scan 사용법

주문 프로그램이란 내용으로 사용법을 설명하겠다.

(각 클래스가 구현한 인터페이스 코드는 생략하겠다)

  • 작성 프로그램 할 프로그램 : OrderApp
  • OrdeApp 프로그램 내용 : 
    1. 한 유저를 프로그램에 회원으로 주문 시킨다.
    2. 가입한 유저가 음식을 주문했을때, 해당 음식의 할인가격까지 포함하여 주문 내역을 출력한다.

- OrderApp 코드

public class OrderApp {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);

        UserService userService = ac.getBean(UserService.class);
        OrderService orderService = ac.getBean(OrderService.class);

        Long userId = 0L;
        User newUser = new User(userId, "제뉴어리맨", UserGrade.GRADE_2);
        userService.signup(newUser); //유저 가입
        Order order = orderService.createOrder(userId, "치킨", 40000);
        System.out.println(order);
    }
}

스프링 컨테이너인 AnnotationConfigApplicationContext의 구성정보인 AutoAppConfig.class

내부를 보면 @Configuration을 사용하여 빈을 등록하는 방법과 확연한 차이를 알 수 있다.

 

 

 

- AutoAppConfig 코드

import org.springframework.context.annotation.ComponentScan;

@ComponentScan // @Component 붙은 클래스들을 모두 빈으로 등록하겠다는 애노테이션
public class AutoAppConfig {
}

 

- UserService 코드

@Component //Component Scan을 위해 추가됨.
public class UserServiceImpl implements UserService{

    private final UserRepository userRepository; //의존성 주입됨
	
    // @Autowired //생성자가 하나뿐일때는 생략이 가능하다. 스프링 컨테이너가 자동으로 해당 생성자를 기본 생성자로 인식하여 호출하여 객체생성과 의존성 주입까지 해준다.
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public void signup(User user) {
        userRepository.saveUser(user);
    }

    @Override
    public User findUser(Long userId) {
        return userRepository.findByUserId(userId);
    }
}

 

-  UserRepositoryImpl

@Component //Component Scan을 위해 추가됨.
public class UserRepositoryImpl implements UserRepository{

    private static Map<Long, User> users = new HashMap<>();

    @Override
    public void saveUser(User user) {
        users.put(user.getId(), user);
    }

    @Override
    public User findByUserId(Long userId) {
        return users.get(userId);
    }
}

 

- OrderServiceImpl 

@Component //Component Scan을 위해 추가됨.
public class OrderServiceImpl implements OrderService{

    DiscountInfo discountInfo;
    UserRepository userRepository;

//    @Autowired //생성자가 하나일때는 생략 가능하다. 자동으로 해당 생성자를 실행시켜 빈을 생성,등록하고 의존성주입도 해준다.
    public OrderServiceImpl(DiscountInfo discountInfo, UserRepository userRepository) {
        this.discountInfo = discountInfo;
        this.userRepository = userRepository;
    }

    @Override
    public Order createOrder(Long userId, String itemName, int itemPrice) {

        User findUser = userRepository.findByUserId(userId);
        int discountPrice = discountInfo.discount(findUser, itemPrice);
        return new Order(userId,itemName,itemPrice,discountPrice);
}

 

- RateDiscountInfo

@Component
public class RateDiscountInfo implements DiscountInfo{

    private int grade_1_rate = 5;
    private int grade_2_rate = 10;

    @Override
    public int discount(User user, int price) {

        if (user.getUserGrade() == UserGrade.GRADE_1) {
            return price * grade_1_rate / 100;
        }
        else if (user.getUserGrade() == UserGrade.GRADE_2) {
            return price * grade_2_rate / 100;
        }
        return 0;
    }
}

 

- 각 자파 파일들의 디렉토리 위치

참고로 OrderApp2가 OrderApp이다.

 

 

- 내용 설명

AutoAppConfig.class 에 @ComponentScan이 적용되어 있으므로,

AutoAppConfig의 위치 포함하여 하위 디렉토리들에서 @Component 애노테이션을 스캔하여 

@Component 가 붙어있는 클래스들을 모두 스프링 빈으로 등록한다.

 

참고로, 

@Configuration, @Controller, @Repository, @Service, @SpringBootApplication 애노테이션도 내부적으로 보면

@Component를 포함하고 있다. (해당 애노테이션에 커서두고 Ctrl + B 눌러서 애노테이션 내부 확인 가능)

그러므로 위에 애노테이션이 붙어있는 클래스들 또한 모두 빈으로 등록 될 것이다.

그리고 해당 애노테이션이 붙은 클래스들은 모두 의존성 주입 또한 자동으로 해주며,

만약 의존성 주입하려는 객체가 빈으로 존재하지 않거나, 빈이 중복으로 존재하거나, 또는 생성자가 오버로딩 되어 있는데 @Autowired로 의존성 주입할 생성자를 지정하지 않은 경우 컴파일 에러를 발생한다. (그러나 기본 생성자만 오버로딩 된경우는 에러가 발생하지 않는다. 어차피 의존성 주입이 안되는 생성자이기에 무시하고 @Autowired가 붙은 생성자를 기본 생성자로 생각하여 의존성 주입하는듯 하다)

 

  • 괄호안에 상황 예
package com.spring_ex01.cmarket.order;
import com.spring_ex01.cmarket.discount.DiscountInfo;
import com.spring_ex01.cmarket.user.User;
import com.spring_ex01.cmarket.user.UserRepository;
import org.springframework.stereotype.Component;


@Component
public class OrderServiceImpl implements OrderService{

    DiscountInfo discountInfo;
    UserRepository userRepository;

//    @Autowired //생성자가 하나일때는 생략 가능하다. 자동으로 해당 생성자를 실행시켜 빈을 생성,등록하고 의존성주입도 해준다.
    public OrderServiceImpl(DiscountInfo discountInfo, UserRepository userRepository) {
        this.discountInfo = discountInfo;
        this.userRepository = userRepository;
    }

    public OrderServiceImpl() { //이렇게 기본 생성자가 오버로딩 되어 있음에도 에러가 발생되지 않는다.
    }

    @Override
    public Order createOrder(Long userId, String itemName, int itemPrice) {

        User findUser = userRepository.findByUserId(userId);
        int discountPrice = discountInfo.discount(findUser, itemPrice);
        return new Order(userId,itemName,itemPrice,discountPrice);
    }

}

코드를 보면

public OrderServiceImpl() { } 와 같이 기본 생성자가 오버로딩 되어 있음에도 에러가 발생하지 않는다.

테스트 해보고 싶다면 위에 OrderApp 예제에서 OrderServiceImpl 클래스에 기본 생성자만 추가해서 테스트해보면 된다.

 

 

 

컴포넌트 스캔 기본 대상

  • @Component : 컴포넌트 스캔에서 사용됩니다.


    아래의 애노테이션들은 내부적으로 모두 @Component를 가지고 있다.
  • @Controller & @RestController : 스프링 MVC 및 REST 전용 컨트롤러에서 사용됩니다.
  • @Service : 스프링 비즈니스 로직에서 사용됩니다.
    • 특별한 처리를 하지 않는다.
    • 개발자들이 핵심 비즈니스 로직이 여기에 있다는비즈니스 계층을 인식하는데 도움이 된다.
  • @Repository : 스프링 데이터 접근 계층에서 사용됩니다.
    • 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.
  • @Configuration : 스프링 설정 정보에서 사용됩니다.
    • 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.

 

@ComponentScan의 주요 옵션

 

  • basePackages
    컴포넌트스캔을 시작할 디렉토리 위치 지정.

    - 사용 예
    @Configuration
    @ComponentScan(basePackages = {"com.package1.component"}) 
    class AppContextConfig{ ... }


    com.package1.component 위치 포함, 하위 디렉토리들을 컴포넌트 스캔의 범위로 지정.
    @ComponentScan("com.package1.component") 로 basePackages를 생략 하여도 된다.



  • 필터
    - includeFilters : 컴포넌트 스캔 대상을 추가로 지정합니다. 
    - excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정합니다.

    - FilterType 옵션 
    --- ANNOTATION: 기본값, 애너테이션으로 인식해서 동작합니다.
    --- ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작합니다.
    --- ASPECTJ: AspectJ 패턴을 사용합니다.
    --- REGEX: 정규 표현식을 나타냅니다.
    --- CUSTOM: TypeFilter라는 인터페이스를 구현해서 처리합니다.




 

더 다양한 옵션은 아래 블로그 참조

https://nankisu.tistory.com/4