Initialisierung von JSF Seiten

240 views
Skip to first unread message

Gan...@j4fry.org

unread,
Feb 3, 2009, 3:16:19 AM2/3/09
to j4fry
This thread was moved from our old sourceforge forum:

Initialisierung von JSF Seiten
By: ganesh (ganeshpuriProject Admin) - 2008-02-16 07:08
Welche Ansätze zur Initialisierung von JSF Seiten haben welche Ver-
und Nachteile? Zur Diskussion stehen bisher ein onLoad Tag und ein
HttpSessionAttributeListener / ServletRequestAttributeListener.


RE: Initialisierung von JSF Seiten
By: ganesh (ganeshpuriProject Admin) - 2008-02-16 20:06
Das war Alex Mail zu dem Thema:

Hallo an alle,

nachdem wir gestern über das Thema gesprochen haben, hab ich mir
nochmal Gedanken drüber gemacht.
Ganesh will ein <fry:onload>-Tag realisieren. Jetzt wäre es doch
noch eine schöne Erweiterung nicht nur eine Action aufzurufen die dann
alle Parameter aus dem Request holt sondern gleich ein Mapping von
Parametern auf der JSP vorzunehmen.

Das würde dann ungeführ so aussehen:
<fry:onload action="#{bean.meineInitialAction}>
<f:attribute name="meinGetParameter" value="#
{parameterAufnameBean.value}" />
<f:attribute name="name" value="#{userBean.username}" />
<f:attribute name="funktion" value="#{controllerBean.function}" /
>
</fry:onload>

Die URL könnte dann so aussehen:
http://myApplication.org/SYSTEM/login.jsf?meinGetParameter=test&name=woody87&funktion=login

D.h. man nimmt innerhalb des onload-Tags ein Mapping von GET-
Parametern auf die entsprechenden Beans vor.
Das müsste alles im encode des fry:onload-Tags passieren.
Natürlich sollte man erst die GET-Parameter in die Beans kippen und
dann die Action aufrufen falls man ggf. hier die Parameter (z.B. für
einen Initial-DB-Zugriff) schon benötigt.

Das hätte den Vorteil das man dann nur eine JSP anfassen müsste
um die GET-Parameter abzufangen und in die Beans zu bekommen.
Ansonsten müsste ich so eine unschöne Sache in der Action machen und
via FacesContext & Co. auf die RequestParameters zuzugreifen.

RE: Initialisierung von JSF Seiten
By: ganesh (ganeshpuriProject Admin) - 2008-02-16 20:08
Heiko hat dazu ein paar intereesante Anmerkungen gemacht:

Hallo allerseits,

ich habe da ein paar Anmerkungen zu dem Tag <fry:onload action="#
{bean.initAction}">
von Alex und Ganesh:

1) Die Methode initAction() in Euer Backing-Bean in dem Tag wird
in jeder Render-Phase
aufgerufen. Hat die Backing-Bean den Scope request ist das so
gewünscht, hat aber Eure
Backing-Bean den Scope session, ist das unter Umständen so nicht
gewünscht. Im Relaunch
wurde ein ähnliches Vorgehen gewählt und dazu eine Basis-
Backingbean geschrieben, etwa so:

public abstract class AbstractBackingBean {
private boolean inited = false;

public void initAction() {
if (!this.inited) {
this.init();
this.inited = true;
}
this.display();
}

protected abstract void init();

protected abstract void display();

}

Die Methode init() wird nur einmal nach dem Erzeugen der Backing-
Bean aufgerufen,
die Methode display() dagegen bei jedem Render der JSF-Seite.

Ich finde die Lösung hässlich, weil entweder die Backing-Beans
alle von obiger Klasse
ableiten müssen, oder der obige Code in allen Backing-Beans
wiederholt werden muß.
Erste Variante führt eine zweifelhafte Abhängigkeit in Eure
backing-Beans ein, die
zweite Variante verstößt gegen das Prinzip DRY.

Eine einfachere Lösung läßt sich mit Mittel des Servlet APIs
unabhängig von JSF etwa
so realisieren:

