기존 스프링은 스프링 컨테이너에 빈을 등록하기 위해서는 XML이나 @Bean을 통하여 스프링 컨테이너에 등록하였다. 그러나 컴포넌트 스캔(@ComponentScan)을 통하여 스프링이 자동으로 빈에 등록하는 기능이 생겼다.
@ComponentScan을 통하여 @Componet, @Configuration, @Service, @Repository, @Controller 붙은 메소드나 클래스는 스프링 컨테이너에 등록이 된다. @Configuration, @Service, @Repository, @Controller 어노테이션 모두 들어가보면 @Componet가 붙어있기 때문에 컴포넌트 스캔에 대상이 된다.
이러한 기능 덕분에 개발자는 일일이 스프링 컨테이너에 등록할 필요가 없어졌고 자동으로 스프링 컨테이너에 등록되기 때문에 더욱 편리해졌다.
@Componet
public class CallServiceImpl{
private final CallRepository callRepository;
@Autowired
public CallServiceImpl(CallRepository callRepository){
this.callRepository = callRepository;
}
}
컴포넌트 스캔은 스프링 컨테이너에 빈을 등록할 때 첫 글자를 소문자로 바꾸어 빈의 이름을 등록한다. 위의 예는 빈으로 등록 된다면 callServiceImpl이 빈 이름이 될 것이다. 그러나 빈 이름을 지정하고 싶다면 @Component("callService") 이렇게 ()사이에 지정하고 싶은 이름을 설정해주면 된다.
또한 빈 객체도 컴포넌트 스캔이 자동으로 지정해주는 이 때 사용하는 것이 @Autowired이다.
생성자에 @Autowired를 붙이면 자동으로 스프링 컨테이너가 해당 스프링 빈을 찾아 주입한다. 이때 기본적으로는 타입이 같은 빈을 찾아준다. 근데 나는 주로 개발을 할 때 @Aurowired보다는 @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
@RequiredArgsConstructor는
public class CallServiceImpl{
private final CallRepository callRepository;
}
그렇다면 @RequiredArgsConstructor는 무엇일까?
@RequiredArgsConstructor는 Lombok이라는 필수적?인 라이브러리가 제공하는 어노테이션이다. @RequiredArgsConstructor는 따로 생성자를 생성할 필요없이 자동으로 private final로 선언된 필드에 맞는 생성자를 만들어 빈 객체를 찾아 주입해준다.
따라서 @Autowired는 생성자를 통해 빈 객체를 주입받지만 @RequriredArgsConstruct는 따로 생성자를 만들지 않고 자동으로 생성자를 만들어주어 빈 객체를 주입한다. 그러나 @Autowired를 사용하게 되면 주입할 필드가 늘어나면 늘어날 때마다 생성자에 파라미터를 추가하고 초기화 해주는 로직이 필요하다. 따라서
@Autowired는 생성자 주입 뿐만아니라 메소드, 필드 주입을 제공한다. 그러나 이는 스프링에서 지양하는 주입하는 방법이다. 왜냐하면
따라서 이러한 문제 때문에 생성자 주입을 지향해야 한다.
컴포넌트 스캔할 때 모든 자바 클래스를 스캔한다면 오랜 시간이 걸릴 것이다. 그래서 범위를 지정하여 컴포넌트 스캔을 할 수 있는데 권장하는 방법은 스프링 정보 클래스의 위치를 패키지 최상단에 두어 패키징 하위 클래스만 스캔을 하는 것을 권장한다.
스프링 프로젝트를 처음 만들면 맨위에 패키지 이름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