컴포넌트 스캔?! 생성자 주입?!

전홍영·2023년 4월 20일
0

Spring

목록 보기
9/21
post-custom-banner

컴포넌트 스캔

기존 스프링은 스프링 컨테이너에 빈을 등록하기 위해서는 XML이나 @Bean을 통하여 스프링 컨테이너에 등록하였다. 그러나 컴포넌트 스캔(@ComponentScan)을 통하여 스프링이 자동으로 빈에 등록하는 기능이 생겼다.

@ComponentScan을 통하여 @Componet, @Configuration, @Service, @Repository, @Controller 붙은 메소드나 클래스는 스프링 컨테이너에 등록이 된다. @Configuration, @Service, @Repository, @Controller 어노테이션 모두 들어가보면 @Componet가 붙어있기 때문에 컴포넌트 스캔에 대상이 된다.

이러한 기능 덕분에 개발자는 일일이 스프링 컨테이너에 등록할 필요가 없어졌고 자동으로 스프링 컨테이너에 등록되기 때문에 더욱 편리해졌다.

@ComponentScan 빈 등록 방식

@Componet
public class CallServiceImpl{
	private final CallRepository callRepository;
    
    @Autowired
    public CallServiceImpl(CallRepository callRepository){
    	this.callRepository = callRepository;
    }
}

컴포넌트 스캔은 스프링 컨테이너에 빈을 등록할 때 첫 글자를 소문자로 바꾸어 빈의 이름을 등록한다. 위의 예는 빈으로 등록 된다면 callServiceImpl이 빈 이름이 될 것이다. 그러나 빈 이름을 지정하고 싶다면 @Component("callService") 이렇게 ()사이에 지정하고 싶은 이름을 설정해주면 된다.

또한 빈 객체도 컴포넌트 스캔이 자동으로 지정해주는 이 때 사용하는 것이 @Autowired이다.

생성자에 @Autowired를 붙이면 자동으로 스프링 컨테이너가 해당 스프링 빈을 찾아 주입한다. 이때 기본적으로는 타입이 같은 빈을 찾아준다. 근데 나는 주로 개발을 할 때 @Aurowired보다는 @RequiredArgsConstructor를 많이 사용했다.어떤 차이점이 있을까?

@Autowired vs @RequiredArgsConstructor

@Autowired는 스프링에서 제공하는 어노테이션으로 자동으로 스프링 빈에 객체를 등록시켜주는 어노테이션이다. (이는 생성자 주입을 기준)

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface RequiredArgsConstructor {
}


@Componet
@RequiredArgsConstructorpublic class CallServiceImpl{
	private final CallRepository callRepository;
    
}

그렇다면 @RequiredArgsConstructor는 무엇일까?

@RequiredArgsConstructor는 Lombok이라는 필수적?인 라이브러리가 제공하는 어노테이션이다. @RequiredArgsConstructor는 따로 생성자를 생성할 필요없이 자동으로 private final로 선언된 필드에 맞는 생성자를 만들어 빈 객체를 찾아 주입해준다.

따라서 @Autowired는 생성자를 통해 빈 객체를 주입받지만 @RequriredArgsConstruct는 따로 생성자를 만들지 않고 자동으로 생성자를 만들어주어 빈 객체를 주입한다. 그러나 @Autowired를 사용하게 되면 주입할 필드가 늘어나면 늘어날 때마다 생성자에 파라미터를 추가하고 초기화 해주는 로직이 필요하다. 따라서

알겠어 그러면 왜 생성자 주입을 하는거지? 필드 주입하면 안돼?? 요기 참고

@Autowired는 생성자 주입 뿐만아니라 메소드, 필드 주입을 제공한다. 그러나 이는 스프링에서 지양하는 주입하는 방법이다. 왜냐하면

  • SRP(단일 책임 원칙)을 위배할 가능성이 높아진다.
  • 코드 변이 가능성이 있다. 필드 주입 방식은 final 키워드를 사용하지 않기 때문에 코드가 변질될 가능성이 있다.
  • 불확실한 참조(이는 @RequiredArgsConstruct도 같은 것 같다..) 만약 같은 타입의 객체가 2개가 등록되어 있으면 참조시 어떤 객체를 참조하게 될지 몰라 에러가 발생한다.
  • 순환참조 발생 가능성: 생성자를 통한 의존성 주입은 클래스가 순환 참조가 의심되면 런타임(RunTime)이 Exception을 발생시키기에 미리 코드의 문제점을 파악할 수 있다.

따라서 이러한 문제 때문에 생성자 주입을 지향해야 한다.

컴포넌트 스캔 범위

컴포넌트 스캔할 때 모든 자바 클래스를 스캔한다면 오랜 시간이 걸릴 것이다. 그래서 범위를 지정하여 컴포넌트 스캔을 할 수 있는데 권장하는 방법은 스프링 정보 클래스의 위치를 패키지 최상단에 두어 패키징 하위 클래스만 스캔을 하는 것을 권장한다.

스프링 프로젝트를 처음 만들면 맨위에 패키지 이름Application.java 클래스에 생성되고 이 위에 @SrpringBootApplication 어노테이션이 붙어있는데 이 어노테이션에 들어가 보면 @ComponentScan이 붙어있다. 따라서 이를 최상단 위치에 두어 실행시 하위 패키지를 스캔하게 두는 것이 좋다.

@ComponentScan에 스캔시 제외할 클래스, 포함할 클래스를 설정하는 옵션도 존재하니 필요시 알아보고 사용하면 될 것 같다.

동일한 이름의 빈이 두개라면??

컴포넌트 스캔으로 빈이 등록될 때 동일한 이름으로 빈이 등록되면 어떻게 될까?

빈을 등록할 때 개발자가 수동으로 등록하는 경우가 있고 자동으로 등록되는 빈이 존재한다. 그렇다면 동일한 이름의 빈이 두개일 경우에 자동 vs 자동, 수동 vs 자동 이렇게 두 경우가 문제가 된다.

자동 vs 자동은 스프링에서 컴포넌트 스캔을 할 때 예외를 발생시킨다(ConflictingBeanDefinitionException) 그렇다면 수동 vs 자동의 경우는 어떻게 될까?

이 경우에는 수동으로 빈을 등록한 것이 자동으로 빈 등록한 것보다 우선순위를 가지게 된다.( 수동이 자동을 오버라이딩 한다.) 이럴 때 로그가 남는데 "Overriding bean definition for bean 'memoryMemberRepository' with a different
definition: replacing" 이러한 로그가 남는다.

그러나 스프링 부트에서 수동 빈 등록과 자동 빈 등록이 충돌하게 되면 오류가 발생하도록 기본 값을 변경하였다.

참조
김영한의 스프링 기본편
https://backendcode.tistory.com/209

profile
Don't watch the clock; do what it does. Keep going.
post-custom-banner

0개의 댓글