컴포넌트 스캔과 의존관계 자동 주입 시작하기
AS-IS
자바 코드의 @Bean이나 XML의 <bean> 등을 통해서 설정 정보에 직접 등록할 스프링 빈을 나열
(반복되는 코드 작성, 누락 가능성, 설정 정보도 커짐)
TO-BE
스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔 기능 활용
의존관계도 자동으로 주입하는 @Autowired 활용
AutoAppConfig.java
package hello.core;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepositroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
@Configuration
@ComponentScan
public class AutoAppConfig {
@Bean(name = "memoryMemberRepositroy")
MemberRepository memberRepository() {
return new MemoryMemberRepositroy();
}
}
강의 내에서는 기존 AppConfig.java 코드를 유지하지 위해서 필터로 제외시킴
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
컴포넌트 스캔은 이름 그대로 @Component 애노테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록
참고: @Configuration 이 컴포넌트 스캔의 대상이 된 이유도 @Configuration 소스코드를 열어보면 @Component 애노테이션이 붙어있기 때문이다.
- MemoryMemberRepository @Component 추가
- RateDiscountPolicy @Component 추가
- MemberServiceImpl @Component, @Autowired 추가
- OrderServiceImpl @Component, @Autowired 추가
@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;
}
}
*@Autowired 를 사용하면 생성자에서 여러 의존관계도 한번에 주입받을 수 있다.
TEST 결과
> output
18:27:30.642 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\spring-inflearn\core\out\production\classes\hello\core\discount\RateDiscountPolicy.class]
18:27:30.645 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\spring-inflearn\core\out\production\classes\hello\core\member\MemebrServiceImpl.class]
18:27:30.645 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\spring-inflearn\core\out\production\classes\hello\core\member\MemoryMemberRepositroy.class]
18:27:30.646 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\spring-inflearn\core\out\production\classes\hello\core\order\OrderServiceimpl.class]
컴포넌트 스캔 및 자동 의존관계 주입 동작 과정
1. @ComponentScan
- @ComponentScan 은 @Component 가 붙은 모든 클래스를 스프링 빈으로 등록한다.
- 이때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다.
빈 이름 기본 전략: MemberServiceImpl 클래스 memberServiceImpl
빈 이름 직접 지정: 만약 스프링 빈의 이름을 직접 지정하고 싶으면 @Component("memberService2") 이런식으로 이름을 부여하면 된다.
2. @Autowired 의존관계 자동 주입
- 설정정보를 작성하지 않기 때문에 Autowired로 생성자를 주입해줌(타입을 통하여)
- getBean(MemberRepository.class) 와 동일
public class MemebrServiceImpl implements MemberService{
private final MemberRepository memberRepository;
@Autowired //ac.getBen(MemberRepository.class) 와 같은 동작함
public MemebrServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
- 생성자에 파라미터가 많아도 다 찾아서 자동으로 주입한다.
탐색 위치와 기본 스캔 대상
@Configuration
@ComponentScan(
basePackages = "hello.core.member",
basePackageClasses = AutoAppConfig.class,
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}
- basePackages : 라이브러리까지 싹다 탐색하기 때문에 비효율, 탐색하고 싶은 것만 탐색할 수 있음
- AutoAppConfing의 패키지를 탐색
- Default 값은 @ComponentScan 이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.
스프링부트를 쓰면 @ComponentScan 을 쓸 필요도 없다. @SpringBootApplication 에서 이미 들어가 있기 때문
패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것을 권장한다.
컴포넌트 스캔 기본 대상
컴포넌트 스캔은 @Component 뿐만 아니라 다음과 내용도 추가로 대상에 포함한다. + 부가기능 수행
- @Component : 컴포넌트 스캔에서 사용
- @Controlller : 스프링 MVC 컨트롤러에서 사용 및 인식
- @Service : 스프링 비즈니스 로직에서 사용 + 사실 @Service 는 특별한 처리를 하지 않는다. 대신 개발자들이 핵심 비즈니스 로직이 여기에 있겠구나 라고 비즈니스 계층을 인식하는데 도움이 된다.
- @Repository : 스프링 데이터 접근 계층에서 사용 및 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다. (예를들어 DB가 바뀌면 예외가 그에 따라 바뀌면 서비스 계층도 흔들려서 스프링이 막기를 위해 예외를 추상화해서 반환을 해준다. 이해가 안되면 데이터접근 강의를 보거라)
- @Configuration : 스프링 설정 정보에서 사용 및 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.
@Component
public @interface Controller {
}
@Component
public @interface Service {
}
@Component
public @interface Configuration {
}
참고: 사실 애노테이션에는 상속관계라는 것이 없다. 그래서 이렇게 애노테이션이 특정 애노테이션을 들고
있는 것을 인식할 수 있는 것은 자바 언어가 지원하는 기능은 아니고, 스프링이 지원하는 기능이다.
참고: useDefaultFilters 옵션은 기본으로 켜져있는데, 이 옵션을 끄면 기본 스캔 대상들이 제외된다. 그냥 이런 옵션이 있구나 정도 알고 넘어가자.
필터
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
FilterType 옵션
- ANNOTATION: 기본값, 애노테이션을 인식해서 동작한다. ex) org.example.SomeAnnotation
- ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작한다. ex) org.example.SomeClass
- Class를 직접 지정해줄 수 있음
- ASPECTJ: AspectJ 패턴 사용 ex) org.example..Service+
- REGEX: 정규 표현식 ex) org\.example\.Default.
- CUSTOM: TypeFilter 이라는 인터페이스를 구현해서 처리 ex) org.example.MyTypeFilter
*ASSIGNABLE_TYPE 간혹 사용하고 Annotation이 이미 구동되기 때문에 굳이 includeFilters 를 사용할 일은 거의 없다. excludeFilters는 여러가지 이유로 간혹 사용할 때가 있지만 많지는 않다.
@ComponentScan(
includeFilters = @Filter(type = FilterType.ANNOTATION, classes =
MyIncludeComponent.class),
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
MyExcludeComponent.class)
)
- includeFilters : 컴포넌트 스캔 대상을 추가로 지정한다.
- excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다.
참고: @Component 면 충분하기 때문에, includeFilters 를 사용할 일은 거의 없다. excludeFilters 는 여러가지 이유로 간혹 사용할 때가 있지만 많지는 않다.
BeanA도 빼고 싶으면 다음과 같이 추가
@ComponentScan(
includeFilters = {
@Filter(type = FilterType.ANNOTATION, classes =
MyIncludeComponent.class),
},
excludeFilters = {
@Filter(type = FilterType.ANNOTATION, classes =
MyExcludeComponent.class),
@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = BeanA.class)
}
)
중복 등록과 충돌
- 자동 빈 등록 vs 자동 빈 등록 : 컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데, 그 이름이 같은 경우 스프링은 오류를 발생시킨다. ConflictingBeanDefinitionException 예외 발생
@Component("service") //이런 식으로 같은 서비스 명을 써주었을 때
- 수동 빈 등록 vs 자동 빈 등록 : 수동 빈 등록이 우선권을 가진다. (수동 빈이 자동 빈을 오버라이딩 해버린다.)
🔽 TEST
@Component
public class MemoryMemberRepository implements MemberRepository {}
@Configuration
@ComponentScan(
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
@Bean(name = "memoryMemberRepository")
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
Overriding bean definition for bean 'memoryMemberRepository' with a different
definition: replacing
설정 정보가 꼬이면 원인을 찾고 오류를 해결하기 힘들다. 스프링 부트는 이 경우 오류 발생하도록 기본 값을 바꾸었다. (설정 값 바꾸면 위와 같이 동작함)
Consider renaming one of the beans or enabling overriding by setting
spring.main.allow-bean-definition-overriding=true
스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술을 수강하며 기록한 글입니다.
'SPRING' 카테고리의 다른 글
[스프링 기본 원리] 의존관계 자동 주입(2/2) (0) | 2022.10.26 |
---|---|
[스프링 기본 원리] 의존관계 자동 주입(1/2) (0) | 2022.10.24 |
[스프링 핵심 원리] 싱글톤 컨테이너 (1) | 2022.10.11 |
[스프링 핵심 원리] 스프링 컨테이너와 스프링 빈 (0) | 2022.10.05 |
[스프링 핵심 원리] 자바에서 스프링으로 전환하기 (0) | 2022.09.26 |