카테고리 없음

AppConfig -> AutoAppConfig 컴포넌트 스캔이란?

kingmusung 2024. 2. 23. 21:40

컴포넌트 스캔이란.

스프링 프레임워크에서 자동으로 빈(Bean)을 찾아 등록하는 기능입니다.

스프링은 컴포넌트 스캔을 통해 자동으로 빈을 찾아 등록할 수 있도록 지원합니다. 컴포넌트 스캔은 다음과 같은 과정을 통해 동작합니다: 스프링 애플리케이션이 시작될 때, 스캔할 패키지를 지정합니다. 지정된 패키지 내에서 스프링이 특정 애노테이션을 찾습니다. 주로 @Component, @Service, @Repository, @Controller 등의 애노테이션이 사용됩니다. 스프링은 해당 애노테이션이 붙은 클래스들을 찾아 빈으로 등록합니다. 등록된 빈들은 스프링 컨테이너에서 관리되며, 필요한 곳에서 주입될 수 있습니다. 컴포넌트 스캔을 사용하면 개발자는 XML 파일에 빈을 일일히 등록하는 대신, 애노테이션을 통해 간편하게 빈을 관리할 수 있습니다. 이는 코드의 가독성을 높이고 유지보수를 용이하게 만들어줍니다.

 

@Service, @Repository, @Configuration, @Controller 등이 있는데.

 

@Service: 

이 애노테이션은 비즈니스 로직이나 서비스 계층의 클래스에 사용됩니다. 주로 해당 클래스가 비즈니스 로직을 수행하거나 트랜잭션을 관리하는 역할을 할 때 사용됩니다. 스프링이 이 애노테이션이 붙은 클래스를 검색하여 해당 클래스의 인스턴스를 빈으로 등록합니다.

@Repository:

이 애노테이션은 데이터 액세스 계층의 클래스에 사용됩니다. 주로 데이터베이스와의 상호 작용이나 데이터 저장소와의 통합을 담당하는 클래스에 사용됩니다. 스프링은 이 애노테이션이 붙은 클래스를 검색하여 데이터 액세스 계층의 빈으로 등록합니다.

@Configuration:

이 애노테이션은 자바 기반의 설정 클래스에 사용됩니다. 주로 XML을 사용하여 설정하는 대신 자바 코드로 스프링 빈을 정의할 때 사용됩니다.@Configuration이 붙은 클래스는 스프링 애플리케이션 컨텍스트에 빈으로 등록됩니다.

@Controller: 이 애노테이션은 스프링 MVC에서 컨트롤러 클래스에 사용됩니다. 주로 웹 애플리케이션의 요청을 처리하고 응답을 반환하는 역할을 수행합니다. 스프링이 이 애노테이션이 붙은 클래스를 검색하여 해당 클래스의 인스턴스를 웹 애플리케이션 컨텍스트에 빈으로 등록합니다.

 

각자의 역할이 있지만 TMI로 @Service는 @Component랑 같은 기능이라고 하는데.

 

차이점은 가독성을 위해 서비스 클레스에는 @Component 말고 @Service를 붙히는거라고 한다.

 

각각 어노테이션 구현체에 가보면 @Component라고 있는데 그래서 컴포넌트 스캔이라고 부른다고 함.

 


AppConfig와 AutoAppConfig의 차이점

공통점으로는 AppConfig와 AutoAppConfig는 모두 스프링에서 빈(Bean)을 설정하기 위해 사용되는 클래스입니다.

 

AppConfig:

@Configuration 애노테이션을 사용하여 스프링 빈을 정의하고,

@Bean 애노테이션을 사용하여 각 빈을 생성합니다. 명시적으로 각각의 빈을 정의하기 때문에 빈의 생성 및 의존성 관리가 명확합니다.

자바 코드로 빈을 정의하기 때문에 컴파일 시에 오류를 발견할 수 있으며, 리팩토링이나 변경에 따른 영향을 파악하기 쉽습니다. 빈의 생성과 의존성을 명시적으로 정의해야 하기 때문에 설정이 복잡할 수 있습니다.

 

AutoAppConfig:

AutoAppConfig는 컴포넌트 스캔(Component Scan)을 사용하여 빈을 자동으로 등록하는 방식의 설정 클래스입니다. 대부분의 경우에는 @ComponentScan 애노테이션을 사용하여 스캔할 패키지를 지정합니다. 클래스에 @Component, @Service, @Repository, @Controller 등의 애노테이션이 붙어있는 경우 해당 클래스를 빈으로 등록합니다. 설정이 간단하고, 빈을 추가하거나 제거할 때 설정 파일을 수정할 필요가 없어 편리합니다. 스캔할 패키지를 지정해야 하며, 실수로 불필요한 클래스가 스캔되거나 누락될 수 있습니다.

 

 


예시

package hello.core;

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

@Configuration
@ComponentScan(
        //basePackages = "hello.core.member", 이거 는 member 안에 있는것만 찾으라는거고 즉 시작위치를 지정하는거지.
        // 이거 안하면 자바코드 다 뒤짐 PC도 뒤짐. 그래서 그냥 내 프로젝트 상단을 찍어주는게 가장 좋음.
        //안적어주면 지금 aotoappconfig파일이 있는 위치가 시작점임 그래서 가급적이면 프로젝트 최상단에 파일 위치시켜주고 basePackage안하는게 제일 편함.
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes =
                Configuration.class))
//Configuration을 빼는 이유는 지금까지는 AppConfig에 @Configuration을 달아서 자동으로 의존관계를 주입했었으나.
//이번에는 Component 스캔을 통해 수동으로 할 예정이라 Configuration이 들어간 부분을 다 빼버렸다.
//실무에서는 굳이 예외처리는 안하는데 테스트를 위해 이렇게 함
//제외 안하면 AppConfig, AutoAppConfig 둘다 등록이 됌

public class AutoAppConfig {
}

 

컴포넌트 스캔을 쓰기 위해서 위처럼 클레스를 만들어 주면 되는데. Appconfig랑 비교 하면 상당히 비어보인다. 하지만 상관없다.

 

 

ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
//기존에 위 처럼 쓰던걸 아래 처럼 쓰는 차이일뿐
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);

 

 

간단한 예시를 가지고 와봤습니다.

public interface UserRepository {
    void save(User user);
}

@Component
public class JdbcUserRepository implements UserRepository {
    // JdbcUserRepository의 구현 내용
}

@Component
public class UserService {
    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

 

보통 인터페이스 작성 후 구현체를 만드는데, AppConfig를 사용해 인터페이스에 어떤 구현체가 들어가는지 아래처럼 "직접" 적어주었다.

@Bean
public MemberService memberService(){

    return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository(){

    return new MemoryMemberRepository();
}

 

컴포넌트스캔 방식을쓰면 위 방식과 반대로 인터페이스에 구현한 구현체 위에다 @Component를 적어주면

 

스프링이 "아!! JdbcUserRepository라는 구현체는 userRepository의 구현체구나 DI컨테이너에 매칭해서 넣어줘야지" 하고 생각한다

 

UserService를 보면.

private final UserRepository userRepository;

@Autowired
public UserService(UserRepository userRepository) {
    this.userRepository = userRepository;

 

UserRepository에 들어가야 하는 구현체를 알아서 주입을 해준다.(@Component,Autowired 빼먹지 말기.)