
만약, 등록해야할 스프링 빈이 수백개라면 설정정보도 커지고 누락하는 문제도 발생한다. 컴포넌트 스캔과 의존관계 자동 주입으로 좀 더 편하게 설정정보를 등록해보자!
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))
// 컴포넌트스캔 괄호안에 내용은 미리 만들어 둔 AppConfig파일을
// 컴포넌트 스캔 목록에서 제외시키는 것
public class AutoAppConfig {
}
@ComponentScan은 @Component 애너테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록해주고, @Autowired 애너테이션은 의존관계를 자동으로 주입해준다. 파일을 수정해보자
@Component // 애너테이션 등록
public class OrderServiceImpl implements OrderService{
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired // 애너테이션 등록
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Component
public class MemberServiceImpl implements MemberService{
private MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Component
public class RateDiscountPolicy implements DiscountPolicy{
private int discountRate = 10;
@Component
public class MemoryMemberRepository implements MemberRepository{
package hello.core;
import hello.core.member.MemberService;
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;
class AutoAppConfigTest {
@Test
@DisplayName("컴포넌트 스캔 확인하기")
void basicSacn(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService bean = ac.getBean(MemberService.class);
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for(String beans : beanDefinitionNames){
BeanDefinition beanDefinition = ac.getBeanDefinition(beans);
if(beanDefinition.getRole()==BeanDefinition.ROLE_APPLICATION){
System.out.println("beanDefinitionNames : " + beans + ", beanDefinition : " + beanDefinition);
}
}
}
}
beanDefinitionNames : autoAppConfig, beanDefinition : Generic bean: class [hello.core.AutoAppConfig$$SpringCGLIB$$0]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null
beanDefinitionNames : rateDiscountPolicy, beanDefinition : Generic bean: class [hello.core.Discount.RateDiscountPolicy]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in file [C:\Users\Dani\Downloads\core\out\production\classes\hello\core\Discount\RateDiscountPolicy.class]
beanDefinitionNames : memberServiceImpl, beanDefinition : Generic bean: class [hello.core.member.MemberServiceImpl]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in file [C:\Users\Dani\Downloads\core\out\production\classes\hello\core\member\MemberServiceImpl.class]
beanDefinitionNames : memoryMemberRepository, beanDefinition : Generic bean: class [hello.core.member.MemoryMemberRepository]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in file [C:\Users\Dani\Downloads\core\out\production\classes\hello\core\member\MemoryMemberRepository.class]
beanDefinitionNames : orderServiceImpl, beanDefinition : Generic bean: class [hello.core.Order.OrderServiceImpl]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in file [C:\Users\Dani\Downloads\core\out\production\classes\hello\core\Order\OrderServiceImpl.class]
beanDefinition의 메타정보를 확인하여 빈이 잘 생성되었음을 확인할 수 있다.
@Component("MemberService Auto") // 이름 지정
public class MemberServiceImpl implements MemberService{
private MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
beanDefinitionNames : autoAppConfig, beanDefinition : Generic bean: class [hello.core.AutoAppConfig$$SpringCGLIB$$0]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null
beanDefinitionNames : rateDiscountPolicy, beanDefinition : Generic bean: class [hello.core.Discount.RateDiscountPolicy]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in file [C:\Users\Dani\Downloads\core\out\production\classes\hello\core\Discount\RateDiscountPolicy.class]
beanDefinitionNames : MemberService Auto, beanDefinition : Generic bean: class [hello.core.member.MemberServiceImpl]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in file [C:\Users\Dani\Downloads\core\out\production\classes\hello\core\member\MemberServiceImpl.class]
beanDefinitionNames : memoryMemberRepository, beanDefinition : Generic bean: class [hello.core.member.MemoryMemberRepository]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in file [C:\Users\Dani\Downloads\core\out\production\classes\hello\core\member\MemoryMemberRepository.class]
beanDefinitionNames : orderServiceImpl, beanDefinition : Generic bean: class [hello.core.Order.OrderServiceImpl]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in file [C:\Users\Dani\Downloads\core\out\production\classes\hello\core\Order\OrderServiceImpl.class]
MemberService Auto로 빈이름이 생성되었다!


컴포넌트 스캔은 @Component 애너테이션이 붙은 클래스들을 자동으로 스프링 빈으로 등록해준다. 그렇다면 모든 클래스 파일들을 다 스캔하는 것일까?
@Configuration
@ComponentScan(
basePackages = "hello.core", // 지정한 패키지를 포함해서 하위 패키지 모두 탐색, {} 으로 여러 시작위치 지정 가능
// basePackageClasses 시작 클래스 위치 설정
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class))
public class AutoAppConfig {
}
위와 같이
basePackages,basePackageClasses로 스캔 시작 위치를 지정할 수 있다.
includeFilters : 컴포넌트 스캔 대상을 추가로 지정한다.excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다.다음의 파일들을 새로 생성하자
package hello.core.scan;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExCludeComponent {
}
package hello.core.scan;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyInCludeComponent {
}
Configuration API
@Component를 제외한 나머지 애너테이션을 추가한다.
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { boolean proxyBeanMethods() default true; boolean enforceUniqueMethods() default true; }
package hello.core.scan.filter;
import hello.core.scan.MyInCludeComponent;
@MyInCludeComponent
public class BeanA {
}
package hello.core.scan.filter;
import hello.core.scan.MyExCludeComponent;
@MyExCludeComponent
public class BeanB {
}
package hello.core;
import hello.core.scan.MyExCludeComponent;
import hello.core.scan.MyInCludeComponent;
import hello.core.scan.filter.BeanA;
import hello.core.scan.filter.BeanB;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
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;
public class filterTest {
@Test
@DisplayName("필터 테스트")
void filter(){
ApplicationContext ac = new AnnotationConfigApplicationContext(ComponenetFilterAppConfig.class);
BeanA beanA = ac.getBean("beanA", BeanA.class);
Assertions.assertThat(beanA).isNotNull();
org.junit.jupiter.api.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 ComponenetFilterAppConfig{
}
}
MyExclude에 등록된BeanB는 등록되지 않았고,MyInclude에 등록된BeanA는 등록되었다.
ANNOTATION : 기본값, 애너테이션을 인식해서 동작ASSIGNABLE_TYPE : 지정한 타입과 자식 타입을 인식해서 동작ASPECTJ : AspectJ 패턴 사용REGEX : 정규 표현식CUSTOM : TypeFilter라는 인터페이스를 구현해서 처리package hello.core;
import hello.core.Order.Order;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
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(
basePackages = "hello.core", // 지정한 패키지를 포함해서 하위 패키지 모두 탐색, {} 으로 여러 시작위치 지정 가능
// basePackageClasses 시작 클래스 위치 설정
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class))
public class AutoAppConfig {
@Bean(name="memoryMemberRepository")
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
}
beanDefinitionNames : autoAppConfig, beanDefinition : Generic bean: class [hello.core.AutoAppConfig$$SpringCGLIB$$0]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null
beanDefinitionNames : rateDiscountPolicy, beanDefinition : Generic bean: class [hello.core.Discount.RateDiscountPolicy]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in file [C:\Users\Dani\Downloads\core\out\production\classes\hello\core\Discount\RateDiscountPolicy.class]
beanDefinitionNames : MemberService Auto, beanDefinition : Generic bean: class [hello.core.member.MemberServiceImpl]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in file [C:\Users\Dani\Downloads\core\out\production\classes\hello\core\member\MemberServiceImpl.class]
beanDefinitionNames : memoryMemberRepository, beanDefinition : Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=autoAppConfig; factoryMethodName=memberRepository; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in hello.core.AutoAppConfig
beanDefinitionNames : orderServiceImpl, beanDefinition : Generic bean: class [hello.core.Order.OrderServiceImpl]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in file [C:\Users\Dani\Downloads\core\out\production\classes\hello\core\Order\OrderServiceImpl.class]
memoryMemberRepository가 수동 빈 등록으로 인해 재정의 된다.
CoreApplication을 실행해보자!package hello.core;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CoreApplication {
public static void main(String[] args) {
SpringApplication.run(CoreApplication.class, args);
}
}
Description:
The bean 'memoryMemberRepository', defined in class path resource [hello/core/AutoAppConfig.class], could not be registered. A bean with that name has already been defined in file [C:\Users\Dani\Downloads\core\out\production\classes\hello\core\member\MemoryMemberRepository.class] and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
수동 빈 등록과 자동 빈 등록이 충돌하면 오류가 발생하도록 기본값을 바꾸었기 때문에 스프링부트에서는 오류가 발생함을 알 수 있다.