Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

[Spring 레퍼런스] 4장 IoC 컨테이너 #10

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.




4.10 클래스패스 스캔과 관리된 컨포넌트

이 챕터의 예제 대부분은 스프링 컨테이너에서 각 BeanDefinition를 만드는 설정메타데이터로 XML을 사용한다. 이전 섹션(Section 4.9, “어노테이션기반의 컨테이너 설정”)에서 소스레벨의 어노테이션으로 어떻게 설정 메타데이터를 사용할 수 있는지 보여주었다. 어노테이션을 사용하는 예제에서도 "base" 빈 정의는 명시적으로 XML파일에 정의하고 어노테이션은 의존성 주입에만 사용한다. 이번 섹션은 클래스패스를 스캔해서 후보 컴포넌트들을 암묵적으로 찾아내는 옵션을 설명한다. 후보 컴포넌트들은 필터 크리테리아와 일치한 클래스들이고 컨테이너에 등록된 빈 정의들 중에 대응되는 것들이다. 이는 빈 등록을 수행하는 XML을 사용하지 않아야 하고 대신 컨테이너에 등록된 빈 정를 갖는 클래스들을 선택하기 위해 어노테이션(예를 들면 @Component)이나 AspectJ 타입의 표현식이나 자신만의 커스텀 필터 크리테리아를 사용할 수 있다.

Note
스프링 3.0에서는 Spring JavaConfig project의 많은 기능들이 스프링 프레임워크 핵심의 일부가 되었다. 이는 전통적인 XML파일 대신 자바로 빈을 정의할 수 있도록 한다. 이러한 새로운 기능들을 어떻게 사용하는가에 대한 예제는 @Configuration, @Bean, @Import, @DependsOn 어노테이션을 봐라.


4.10.1 @Component와 스테레오타입(stereotype) 어노테이션
스프링 2.0 이상의 버전에서 @Repository 어노테이션은 어떤 클래스가 그 역할을 충족시켰거나 레파지토리의 stereotype (또는 데이터 접근계층이나 DAO로 알려진)이라는 표시이다. 이 표시의 사용은 Section 14.2.2, “Exception translation”에서 설명했듯이 예외의 자동 변환이다.

스프링 2.5에 도입된 스테레오타입 어노테이션: @Component, @Service, @Controller. @Component 는 스프링이 관리하는 모든 컴포넌트에 대한 제너릭 스테레오타입이다. @Repository, @Service, @Controller는 더 특정한 유즈케이스에 대한 @Component의 특수한 형태이다. 예를 들어 퍼시스턴스, 서비스, 프리젠테이션 계층에서 각각 사용한다. 그러므로 컴포넌트 클래스에 @Component 어노테이션을 붙힐 수도 있지만 대신 @Repository, @Service, @Controller 어노테이션을 붙힘으로써 클래스들이 도구가 처리하는데 더 적합하도록 할 수 있고 관점에 더 연관성이 있게 한 수 있다. 예를 들어 이러한 스테레오타입 어노테이션은 포인트컷에 대한 이상적인 타겟을 만든다. 또한 @Repository, @Service, @Controller는 스프링 프레임웍의 차기 릴리즈버전에서 추가적인 의미가 생길 가능성도 있다. 그래서 서비스계층에 @Component나 @Service 중에서 어느 것을 사용할기 선택해야 한다면 @Service가 명확하게 더 나은 선택이다. 비슷하게 앞에서 얘기했듯이 @Repository는 퍼시스턴스 계층에서 자동 예외변환에 대한 표시로서 이미 지원된다.

4.10.2 자동 클래스 탐지와 빈 정의 등록
스프링은 자동적으로 스테레오타입의 클래스들을 탐지하고 대응되는 ApplicationContext의 BeanDefinition를 등록한다. 예를 들어 다음 두 클래스는 이러한 자동탐지에 알맞는 클래스들이다.

@Service
public class SimpleMovieLister {