import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;

public class BeanInitializer implements
HttpSessionAttributeListener,
// ServletRequestAttributeListener erst ab BEA 9, Tomcat 5.5
ServletRequestAttributeListener {

public void attributeAdded(HttpSessionBindingEvent ev) {
this.initBean(ev.getValue());
}
public void attributeAdded(ServletRequestAttributeEvent ev) {
this.initBean(ev.getValue());
}
public void attributeRemoved(HttpSessionBindingEvent ev) {
}
public void attributeRemoved(ServletRequestAttributeEvent ev) {
}
public void attributeReplaced(HttpSessionBindingEvent ev) {
}
public void attributeReplaced(ServletRequestAttributeEvent ev)
{
}

private void initBean(Object bean) {
if (bean instanceof IInitializingBean) {
((IInitializingBean) bean).init();
}
}
}

public interface IInitializingBean {
void init();
}

public class MyBackingBean implements IInitializingBean {
public void init() {
// Init Aktion
}
}

Damit da Ganze funktioniert, braucht Ihr noch eien Eintrag in
der web.xml:
<listener>...BeanInitializer</listener>
Oder so ähnlich.

Die restlichen Methoden der Klasse BeanInitializer lassen sich
für weitere Hooks benutzen,
etwa eine Methode destroy()

Das Ganze hat aber auch ein paar Fallen: Es funktioniert nur
dann, wenn JSF Backing-Beans
wirklich in der Http-Session oder im Servlet-Request ablegt, und
fügt man eine der Bean
zweimal in die Http-Session oder den ServletRequest ein, wird
die Methode init() auch zweimal
ausgeführt.

Im Relaunch ist das so oder so ähnlich umgesetzt (bis auf den
Abschnitt mit dem Interface
ServletRequestAttributeListener, das Interface kennt BEA 8 noch
nicht), und funktioniert da
ganz gut, jedenfalls habe ich noch keine Klagen gehört.

2) Die Übergabe der Request-Parameter mittels
<fry:onload ...>
<f:attribute name="name" value="#{bean.name}" />
...
</fry:onload>
hat leider auch eine Falle:
Wird die Seite das erste mal gerendert, sollte es passen, da
sehe ich noch keine Schwierigkeiten.
Wird aber auf der Seite eine Aktion, bspw. von einem Html-Button
ausgeführt, dann kennt die
Backing-Bean die Parameter, die über das Tag <f:attribute .../>
übergeben werden noch nicht,
vorallem wenn die Backing-Bean den Scope request hat.

Ich würde das eher mit JSF-Bordmittel machen, etwa so (für Scope
request):

JSF-Konfiguration:
<managed-bean>
<managed-bean-name>bean</managed-bean-name>
<managed-bean-class>...</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>name</property-name>
<value>#{param.name}</value> // Zugriff auf HTTP Request
Parameter
</managed-property>
...
</managed-bean>

JSP:
<fry:onload action="{bean.initAction}" />

Für Scope session geht allerdings obige Lösung so nur mit etwas
Basteln.

Viele Grüße
Heiko


Listener vs. onLoad Tag
By: ganesh (ganeshpuriProject Admin) - 2008-02-17 07:01
Hallo Heiko,

Wo liegt eigentlich der Unterschied zwischen der Listener Lösung
und einem init(); im Konstruktor?

Beide haben den Nachteil, dass sie als Seiteneffekt des ersten
Getter Aufrufs der Seite auftreten. Wenn man dabei mehrere Beans
initialisiert, kann ein Umstellen der Reihenfolge der
Oberflächenelemente Fehler erzeugen.

Wie wärs mit zwei zusätzlichen Attributen, die feststellen, ob
ein GET oder POST Request abgesetzt wurde? Die Initialisierung von
Seiten, die über Links angesprochen werden, war ja das eigentliche
Problem.

<fry:onLoad action="..." facesRequest="true/false
(default:false)" nonfacesRequest="true/false"(default:true) />

Was hältst Du davon? Für die Initialisierung von Flows geht das
so nicht, aber da sollte das eh die Flowengine übernehmen.

