스프링 프레임워크(Spring Framework) 톺아보기 - 컴포넌트 스캔과 의존 관계 자동 주입

Janek·2023년 1월 6일
0

Spring 톺아보기

목록 보기
4/10
post-thumbnail
post-custom-banner

해당 포스팅은 인프런에서 제공하는 김영한 님의 '스프링 핵심원리 기본편'을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.

스프링 빈을 등록할 때 자바 코드의 @Bean이나 XML의 <bean>등을 통해서 설정 정보에 직접 작성한다. 그러나 등록해야 할 스프링 빈의 수가 늘어날 수록 설정 파일에 일일히 등록하는 방법은 생산성이 떨어질 수 밖에 없으며, 설정 정보가 커지고 빈이 누락되는 문제도 발생할 수 있다. 그렇기에 스프링은 설정 정보 없이도 자동으로 스프링 빈을 등록할 수 있도록 컴포넌트 스캔(Component Scan)이라는 기능을 제공하며, 의존 관계 주입을 위해 @Autowired 기능을 제공한다.

@Configuration
@ComponentScan
public class SampleAppConfig {...}

컴포넌트 스캔 사용을 위해서는 @ComponentScan을 설정 정보에 명시해준다. @Bean으로 클래스 등록을 하지 않아도 되며, @SpringBootApplication 어노테이션을 따라가 보면 @ComponentScan이 등록되어 있는 것을 확인할 수 있다.

@ComponentScan
public class SampleRepository {...}
@ComponentScan
public class SampleService {
	private final SampleRepository sampleRepository;
    
    @Autowired
    public SampleService(SampleRepository sampleRepository) {
    	this.sampleRepository = sampleRepository;
    }
}

@Component@Autowired 어노테이션을 사용할 경우 Config 파일에서 @Bean을 통해 직접 설정 정보를 작성하고 의존 관계를 명시하지 않아도 스프링 빈으로 등록해주며, 의존 관계도 자동으로 주입해준다.

컴포넌트 스캔(Component Scan)

@ComponentScan

@ComponentScan@Component가 붙은 모든 클래스를 스프링 빈으로 등록한다. 스프링 빈의 기본 이름은 클래스명의 앞글자만 소문자로 사용하며, @Component("sampleBean") 처럼 직접 지정할 수도 있다.

@Autowired

생성자에 @Autowired를 지정하면 스프링 컨테이너가 해당 스프링 빈을 찾아 자동으로 의존 관계를 주입해준다. 기본 조회 전략은 getBean(SampleRepository.class)와 같이 타입이 같은 빈을 찾아 주입하는 것이다.

탐색 위치와 기본 스캔 대상

모든 자바 클래스를 모두 컴포넌트 스캔할 경우 탐색에 필요한 시간이 증가하여 성능이 떨어질 수 있다. 그렇기에 꼭 필요한 위치부터 탐색하도록 @ComponentScan(basepackage = "sample.repository")와 같이 탐색 시작 위치를 지정할 수도 있다.

또한 basepackage = {"sample.repository", "sample.service"}와 같이 다중 설정도 가능하며, 지정하지 않을 경우 @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.

스프링 부트가 기본적으로 제공하는 방법은 패키지 위치 지정 없이 설정 정보 클래스를 프로젝트 최상단에 두는 것이다. 위에서 언급한 @SpringBootApplication@ComponentScan이 붙어 있는 이유 또한 이러한 관례 때문이다.

기본 스캔 대상

컴포넌트 스캔은 @Component을 포함하여 다음 어노테이션들을 스캔하여 빈으로 등록하며, 각 어노테이션 별로 부가 기능을 수행하기도한다.

  • @Component : 컴포넌트 스캔 사용
  • @Controller : 스프링 MVC 컨트롤러 사용, 스프링 MVC 컨트롤러로 인식
  • @Service : 스프링 비지니스 로직 사용, 특별한 부가 기능은 없지만 개발자들이 비지니스 계층을 인식하는데 도움이 된다.
  • @Repository : 스프링 데이터 접근 계층 사용, 스프링 데이터 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.
  • @Configuration : 스프링 설정 정보 사용, 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.

위 어노테이션들을 추적해보면 모두 @Component가 포함되어 있는 것을 확인할 수 있다.

필터

필터는 컴포넌트 스캔 대상을 추가로 지정하고나 제외할 대상을 지정하는데 사용한다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SampleComponent {...}
  • includeFilters : 컴포넌트 스캔 대상을 추가로 지정한다.