  private MovieFinder movieFinder;

  @Autowired
  public SimpleMovieLister(MovieFinder movieFinder) {
      this.movieFinder = movieFinder;
  }
}


@Repository
public class JpaMovieFinder implements MovieFinder {
  // 명확하도록 구현은 생략한다
}

이러한 클래스들을 자동탐지하고 대응하는 빈을 등록하려면 XML에 다음 요소를 포함시켜야 한다. base-package 요소는 두 클래스에 대한 공통 부모 팩키지이다. (대신 각 클래스의 부모 팩키지를 포함하는 리스트를 콤마로 분리해서 지정할 수도 있다.)



<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:component-scan base-package="org.example"/>

</beans>

Note
클래스패스 패키지의 스캔은 클래스패스에서 대응되는 디렉토리 진입점이 있어야 한다. Ant로 JAR를 빌드할 때 JAR 태스크의 files-only 스위치가 켜져있지(activate) 않아야 한다.

게다가 component-scan 요소를 사용하면 AutowiredAnnotationBeanPostProcessor와 CommonAnnotationBeanPostProcessor는 둥다 암묵적으로 포함된다. 이는 두 컴포넌트가 자동으로 탐지되고 함께 연결된다. - 모두 XML에서 제공된 어떤 빈 설정 메타데이터는 없다.

Note
annotation-config 속성의 값을 false로 하면 AutowiredAnnotationBeanPostProcessor 와 CommonAnnotationBeanPostProcessor가 등록되지 않게 할 수 있다.


4.10.3 스캐닝을 커스터마이징하기 위한 필요 사용
기본적으로 @Component, @Repository, @Service, @Controller 어노테이션들이나 @Component 어노테이션이 붙은 커스텀 어노테이션이 붙은 클래스들만 후보 컴포넌트로 탐지된다. 하지만 커스텀 필터를 적용해서 간단하게 이러한 행동을 수정하거나 확장할 수 있다. component-scan 요소의 하위요소로 include-filter나 exclude-filter를 추가한다. 각 필터 요소들은 type와 expression속성을 필요로 한다. 다음 표는 필터링 옵션을 설명한다.

Table 4.5. Filter Types
필터 타입표현식 예시설명
annotationorg.example.SomeAnnotation타겟 컴포넌트에서 타입 레벨에서 표현되는 어노테이션
assignableorg.example.SomeClass타겟 컴포넌트들을 할달할 수 있는 (확장(extend)/구현(implement)) 클래스(또는 인터페이스)
aspectjorg.example..*Service+타겟 컴포넌트들과 일치되는 AspectJ 타입 표현식
regexorg\.example\.Default.*타겟 컴포넌트 클래스명과 일치되는 정규 표현식
customorg.example.MyTypeFilterorg.springframework.core.type .TypeFilter 인터페이스를 구현한 커스텀 구현체











다음 예제는 모든 @Repository 어노테이션을 무시하고 "stub" 레파지토리를 대신 사용하는 XML 설정을 보여준다.



<beans>

  <context:component-scan base-package="org.example">
     <context:include-filter type="regex" expression=".*Stub.*Repository"/>
     <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
  </context:component-scan>


</beans>

Note
<component-scan/> 요소의 속성으로 use-default-filters="false"를 지정해서 기본 필터를 사용안하도록 할 수도 있다. 이는 @Component, @Repository, @Service, @Controller 어노테이션이 붙은 클래스들을 자동 탐지하는 기능을 사용하지 않게 한다.


4.10.4 컴포넌트내에서 빈 메타데이터 정의
스프링 컴포넌트들은 컨테이너에 빈 정의 메타데이터를 제공할 수도 있다. 이는 @Configuration 어노테이션이 붙은 클래스들 내에서 빈 메타데이터를 정의하기 위해서 동일한 @Bean 어노테이션을 사용해서 할 수 있다. 다음은 간단한 예제이다.


@Component
public class FactoryMethodComponent {

