SeamFramework.orgCommunity Documentation

4장. 의존성 삽입

4.1. 바인딩 어노테이션
4.1.1. 멤버와 바인딩 어노테이션
4.1.2. 바인딩 어노테이션의 조합
4.1.3. 바인딩 어노테이션 및 생산자 방식
4.1.4. 기본값 바인딩 유형
4.2. 배치 유형
4.2.1. 배치 유형 활성화
4.2.2. 배치 유형 우선 순위
4.2.3. 배치 유형 예시
4.3. 불만족스러운 의존성 수정
4.4. 클라이언트 프록시
4.5. 프로그램적 lookup에 의해 Web Bean 획득
4.6. 수명 주기 콜백, @Resource, @EJB@PersistenceContext
4.7. InjectionPoint 객체

Web Beans는 의존성 삽입에 대한 세 가지 주요 메카니즘을 지원합니다:

생성자 매개 변수 삽입:

public class Checkout {

        
    private final ShoppingCart cart;
    
    @Initializer
    public Checkout(ShoppingCart cart) {
        this.cart = cart;
    }
}

초기화 방식 매개 변수 삽입:

public class Checkout {

        
    private ShoppingCart cart;
    @Initializer 
    void setShoppingCart(ShoppingCart cart) {
        this.cart = cart;
    }
    
}

직접적 영역 삽입:

public class Checkout {


    private @Current ShoppingCart cart;
    
}

Web Bean 인스턴스가 먼저 인스턴스화되었을 경우 의존성 삽입이 발생합니다.

EJB가 Web Bean 관리자가 아닌 EJB 컨테이너에 의해 인스턴스화된 이래 생성자 매개변수 삽입은 EJB beans을 지원하지 않습니다.

기본값 바인딩 유형 @Current가 적용되었을 경우 생성자의 매개 변수 및 초기화 방식에서는 명시적으로 어노테이션할 필요가 없습니다. 하지만 삽입된 영역에는 반드시 바인딩 유형을 지정해야 하며, 언제 기본값 바인딩 유형을 적용할 지도 지정해야 합니다. 영역에 바인딩 유형이 지정되어 있지 않을 경우, 이는 삽입되지 않게 됩니다.

생산자 방식은 매개 변수 삽입을 지원합니다:

@Produces Checkout createCheckout(ShoppingCart cart) {

    return new Checkout(cart);
}

마지막으로 옵저버 방식 (9장. 이벤트 에서 살펴보게됨), 폐지 방식 및 소멸자 방식 모두는 매개 변수 삽입을 지원합니다.

Web Beans 사양은 타입 안정적 해상도 알고리즘이라는 절차를 정의하여, Web Bean 관리자가 삽입 지점에 삽입하기 위해 Web Bean을 인식할 때 이를 따르게 됩니다. 이러한 알고르짐은 처음에는 복잡하게 보이지만, 일단 이를 이해하면, 이는 다소 직관적입니다. 타입 안정적 해상도는 시스템 초기화시에 실행되어, 관리자는 사용자에 과한 정보를 즉각적으로 알게되며 Web Bean의 의존성에 만족하지 않을 경우, UnsatisfiedDependencyException 또는 AmbiguousDependencyException을 넘기게 됩니다.

이러한 알고리즘은 여러 Web Beans를 허용하여 동일한 API 유형을 구현하기 위한 것입니다:

Web Beans 관리자가 어떻게 Web Bean을 삽입할 지를 결정하는 방법에 대해 살펴봅시다.

특정 API 유형을 구현하는 하나 이상의 Web Bean이 있을 경우, 삽입 지점은 바인딩 어노테이션을 사용하여 어떤 Web Bean이 삽입되어야 할 지를 지정할 수 있습니다. 예를 들어, PaymentProcessor의 두 가지 구현이 있을 수 있습니다:

@PayByCheque

public class ChequePaymentProcessor implements PaymentProcessor {
    public void process(Payment payment) { ... }
}
@PayByCreditCard

public class CreditCardPaymentProcessor implements PaymentProcessor {
    public void process(Payment payment) { ... }
}

여기서 @PayByCheque@PayByCreditCard는 바인딩 어노테이션입니다:

@Retention(RUNTIME)

@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayByCheque {}
@Retention(RUNTIME)