@Configuration
@ComponentScan(
	includeFilters = @Filter(type = FilterType.ANNOTATION, 	
    classes = MyIncludeComponent.class))
static class ComponentFilterAppConfig {}
  • excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다.
@Configuration
@ComponentScan(
	excludeFilters = @Filter(type = FilterType.ANNOTATION, 	
    classes = MyIncludeComponent.class))
static class ComponentFilterAppConfig {}

FilterType 옵션

  • ANNOTATION : 기본값. 어노테이션을 인식해서 동작한다.
    org.example.SampleAnnotation
  • ASSIGNABLE_TYPE : 지정한 타입과 자식 타입을 인식해서 동작한다.
    org.example.SmapleClass
  • ASPECTJ : AspectJ 패턴 사용
    org.example..*Service+
  • REGEX : 정규 표현식
    org\.example\.Default.*
  • CUSTOM : TypeFilter라는 인터페이스를 구현해서 처리
    org.example.SampleTypeFilter

중복 등록과 충돌

컴포넌트 스캔에서 같은 빈 이름을 등록할 경우

  1. 자동 빈 등록 vs 자동 빈 등록 : ConflictingBeanDefinitionException 예외 발생
  2. 수동 빈 등록 vs 자동 빈 등록 : 수동 빈이 자동 빈을 오버라이딩 해서 우선권을 가진다.(최근 스프링 부트에서는 수동 빈과 자동 빈 충돌시 오류 발생)

의존관계 자동 주입

스프링이 관리하는 스프링 빈을 사용해 의존관계를 주입하는 방법은 크게 생성자 주입수정자 주입, 필드 주입 그리고 일반 메서드 주입으로 나뉠 수 있다.

생성자 주입

생성자를 통해 의존 관계를 주입 받는 방법으로, 생성자 호출 시점에 딱 한 번만 호출되는 것이 보장된다. 불변, 필수 의존관계에 사용되며, 생성자가 딱 한개만 있을 경우 @Autowired를 생략할 수 있다.(스프링 빈에만 해당)

@Component
public class Test {

	private final TestBean testBean;
    
	@Autowired	// 생성자가 하나일 경우 생략 가능
    public Test(TestBean testBean) {
    	this.testBean = testBean;
    }

}

수정자 주입

자바빈 프로퍼티 규약의 수정자 메서드 방식을사용한 setter 메서드를 통해 의존관계를 주입하는 방법으로, setter 주입이라고도 한다. 선택, 변경 가능성이 있는 의존관계에 사용된다. @Autowired는 기본적으로 주입할 대상이 없으면 오류가 발생한다. 주입할 대상 없이 동작하게 하기 위해서는 @Autowired(required = false) 속성을 사용하면 된다.

@Component
public class Test {

	private TestBean testBean;
    
    @Autowired
    public void setTest(TestBean testBean) {
    	this.testBean = testBean;
    }

}

필드 주입

필드에 바로 주입하는 방법으로, 코드가 간결하다는 장점이 있지만 외부에서 변경이 불가능하기에 테스트가 어려우며, DI 프레임워크 없이는 아무 것도 할 수 없다는 단점이 존재하기 때문에 사용을 지양해야 한다. 어플리케이션의 실제 코드와 관계 없는 테스트 코드나 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용한다.

@Component
public class Test {

	@Autowired
	private TestBean testBean;

}

일반 메서드 주입

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

@Component
public class Test {

	private TestBean testBean;
   
	@Autowired
    public void init(TestBean testBean) {
    	this.testBean = testBean;
	}

}

생성자 주입을 사용해야하는 이유

불변

대부분의 의존관계는 어플리케이션이 종료되기 까진 변하면 안된다. 수정자 주입을 사용할 경우 setter 메서드를 public으로 열어두어야 하며, 변경 하면 안되는 메서드를 열어두는 것은 좋은 설계가 아니다. 생서자 주입을 사용할 경우 생성 단계에서 단 한 번만 생성해 싱글턴으로 관리하게 되므로 불변하게 설계할 수 있다.

누락

생성자 주입을 사용할 경우 주입 데이터를 누락 했을 때 컴파일 오류가 발생한다. 또한 IDE를 통해 필수로 주입되어야 하는 값을 확인할 수 있다.

생성자 주입 방식은 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 살린 방법이다. 기본적으로 생성자 주입을 사용하고, 필수 값이 아닌 경우 수정자 주입 방식을 옵션으로 선택하면 된다.

profile
만들고 나누며, 세상을 이롭게 하고 싶습니다.
post-custom-banner

0개의 댓글