  @Bean @Qualifier("public")
  public TestBean publicInstance() {
    return new TestBean("publicInstance");
  }

  public void doWork() {
    // 컴포넌트 메서드 구현체는 생략한다
  }
}

이 클래스는 내부에 doWork() 메서드가 있는 어플리케이션에 특화된 코드가 있는 스프링 컴포넌트다. 하지만 publicInstance() 메서드를 참조하는 팩토리 메서드가 있는 빈 정의도 제공한다. @Bean 어노테이션은 팩토리 매서드와 @Qualifier 어노테이션을 통한 제한자의 값같은 다른 빈 정의 프로퍼티들을 식별한다. 다른 메서드 레벨의 어노테이션들은 @Scope, @Lazy와 커스텀 qualifier 어노테이션으로 지정할 수 있다. 앞에서 얘기했듯이 추가적인 @Bean 메서드의 자동연결 지원과 함께 필드와 메서드들의 자동연결을 지원한다.


@Component
public class FactoryMethodComponent {

  private static int i;

  @Bean @Qualifier("public")
  public TestBean publicInstance() {
    return new TestBean("publicInstance");
  }

  // 커스텀 qualifier와 메서드 파라미터의 자동연결의 사용

  @Bean
  protected TestBean protectedInstance(@Qualifier("public") TestBean spouse, @Value("#{privateInstance.age}") String country) {
    TestBean tb = new TestBean("protectedInstance", 1);
    tb.setSpouse(tb);
    tb.setCountry(country);
    return tb;
  }

  @Bean @Scope(BeanDefinition.SCOPE_SINGLETON)
  private TestBean privateInstance() {
    return new TestBean("privateInstance", i++);
  }

  @Bean @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
  public TestBean requestScopedInstance() {
    return new TestBean("requestScopedInstance", 3);
  }
}

예제는 String 메서드 파라미터 country를 또 다른 빈 privateInstance의 Age 프로퍼티의 값으로 자동연결한다. 스프링 표현언어(Spring Expression Language)요소는 #{ <expression> } 문법으로 프로퍼티의 값을 정의한다. @Value 어노테이션에 대한 표현식 리졸버는 표현식 문자를 처리할 때 빈 이름을 찾으려머 먼저 설정된다.

스프링 컴포넌트의 @Bean 메서드들은 스프링 @Configuration 클래스 내부의 @Bean 메서드들과는 다르게 수행된다. @Component 클래스들이 메서드와 필드의 호출을 가로채려고 CGLIB로 향상(enhanced)되지 않는다는 것이 차이점이다. CGLIB 프록싱은 @Configuration 클래스의 @Bean 메서드내에서 메서드와 필드를 호출하는 것은 협력객체를 참조하는 빈 메타데이터를 생성한다. 메서드들은 일반적인 자바 시맨틱으로 호출되지 않는다. 반면에 @Component 클래스의 @Bean 메서드내에서 메서드나 필드를 호출하는 것은 표준 자바 시맨틱을 갖는다.

4.10.5 이름으로 자동탐지되는 컴포넌트
스캐닝 과정 중에 컴포넌트를 자동탐지했을 때 그 빈의 이름은 해당 스캐너가 알고 있는 BeanNameGenerator 전략에 의해 생성된다. 기본적으로 name 값이 있는 어떤 스프링 스테레오타입 어노테이션 (@Component, @Repository, @Service, @Controller)도 name 값에 따라서 대응되는 빈 정의에 이름을 제공할 것이다.

어노테이션이 name 값이 없거나 탐지된 다른 컴포넌트(커스텀 필터로 발견된 컴포넌트 같은)가 있다면 기본 빈이름 생성기는 대문자로 쓰지 않고 정규화되지 않은 클래스명을 리턴한다. 예를 들어 다음 두 컴포넌트가 담지되었다면 이름은 myMovieLister와 movieFinderImpl가 될 것이다.


