SeamFramework.orgCommunity Documentation

Kapitel 4. Dependency-Einspeisung

4.1. Binding-Annotationen
4.1.1. Binding-Annotationen mit Mitgliedern
4.1.2. Kombinationen von Binding-Annnotationen
4.1.3. Binding-Annotationen und Producer-Methoden
4.1.4. Der standardmäßige Binding-Typ
4.2. Deployment Typen
4.2.1. Aktivierung von Deployment-Typen
4.2.2. Deployment-Typ Präzedenz
4.2.3. Beispiel Deployment-Typen
4.3. Unbefriedigende Abhängigkeiten beheben
4.4. Client-Proxies
4.5. Erhalt eines Web Beans durch programmatsichen "Lookup"
4.6. Lebenszyklus-Callbacks, @Resource, @EJB und @PersistenceContext
4.7. Das InjectionPoint-Objekt

Web Beans unterstützt drei primäre Mechanismen für "Dependency"-Einspeisung:

Konstruktorparameter-Einspeisung:

public class Checkout {

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

Initializer-Methode Parameter-Einspeisung:

public class Checkout {

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

Und direkte Feldeinspeisung:

public class Checkout {


    private @Current ShoppingCart cart;
    
}

Dependency-Einspeisung findet stets bei der ersten Instantiierung der Web Bean Instanz statt.

Die Einspeisung von Konstruktorparametern wird für EJB-Beans nicht unterstützt, da das EJB durch den EJB-Container und nicht den Web Bean Manager instantiiert wird.

Parameter von Konstruktoren und Initialisierermethoden müssen nicht explizit annotiert werden, wenn der standardmäßige Binding-Typ @Current gilt.Eingespeiste Felder jedoch müssen einen Binding-Typ festlegen, selbst wenn der standardmäßige Binding-Typ gilt. Legt das Feld keinen standardmäßige Binding-Typ fest, so wird es nicht eingespeist.

Producer-Methoden unterstützen Parametereinspeisung ebenfalls:

@Produces Checkout createCheckout(ShoppingCart cart) {

    return new Checkout(cart);
}

Observer-Methoden (auf die wir in Kapitel 9, Ereignisse näher eingehen), "Disposal"-Methoden und "Destructor"-Methoden unterstützen allesamt die Parametereinspeisung.

Die Web Beans Spezifikation definiert eine Prozedur namens typesicherer Auflösungsalgorithmus (sog. typesafe resolution algorithm), den der Web Bean Manager bei der Identifizierung des an einem Einspeisungspunkt einzuspeisenden Web Beans folgt. Dieser Algorithmus sieht auf den ersten Blick recht komplex aus, ist es aber nach kurzer Eingewöhnung nicht. Typensichere Auflösung wird zum Initialisierungszeitpunkt des Systems durchgeführt, was bedeutet, dass der Manager den Benutzer sofort darüber informiert, falls die Abhängigkeiten eines Web Beans nicht erfüllt werden können - dies erfolgt durch Meldung von UnsatisfiedDependencyException oder AmbiguousDependencyException.

Der Zweck dieses Algorithmus ist es, mehreren Web Beans die Einspeisung desselben API-Typs zu gestatten und entweder:

Schauen wir uns jetzt näher an, wie der Web Beans Manager ein einzuspeisendes Web Bean bestimmt.

Falls mehr als ein Web Bean existiert, das einen bestimmten API-Typ implementiert, so kann der Einspeisungspunkt genau festlegen welches Web Bean eingespeist wird mittels Binding-Annotation. Zum Beispiel können zwei Implementierungen von PaymentProcessor vorhanden sein:

@PayByCheque

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

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

Wo @PayByCheque und @PayByCreditCard Binding-Annotationen sind:

@Retention(RUNTIME)

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

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

Ein Client Web Bean Entwickler verwendet die Binding-Annotation um genau festzulegen, welches Web Bean eingespeist werden sollte.

Verwendung der Feldeinspeisung:

@PayByCheque PaymentProcessor chequePaymentProcessor;

@PayByCreditCard PaymentProcessor creditCardPaymentProcessor;

Verwendung der Initialisierermethoden-Einspeisung:

@Initializer

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

Oder Verwendung der Konstruktoreinspeisung:

@Initializer

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

Alle Web Beans besitzen einen Deployment-Typ. Jeder Deployment-Typ identifiziert einen Satz von Web Beans mit Vorbehalt in einigen Deployments des Systems installiert werden sollten.

Zum Beispiel könnten wir einen Deployment-Typ namens @Mock definieren, der Web Beans identifiziert, die nur installiert werden sollen, wenn das System innerhalb einer Integrationstestumgebung ausgeführt wird:

@Retention(RUNTIME)

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

Nehmen wir an, wir hätten ein Web Bean, das mit einem externen System interagiert, um Zahlungen zu bearbeiten:

public class ExternalPaymentProcessor {

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

Da dieses Web Bean nicht explizit einen Deployment-Typ festlegt gilt der standardmäßige Deployment-Typ @Production.

Für Integration oder das Testen von Einheiten ist das externe System langsam oder nicht verfügbar. Daher würden wir ein "Mock"-Objekt erstellen:

@Mock 

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

Wie aber bestimmt der Web Bean Manager, welche Implementierung in einem bestimmten Deployment verwendet werden soll?

Wenn Sie aufgepasst haben, fragen Sie sich jetzt wahrscheinlich, wie der Web Bean Manager entscheidet, welche Implementierung — ExternalPaymentProcessor oder MockPaymentProcessor — er wählt. Überlegen Sie sich, was passiert, wenn der Manager auf diesen Einspeisungspunkt trifft:

@Current PaymentProcessor paymentProcessor

Es gibt jetzt zwei Web Beans, die den PaymentProcessor-Vertrag erfüllen. Natürlich können wir keine Binding-Annotation zur eindeutig Machung verwenden, da Binding-Annotationen in die Quelle am Einspeisungspunkt hardcodiert und wir wollen, dass der Manager zum Deployment-Zeotpunkt entscheiden können soll!

Die Lösung dieses Problems ist, dass jeder Deployment-Typ eine andere Präzendenz besitzt. Die Präzendenz der Deployment-Typen wird durch die Reihenfolge, in der sie in web-beans.xml erscheinen, festgelegt. In unserem Beispiel erscheint @Mock später als @Production, so dass es eine höhere Präzendenz besitzt.

Findet der Manager mehr als ein Web Bean, das den von einem Einspeisungspunkt festgelegten Vertrag erfüllt (API-Typ plus Binding-Annotationen), so gilt die relative Präzedenz der Web Beans. Besitzt eines eine höhere Präzedenz als andere, so wird es für die Einspeisung gewählt. In unserem Beispiel speist der Web Bean Manager also MockPaymentProcessor bei der Ausführung unserer Integrationstestumgebung aus (und das ist es auch, was wir möchten).

Es ist interessant dies mit den heutzutage beliebten Manager Architekturen zu vergleichen. Verschiedene "leichtgewichtige" Container gestatten uns auch das bedingte Deployment von im Klassenpfad existierenden Klassen, aber Klassen, die deployt werden sollen müssen explizit, individuell im Konfigurationscode oder einer XML-Konfigurationsdatei aufgeführt sein. Web Beans unterstützt die Web Bean Definition und Konfiguration via XML, aber im gängigen Fall, in dem keine komplexe Konfiguration erforderlich ist, gestatten Deployment-Types die Aktivierung eines gesamten Satzes von Web Beans mittels einer einzigen XML-Zeile. Währenddessen kann ein den Code durchsehender Entwickler leicht einsehen, in welchen Deployment-Szenarien das Web Bean eingesetzt wird.

Der typensichere Auflösungsalgorithmus schlägt fehl, wenn - nach Betrachtung der Binding-Annotationen und der Deployment-Typen aller den API-Typ implementierender Web Beans eines Einspeisungspunktes - der Web Bean Manager nicht dazu in der Lage ist, ein einzuspeisendes Web Bean zu identifizieren.

Es ist in der Regel einfach, Probleme mit einer UnsatisfiedDependencyException oder AmbiguousDependencyException zu beheben.

Um eine UnsatisfiedDependencyException zu beheben, stellen Sie einfach ein Web Bean bereit, das den API-Typ implementiert und die Binding-Typen des Einspeisungspunkts besitzt— oder aktivieren Sie den Deployment-Typ eines Web Beans, das den API-Typ bereits implementiert und die Binding-Typen besitzt.

Um eine AmbiguousDependencyException zu beheben, führen Sie einen Binding-Typ ein, um zwischen den beiden Implementierungen des API-Typs zu unterscheiden oder ändern Sie den Deployment-Typ einer der Implementierungen damit der Web Bean Manager Deployment-Typ Präzedenz zur Auswahl zwischen den beiden verwenden kann. Eine AmbiguousDependencyException kann nur vorkommen, wenn zwei Web Beans sich einen Binding-Typ teilen und genau denselben Deployment-Typ besitzen.

Es gibt eine weitere Sache, derer man sich bei der Verwendung von "Dependency"-Einspeisung in Web Beans gewahr sein sollte.

Clients eines eingespeisten Web Beans enthalten in der Regel keinen direkten Verweis an eine Web Bean Instanz.

Stellen wir uns vor, ein an den Geltungsbereich der Anwendung gebundenes Web Bean hielte einen direkten Verweis auf ein an den Geltungsbereich der Anfrage gebundenes Web Bean. Das an den Geltungsbereich der Anwendung gebundene Web Bean wird von vielen verschiedenen Anfragen geteilt. Jedoch sollte jede Anfrage eine andere Instanz des an den Geltungsbereich der Anfrage gebundenen Web Beans sehen!

Stellen Sie sich nun vor das an den Geltungsbereich der Session gebundene Web Bean hielte einen direkten Verweis auf ein an den Geltungsbereich der Anwendung gebundenes Web Bean. FVon Zeit zu Zeit wird der Session Kontext auf Disk serialisiert, um den Speicher effizienter zu nutzen. Die durch den Geltungsbereich der Anwendung begrenzte Instanz des Web Beans sollte jedoch nicht mit dem durch den Geltungsbereich der Session begrenzetn Web Bean serialisiert werden!

Daher muss der Web Bean Manageralle eingespeisten Verweise auf das Web Bean durch ein Proxy-Objekt einleiten, wenn das Web Bean nicht den Standard-Geltungsbereich @Dependent besitzt. Dieser Client-Proxy ist verantwortlich dafür sicher zu stellen, dass die einen Methodenaufruf erhaltende Web Bean Instanz, die mit dem aktuellen Kontext assoziierte ist. Der Client-Proxy gestattet außerdem die Serialisierung auf Disk von an Kontexte gebundenen Web Beans, ohne dassrekursiv andere eingespeiste Web Beans serialisiert werden.

Leider können aufgrund von Einschränkungen von Java einige Java-Typen nicht vom Web Bean Manager geproxiet werden. Daher meldet der Web Bean Manager eine UnproxyableDependencyException, wenn der Typ eines Einspeisungspunkts nicht geproxiet werden kann.

Die folgenden Java-Typen können nicht durch den Web Bean Manager geproxiet werden:

Es ist in der Regel ganz leicht eine UnproxyableDependencyException zu beheben. Fügen Sie der eingespeisten Klasse einfach einen Konstruktor ohne Parameters hinzu, führen Sie ein Interface ein oder ändern Sie den Gelstungsbereich des eingespeisten Web Bean zu @Dependent.

Die Anwendung kann durch Einspeisung eine Instanz des Interface Manager erhalten:

@Current Manager manager;

Das Manager-Objekt liefert einen Satz von Methoden zum programmatischen Erhalt einer Web Bean Instanz.

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

Binding-Annotationen können durch Subklassifizierung der Helferklasse AnnotationLiteral festgelegt werden, da es ansonsten schwierig ist, einen Annotationstyp in Java zu instantiieren.

PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class, 

