@ComponentScan과 의존관계 자동 주입

김신영·2023년 10월 7일
0

SpringFramework

목록 보기
2/4
post-thumbnail

@ComponentScan 과 의존관계 자동 주입

  1. @ComponentScan 어노테이션을 사용하면, @Component 어노테이션이 붙은 클래스를 찾아서 Bean으로 등록한다.
  2. @Autowired 어노테이션을 사용하면, 해당 타입의 Bean을 찾아서 의존관계를 자동으로 주입한다.

@ComponentScan

  • basePackages : String[]

  • basePackageClasses : Class<?>[]

  • includeFilters : Filter[]

  • excludeFilters : Filter[]

  • useDefaultFilters : boolean

    • 기본값: true
    • @Component, @Repository, @Service, @Controller, @Configuration 어노테이션을 스캔 대상으로 설정한다.
  • lazyInit : boolean

    • 기본값: false
    • @Component 어노테이션을 가진 Bean을 생성할 때, Lazy Initialization을 사용할지 여부
  • nameGenerator : BeanNameGenerator

    • Bean의 이름을 생성하는 방식을 설정한다.
    • 구현체 예시
      • DefaultBeanNameGenerator
      • AnnotationBeanNameGenerator
      • FullyQualifiedAnnotationBeanNameGenerator
    public interface BeanNameGenerator {
        String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);
    }
  • scopeResolver : ScopeMetadataResolver

    • Bean의 Scope를 결정하는 방식을 설정한다.
    • 구현체 예시
      • AnnotationScopeMetadataResolver
      • Jsr330ScopeMetadataResolver
    public interface ScopeMetadataResolver {
        ScopeMetadata resolveScopeMetadata(BeanDefinition definition);
    }
  • scopedProxy : ScopedProxyMode

    • 기본값: ScopedProxyMode.DEFAULT
    • ScopedProxyMode.NO
    • ScopedProxyMode.INTERFACES : 인터페이스를 사용하여 JDK dynamic Proxy를 생성한다.
    • ScopedProxyMode.TARGET_CLASS : CGLIB 라이브러리를 사용하여 Proxy를 생성한다.
  • lazyInit : boolean

    • 기본값: false
    • @Component 어노테이션을 가진 Bean을 생성할 때, Lazy Initialization을 사용할지 여부
  • resourcePattern : String

    • 기본값: **/*.class
      • ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN
    • 클래스를 찾을 때 사용할 패턴을 설정한다.

@ComponentScan 기본 대상

  • @Component
  • @Controller
    • Spring MVC 컨트롤러로 인식
  • @Repository
    • Spring Data 접근 계층으로 인식
    • Data 계층의 예외를 스프링 예외로 변환
  • @Service
    • 비즈니스 로직을 처리하는 서비스 계층으로 인식
  • @Configuration
    • 스프링 설정 정보로 인식
    • @Bean 어노테이션을 가진 메서드를 호출하여 Singleton Bean으로 등록한다.

@Filter

  • @ComponentScan(includeFilters = {...})
    • 컴포넌트 스캔 대상을 추가로 지정한다.
  • @ComponentScan(excludeFilters = {...})
    • 컴포넌트 스캔에서 제외할 대상을 지정한다.
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {

    FilterType type() default FilterType.ANNOTATION;

    @AliasFor("classes")
    Class<?>[] value() default {};

    @AliasFor("value")
    Class<?>[] classes() default {};

    String[] pattern() default {};
}

FilterType

  • ANNOTATION
    • 특정 Annotation을 가진 클래스를 찾는다.
  • ASSIGNABLE_TYPE
    • 특정 타입과 그 타입을 상속받은 클래스를 찾는다.
  • ASPECTJ
    • AspectJ pattern을 사용하여 클래스를 찾는다.
  • REGEX
    • 정규 표현식 pattern을 사용하여 클래스를 찾는다.
  • CUSTOM
    • TypeFilter 인터페이스를 구현한 클래스를 사용하여 클래스를 찾는다.
public enum FilterType {
    
	ANNOTATION,
  
	ASSIGNABLE_TYPE,
  
	ASPECTJ,
  
	REGEX,
  
	CUSTOM
}
public interface TypeFilter {
	boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
			throws IOException;
}

중복 등록과 충돌

  1. 자동 빈 등록 vs 자동 빈 등록
  2. 수동 빈 등록 vs 자동 빈 등록

자동 빈 등록 vs 자동 빈 등록

  • ConflictingBeanDefinitionException 예외 발생

수동 빈 등록 vs 자동 빈 등록

  • 수동 빈 등록이 우선권을 가진다.
  • 하지만 최근 SpringBoot에서는 수동 빈 등록과 자동 빈 등록이 충돌하는 경우, 오류를 발생시킨다.
    • Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
  • spring.main.allow-bean-definition-overriding=true 설정을 통해 오류를 해결할 수 있다.

의존관계 자동 주입 (Dependency Injection)

의존관계 주입에는 크게 4가지 방법이 있다.

  1. 생성자 주입
  2. 수정자 주입(setter 주입)
  3. 필드 주입
  4. 일반 메서드 주입
  5. [추가] 옵션 처리

1. 생성자 주입

  • 생성자를 통해 의존관계를 주입하는 방법
  • 생성자 호출 시점에 딱 1번만 호출되는 것이 보장된다.
  • 생성자가 딱 1개만 있으면, @Autowired 어노테이션을 생략할 수 있다.
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;
}

Lombok 라이브러리 적용 방법

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

dependencies {
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
}

2. 수정자 주입(setter 주입)

  • setter 메서드를 통해 의존관계를 주입하는 방법
  • @Autowired(required = false) 로 설정할 경우
    • 주입할 대상이 없으면, 메서드 자체가 호출되지 않는다.
  • 단점
    • setter 메서드를 공개해야 한다.
    • 개발자가 setter 메서드를 호출할 수 있다.
    • Spring Framework가 없다면, 의존관계 주입이 누락된 것을 알 수 없다.
      • 컴파일 시점에 오류를 확인할 수 없다.
@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Autowired
    public void setDiscountPolicy(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }
}

3. 필드 주입

  • 필드에 바로 주입하는 방법
  • 외부에서 변경이 불가능
  • DI를 제공하는 Framework가 없다면, 아무것도 할 수 없다.
    • 테스트 하기 힘들다.
  • @SpringBootTest 처럼 Spring Container를 기반으로 테스트하는 경우, 사용할 수 있다.
@Component
public class OrderServiceImpl implements OrderService {

    @Autowired
    private MemberRepository memberRepository;

    @Autowired
    private DiscountPolicy discountPolicy;
}

4. 일반 메서드 주입

  • 일반 메서드를 통해 의존관계를 주입하는 방법
  • 일반적으로 잘 사용하지 않는다.
@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired
    public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}

옵션 처리

  1. @Autowired(required = false)
    • 주입할 대상이 없으면, 주입 시도 자체를 하지 않는다.
  2. @Nullable : org.springframework.lang.Nullable
    • 주입할 대상이 없으면, null이 주입된다.
  3. Optional<T>
    • 주입할 대상이 없으면, Optional.empty가 주입된다.

@Autowired 시, 동일한 타입의 Bean이 2개 이상인 경우

  • NoUniqueBeanDefinitionException 예외 발생
  • 아래와 같은 방법으로 해결한다.
    • @Autowired 필드 명 매칭
    • @Qualifier 사용
    • @Primary 사용

@Autowired 필드 명 매칭

  1. Type 매칭
  2. Type 매칭의 결과가 2개 이상일 경우, 필드 명 혹은 파라미터 명으로 Bean 이름 매칭
@Autowired
private DiscountPolicy rateDiscountPolicy;

@Qualifier 사용

  • @Qualifier 는 추가적인 구분자를 붙여준다.
    • Bean 이름을 지정하는 것이 아니다!

@Qualifier 사용 방법

  1. Bean 등록 시, @Qualifier 어노테이션을 사용하여 추가적인 구분자를 붙여준다.
  2. @Autowired 어노테이션을 사용할 때, @Qualifier 어노테이션을 사용하여 구분자를 지정한다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {
    // ...
}
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, 
                        @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}

@Primary 사용

  • @Primary 어노테이션을 사용하여 우선권을 가지는 Bean을 설정한다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {
    // ...
}

@Component
public class FixDiscountPolicy implements DiscountPolicy {
    // ...
}

@Qualifier, @Primary 우선순위

@Qualifier@Primary 보다 우선순위가 높다.

  1. @Qualifier 끼리 매칭
  2. @Primary 매칭
  3. Bean 이름 매칭

조회한 빈이 모두 필요할 경우 (List, Map)

  • List<T> : 타입의 모든 Bean을 주입받을 수 있다.
  • Map<String, T> : 타입의 모든 Bean을 이름을 키로 조회할 수 있다.
@Component
@RequiredArgsConstructor
public class DiscountService {

    private final Map<String, DiscountPolicy> policyMap;

    private final List<DiscountPolicy> policies;

    public int discount(Member member, int price, String discountCode) {
        DiscountPolicy discountPolicy = policyMap.get(discountCode);

        return discountPolicy.discount(member, price);
    }
}

Bean 자동 등록 (@Component) vs 수동 등록 (@Bean)

  • 업무 로직 관련 Bean은 자동 등록을 사용하자!
    • @Component, @Controller, @Service, @Repository
  • 기술 지원 관련 Bean은 수동 등록을 사용하자!
    • @Configuration, @Bean
    • 자동 등록을 사용할 경우, 특정 패키지에 모아두는 것이 좋다.
profile
Hello velog!

1개의 댓글

comment-user-thumbnail
2023년 10월 7일
답글 달기