SeamFramework.orgCommunity Documentation
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.
Zuerst ruft der Web Bean Manager den Web Bean Konstruktor auf, um eine Instanz des Web Beans zu erhalten.
Als nächstes initialisiert der Web Bean Manager die Werte aller eingespeisten Felder des Web Beans.
Anschließend ruft der Web Bean Manager alle Initialisierermethoden des Web Beans auf.
Zuletzt wird die @PostConstruct
-Methode des Web Bean, falls vorhanden, aufgerufen.
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:
Dem Client mittels Binding-Annotationen zu gestatten auszuwählen, welche Implemetierung er benötigt,
Dem Anwendungs-Deployer durch Aktivierung oder Deaktivierung von Deployment-Typen gestatten auszuwählen, welche Implementierung die passende für eine bestimmte Umgebung ist, ohne dass es zu Änderungen am Client kommt oder
Einer Implementierung eines API mittels Deployment-Typ Präzedenz ("Deployment Type Precedence") gestatten, zum Deployment-Zeitpunkt eine andere Implementerung desselben API außer Kraft zu setzen, ohne dass dies zu Änderungen am Client führt..
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;
}
Binding-Annotationen können Mitglieder besitzen:
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayBy {
PaymentType value();
}
Wobei in diesem Fall der Mitgliederwert von Bedeutung ist:
@PayBy(CHEQUE) PaymentProcessor chequePaymentProcessor;
@PayBy(CREDIT_CARD) PaymentProcessor creditCardPaymentProcessor;
Sie können den Web Bean Manager anweisen, ein Mitglied eines Binding-Annotationstyps zu ignorieren, indem Sie das Mitglied mit @NonBinding
annotieren.
Ein Einspeisungspunkt kann sogar mehrere Binding-Annotationen festlegen:
@Asynchronous @PayByCheque PaymentProcessor paymentProcessor
In diesem Fall würde nur ein Web Bean mit beiden Binding-Annotationen eingespeist.
Sogar Producer-Methoden können Binding-Annotationen festlegen:
@Produces
@Asynchronous @PayByCheque
PaymentProcessor createAsyncPaymentProcessor(@PayByCheque PaymentProcessor processor) {
return new AsynchronousPaymentProcessor(processor);
}
Web Beans definiert einen Binding-Typ @Current
, der der standardmäßige Binding-Typ für jeden Einspeisungspunkt oder Web Bean ist, der nicht explizit einen Binding-Typ festlegt.
Es existieren zwei gängige Umstände, bei denen es notwendig ist, @Current
festzulegen:
An einem Feld, um es als eingespeistes Feld zu deklarieren mit dem standardmäßigen Binding-Typ und
an einem Web Bean, das neben dem standardmäßigen Binding-Type einen weiteren Binding-Typ besitzt.
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?
Web Beans definieren zwei eingebaute Deployment-Typen: @Production
und @Standard
. Standardmäßig sind nur Web Beans mit den eingebauten Deployment-Typen bei Deployment des Systems aktiviert. Wir können weitere Deployment-Typen identifizieren, die bei einem bestimmten Deployment aktiviert werden sollen, indem wir diese in web-beans.xml
aufführen.
Kehren wir zu unserem Beispiel zurück, wenn wir Integrationsstests deployen und wir möchten, dass alle unsere @Mock
-Objekte installiert werden:
<WebBeans>
<Deploy>
<Standard/>
<Production/>
<test:Mock/>
</Deploy>
</WebBeans
>
Jetzt identifiziert und installiert der Web Bean Manager alle mit @Production
, @Standard
oder @Mock
annotierten Web Beans zum Zeitpunkt des Deployments.
Der Deployment-Typ @Standard
wird nur für bestimmte, spezielle durch die Web Beans Spezifikation definierte Web Beans verwendet. Wir können ihn nicht für unsere eigenen Web Beans benutzen und wir können ihn nicht deaktivieren.
Der Deployment-Typ @Production
ist der standardmäßige Deployment-Typ für Web Beans, die keinen expliziten Deployment-Typ festlegen oder deaktiviert sind.
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.
Deployment-Typen sind hilfreich für allerlei Dinge, hier sind ein paar Beispiele:
@Mock
und @Staging
Deployment-Typen zu Testzwecken
@AustralianTaxLaw
für site-spezifische Web Beans
@SeamFramework
, @Guice
für Frameworks Dritter, die auf Web Beans bauen
@Standard
für standardmäßige, durch die Web Beans Spezifikation definierte Web Beans
Ihnen fallen sicher noch andere Anwendungen ein...
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:
Als final
deklarierte Klassen oder die eine final
-Methode besitzen,
Klassen, die keinen nicht-privaten Konstruktor ohne Parameter besitzen sowie
Arrays und primitive Typen.
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:
Die Protokollkategorie für einen Logger
hängt von der Klasse des sie besitzenden Objekts ab.
Die Einspeisung eines HTTP-Parameters oder Header-Werts hängt davon ab, welcher Parameter oder Header-Name am Einspeisungspunkt festgelegt wurde.
Einspeisung als Ergebnis der Evaluierung eines EL-Ausdrucks hängt von vom am Einspeisungspunkt festgelegten Ausdruck ab.
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 <T extends Annotation
> T getAnnotation(Class<T
> annotation);
public Set<T extends Annotation
> getAnnotations();
}