                                               new AnnotationLiteral<CreditCard
>(){});

Besitzt der Binding-Typ ein Annotationsmitglied, so können wir keine anonyme Unterklasse von AnnotationLiteral — verwenden - stattdessen werden wir eine benannte Unterklasse erstellen müssen:

abstract class CreditCardBinding 

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

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

Enterprise Web Beans unterstützen alle durch die EJB-Spezifikation definierten Lebenszyklus-Callbacks: @PostConstruct, @PreDestroy, @PrePassivate und @PostActivate.

Einfache Web Beans unterstützen nur die @PostConstruct und @PreDestroy Callbacks.

Sowohl Enterprise als auch einfache Web Beans unterstützen den Gebrauch von @Resource, @EJB und @PersistenceContext zur Einspeisung von Java EE Ressourcen bzw. EJBs und JPA-Persistenzkontexten. Einfache Web Beans unterstützen den Gebrauch von @PersistenceContext(type=EXTENDED) nicht.

Der @PostConstruct-Callback erfolgt immer, nachdem alle Abhängigkeiten eingespeist wurden.

Es gibt bestimmte Arten abhängiger Objekte — Web Beans mit Geltungsbereich @Dependent — die etwas über das Objekt oder den Einspeisungspunkt in die sie eingespeist werden wissen müssen, um ihre Aufgabe zu erledigen. Zum Beispiel:

Ein Web Bean mit Geltungsbereich @Dependent kann eine Instanz von InjectionPoint einspeisen und auf Metadaten zugreifen, die mit dem zugehörigen Einspeisungspunkt zu tun haben.

Sehen wir uns ein Beispiel an. Der folgende Code ist umfangreich und empfänglich für Refaktorierungsprobleme:

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

Diese schlaue kleine Producer-Methode gestattet die Einspeisung eines JDK Logger, ohne dass explizit eine Protokollkategorie festgelegt werden müsste:

class LogFactory {


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

Wir können jetzt schreiben:

@Current Logger log;

Sie sind noch nicht ganz überzeugt? Dann sehen Sie sich ein weiteres Beispiel an. Zur Einspeisung von HTTP-Parametern müssen wir einen Binding-Typ festlegen:

@BindingType

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

Wir würden diesen Binding-Typ an Einspeisungspunkten wie folgt verwenden:

@HttpParam("username") String username;

@HttpParam("password") String password;

Die folgende Producer-Methode erledigt die Arbeit:

class HttpParams


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

(Beachten Sie, dass das value()-Mitglied der HttpParam-Annotation vom Web Bean Manager wird, da es mit @NonBinding. annotiert ist)

Der Web Bean Manager liefert ein eingebautes Web Bean, das das InjectionPoint-Interface implementiert:

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(); 
}