@Service("myMovieLister")
public class SimpleMovieLister {
  // ...
}


@Repository
public class MovieFinderImpl implements MovieFinder {
  // ...
}

Note
기본 빈이름 전력에 의존하고 싶지 않다면 커스텀 빈이름 전략을 제공할 수 있다. 일단 BeanNameGenerator 인터페이스를 구현하고 아규먼트가 없는 기본 생성자를 만든다. 그 다음 스캐너를 설정할 때 정규화된 클래스명을 제공해라.



<beans>

  <context:component-scan base-package="org.example" name-generator="org.example.MyNameGenerator" />

</beans>

일반적인 규칙에 따라 다른 컴포넌트들을 명시적으로 참조하도록 만들 때마다 어노테이션에 이름을 지정하는 것을 고려해라. 반면 컨테이너가 연결에 대한 책임이 있는 경우에는 자동생성되는 이름으로 충분하다.

4.10.6 자동탐지된 컴포넌트에 범위(scope) 제공하기
보통의 스프링이 관리하는 컴포넌트처럼 자동탐지된 컴포넌트의 기본 범위와 가장 일반적인 범위는 싱글톤이다. 하지만 때로는 다른 범위가 필요하고 스프링 2.5가 제공하는 새로운 @Scope 어노테이션을 제공한다. 어노테이션에 범위의 이름을 지정하면 된다.


@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
  // ...
}

Note
어노테이션에 기반한 접근대신에 커스텀 범위 처리 전략을 제공하려면 ScopeMetadataResolver 인터페이스를 구현하고 아규먼트가 없는 기본 생성자를 만들어라. 그 다음 스캐너를 설정할 때 정규화된 클래스명을 제공해라.



<beans>

  <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver" />

</beans>

싱글톤이 아닌 어떤 범위를 사용하면 범위를 가진 객체에 대한 프록시를 생성할 필요가 있을 수 있다. 프록시가 필요한 이유는 Section 4.5.4.5, “의존성에서 범위를 가진 빈”에서 설명했다. 이 목적을 위해 component-scan 요소에서 scoped-proxy 속성을 사용할 수 있다. 이 속성에는 no, interfaces, targetClass 세 가지 값을 지정할 수 있다. 예를 들어 다음 설정은 표준 JDK 다이나믹 프록시의가 될 것이다.



<beans>

  <context:component-scan base-package="org.example" scoped-proxy="interfaces" />

</beans>

4.10.7 어노테이션으로 qualifier 메타데이터 제공하기
@Qualifier 어노테이션은 Section 4.9.3, “qualifiers를 사용한 어노테이션 기반 자동연결의 미세조정”에서 이야기 했다. 해당 섹션의 예제들은 자동연결 후보를 처리할 때 세밀한 제어를 제공하는 @Qualifier 어노테이션과 커스텀 qualifier 어노테이션의 사용방법을 보여준다. 이 예제들은 XML 빈 정의에 기반하기 때문에 qualifier 메타데이터는 XML의 bean 요소의 하위요소로 qualifier나 meta 사용하는 후보 빈 정의에서 제공된다. 컴포넌트 자동탐지를 클래스패스에 기반해서 스캔할 때는 후보 클래스의 타입레벨 어노테이션으로 qualifier 메타데이터를 제공한다. 다음 세 가지 예제는 이러한 기법을 보여준다.


@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
  // ...
}


@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
  // ...
}


@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
  // ...
}

Note
어노테이션에 기반한 대부분의 대체방법들 처럼 어노테이션 메타데이터는 클래스 정의 자체와 밀접한 관계가 된다는 것을 기억해야 한다. 반면 XML을 사용하면 해당 메타데이터가 클래스마다가 아니라 인스턴스마다 제공되기 때문에 qualifier 메타데이터에서 다양함을 제공하기 위해 같은 타입의 여러 가지 빈을 사용할 수 있다.


2012/04/26 01:00 2012/04/26 01:00