@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayByCreditCard {}

클라이언트 Web Bean 개발자는 바인딩 어노테이션을 사용하여 정확히 어떤 Web Bean이 삽입되어야 하는 지를 지정합니다.

영역 삽입 사용:

@PayByCheque PaymentProcessor chequePaymentProcessor;

@PayByCreditCard PaymentProcessor creditCardPaymentProcessor;

초기화 방식 삽입 사용:

@Initializer

public void setPaymentProcessors(@PayByCheque PaymentProcessor chequePaymentProcessor, 
                                 @PayByCreditCard PaymentProcessor creditCardPaymentProcessor) {
   this.chequePaymentProcessor = chequePaymentProcessor;
   this.creditCardPaymentProcessor = creditCardPaymentProcessor;
}

생성자 삽입 사용:

@Initializer

public Checkout(@PayByCheque PaymentProcessor chequePaymentProcessor, 
                @PayByCreditCard PaymentProcessor creditCardPaymentProcessor) {
   this.chequePaymentProcessor = chequePaymentProcessor;
   this.creditCardPaymentProcessor = creditCardPaymentProcessor;
}

모든 Web Beans는 배치 유형을 갖습니다. 각각의 배치 유형은 시스템의 일부 배치에 조건부로 설치되어야 하는 Web Beans 모음을 인식합니다.

예를 들어, @Mock라는 배치 유형을 정의할 수 있으며, 이는 시스템이 통합적인 테스트 환경 내에서 실행될 때 설치되어야 하는 Web Beans를 인식합니다:

@Retention(RUNTIME)

  @Target({TYPE, METHOD})
  @DeploymentType
  public @interface Mock {}

지불 절차를 처리하기 위한 외부적 시스템과 상호 작용하는 일부 Web Bean이 있다고 가정합시다:

public class ExternalPaymentProcessor {

        
    public void process(Payment p) {
        ...
    }
    
}

이러한 Web Bean이 배치 유형을 명시적을 지정하지 않고 있으므로, 이는 기본값 배치 유형 @Production을 갖게 됩니다.

통합 또는 단위 테스트를 위해 외부적 시스템은 속도가 느리거나 또는 사용 불가능합니다. 따라서 모의 객체를 생성할 수 있습니다:

@Mock 

public class MockPaymentProcessor implements PaymentProcessor {
    @Override
    public void process(Payment p) {
        p.setSuccessful(true);
    }
}

하지만 Web Bean 관리자가 특정 배치에 어떤 구현을 사용할 지를 어떻게 결정할까요?

관심을 기울이셨다면, Web Bean 관리자가 어떤 구현 — ExternalPaymentProcessor 또는 MockPaymentProcessor — 을 선택할 지를 어떻게 결정하는지에 대해 궁금해 하셨을 것입니다. 관리자기 이러한 삽입 지점에 이르렀을 때 무엇이 발생할 지를 고려합니다:

@Current PaymentProcessor paymentProcessor

PaymentProcessor 계약을 만족하는 두 개의 Web Beans가 있습니다. 삽입 지점에서 바인딩 어노테이션은 소스로 하드-코드화되어 있으므로, 명확하게 하기 위해 바인딩 어노테이션을 사용할 수 없으며, 관리자가 배치시 결정할 수 있게 하기를 원합니다!

이러한 문제의 해결책은 각각의 배치 유형이 다른 우선 순위를 갖게 하는 것입니다. 배치 유형의 우선 순위는 web-beans.xml에 나타나는 순서에 의해 결정됩니다. 예에서 @Mock@Production 보다 나중에 나타나므로 보다 높은 우선 순위를 갖습니다.

하나 이상의 Web Bean이 삽입 지점에 의해 지정된 계약 (API 유형 및 바인딩 어노테이션)에 만족할 수 있음을 관리자가 발견할 때 마다, 이는 Web Beans의 관련된 우선 순위를 고려합니다. 하나의 Web Bean이 다른 것 보다 높은 우선 순위를 갖을 경우, 삽입될 보다 높은 우선 순위의 Web Bean을 선택합니다. 예에서, 통합 테스트 환경 (실제적으로 원하는 환경)에서 실행할 때 Web Bean 관리자는 MockPaymentProcessor를 삽입하게 됩니다.

