01. 컴포넌트 스캔과 의존관계 자동 주입 시작하기
- 지금까지 스프링 빈을 등록할 때는 자바 코드의 @Bean이나 XML의 <bean> 등을 통해 스프링 빈을 등록
- 만약 이렇게 등록해야 할 빈이 수백개인 경우 누락이 되는 문제가 발생함
- 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능 제공
- 또한 의존 관계도 자동으로 주입하는 @Autowired 기능도 제공
01-1. AutoAppConfig 생성
@ComponentScan(
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Configuration.class
)
)
@Configuration
public class AutoAppConfig {
...
}
- @ComponentScan
- @Component가 어노테이션이 붙은 클래스를 자동으로 빈으로 등록
- excludeFilters
- component scan에서 제외할 클래스 지정
- AppConfig 안에 @Component가 있기에 excludeFilters에 지정
- 기존 AppConfig와 다르게 @Bean을 사용하지 않음
- 실무에서는 전부 다 Component Scan을 하지만 현재는 일단 안함
@ComponentScan은 @Component가 붙은 클래스를 자동으로 빈 등록
01-2. RateDiscountPolicy, MemberServiceImpl, MemoryMemberRepository, OrderServiceImpl @Component 어노테이션 사용
@Component
public class RateDiscountPolicy implements DiscountPolicy {
...
}
@Component
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
@Component
public class MemoryMemberRepository implements MemberRepository {
...
}
@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;
}
}
01-3. 컴포넌트 스캔 테스트 코드 작성
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 test() throws Exception {
//given
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService memberService = ac.getBean(MemberService.class);
//when
//then
Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
}
}
- AnnotationConfigApplicationContext 사용은 이전과 동일함
- ac.getBean을 통해 bean을 가져와도 정상 동작
01-4. @ComponentScan
- @ComponentScan은 @Component가 붙은 모든 클래스를 스프링 빈(싱글톤)으로 등록 한다
- 이때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자로 사용 한다
- 빈 이름 기본 전략 : MemberSerivceImpl 클래스 → memberServiceImp
- 빈 이름 직접 지정 : 만약 스프링 빈의 이름을 직접 지정하고 싶으면 @Component(”memberServiceImpl”) 이름을 부여하면 된다
01-5. @Autowired 의존관계 자동 주입
- 생성자에 @Autowired를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입 한다
- 이때 기본 조회 전략은 타입이 같은 빈을 찾아서 주입한다
- getBean(MemberRepository.class) 와 동일
01-6. 의존관계 자동 주입
- OrderServiceImpl의 경우 파라미터가 많은데, 이렇게 파라미터가 많은 생성자여도 스프링 컨테이너가 모두 빈을 주입 해준다
01-7. 정리
- 이전에는 자바 소스의 @Bean, XML의 <bean>을 통해 스프링 빈을 등록 하였다
- 하지만 @ComponentScan 어노테이션을 사용하게 되면 위와 같이 자바 소스에 직접 Bean을 등록할 필요가 없이 스프링이 알아서 @Component가 붙은 클래스를 대상으로 Bean(빈) 등록을 수행 한다
- @Bean을 사용하지 않았기 때문에 AppConfig 처럼 의존관계 주입 설정이 어려워지는 점이 존재 하였으나, @Autowired를 생성자에 붙여 자동 의존 관계 주입이 되도록 설정 하였다
02. 탐색 위치와 기본 스캔 대상
02-1. 탐색할 패키지의 시작 위치를 지정
package hello.core;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
@ComponentScan(
basePackages = "hello.core.member",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Configuration.class
)
)
@Configuration
public class AutoAppConfig {
}
- basePackages 키워드를 사용하여 ComponentScan의 대상 패키지 지정 가능
- 해당 시작 위치를 기준으로 하위 패키지를 전부 스캔(Scan)
- basePackages는 여러개 지정이 가능
- 또한 basepackgesClass를 통해 클래스를 기준으로 스캔 가능
- 권장 방법
- 설정 정보 클래스의 위치를 프로젝트 최상단에 위치 한다
- Spring Boot는 @SpringBootApplication 안에 @ComponentScan 어노테이션이 존재한다
package org.springframework.boot.autoconfigure;
/**
* Indicates a {@link Configuration configuration} class that declares one or more
* {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
* auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
* annotation that is equivalent to declaring {@code @Configuration},
* {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Andy Wilkinson
* @since 1.2.0
*/
@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) })
public @interface SpringBootApplication {
...
}
02-2. 컴포넌트 스캔의 기본 대상
⚠️ 핵심 포인트임, 자동 컴포넌트 스캔을 지원함
- 컴포넌트 스캔 @Component 뿐만 아니라 다음 내용도 추가로 대상에 포함
- @Component : 컴포넌트 스캔에서 사용
- @Controller : 스프링 MVC 컨트롤러에서 사용
- @Service : 스프링 비즈니스 로직에서 사용
- @Repository : 스프링 데이터 접근 계층에서 사용
- @Configuration : 스프링 설정 정보에서 사용
02-3. 컴포넌트 스캔 관련 부연 설명
- 어노테이션의 경우 상속 관계가 없다
- 즉, 어노테이션이 특정 어노테이션을 들고 있는 것을 인식할 수 있는 것은 자바 언어가 지원하는 기능이 아니고, 스프링이 지원하는 기능이다
- 컴포넌트 스캔의 용도 뿐만이 아니라 어노테이션을 통해 부가 기능 수행
- 마커 인터페이스
- @Controller : 스프링 MVC 컨트롤러로 인식
- @Repository : 스프링 데이터 계층으로 인식, 데이터 계층의 예외를 스프링 예외로 변환
- @Configuration : 스프링 설정 정보 인식, 스프링 빈이 싱글톤을 유지 하도록 추가 처리
- @Service : 특별한 처리 안함, 개발자가 비즈니스 계층을 인식하는데 사용
- 마커 인터페이스
03. 필터
- includeFilters : 컴포넌트 스캔 대상을 추가로 지정
- excludeFilters : 컴포넌트 스캔에서 제외할 대상 지정
03-1. 컴포넌트 스캔 대상에 추가할 어노테이션
package hello.core.scan.filter;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
- MyIncludeComponent 라는 어노테이션 생성
- ComponentScan 대상이 되는 클래스 지정 시 사용
03-2. 컴포넌트 스캔 대상에서 제외할 어노테이션
package hello.core.scan.filter;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
- MyExcludeComponent 라는 어노테이션 생성
- ComponentScan 대상에서 제외 될 클래스 지정 시 사용
03-3. BeanA 생성
package hello.core.scan.filter;
@MyIncludeComponent
public class BeanA {
}
03-4. BeanB 생성
package hello.core.scan.filter;
@MyExcludeComponent
public class BeanB {
}
03-5. 컴포넌트 스캔 관련 테스트 코드 작성
package hello.core.scan.filter;
...
public class ComponentFilterAppConfigTest {
@Test
void test() throws Exception {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
BeanA beanA = ac.getBean("beanA", BeanA.class);
assertThat(beanA).isNotNull();
Assertions.assertThrows(
NoSuchBeanDefinitionException.class,
() -> ac.getBean("beanB", BeanB.class)
);
}
@Configuration
@ComponentScan(
includeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION, classes = MyIncludeComponent.class
),
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION, classes = MyExcludeComponent.class
)
)
static class ComponentFilterAppConfig {
}
}
- AnnotationConfigApplicationContext를 통해 Bean을 받아 오는데 includeFilters, excludeFilters 옵션에 따라서 Bean 스캔 대상 테스트를 진행 하였다
03-6. FilterType 옵션
FilterType은 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
참고 : 최근에는 @Component면 충분하기에 includ, exclude는 자주 사용하지 않는다. 즉, 스프링 기본 설정에 맞춰 개발을 하는 것을 권장함
04. 중복 등록과 충돌
컴포넌트 스캔에서 같은 빈 이름을 등록하면 어떻게 될까? 다음 두 가지 상황이 존재한다.
- 자동 빈 등록 vs 자동 빈 등록
- 수동 빈 등록 vs 자동 빈 등록
04-1. 자동 빈 등록 vs 자동 빈 등록
- 컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록됨
- 이 때 이름이 같은 경우 스프링은 오류를 발생 시킴
- ConflictingBeanDefinitionException @ 예외 발생
04-2. OrderServiceImpl, MemberServiceImpl
@Component("service")
public class OrderServiceImpl implements OrderService {
...
}
@Component("service")
public class MemberServiceImpl implements MemberService {
...
}
- @Component 안에 Bean 이름을 service로 동일하게 지정
- ConflictingBeanDefinitionException 예외가 발생 한다
04-3. 수동 빈 등록 vs 자동 빈 등록
만약 수동 빈 등록과 자동 빈등록에서 빈 이름이 충돌되면 어떻게 될까?
@Component
public class MemoryMemberRepository implements MemberRepository {}
@ComponentScan(
// basePackages = "hello.core.member",
// basePackageClasses = AutoAppConfig.class,
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Configuration.class
)
)
@Configuration
public class AutoAppConfig {
@Bean(name = "memoryMemberRepository")
MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
- 이 경우 수동 빈 등록이 우선권을 가짐
- 수동 빈이 자동 빈을 오버라이딩
- 최근 스프링 부트에서는 수동 빈과 자동 빈의 이름이 충돌나면 자동으로 오류가 발생되도록 수정이 되었다
애매한 경우 적용을 하지 말자, 또한 어설픈 추상화 어설픈 빈 주입 등등… 애매하면 그냥 하지말고 정확하게 이해하고 적용하는게 좋음
'Spring MVC > Spring MVC - 핵심 원리 기본 1탄' 카테고리의 다른 글
[Spring MVC - 핵심 원리 기본] 빈 생명주기 콜백 (0) | 2023.04.25 |
---|---|
[Spring MVC - 핵심 원리 기본] 의존관계 자동 주입 (0) | 2023.04.25 |
[Spring MVC - 핵심 원리 기본] 싱글톤 컨테이너 (1) | 2023.04.25 |
[Spring MVC - 핵심 원리 기본] 스프링 핵심 원리 이해2 - 객체 지향 원리 적용 (0) | 2023.04.25 |
[Spring MVC - 핵심 원리 기본] 스프링 핵심 원리 이해1 - 예제 만들기 (0) | 2023.04.25 |