Spring/스프링5 프로그래밍 입문

Chapter5. 컴포넌트 스캔

지구우중 2023. 2. 21. 13:03
📌 자동 주입과 함께 사용하는 추가 기능이 컴포넌트 스캔이다. 컴포넌트 스캔은 스프링이 직접 클래스를 검색해서 빈으로 등록해주는 기능이다. 설정 클래스에 빈으로 등록하지 않아도 원하는 클래스를 빈으로 등록할 수 있으므로 컴포넌트 스캔 기능을 사용하면 설정 코드가 크게 줄어든다.

@Component 애노테이션으로 스캔 대상 지정

@Component
@Component("infoPrinter")

@Component 애노테이션에 값을 주었는지에 따라 빈으로 등록할 때 사용할 이름이 결정된다.

 

값을 주지 않을 때에는?

@Component 애노테이션이 붙은 클래스 이름을 따와 빈 이름으로 사용한다. 이때 첫 글자를 소문자로 바꾼 이름을 사용한다. ex) MainClass → mainClass

 

@ComponentScan 애노테이션으로 스캔 설정

@ComponentScan(basePackages = {"spring"}) // 패키지에 속한 클래스를 스캔 대상으로 설정
//component애노테이션이 붙은 클래스의 객체를 생성해서 빈으로 등록
@ComponentScan(basePackages = {"org.example.chap05.spring"}) // 패키지 풀네임을 적어야함

@Component 애노테이션을 붙인 클래스를 스캔해서 스프링 빈으로 등록하려면 설정 클래스에 @ComponentScan 애노테이션을 적용해야 한다.

스프링 컨테이너가 @Component 애노테이션을 붙인 클래스를 검색 해서 빈으로 등록 해주기 때문에 설정 코드가 줄어들 수 있다.

 

스캔 대상에서 제외하거나 포함하기

excludeFilters 속성을 사용하면 스캔할 때 특정 대상을 자동 등록 대상에서 제외할 수 있다.

 

정규식(FilterType.REGEX)

@ComponentScan(basePackages = {"spring" }, 
excludeFilters = @Filter(type = FilteType.REGEX, pattern = "spring\\\\..*Dao"))
// spring. 으로 시작하고 Dao로 끝나는 클래스를 스캔대상에서 제외한다.

정규표현식을 사용해서 제외 대상을 지정한다는 것을 의미한다.

 

AspectJ 패턴(FilterType.ASPECTJ)

@ComponentScan(basePackages = {"spring" }, 
excludeFilters = @Filter(type = FilterType.ASPECTJ, pattern = "spring.*Dao"))
// spring 패키지 에서 이름이 Dao로 끝나는 타입을 컴포넌트 스캔 대상에서 제외한다.

"spring.*Dao" AspectJ 패턴은 spring 패키지의 Dao로 끝나는 타입을 지정한다는 정도로만 알고 넘어가자.

AspectJ 패턴이 동작하려면 의존 대상에 aspectjweaver 모듈을 추가해야 한다.

 

특정 애노테이션을 붙인 타입(FilterType.ANNOTATION)

@Retention(RUNTIME) 
@Target(TYPE) 
public @interface NoProduct { 
} 
@Retention (RUNTIME) 
@Target(TYPE) 
public @interface ManualBean { 
}
@ComponentScan(basePackages = {"spring", "spring2"}, 
excludeFilters = @Filter(type = FilterType.ANNOTATION, 
classes = {NoProduct.class, ManualBean.class } ))
// @NoProduct, @ManualBean 애노테이션이 붙은 클래스를 컴포넌트 스캔 대상에서 제외한다.
@ManualBean 
@Component 
public class MemberDao { 
}

클래스 기준(FilterType.ASSIGNABLE_TYPE)

@ComponentScan(basePackages = { 11spring11 }, 
excludeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, 
classes = MemberDao.class ))

classes에 할당할 수 있는 클래스, 즉 상속이나 구현한 클래스까지 포함한다.

 

필터가 2개 이상일 때

@ComponentScan의 excludeFilters 속성에 배열을 사용해서 @Filter 목록을 전달하면 된다.

@ComponentScan(basePackages = {"spring"}, 
excludeFilters = { 
@Filter(type = FilterType.ANNOTATION, classes = ManualBean.class ), 
@Filter(type = FilterType.REGEX, pattern = "spring2\\\\ ..*") 
})

기본 스캔 대상

@Component 애노테이션을 붙인 클래스만 컴포넌트 스캔 대상에 포함되는 것은 아니다. 다음 애노테이션을 붙인 클래스가 컴포넌트 스캔 대상에 포함된다.

  • @Component(org . springlramework . stereotype 패키지)
  • @Controller(org . springlramework . stereotype 패키지)
  • @Service(org . springlramework . stereotype 패키지)
  • @Repository(org . springlramework . stereotype 패키지)
  • @Aspect(org . aspectj .lang . annotation 패키지)
  • @Configuration(org . springframework . context . annotation 패키지)

@Aspect 애노테이션을 제외한 나머지 애노테이션은 실제로는 @Component 애노테이션에 대한 특수 애노테이션이다.(지금은 가독성에 따른다고 생각하자)

 

컴포넌트 스캔에 따른 충돌 처리

빈 이름 충돌

각각 다른 패키지에 같은 이름의 빈이 등록되어 있다. 이는 빈 충돌을 발생 시킨다.

ConflictingBeanDefinitionException

서로 다른 타입인데 같은 빈 이름을 사용하는 경우가 있다면 둘 중 하나에 명시적으로 빈 이름을 지정해서 이름 충돌을 피해야 한다.

수동 등록한 빈과 충돌

@Component 
public class MemberDao { 
}

@Configuration 
@ComponentScan(basePackages = {"spring"}) 
public class AppCtx { 
@Bean 
	public MemberDao memberDaoO { 
		MemberDao memberDao = new MemberDao() ; 
		return memberDao: 
	}

스캔할 때 사용하는 빈 이름과 수동 등록한 빈 이름이 같은 경우 수동 등록한 빈이 우선한다. 즉 MemberDao 타입 반은 AppCtx에서 정의한 한 개만 존재한다.

@Configuration 
@ComponentScan(basePackages = {"spring"}) 
public class AppCtx { 
	@Bean 
	public MemberDao memberDao2() { 
	}

이 경우 스캔을 통해 등록한 "memberDao" 빈과 수동 등록한 "memberDao2" 빈이 모두 존재한다. MemberDao 타입의 빈이 두 개가 생성되므로 자동 주입하는 코드는 @Qualifier 애노테이션을 사용해서 알맞은 빈을 선택해야 한다.