이러한 기능을 오늘날의 관리자 구조와 비교하는 것은 흥미로운 일입니다. 다양한 "경량"의 컨테이너는 classpath에 있는 클래스의 조건부 배치를 허용하지만, 배치될 클래스는 명시적, 개별적이어하며, 설정 코드나 일부 XML 설정 파일에 나열되어 있어야 합니다. Web Beans는 XML을 통한 Web Bean 정의 및 설정을 지원하지만 복잡한 설정을 필요로 하지 않는 대부분의 경우, 배치 유형은 전체 Web Beans 설정이 단일 XML 행으로 활성화되게 합니다. 동시에, 코드를 검색하는 개발자는 어떤 배치 시나리오를 Web Bean이 사용할 지를 쉽게 인식할 수 있습니다.

바인딩 어노테이션 및 삽입 지점의 API 유형을 구현하는 모든 Web Beans의 배치 유형을 고려한 후, Web Bean 관리자는 삽입할 하나의 Web Bean을 인식하지 못할 경우 타입 안정적 해상도 알고리즘은 실패하게 됩니다.

UnsatisfiedDependencyException 또는 AmbiguousDependencyException을 수정하기가 쉽습니다.

UnsatisfiedDependencyException을 수정하려면, API 유형을 구현하는 Web Bean을 제공하고 삽입 지점의 바인딩 유형을 갖거나 — 또는 API 유형을 이미 구현하고 있는 Web Bean의 배치 유형을 활성화하고 바인딩 유형을 갖습니다.

AmbiguousDependencyException을 수정하려면, 두 개의 API 유형 구현 사이에서 구별하기 위해 바인딩 유형을 소개하거나 구현 중 하나의 배치 유형을 변경하여 Web Bean 관리자가 이들 사이에서 선택하기 위해 배치 유형 우선 순위를 사용할 수 있습니다. AmbiguousDependencyException은 두 개의 Web Beans가 바인딩 유형을 공유하여 동일한 배치 유형을 갖고 있을 때에만 발생할 수 있습니다.

Web Beans에서 의존성 삽입을 사용할 때 유의하셔야 할 사항이 한 가지 더 있습니다.

삽입된 Web Bean 클라이언트는 Web Bean 인스턴스에 직접적인 참조를 유지하지 않습니다.

요청 범위로 바운딩된 Web Bean으로의 직접 참조를 보유하고 있는 애플리케이션 범위에 바운딩된 Web Bean을 가정해 봅시다. 애플리케이션 범위에 바운딩된 Web Bean은 다른 많은 요청 사이에서 공유됩니다. 하지만, 각각의 요청은 요청 범위로 바운딩된 Web Bean의 다른 인스턴스를 확인해야 합니다!

이제 애플리케이션 범위로 바운딩된 Web Bean으로의 직접 참조를 보유하고 있는 세션 범위에 바운딩된 Web Bean을 가정해 봅시다. 때때로 세션 컨텍스트는 메모리를 보다 효과적으로 사용하기 위해 디스크 순서대로 나열됩니다. 하지만, 애플리케이션 범위로 바운딩된 Web Bean 인스턴스는 세션 범위로 바운딩된 Web Bean과 함께 나열되어서는 안됩니다!

따라서, Web Bean은 기본값 범위 @Dependent를 갖지 않는 한, Web Bean 관리자는 프록시 객체를 통해 삽입된 모든 참조를 Web Bean으로 돌려 놓아야 합니다. 이러한 클라이언트 프록시는 방식 호출을 받는 Web Bean 인스턴스가 현재 컨텍스트와 관련된 인스턴스임을 확인해야 할 책임이 있습니다. 또한 클라이언트 프록시는 Web Beans가 삽입된 다른 Web Beans를 귀납적으로 나열하지 않고 디스크 순서대로 나열한 세션 컨텍스트와 같은 컨텍스트로 바운딩되게 합니다.

Java 언어의 한계로 인해, 일부 Java 유형은 Web Bean 관리자에 의해 프록시될 수 없습니다. 따라서, 삽입 지점 유형이 프록시될 수 없을 경우 Web Bean 관리자는 UnproxyableDependencyException을 던지게 됩니다.

다음과 같은 Java 유형은 Web Bean 관리자에 의해 프록시될 수 없습니다:

