컴포넌트 스캔과 의존관계 자동 주입 시작하기
스프링은 설정 정보가 없어도 자동으로 스프링 빈으로 등록하는 컴포넌트 스캔이라는 기능을 제공한다.
package hello.core;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig
{
}
기존의 AppConfig와 다르게 아무것도 안에 없다. excludeFilters 는 예제를 유지하기 위함이다.
package hello.core.member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MemberServiceImpl implements MemberService
{
private final MemberRepository memberRepository;
@Autowired //ac.getBean(MemberRepository.class)
public MemberServiceImpl(MemberRepository memberRepository)
{
this.memberRepository = memberRepository;
}
@Override
public void join(Member member)
{
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId)
{
return memberRepository.findById(memberId);
}
//테스트 용도
public MemberRepository getMemberRepository()
{
return memberRepository;
}
}
@Component를 쓰는것 만으로 생성자에 필요한 MemberRepository를 주입해줄 수 없다. Autowired를 통해서 injection 을 해준다.
package hello.core.scan;
import hello.core.AutoAppConfig;
import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AutoAppConfigTest
{
@Test
void basicScan()
{
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService memberService = ac.getBean(MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
}
}
잘동작하는 지 테스트 한 결과이다. 로그를 살펴보겠다. 이때 AutoAppConfig가 스프링 컨테이너를 생성할 때 넘겨주는 클래스인데 이 순간 빈으로 자동으로 등록이 되는것이다.
23:47:30.751 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [D:\Java things\core\core\out\production\classes\hello\core\discount\RateDiscountPolicy.class]
23:47:30.759 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [D:\Java things\core\core\out\production\classes\hello\core\member\MemberServiceImpl.class]
23:47:30.779 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [D:\Java things\core\core\out\production\classes\hello\core\member\MemoryMemberRepository.class]
23:47:31.281 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
23:47:31.290 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
23:47:31.293 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
23:47:31.305 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
23:47:31.398 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'autoAppConfig'
23:47:31.411 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'rateDiscountPolicy'
23:47:31.412 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'memberServiceImpl'
23:47:31.493 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'memoryMemberRepository'
23:47:31.495 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'memberServiceImpl' via constructor to bean named 'memoryMemberRepository'
어떤 클래스들이 빈으로 만들어졌는지 마지막줄에는 어떤 Bean이 생성자의 인자로서 Autowired 되었는지 알려준다.
@ComponetScan은 @Component 가 붙은 모든 클래스를 스프링 빈으로 등록한다. Bean의 기본이름은 클래스이름이다. 지정하고싶으면 @CompoentScan("name") 이런식으로 지정하면 된다.
@Autowired는 우선적으로 타입으로 넣어줄 Bean을 찾는다. 같은 타입이 여러개 일 경우에는 뒤에 설명한다. 기본적으로 getBean(MemberRepostiory.class) 와 같다.
@Configuration
@ComponentScan(
basePackages = "hello.core.member",
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
basepackage 로 이 패키지를 포함에서 밑으로만 스캔해달라고 할 수있다. basePackages = {"hello.core", "hello.service"} 이런식으로 도 지정할 수 있다.
basePackageClasses = AutoAppConfig.class 지정한 클래스의 패키지를 탐색 시작위로 지정할 수 있다.
그럼 basePackages 설정이 없으면 어떻게 될까, @ComponetScan이 있는 패키지위치부터 밑으로 스캔한다.
참고로 스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인 @SpringBootApplication 를 이 프로젝트 시작 루트 위치에 두는 것이 관례이다. @ComponentScan이 포함되어 있기 때문이다.
또한 main 메소드가 실행되면서 run 메소드 인자로 CoreApplication.class 를 넣어주기에 따로 Bean으로 등록할 필요가 없다.
@SpringBootApplication
public class CoreApplication {
public static void main(String[] args) {
SpringApplication.run(CoreApplication.class, args);
}
}
애노태이션에는 상속관계라는 것이 없다. 그래서 이렇게 애노테이션이 특정 애노테이션을 들고 잇는 것을 인식 할 수 있는 것은 자바 언어가 지원하는 기능이 아니고, 스프링이 지원하는 기능이다.
@Controller = 스프링 MVC 컨트롤러로 인식
@Repository = 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 에외를 스프링 예외로 변환해준다.
@Configuration = 앞서 보았듯이 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.
@Service = 개발자로 하여금 핵심 비지니스 로지깅 여기에 있겠구나 라고 비즈니스 계층을 인식하는데 도움이 된다.
필터
package hello.core.scan.filter;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent
{
}
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 org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Component;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.context.annotation.ComponentScan.*;
public class ComponentFilterAppConfigTest
{
@Test
void filterScan()
{
ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
BeanA beanA = ac.getBean("beanA", BeanA.class);
assertThat(beanA).isNotNull();
org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class,
() -> ac.getBean("beanB",BeanB.class));
}
@Configuration
@ComponentScan(
includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)
static class ComponentFilterAppConfig
{
}
}
includeFilter 에 MyIncludeComponet 애노태이션을 추가해서 BeanA가 스프링 빈에 등록이된다. 반대로 excludeFilter에 MyExcludeComponent 애노테이션을 추가해서 BeanB는 스프링 빈에 등록되지 않는다.
type = FilterType.ANNOTATION 이 default임으로 생략해도 잘 동작한다.
중복 등록과 충돌
1. 자동 빈 등록 vs 자동 빈 등록
ConflictingBeanDefinitionException 예외 발생시킨다.
2. 수동 빈 등록 vs 자동 빈 등록
수동빈이 우선권을 가지며 자동 빈을 오버라이드 해버린다.
개발은 항상 명확한것만 해야한다.
'WEB > Spring' 카테고리의 다른 글
김영한 (스프링 핵심원리 8) 빈 생명주기 콜백 시작 (0) | 2021.02.14 |
---|---|
김영한 (스프링 핵심 원리 7) 의존관계 자동 주입 (0) | 2021.02.14 |
김영한 (스프링 핵심원리 5) 싱글톤 컨테이너 (0) | 2021.02.12 |
김영한 (스프링 핵심원리 4) 스프링 컨테이너와 스프링 빈 (0) | 2021.02.12 |
김영한 (스프링 핵심원리 3) 스프링의 핵심원리이해2 - 객체 지향원리 적용 (0) | 2021.02.10 |