Deinen Vorschlag, die Parameterübernahme per faces-config zu
machen, scheint mir der richtige Weg. Eine Parameterübernahme ist ja
nur bei HTTP GET Aufrufen nötig und dafür reicht es so auf alle
Fälle.

Viele Grüße,
Ganesh

RE: Listener vs. onLoad Tag
By: Heiko Wolf (heiko_wolf) - 2008-02-17 18:03
Hallo,

Zu 1) Initialisierung im Konstruktor:
ein Init im Konstruktor funktioniert nicht mit Dependency
Injection:
Erst wird der Konstruktor aufgerufen, dann die Setter-
Methode durch
das Dependency-Injection von JSF. Sprich, wenn die
Initialisierung
Deiner Bean auf den Werten basiert, die durch die Setter-
Methoden
erst gesetzt werden, funkitoniert keine Initialisierung
im
Konstruktor

Zu 2) Erweiterung des Tags onLoad:
Die Reihenfolge, wie die Backing-Beans initialisiert
werden, wird
durch die Abhängigkeiten zwischen Deinen Backing-Beans
bestimmt.
Sollten Deine Backing-Beans keine Abhängigkeiten haben, so
sollte
die Reihenfolge ihrer Initialisierung egal sein.
Andernfalls haben
Deine Backing-Beans eine verdeckte Abhängigkeit und Deine
Anwendung
könnte ein Problem haben

Die Idee ändert nichts am Problem.
Beispiel:
Du hast ein Formular mit einem Submit-Button, der eine
Aktion doSubmit()
in Deiner Backing-Bean aufruft, dann passiert folgendes:
1) Der Benutzer klickt den Submit-Button an.
2) JSF erzeugt Deine Backing-Bean, falls sie Scope Request
hat
3) JSF ruft die Aktion doSubmit() auf
4) JSF rendert die JSP
5) Während des Renders, ruft JSF Deine Init-Methode auf.

Oder kurz: Der Aufruf von doSubmit() findet vor dem Aufruf
Deiner
Init-Methode statt.

Ist das von Dir gewollt ? Wenn ja, dann ist der Name Init-
Methode
falsch und sollte einen anderen Namen bekommen, zum
Beispiel
beforeRender() oder so.


RE: Listener vs. onLoad Tag
By: ganesh (ganeshpuriProject Admin) - 2008-02-18
09:17
Hallo Heiko,

Danke für Dein ausfhrliches Beispiel. Ich denke, wir
sollten das Szenario, für das die Initialisierung benötigt wird,
genauer spezifizieren, um nicht Gefahr zu laufen, aneinander vorbei zu
reden (oder zu schreiben :-).

Ich habe drei Szenarios in Kopf:

1. Du gelangst mit einem HTTP GET Non-Faces Request
auf eine JSF Seite und möchtest vor dem Rendern Daten aus der
Persistenzschicht in die Beans legen (z.B. Einstieg über Bookmark).

2. Du möchtest vor jedem Rendern einer JSF Seite
frische Daten aus der Persistenzschicht lesen (z.B. Pflege einer
Datenbanktabelle, bei jedem Laden der Seite soll der aktuelle
Seiteninhalt dargestellt werden).

3. Du gelangst mit einem Faces Request auf eine JSF
Seite und möchtest nur beim ersten Aufruf der Seite (also nicht bei
nachfolgenden eventuellen Roundtrips) die darauf befindlchen Beans
initialisieren (z.B. Einstieg in einen Assistenten aus einem JSF
Menü).

Während das onLoad Tag auf Szenario 1 und 2 zielt,
beschreibst Du in Deinem Beispiel Probleme mit Szenario 3. Für
Szenario 3 gibt doch aber eine ganz einfache Lösung: Die Action, die
auf die JSF Seite führt, kann die Initialisierung doch einfach
aufrufen.

Die Vorteile des Listeners gegenüber dem Konstruktor
verstehe ich jetzt besser. Wie sieht das Szenario aus, für das Du die
Listener Lösung vorsehen würdest?

