Java Server Faces - ett första intryck
Publicerad 2004-10-11
Inledning
Java Server Faces (JSF) är ett nytt ramverk för att bygga webbapplikationer. Genom att beskriva konverteringen av en enkel Strutsapplikation till JSF försöker denna artikel visa hur JSF kan användas för att implementera en MVC-baserad webbapplikation. Artikeln förutsätter generella kunskaper i design av webbapplikationer och Struts i synnerhet.
Varför behövs Java Server Faces?
JSF (Java Server Faces) är ett ramverk för att bygga javabaserade webbapplikationer. Syftet är att underlätta utveckling och underhåll av serverbaserade applikationer som skickar sina användargränssnitt till klienten för utritning. Även om den första implementationen från Sun är inriktad mot HTML och jspsidor så är JSF i grunden oberoende av såväl markup language som mottagande klient.
I dagsläget har en utvecklare som skall bygga en webbapplikation (lite förenklat må vara) två huvudspår att följa:
- att med hjälp av en "drag och släpp editor" relativt enkelt skapa grafiska gränssnitt, oftast med en stark koppling till en specifik back-end leverantör. Ett exempel på detta är applikationer utvecklade med Microsoft ASP.Net.
- alternativt skriva massor av kod (och xml) för att skapa en robust och skalbar applikation som kan köras på ett antal olika applikationsservers, i olika datormiljöer. Gränssnittet mot användaren är ofta mödosamt ihopknåpade jsp sidor. Ett exempel på detta är J2EE applikationer med exempelvis Struts som ramverk för klientdelen.
I den bästa av utvecklingsvärldar finns naturligtvis möjligheten för både och, dvs stöd för att bygga en stabil,skalbar och plattformsoberoende serverapplikation kombinerat med en grafisk miljö för att bygga gränssnitten mot användaren.
JSF är ett försök att ge javautvecklare detta genom att bl a:
- skapa en standard för gränssnittskomponenter avsedda för webbapplikationer
- förbättra möjligheterna för verktygsstöd vid utvecklingen av användargränssnitt
- hålla state för gränssnittet mellan requests
- knyta händelser i gränssnittet till kod i serverapplikationen
- ge utvecklare möjlighet att tillverka egna, återanvändbara gränssnittskomponenter
Allt detta möjliggörs via:
- Java API:er för komponenthantering, konvertering av data mellan modell och gränssnitt, validering av inmatat data, händelsehantering mm
- ett taggbibliotek för att lägga ut komponenter på jspsidor
- nya utvecklingsmiljöer som ger stöd för att grafiskt bygga jsp-sidor med JSF-komponenter. Ett exempel på detta är Sun's egen Sun Java Studio Creator
Hur använder du Java Server Faces? Ett litet exempel.
Det finns mängder av artiklar på nätet som tar upp JSF. Det stora flertalet beskriver dock JSF från ett tämligen teoretiskt perspektiv, utan att ta upp väsentliga frågor som:
- var passar JSF in i en MVC baserad design?
- hur kan JSF ersätta/komplettera Struts och andra webbramverk?
- var skall affärslogiken implementeras i en JSF applikation?
Angreppssättet i denna artikel för att försöka ge svar på dessa frågor var att ta en exempelapplikation från Strutsdistributionen och konvertera denna till JSF. Valet föll på "MailReader", ett embryo till en webbapplikation där användare kan registrera sig och administrera sina e-postkonton.
Domänobjektmodellen är mycket enkel, en registrerad användare (User) kan lägga upp ett godtyckligt antal prenumerationer (Subscription) på e-postkonton.

