컴포넌트 스캔과 자동주입 시작하기
이제까지는 @bean이나 xml을 통해서 bean을 설정정보에 직접 등록하도록 하였는데, 이렇게 하면 누락문제와 반복문제가 생긴다.
그래서 스프링은 설정정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능과, 의존관계를 자동으로 주입해주는 @Autowired 기능도 제공한다.
컴포넌트 스캔을 사용하려면 @ComponentScan을 설정정보에 붙여주면 된다.
실제 프로젝트에서도 이를 사용하는지 알아보려고
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
@SpringBootApplication으로 들어갔더니 위와 같이 @ComponentScan을 사용하는 것을 알 수 있었다.
컴포넌트 스캔은 이름 그대로 @Component 어노테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록한다.
@Component
public class MemoryMemberRepository implements MemberRepository {}
이와 같이 특정 클래스에 @Component 어노테이션을 붙이고,
@Component
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
여기서 주입하면 된다.
그림으로 보면
위와 같이 @Component 어노테이션이 붙은 클리스들을 빈 객체로 저장해둔다.
기본적으로 빈 이름의 맨 앞글자는 소문자이다.
생성자에 @Autowired를 지정하면 스프링 컨테이너가 자동으로 주입해준다. 파라미터가 많아도 다 알아서 해준다.
이때 타입이 같은 빈을 찾아서 주입한다.
-> 그래서 memberRepository에 memoryMemberRepository가 주입된 것!
탐색 위치와 기본 스캔 대상
모든 자바 클래스를 다 스캔하면 시간이 오래 걸린다. -> 꼭 필요한 시작 위치부터 탐색하도록 시작 위치를 지정할 수 있다.
탐색 위치
@ComponentScan(
basePackages = "hello.core",
}
이런식으로 작성해두면 hello.core패키지와 그 하위패키지까지 탐색한다.
권장되는 방법은 패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것이다.
그래서 우리 @SpringBootApplication 어노테이션도 프로젝트의 시작 루트 위치에 둔다.
컴포넌트 스캔 기본 대상
- @Component : 컴포넌트 스캔에서 사용
- @Controller : 스프링 MVC 컨트롤러에서 사용
- @Service : 스프링 비지니스 로직에서 사용, @Service는 사실 특별한 처리를 하는 것ㅇ른 아니다. 하지만 개발자들이 비지니스 로직의 위치를 확인하는데 도움이 된다.
- @Repository : 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다. (DB 상의 예외를 다른 계층까지 침범하지 않게 도와준다.)
- @Configuration : 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.
참고로 어노테이션은 상속관계라는 것이 존재하지 않는다. 그래서 특정 어노테이션이 다른 어노테이션을 포함하고 있는 것을 인식할 수 있는 것은 자바 언어가 지원하는 기능이 아니고, 스프링이 지원하는 기능이다.
필터
package hello.core.scan.filter;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
package hello.core.scan.filter;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
이런 식으로 어노테이션을 만든 후
package hello.core.scan.filter;
@MyIncludeComponent
public class BeanA {
}
package hello.core.scan.filter;
@MyExcludeComponent
public class BeanB {
}
BeanA 클래스와 BeanB 클래스에 어노테이션을 각각 적용한다.
@ComponentScan(
includeFilters = @Filter(type = FilterType.ANNOTATION, classes =
MyIncludeComponent.class),
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
MyExcludeComponent.class)
)
그 다음 이런식으로 ComponentScan에 필터를 적용한다. includeFilters 내에는 포함할 클래스들이, excludeFilters 내에는 제외할 클래스들이 들어간다.
Filter에는 5가지 옵션이 있다.
- ANNOTATION: 기본값, 애노테이션을 인식해서 동작한다. ex) `org.example.SomeAnnotation`
- ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작한다. ex) `org.example.SomeClass`
- ASPECTJ: AspectJ 패턴 사용
ex) `org.example..*Service+` - REGEX: 정규 표현식
ex) `org\.example\.Default.*` - CUSTOM: `TypeFilter` 이라는 인터페이스를 구현해서 처리 ex) `org.example.MyTypeFilter`
excludeFilters = {
@Filter(type = FilterType.ANNOTATION, classes =
MyExcludeComponent.class),
@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = BeanA.class)
}
따라서 위와 같이 구현하면 BeanA도 제외할 수 있다.
최근 스프링부트는 컴포넌트 스캔을 기본적으로 제공하는데, 옵션을 변경하는 것보다 기본 설정에 최대한 맞춰서 사용하는 것이 선호되는편이다.
중복등록과 충돌
컴포넌트 스캔에서 같은 빈 이름을 등록하는 경우는 크게 두가지가 있다.
1. 자동빈 VS 자동빈
2. 자동빈 VS 수동빈
자동빈과 자동빈이 충돌하면 ConflictingBeanDefinitionException 에러가 발생한다.
수동빈과 자동빈이 충돌하면 수동빈이 우선권을 가진다. 하지만, 이렇게 되면 애매한 오류가 발생하는 문제점이 발생한다. 따라서 최근 스프링 부트에서는 수동빈 등록과 자동빈 등록이 충돌하면 오류가 발생하도록 기본값을 바꾸었다.
'Back-end > Spring' 카테고리의 다른 글
[Spring] 빈 생명주기 콜백 (1) | 2025.02.27 |
---|---|
[Spring] 의존관계 자동 주입 (1) | 2025.02.27 |
[Spring] 싱글톤 컨테이너 (3) | 2024.11.14 |
[spring] 스프링 컨테이너와 스프링 빈 (1) | 2024.10.04 |
[spring] 관심사 분리 (1) | 2024.10.02 |