<aside> ❗
</aside>
<aside> ❗
package hello.core;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
@Configuration
// @Component 애너테이션이 붙은 클래스를 찾아서 자동으로 스프링 빈으로 등록
@ComponentScan(
// @Configuration 내부에 @Component 애너테이션이 들어있다. 그러므로 컴포넌트 스캔시 스프링 빈으로 자동 등록됨
// 컴포넌트 스캔으로 검색하는데 스프링 빈으로 등록하지 않을 것을 설정 (여기서는 @Configuration이 붙은 클래스를 제외)
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
// -> Configuration 애너테이션이 붙은 클래스를 제외하겠다는 필터
)
public class AutoAppConfig {
}
[참고]
[참고]
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;
}
}
package hello.core.scan;
import hello.core.AutoAppConfig;
import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.*;
public class AutoAppConfigTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
@Test
@DisplayName("모든 빈 출력하기")
void findAllBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for(String beanDefinitionName : beanDefinitionNames){
Object bean = ac.getBean(beanDefinitionName);
System.out.println("bean = " + bean);
}
}
@Test
@DisplayName("내가 등록한 빈 출력하기")
void findByMyApplication() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for(String beanDefinitionName : beanDefinitionNames){
Object bean = ac.getBean(beanDefinitionName);
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
System.out.println("bean = " + bean);
}
}
}
@Test
void basicScan() {
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberService.class);
}
}
<aside> ❗
<aside> ❗
탐색할 패지지의 시작 위치 지정
모든 자바 클래스를 다 컴포넌트 스캔하면 시간이 오래걸린다. 그래서 꼭 필요한 위치부터 탐색하도록 시작 위치를 지정할 수 있다.
@Component(
// hello.core 부터 탐색 시작
basePackages = "hello.core",
)
<aside> ❗
@Component 뿐만 아니라 다음과 내용도 추가로 대상에 포함됨
@Component - 컴포넌트 스캔에서 사용
@Controller - 스프링 MVC 컨트롤러에서 사용
@Service - 스프링 비즈니스 로직에서 사용
@Repository - 스프링 데이터 접근 계층에서 사용
@Configuration - 스프링 설정 정보에서 사용
컴포넌트 스캔 뿐만 아니라, 다음 애너테이션이 있으면 스프링은 부가 기능을 수행
애너테이션은 메타 정보, 이 정보를 보고 부가 기능 수행
[참고]
<aside> ❗
includeFilter - 컴포넌트 스캔 대상을 추가로 지정
excludeFilter - 컴포넌트 스캔에서 제외할 대상 지정
// 해당 애너테이션이 붙은 클래스는 스프링 빈으로 등록하기 위해 생성
package hello.core.scan.filter;
import java.lang.annotation.*;
// Target - 어디에 붙는지 지정할 때 사용, Type이라고 하면 클래스에 붙는 것
@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 {
}
// 애너테이션에 따라 스프링 빈이 등록되는지 안 되는지 테스트
package hello.core.scan.filter;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
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 static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.context.annotation.ComponentScan.*;
public class ComponentFilterAppConfigTest {
@Test
void filterScan(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(FilterConfig.class);
assertThat(ac.getBean("beanA", BeanA.class)).isNotNull();
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 FilterConfig {
}
}
ANNOTAION - 기본값, 애너테이션을 인식해서 동작 (생략 가능)
ASSIGNABLE_TYPE - 지정한 타입과 자식 타입을 인식해서 동작 (클래스 직접 선택 가능)
ASPECTJ - AspectJ 패턴 사용 (패턴으로 찾아오는 것)
REGEX - 정규 표현식 (정규 표현식 사용해서 가져오는 것
CUSTOM - TypeFilter 이라는 인터페이스를 구현해서 처리 (조건을 직접 프로그래밍해서 사용)
@Component
includeFilters =
@Filter(FilterType.ANNOTATION, classes = MyIncludeComponent.class)
excludeFilters = {
@Filter(FilterType.ANNOTATION, classes = MyExcludeComponent.class),
@Filter(FilterType.ASSIGNABLE, classes = BeanA.class)
}
[참고]
<aside> ❗
컴포넌트 스캔에서 같은 빈 이름을 등록하면 어떻게 될까 ??
다음 두 가지 상황이 있다.
** 수동 빈 등록시 남는 로그
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`
**
개발에서 명확하지 않은 것은 하면 안 된다. 개발은 혼자하는 것이 아니기 때문에 애매한 상황을 만들지 않는게 좋다. 어설픈 우선순위가 있을 때, 잡기 힘든 버그가 된다. 코드를 조금 더 쓰더라도 명확한 것을 항상 선택하는 것이 베스트이다.
</aside>