I den Strutsbaserade representeras modellen i MVC:et av Action-klasser medans motsvarande ActionForms utgör kittet mellan modell och vy. Vyn består av ett antal jspsidor.
Persistens hanteras i ett databaslager vars externa protokoll definieras i UserDataBase. UserDataBase definierar metoder för att skapa, spara och ta bort användare och prenumerationer. Implementationen i "MailReader" håller all data i minnet och den permanenta lagringen sker i en xml-fil på disk. Applikationen i sin nuvarande version använder alltså inga O/R-mappningsramverk som exempelvis Hibernate. Detta kan vara ett lämpligt ämne för en framtida artikel.
JSF och MVC
JSF passar väl in i MVC-modellen där jspsidor med komponenter utlagda mha JSF taggbibliotek utgör vyn och "backing beans" utgör modellen. "Backing beans" är ett JSF-begrepp som beskrivs längre ned i artikeln. Dessa bönor, som för övrigt är Plain old Java objects (POJO), innehåller också actionmetoder som delegerar till domänmodellen för hantering av affärslogiken vid exemepelvis en submit i jspsidan. Dessa metoder utgör tillsammmans med den specifika JSF-servleten controllerdelen i MVC:et.
"Managed beans and backing beans"
JSF introducerar två begrepp: "managed bean" och "backing bean". (I brist på vettiga svenska översättningar kommer dessa begrepp att i fortsättningen användas oöversatta)
En "managed bean" är helt enkelt en javaböna som hanteras av JSF där man i konfigurationsfilen beskriver hur den skapas och i vilket scope den kommer att existera. En "backing bean" är en "managed bean" som är kopplad till en eller flera jspsidor. Dess attribut har en tvåvägskoppling till sidans komponenter eller dess värden och dess metoder används för att bl a validera inmatat data och hantera navigeringen på sidan. Tvåvägskoppling innebär att när sidan visas upp så populeras sidans komponenterna med värden från bönan och när sidan submittas så sker en validering och bönan uppdateras med de värden som matats in på sidan. En "backing bean" ersätter såväl en Struts Action som ActionForm i och med att den dels innehåller data i form av attribut, dels implementerar logik i form av metoder som anropas från JSF när användaren submittar.
Exempel: "Registrering av ny användare"
För att illustrera implementationen och hur allting sätts samman tittar vi på implementeringen av "Registrering av nu användare".
JSP: JSP-sidan som används för att registrera en ny användare är createRegistration.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<f:loadBundle basename="se.bluefish.jsf.example.ApplicationResources" var="bundle"/>
<f:view>
<head>
<title>
<c:out value="${bundle['registration.title.create']}" />
</title>
<link rel="stylesheet" type="text/css" href='<%= request.getContextPath() + "/css/styles.css" %>'>
</head>
<body bgcolor="white">
<h:form id="registration_form">
<h:panelGrid columns="3">
<h:outputText value="#{bundle['prompt.username']}" />
<h:inputText id="username" value="#{user.username}" required="true"/>
<h:message for="username"/>
<h:outputText value="#{bundle['prompt.password']}" />
<h:inputSecret id="password" value="#{user.password}" redisplay="true" required="true"/>
<h:message for="password"/>
<h:outputText value="#{bundle['prompt.password2']}" />
<h:inputSecret id="password2" value="#{user.password}" redisplay="true" required="true"/>
<h:message for="password2"/>
<h:outputText value="#{bundle['prompt.fullName']}" />
<h:inputText id="fullname" value="#{user.fullName}" required="true"/>
<h:message for="fullname"/>
<h:outputText value="#{bundle['prompt.fromAddress']}" />
<h:inputText id="fromAddress" value="#{user.fromAddress}" required="true"/>
<h:message for="fromAddress"/>
<h:outputText value="#{bundle['prompt.replyToAddress']}" />
<h:inputText id="replyToAddress" value="#{user.replyToAddress}" required="true"/>
<h:message for="replyToAddress"/>
<h:commandButton value="#{bundle['button.save']}" action="#{user.saveRegistration}"/>
<h:panelGroup>
<h:commandButton value="#{bundle['button.reset']}" type="RESET" />
<h:commandButton value="#{bundle['button.cancel']}" action="cancel" immediate="true"/>
</h:panelGroup>
</h:panelGrid>
<h:message for="registration_form" style="color: blue"/>
</h:form>
</body>
</f:view>
Som synes så använder denna sida ett stort antal JSF-specifika taggar, bl a:
<f:loadBundle basename="se.bluefish.jsf.example.ApplicationResources" var="bundle"/>
som definierar vilken resursfil som skall användas på sidan. Alla resurser kan sedan kommas åt via ett JFS-uttryck som exempelvis "${bundle['registration.title.create']}".
<h:panelGrid columns="3">
<h:outputText value="#{bundle['prompt.username']}" />
<h:inputText id="username" value="#{user.username}" required="true"/>
...
</h:panelGrid>
som definierar en tabell med tre kolumner. Alla inmatningsfält etc är kopplade till motsvarande attribut i objektet "user", en "backing bean" av typen UserBean som automatiskt skapas av JSF vid första accessen (om den inte redan finns förstås),dvs när sidan först visas upp så anropas i ovanstående fall getUsername hos "user för att ge textfältet ett värde. När sidan sedan submittas så uppdateras "user" på motsvarande sätt med ett anrop till setUsername.
<h:commandButton value="#{bundle['button.save']}" action="#{user.saveRegistration}"/>
som definierar en Spara-knapp i formuläret. När knappen aktiveras submittas formuläret och (efter att all validering har gått igenom) ett anrop sker till metoden saveRegistration i "user".
User "backing bean": Klassen UserBean innehåller dels de attribut som skall koppls till komponenterna på createRegistration.jsp, dels också de metoder som kan anropas som en följd av användarens aktivitet på sidan. Hur bönan skapas och i vilket scope beskrivs i konfigurationsfilen.
Utdrag ur faces-config.xml:
...
<managed-bean>
<managed-bean-name>user</managed-bean-name>
<managed-bean-class>se.bluefish.jsf.example.UserBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
...
Här ser vi att namnet som skall användas för att komma åt bönan är "user" och att den får session-scope, dvs samma instans av UserBean kommer att användas för hela sessionen. Det är även möjligt att definiera attributvärden som skall sättas när bönan skapas.
Metoden saveRegistration i UserBean anropas när användaren klickar på Spara-knappen i createRegistration.jsp.
...
public String saveRegistration()
{
FacesContext facesContext = FacesContext.getCurrentInstance();
UserDatabase database = getDatabase(facesContext);
try
{
if (mUser == null)
mUser = getDatabase(facesContext).createUser(mUserName);
writeProperties();
database.save();
return "success";
}
catch (Exception e)
{
facesContext.addMessage ("registration_form", new FacesMessage(e.getMessage()));
return "failure";
}
}
..
- Databasobjektet plockas fram mha context
- Om det inte redan finns en användare (samma klass och metod används för editering av en befintlig användare) så hämtasen ny användare mha databaslagret
- Användaren uppdateras med nya värden
- Användaren sparas av databaslagret
Värdet på den sträng som returneras används internt av JSF för att navigera användaren till nästa (ev. samma) sida. Navigationsreglerna definieras också i konfigurationsfilen. Vid fel sparas ett felmeddelande för att sedan kunna visas upp för användaren. Observera att innan denna metod anropas av JSF endast om en validering av inmatad data inte fallerar.
Navigationsregler: Navigeringen i applikationen, dvs var användaren hamnar när hon lämnar en sida, beskrivs även den i konfigurationsfilen faces-config.xml:
...
<navigation-rule>
<from-view-id>/WEB-INF/jsp/createRegistration.jsp</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/WEB-INF/jsp/editRegistration.jsp</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>failure</from-outcome>>
<to-view-id>/WEB-INF/jsp/createRegistration.jsp</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>cancel</from-outcome>
<to-view-id>/index.jsp</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
...
Här kan vi se att vid "success", dvs efter en lyckad Spara, så skall användaren hamna på editRegistration.jsp. Misslyckas Spara, dvs om saveRegistration returnerar "failure" så kommer användaren tillbaka till createRegistration.jsp där eventuella felmeddelanden visas upp. Avbryt, dvs "cancel" tar användaren tillbaka till startsidan.
Hur är då JSF jämfört med Struts?
Många brukar i diskussioner om ramverk för att bygga webbapplikationer dela upp dessa i två olika typer, dels
? gränssnittsorienterade, dvs ramverk vars främsta syfte är att underlätta byggandet av själva webbgränssnittet. Dessa ramverk koncentrerar sig ofta på att erbjuda återanvändbara komponenter med möjlighet att knyta händelser till en komponent och lägger inte lika mycket krut på infrastrukturen. Detta är jämförbart med ramverk för att bygga traditionella gränssnitt, exempelvis Swing och SWT. Ett exempel på ett sådant ramverk är Tapestry.
? strukturorienterade, dvs ramverk vars syfte är att ge webbapplikationerna en infrastruktur genom att erbjuda stöd för resurshantering, navigering, i18n, explicit hantering av http-requests mm. Struts tillhör denna kategori av ramverk.
JSF är väl närmast att hänföra till den första kategorin, men det överlappar en del eftersom det även ger stöd för navigering etc.
Mitt första spontana känsla när jag började titta på JSF var att jag kände mig vilsen och saknade den struktur som exempelvis Struts ger mig. Har du gjort några Strutsbaserade webbapplikationer vet du hur du skall börja när det är dags att göra nästa app. Du skriver några actions med tillhörande forms, sätter ihop dem i struts-config.xml och vips (i bästa fall) är din applikation uppe och snurrar.
Denna känsla försvann dock alltmer ju mera jag trängde in i JSF. Jag insåg att JSF erbjöd mig i stort sett samma funktionalitet som Struts men i en annorlunda förpackning. Exempelvis så kan ju en "managed bean", som jag tidigare beskrivit, fungera som både action och form, dvs det blir inte samma explosion av klasser som det lätt kan bli i Struts. Å andra sidan finns det en hel del i Struts som det inte finns någon motsvarighet till (ännu så länge) i JSF, exempelvis plugins och möjligheten att dela upp appen i moduler. Arbete pågår med att ge stöd för att integrera JSF med Struts, en möjlighet för de som vill använda JSF endast för det som Struts inte erbjuder.
Kommer jag att använda JSF nästa gång jag skall bygga en webbapplikation?
Ja, det tror jag. I så fall skulle det vara intressant att använda någon av de verktyg som nu börjar dyka upp för att visuellt skapa jsp-sidorna. Exempel på sådan verktyg är Sun's egna utvecklingsmiljö för JSF Java Studio Creator och JSF Studio som antingen kan köras som en egen app eller användas som en plugin i Eclipse.
Sammanfattning
Efter vissa initiala problem gick det över förväntan att konvertera Strutsapplikationen till JSF. Ett antal klasser försvann, några kom till men totalt så minskade antalet klasser från 21 till 12, mestadels beroende på att alla action- och formklasser nu ersatts av två "backing bean" klasser, UserBean och SubscriptionBean.
Vilka var problemen då? Ja, bl a så tog det ett tag att komma underfund med hur man skickar med requestparametrar vid submit, exempelvis vid editering av en prenumeration på ett e-postkonto. Editering görs i editSubscription.jsp och den SubscriptionBean som skapas som "backing bean" måste veta vilken prenumeration som skall editeras. Följande kodsnutt från editRegistration.jsp visar hur det går till:
...
<h:commandLink id="editSubscription" action="editSubscription">
<h:graphicImage url="../../images/edit.gif" styleClass="no-hover" style="border:0px"/>
<f:param name="host" value="#{var_subscription.host}" />
</h:commandLink>
...
<f:param name="host" value="#{var_subscription.host}" />
skapar ett dolt fält som vid aktivering av länken skickas med som requestparameter. Detta fungerar dock bara för commandLink, inte för commandButton.
Jag försökte också använda JSTL:s iterationstagg forEach för att addera element till en meny:
<h:selectOneMenu id="type" value="#{subscription.type}" required="type">
<c:forEach var="serverType" items="${applicationScope.serverTypes}">
<f:selectItem itemValue="#{serverType.key}" itemLabel="#{serverType.value}"/>
</c:forEach>
</h:selectOneMenu>
Servertyperna var definierade i en map där nyckeln var protokollet, dvs IMAP eller POP3, och värdet var protokollet i klartext, dvs IMAP server eller POP3 server. Ovanstående kodsnutt gav en NPE i SelectItem.setValue. Utan att gå in på detaljerna (det är mera invecklat än vad man kan tro) beror detta på att det inte går att mixa en JSF-tagg med forEach på detta sätt.
Jag gick runt detta genom att i stället definiera servertyperna som SelectItems vilket innebar att jag kunde använda följande kod:
<h:selectOneMenu id="type" value="#{subscription.type}" required="type">
<f:selectItems value="#{applicationScope.serverTypes}" />
</h:selectOneMenu>
Vi kan konstatera att JSF fungerar bra för presentationslagret i en MVC baserad webbapplikation och kan åtminstone i denna exempelapplikation utan problem ersätta Struts. Då denna applikation är tämligen enkel och saknar den tydliga skiktning som rekommenderas för en mera komplex applikation skulle det vara intressant att bygga vidare på applikationen och kombinera JSF med exempelvis Spring och Hibernate.
/Lars Svadängs
Länkar
JSF hos Sun JSF Central Bra artikel om JSF, Spring och Hibernate JSF vs Struts en artikel som jämför JSF och Struts Struts Or JSF? Struts And JSF? där Craig McClanahans redogör för sin syn på Struts och JSF. Han är osedvanligt kompetent till detta eftersom han dels är pappa till Struts, dels är inblandad i specifikations- och implementeringsarbetet med JSF.
|