In Szenario 1 und 2 entsteht die Problematik der
verdeckten Beanabhängigkeiten m.E., wenn die Initialisierung als
Seiteneffekt des ersten Getter Aufrufs der Bean während der Rendering
Phase aufgerufen wird (also mit der Listener Lösung).

Beispiel zum Entstehen der Beanabhängigkeit:
Deine Seite ist z.B. aus zwei Facelets A und B
aufgebaut ist, die auf die BackingBeans ABean und BBean zugreifen. Der
Service S versorgt das Modell, auf des ABean und BBean zugreifen, mit
Daten. Die verdeckte Abhängigkeit entsteht, wenn Du Dich nun
entscheiden mußt, welche der beiden Beans das
Initialisierungsinterface implementieren und dem Service triggern
soll. Du mußt Dich dann nämlich entscheiden, ob erst A oder erst B auf
der Seite stehen muss, um die dazugehörige Bean zu laden und damit die
Initialisierung zu starten.

Zur Namensproblematik: Das Tag soll ja nicht init,
sondern onLoad heißen, was ja beforeRender() sehr ähnlich ist. Das
Problem, das ich lösen wollte, war auch nicht in erster Linie die
Initialisierung von Beans, sondern die Initialisierung von JSF Seiten.
Soweit ich Michael Karras verstanden habe, war er in erster Linie an
einer Lösung für Szenario 1 interessiert.

Überzeugt Dich das von den Vorteilen des onLoad
Tags?

Viele Grüße,
Ganesh

RE: Listener vs. onLoad Tag
By: Heiko Wolf (heiko_wolf) - 2008-02-18 20:19
Hi Ganesh,

klar Antwort nein und zwar aus folgenden
Gründen (wenn ich noch länger drüber nachdenke, fallen mir wohl noch
mehr ein):

1) Es behindert die Wiederverwendung Deiner
Backing-Bean, weil die Initialisierung Deines Models (eben der Backing-
Bean) abhängig ist von Deiner View (der JSP Seite mit dem Tag), oder
anders ausgedrückt, wenn
Du Deine Backing-Bean mit einer anderen View
(andere JSP-Seite) verwenden möchtest, und Du in dieser anderen JSP
Seite da Tag vergißt, hast Du einen netten Fehler zu suchen, oder wenn
Du an das Tag gedacht hast, hast Du gegen das Prinzip DRY verstossen.

2) Ich kenne nur ein Web-UI Framework, wo über
eine solche Lösung nachgedacht wird, eben JSF. Damit steht fest, das
JSF hier ein Feature für ein Problem, das in der Praxis recht häufig
vorkommt, fehlt.

Das zeigt sich auch in den diversen Addon-
Libraries zu JSF, die gerade Lösungen für derartige Probleme liefern.

Die einfachste und eleganteste Lösung wäre die
Ergänzung der JSF Spec, damit man eine Init-Methode/ Init-Action in
der Konfiguration der Backing-Beans angeben kann, etwa so:

<managed-bean>
<managed-bean-name>bean</managed-bean-name>
<managed-bean-class>...</managed-bean-class>
<managed-bean-scope>request</managed-bean-
scope>
<init-action>init</init-action>
<managed-property>
...
</managed-property>
...
</managed-bean>

Ich persönlich ziehe die Definition der
Backing-Beans mit Spring 2.x vor, zum Beispiel so:

<bean id="bean" class="..." init-method="init"
scope="request">
<property name="..." value="..." />
...
</bean>

Da habe ich meine Init-Methode. Die
Initialisierung von Properties mittels #{param.name} direkt aus den
Request-Parameter geht damit allerdings nicht.

Zu Deinen Szenarien:
Sicher läßt sich mit Deinem onload-Tag alle
Szenarien abdecken, es kommt darauf an, wie Ihr es implementiert. Die
dahinterliegende JSF Kompenente kann die Init-Aktion ja je nach
Situation in unterschiedlichen Phasen auslösen.

Viele Grüße
H
Reply all
Reply to author
Forward
0 new messages