UnproxyableDependencyException을 수정하기 쉽습니다. 간단하게 삽입된 클래스에 매개 변수가 없는 생성자를 추가하고, 인터페이스를 소개하거나, 삽입된 Web Bean의 범위를 @Dependent로 변경합니다.

애플리케이션은 삽입에 의해 Manager 인터페이스의 인스턴스를 획득할 수 있습니다:

@Current Manager manager;

Manager 객체는 프로그램적으로 Web Bean 인스턴스의 획득을 위한 방식 모음을 제공합니다.

PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class);

바인딩 어노테이션은 AnnotationLiteral 도우미 클래스를 하부클래스화하여 지정될 수 있으며, 그렇지 않으면 Java에서 어노테이션 유형을 인스턴스화하기 어렵습니다.

PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class, 

                                               new AnnotationLiteral<CreditCard
>(){});

바인딩 유형이 어노테이션 멤버를 갖을 경우, AnnotationLiteral의 임의의 하부클래스를 사용할 수 없습니다 — 대신 named 하부 클래스를 생성해야 합니다:

abstract class CreditCardBinding 

    extends AnnotationLiteral<CreditCard
> 
    implements CreditCard {}
PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class, 

                                               new CreditCardBinding() { 
                                                   public void value() { return paymentType; } 
                                               } );

Enterprise Web Beans는 EJB 사양에 의해 정의된 모든 수명 주기 콜백을 지원합니다: @PostConstruct, @PreDestroy, @PrePassivate@PostActivate.

심플 Web Beans는 @PostConstruct@PreDestroy 콜백 기능만을 지원합니다.

엔터프라이즈 및 심플 Web Beans는 Java EE 리소스 삽입, EJB 및 JPA 영구적 컨텍스에 대해 @Resource, @EJB@PersistenceContext의 사용을 각각 지원합니다. 심플 Web Beans는 @PersistenceContext(type=EXTENDED)의 사용을 지원하지 않습니다.

@PostConstruct 콜백은 모든 의존성이 삽입된 후에 나타납니다.

특정 종류의 의존성 객체가 있습니다 — @Dependent 범위와 함께 Web Beans — 객체 또는 이는 실행 가능하게 되기 위해 삽입되는 삽입 지점에 관해 알아야 합니다. 예:

@Dependent 범위와 함께 Web Bean은 InjectionPoint 인스턴스를 삽입하고 해당 삽입 지점에 관련된 메타데이트를 액세스합니다.

다음 예를 살펴봅시다. 다음과 같은 코드는 프로그램을 리팩토링하기에 취약하고 장황합니다:

Logger log = Logger.getLogger(MyClass.class.getName());

이와 같이 현명한 생산자 방식은 로그 범주를 명시적으로 지정하지 않고 JDK Logger를 삽입하게 합니다:

class LogFactory {


   @Produces Logger createLogger(InjectionPoint injectionPoint) { 
      return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName()); 
   }
}

이제 다음을 작성할 수 있습니다:

@Current Logger log;

확신하시지 않습니까? 그러시다면 여기에 두 번째 예제가 있습니다. HTTP 매개 변수를 삽입하려면, 바인딩 유형을 정의해야 합니다:

@BindingType

@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface HttpParam {
   @NonBinding public String value();
}

다음과 같이 삽입 지점에서 이러한 바인딩 유형을 사용할 수 있습니다:

@HttpParam("username") String username;

@HttpParam("password") String password;

다음의 생산자 방식은 다음과 같이 작업합니다:

class HttpParams


   @Produces @HttpParam("")
   String getParamValue(ServletRequest request, InjectionPoint ip) {
      return request.getParameter(ip.getAnnotation(HttpParam.class).value());
   }
}

HttpParam 어노테이션의 value() 멤버는@NonBinding.을 어노테이션하므로 Web Bean 관리자에 의해 무시됨에 유의하시기 바랍니다.

Web Bean 관리자는 InjectionPoint 인터페이스를 구현하는 내장된 Web Bean을 제공합니다:

public interface InjectionPoint { 

   public Object getInstance(); 
   public Bean<?> getBean(); 
   public Member getMember(): 
   public <extends Annotation
> T getAnnotation(Class<T
> annotation); 
   public Set<extends Annotation
> getAnnotations(); 
}