[ Spring ] @ComponentScan Annotation에 대해서

duck-ach·2024년 3월 6일
0

Spring

목록 보기
16/16
post-custom-banner

개요

기존 Spring에서는 자바 코드의 @Bean이나, xml파일의 <bean> 태그 등을 통해서 직접 등록할 스프링 빈을 나열했다.
실무에서는 코드가 수십, 수백개가 될텐데 이 Bean들을 모두 일일히 등록해주려면 귀찮기도하고 실제로 누락되는 정보도 많을 것이다.

그래서 Spring은 설정 정보가 없어도 자동으로 Spring Bean을 등록해주는 @ComponentScan 기능을 제공한다.

@ComponentScan Annotation은@Component@Controller, @Service, @Repository, @Configuration 등 과 같은 Annotation을 스캔하여 해당 Annotation이 부여된 Class(객체)들을 자동으로 Scan하여 스프링 빈(Bean)에 등록 해주는 역할을 한다.

그리고 @Autowired를 통해 의존 관계도 자동으로 주입(Dependency Injection)해준다.


@ComponentScan

@ComponentScan 을 지정할 파일 위치

@ComponentScan Annotation은 따로 basePackages를 지정해 주지 않는 이상 자기가 위치한 곳부터 패키지 아래의 모든 @Component@Controller, @Service, @Repository, @Configuration와 같은 Annotation 들을 Scan하여 객체를 @Bean으로 등록해준다.

그렇기 때문에 패키지의 가장 바깥쪽에 위치하는 곳에 두고 Scan을 한다.

@ComponentScan을 적용한 예시

@Configuration
@ComponentScan
public class AppConfig {
	
}

@ComponentScan 내 Scan 패키지 범위 지정하기

basePackages 를 이용하면 탐색할 패키지의 시작 위치를 지정할 수 있다. basePackages에 지정한 이 패키지를 포함해서 하위 패키지를 모두 탐색한다.

com.kyh.heera.member 패키지와 그 하위 패키지 내 @Component 객체 Scan하기

@ComponentScan(
          basePackages = "com.kyh.heera.member",
}

@Component와 같은 Annotation을 Scan 할 수 있는 이유

@ComponentScan은 말 그대로 @Component Annotation이 붙은 Class를 Scan해서 스프링에 Bean으로 등록한다.

@Controller, @Service, @Repository, @Configuration와 같은 Annotation들을 들어가보면 @Component가 등록되어 있는 것을 확인할 수 있다.

@Controller Annotation 내부

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {

	/**
	 * Alias for {@link Component#value}.
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

}

@Service Annotation 내부

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {

	/**
	 * Alias for {@link Component#value}.
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

}

@Repository Annotation 내부

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {

	/**
	 * Alias for {@link Component#value}.
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

}

@Configuration Annotation 내부

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

	@AliasFor(annotation = Component.class)
	String value() default "";

	boolean proxyBeanMethods() default true;
    boolean enforceUniqueMethods() default true;
}

Bean 등록 테스트

package com.kyh.heera.scan;

import com.kyh.heera.AutoAppConfig;
import com.kyh.heera.member.MemberRepository;
import com.kyh.heera.member.MemberService;
import com.kyh.heera.order.OrderServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import static org.assertj.core.api.Assertions.assertThat;

public class AppConfigTest {

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

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

        OrderServiceImpl bean = ac.getBean(OrderServiceImpl.class);
        MemberRepository memberRepository = bean.getMemberRepository();
        
        System.out.println("memberRepository = " + memberRepository);
    }

}

Test 결과

@Autowired

@Autowired를 지정하면, Spring Container가 해당 스프링 빈을 찾아서 주입한다.
참고로 의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 Bean이어야 동작한다. Member와 같은 DTO에서 @Autowired 코드를 적용해도 아무 기능도 동작하지 않는다.

기본적으로 Type 기반으로 조회를 하며, 여러 빈이 있다면 필드이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.

@Autowired는 4가지의 경우에 적용할 수 있다.

  • Constructor(생성자)
  • setter(수정자)
  • field(필드)
  • method(메소드)

Constructor(생성자)

생성자 주입은 생성자에 의존성 주입을 받고자 하는 field를 나열하는 방법으로, 3가지 경우 중 가장 권고되는 방법이다.

특징

  • 생성자 호출 시점에 딱 1번만 호출되는 것이 보장된다.
  • 불변이 보장된다.
  • 생성자를 호출(=객체를 호출) 하는 시점에 Bean이 생성되기 때문에 필수적인 의존관계에 사용된다.
  • 생성자가 딱 1개 있다면 @Autowired를 생략해도 자동 주입된다.

Constructor(생성자) 주입 예시

@Component
  public class OrderServiceImpl implements OrderService {
  
      private final MemberRepository memberRepository;
      private final DiscountPolicy discountPolicy;
      
      @Autowired // 이렇게 생성자가 딱 하나만 있다면 생략 가능
      public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy
  discountPolicy) {
          this.memberRepository = memberRepository;
          this.discountPolicy = discountPolicy;
      }
}

setter(수정자)

수정자 주입은 setter라 불리는 field의 값을 변경하는 수정자 Method를 통해 의존관계를 주입하는 방법이다.

특징

  • 선택적으로 의존성을 주입해야 하는 경우 사용한다.
  • 변경 가능성이 있는 의존관계에 사용한다.

참고
선택적으로 의존성을 주입할 경우에 @Autowired의 기본동작은 주입할 대상이 없으면 오류가 발생하기 때문에 @Autowired(required=false) 와 같은 옵션을 추가해주어야 한다.

setter(수정자) 주입 예시

@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;
        }

}

field(필드)

필드에 바로 주입하는 방식이다. 예전에 나도 많이 사용했었지만, 지금은 권장하지 않는다고 한다.

특징

  • 코드가 간결해서 많이 사용되었다.
  • 외부에서 변경이 불가능(private keyword 등)하기 때문에 테스트하기 힘들다는 치명적인 단점이 있다.
  • DI(Dependency Injection) 프레임워크가 없으면 아무것도 할 수 없다.

field(필드) 주입 예시

@Component
  public class OrderServiceImpl implements OrderService {
  
		@Autowired
        private MemberRepository memberRepository;
        
        @Autowired
        private DiscountPolicy discountPolicy;

}

method(메소드)

일반 method(메소드)를 통해서 주입 받을 수 있다.

특징

  • 한번에 여러 필드를 주입 받을 수 있다.
  • 일반적으로 잘 사용하지 않는다.

method(메소드) 주입 예시

@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;
        }
}

profile
자몽 허니 블랙티와 아메리카노 사이 그 어딘가
post-custom-banner

0개의 댓글