Huomautus tiedostojen nimeämisestä
5.5 Pavuista eli JavaBeaneista
5.5.1 Esimerkin toimintaan laittaminen:
5.5.2 Pavun elinaika ja näkyvyys eli scope
5.6 Kuinka käsitellä ongelmatilanteet
JSP-sivuilla
Tässä osassa tutoriaalia paneudutaan JSP sivujen toimintaan.
Ennen JSP-tekniikkaa pelkästään servlettejä käytettiin palvelinsovellusten ohjelmointiin. Dynaamisen käyttöliittymien muodostaminen osoittautui erittäin hankalaksi, sillä kaikki tulostettava HTML-koodi piti upottaa servlettiin. Käyttöliittymän ylläpito osoittautui hankalaksi. JSP oli tähän ongelmaan pelastava enkeli. JSP on tekniikkana verrattavissa ASP:hen[1] ja PHP:hen[2]
JSP sivut ovat puhtaita tekstidokumentteja. Selaimen pyytäessä JSP sivua JSP moottori muuttaa JSP koodit servletiksi, kääntää servletin ja upottaa servletin tulostuksen vastaukseen. JSP sivujen avulla on mahdollista prosessoida asiakkaan pyyntö (request) ja vastata siihen (response) aivan kuten servleteilläkin.
JSP sivujen avulla on helpompaa erotella staattinen ja dynaaminen sisältö sovelluksessasi kuin servleteillä. Syy tähän on selvä; servletit tulostavat HTML-sivun, kun taas JSP koodi upotetaan HTML:n joukkoon. Okei, tästä saattaa tulla sellainen kuva, että JSP sivut ovat yhtä sekamelskaa. Välillä HTML:ää ja välillä Java koodia. Oikein koodattuna lopputulos on kaukana tästä. Itse asiassa JSP ohjaa kirjoittajan mielestä paremmin olio-pohjaiseen ohjelmointiin kuin servletit (tästä esimerkkinä myöhemmin osassa käytetyt pavut eli JavaBeanit)! Servlettejä kehittäessä tulee helposti tehtyä lähes proseduraalista ohjelmointia sovelluslogiikan nojatessa doGet ja doPost metodeihin sekä muihin aliohjelmiin.
JSP sivuilla usein käytetty tekniikka on käyttää papuja[3] (JavaBeans). Näin toimiessa ohjelmoija luo luokan, jonka ilmentymää (siis oliota) JSP sivuilla käytetään. Tästä lisää kappaleessa JSP ja JavaBeans.
JSP sivu on tekstipohjainen, ja sisältää kahdenlaisia osia:
Esimerkissä (muokattu lähteestä [9]) luodaan JSP sivu ja kaksi papua. Sovelluksessa käyttäjä valitsee kielen (lokaalin) jonka mukaan nykyinen päivä näytetään.
Kuva 1. locales.jsp sivu
Kun olet klikannut get date - painiketta, sivu ladataan uudestaan ja siihen sisällytetään date.jsp sivu.
Kuva 2. locales.jsp sivun suoritus
Ole varovainen, kun vaihdat kapitaaleja JSP tiedostojesi nimissä (myös Windowsin käyttäjät)!
Jos sinulla on esimerkiksi ollut tiedosto nimellä testisivu.jsp ja olet ajanut sen, ja myöhemmin nimeät sen uudelleen testiSivu.jsp, niin saatat joutua käymään poistamassa TOMCAT_HOME\work\standalone\localhost\tutor\testisivu_jsp.java[4] tiedoston ennen kuin saat uudelleen nimeämäsi JSP sivun toimintaan.
Edellä
mainittu hakemisto sisältää JSP sivuista tuotettujen servlettien lähdekoodit!
Kyseisiä koodeja voit käyttää hyväksesi esimerkiksi debugatessasi[5]
JSP sivujasi.
Jos sinulla on käytössä Java versio 1.4 tai uudempi , niin kaikki pavut pitää olla paketissa (package paketin_nimi;)! Aiemmissa Java-versioissa papuja ei tarvitse paketoida (lisäinformaatiota http://www.thejspbook.com/faq/details.jsp?id=1007) .
Esimerkin toimintaan laittamisen vaiheet:
Oletan, että olet tottunut HTML:n rakenteeseen ja syntaksiin, joten sitä en selitä. Seuraavassa lyhyet selitykset eri JSP elementeistä joita esimerkissä on käytetty. Tarkemmat selitykset löydät ensi kappaleesta. Katso tiedostojen
lähdekoodeja. Papujen koodit kannattaa ehkäpä tutkia vasta kappaleen ”pavuista” lukemisen jälkeen. Seuraavassa taulukossa on lyhyesti selitetty käytettyä JSP syntaksia.
Taulukko 1. JSP syntaksista
KOODI |
KUVAUS |
<%@ page
import="java.util.*" %> |
JSP:n page direktiivi, attribuuttina import. Otetaan tarvittavat kirjastot käyttöön. |
<jsp:useBean
id="locales" scope="application"
class="pavut.MyLocales"/> |
Jsp_useBean elementillä luodaan olio määritetystä luokasta. |
<%
… %> |
Skripletti, eli java koodia, jossa on varsinainen sivun toiminta. |
<%=
… %> |
Tämän elementillä (expression-elementti) sisällä olevien muuttujien arvot tulkitaan ja muunnetaan tarvittaessa merkkijonoiksi sekä näytetään sivulla. |
<jsp:include
… /> |
kutsutaan toista sivua ja sivun vastaus (response) liitetään tämän sivun vastaukseen. |
Jokaisella JSP sivulla on muutama implisiittinen objekti valmiina käytettäväksi. Objektit näet katsomalla JSP sivusta tuotetun servletin lähdekoodia (käännettyjen servlettien koodit löytyvät hakemistosta TOMCAT_HOME\work\Standalone\localhost\tutor).
javax.servlet.jsp.PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
Lisäksi implisiittisiä objekteja ovat
Aivan kuten servleteissäkin, on JSP sivulle mahdollista määritellä init ja destroy metodit (ks. sendEmailGroup.jsp). Tämä tapahtuu määrittelemällä metodit
· jspInit (vastaa servletin itit metodia)
· jspDestroy (vastaa servletin destroy metodia)
Metodien toiminta on aivan vastaavaa kuin servleteillä. JspInit() metodi tapahtuu, kun JSP sivua pyydetään ensi kertaa. JspDestroy metodi puolestaan tapahtuu, kun sivua vastaava servletti vapautetaan muistista. Tähän voi olla syynä joko JSP moottorin sulkeminen tai että sivua ei ole tarvittu vähään aikaan jolloin JSP moottori saattaa vapauttaa muistit tehokkuussyistä.
Pavut ovat aivan tavallisia Javalla kirjoitettuja luokkia, mutta papujen anatomian tulisi toteuttaa seuraavat kolme sääntöä[6]:
Seuraavassa esimerkissä on toteutettu muutama papu.
Esimerkin toimintaan laittamiseksi pitää suorittaa seuraavat vaiheet:
Kun menet sivulle papuTesti.jsp ensimmäistä kertaa, näet tekstin: papu luotu. Nyt kun käyt jossakin toisella sivulla tai suljet selaimen niin huomaat, että papu luodaan uudestaan. Tämä johtuu pavun scope-ominaisuudesta.
Muuta paputesti.jsp tiedostosta teksti
<jsp:useBean id="papu" class="pavut.LukuPapu" scope="page"/>
seuraavanlaiseksi
<jsp:useBean id="papu" class="pavut.LukuPapu" scope="session"/>
Muuta pavun arvoa ja sulje selain (pavun alkuarvona on 1, ks Papu.java). Palaa sivulle takaisin. Huomaat, kuinka papu on säilyttänyt antamasi arvon. Kun muutat pavun arvoa uudestaan, uusi arvo lisätään vanhaan ja tulos näytetään.
Älä ikinä käytä skripleteissä luomiasi olioita <jsp:useBean .. /> tagissa. Skripleteissä luodut oliot eivät välttämättä (lue:todennäköisesti) ole saatavilla pageContextista useimmissa JSP moottoreissa.
Pyri aina käyttämään <jsp:setProperty … /> ja <jsp:getProperty … /> tageja vaikka voitkin kutsua olion get ja set-metodeja suoraan.
JSP sivusta käännetyt käännetyt servletit löydät lähdekoodeineen TOMCAT_HOME\work\Standalone\localhost\tutor hakemistosta.
Voit asettaa pavun scope ominaisuuteen yhden seuraavista arvoista
Jossain tapauksissa voit haluta poistaa pavun ennen sen ”luonnollista kuolemaa”. Tämä parantaa palvelimesi suorituskykyä sekä lisää turvallisuutta. Esimerkiksi voisit haluta poistaa käyttäjän loggautumistiedot käyttäjän loggautuessa ulos. Tämän teet seuraavasti pavun nimeä käyttäen. Pavun poistaminen on riippuvainen pavun scopen asetuksesta kuten seuraavasta taulukosta ilmenee:
Taulukko 2. Papujen käyttö
Pavun scope |
Poisto skripletissä |
Poisto servletissä |
session |
session.removeAttribute(name) |
HttpSession.
removeAttribute(name) |
request/page |
pageContext.removeAttribute(name) |
ServletRequest.
removeAttribute(name) |
Application |
application.removeAttribute(name) |
ServletContext.
removeAttribute(name) |
Palataanpa äskeiseen papuesimerkkiin. Syötä pavulle uudeksi arvoksi ”iso”. Tomcat palvelin generoi virheilmoituksen. Mistä tämä johtuu? Katsotaan pavun koodia (LukuPapu.jsp), jota JSP sivu kutsuu.
public int muutaLukua( String Luku )
{
if( Luku != null && Luku != "" )
return muutaLukua( Integer.parseInt(Luku) ); //Integer.parseInt aiheuttaa poikkeuksen
return luku; //jos ehto epätosi
}
Mitä sitten voimme tehdä tämän estämiseksi? Seuraavassa on esitelty kolme vaihtoehtoa.
public int muutaLukua( String Luku )
{
int muutos = 0;
try
{
muutos = Integer.parseInt(Luku);
this.muutaLukua(muutos);
}catch ( NumberFormatException e )
//poikkeuksen käsittely (error handling)
{ //merkkijonoa ei voitu muuntaa kokonaisluvuksi
virheLippu = true;
}
finally {
return luku;
}
}
<%
String arvo = "";
int muutos = 0;
try {
arvo =
request.getParameter("arvo");
if( arvo == null )
out.print("papu luotu");
else
{
muutos = Integer.parseInt( arvo );
out.print("<h3>Pavun arvo nyt " + papu.muutaLukua( muutos ) );
}
}
catch (NumberFormatException e )
//käsittellään NumberFormatException
{
out.print("Anna kokonaislukuarvo.<BR>");
out.print( "(" + e.toString() + ")"); //tulostetaan virheilmoitus suluissa
}
catch (Exception ee ) //muut poikkeukset
{
out.print("Tapahtui odottamaton poikkeus" + ee.toString());
}
%>
Kolmannessa tekniikassa (eli errorPages) käytössä on huomattava, että minkäänlaista tulostusta ei saisi olla syntynyt (eli puskurointi on pois päältä tai tulostuspuskuri on täyttynyt) ennen virheen syntymistä. Jos näin on, tämä tulostus näkyy virhesivulla. Katso esimerkki (aiheutaPoikkeusEiToimi.jsp). Esimerkissä tulostuspuskuri täyttyy mikä puolestaan johtaa puskurin tyhjenemiseen, jolloin tulostuksemme ehtii tapahtua ennen kontrollin siirtymistä (forward) virhesivulle. Huomaa, ettei koko silmukan tulostus ehdi tapahtua. Mieti miksi!
JSP sivusta tehdään poikkeussivu seuraavalla määrittelyllä (ks. poikkeusSivu.jsp)
<%@ page isErrorPage="true" %>
Seuraavaan olen koonnut muutamia pieniä esimerkkejä, jotka saattavat olla sinulle hyödyksi.
koodeissa lisäksi päivämäärä ja aika formatoitu Suomen mallisiksi
<%
File f = new File(application.getRealPath(
request.getServletPath() ));
Date modified = new Date( f.lastModified()
);
%>
<jsp:useBean
id="date" class="pavut.MyDate" type="MyDate"
/>
<HTML>
<BODY>
Sivua on viimeksi muokattu
<%
out.print( date.toFinnishDateTime(modified)
);
%>
<%@
page import="javax.servlet.jsp.JspFactory" %>
<%
JspFactory factory = JspFactory.getDefaultFactory(); %>
<body>
<p>
Käytössä on JSP versio
<%=
factory.getEngineInfo().getSpecificationVersion() %>
</body>
<%@ page import="java.io.*" %>
<%!
public String run(String cmd) {
try {
Runtime rt = Runtime.getRuntime();
Process p = rt.exec(cmd);
InputStreamReader in = new InputStreamReader(p.getInputStream());
BufferedReader reader = new BufferedReader(in);
StringBuffer buf = new StringBuffer();
String line;
String newline = "\n";
while ((line = reader.readLine()) != null) {
buf.append(line);
buf.append(newline);
}
reader.close();
p.getInputStream().close();
p.getOutputStream().close();
p.getErrorStream().close();
p.waitFor();
return buf.toString();
}
catch
(Exception e) {
return (e.getMessage());
}
}
%>
<html>
<head>
<title>Webapplication tutoriaali - järjestelmäkomentojen suorittaminen</title>
</head>
<body>
<%-- Unix ollut pystyssä <%= run("/usr/bin/uptime") %> --%>
Ajetaan c:\temp\winver.bat tiedosto, joka sisältää ainoastaan komennon: <BR>
ver <BR>
Tulos näkyy alla <BR>
<%= run("c:\\temp\\aja.bat") %>
</body>
</html>
(eihän tässä muuten mitään ongelmaa ole, mutta kun Tomcat suorittaa kaikki JSP tiedostot niiden koodisisällön näyttämisen sijaan)
http://localhost/tutor/viewSource.jsp?url=JSPversio.jsp)
<jsp:include
page=”vsbutton.jsp” flush=”true” />
saat sivullesi painikkeen, jonka avulla sivun koodi näytetään uudessa ikkunassa (apuna käytetty JavaScriptiä)
·
viewAllSource.jsp. ViewAllSource tulostaa linkkilistan
hakemistossa olevista jsp sivuista. Linkkiä klikkaamalla kutsutaan
viewSource.jsp tiedostoa, jonka tulostus näytetään. (Antamalla parametrin all
listataan kaikkien JSP tiedostojen koodit allekkain. Tätä käyttämällä listasin
itse tutoriaalin loppuun kaikki JSP koodit. Lisäksi antamalla parametrille hake
arvon, listataan kyseisessä hakemistossa olevat tiedostot, myös muut kuin JSP
tiedostot)
Listaa kaikki JSP koodit:
http://localhost/tutor/viewAllSource.jsp?all=ihan_sama_mita_arvoksi
Listaa kaikki tiedostot tutor/src hakemistosta
Kuva 3. aiheutaPoikkeus.jsp lähdekoodit viewSource.jsp sivulla katsottuna
Listan
tulostus
http://localhost/tutor/viewAllSource.jsp
Kuva 4. viewAllSource.jsp sivun suoritus
Seuraavassa koodien kiinnostavat osat kommentoituna:
<%@
page import="java.io.*" %>
<%
File
path = new File(application.getRealPath("viewAllsource.jsp")).getParentFile();
%> //Haetaan viewAllsource.jsp
tiedoston absoluuttisen polun hakemisto
//eli esim c:\programs\…\tomcat\webapps\tutor
…
<%
File [] files = path.listFiles(); //Tiedostolistaus tästä hakemistosta
if( files != null )
for( int i = 0; i < files.length; i++ )
{
// tiedostot käydään läpi silmukalla, jos ,ei jsp tiedosto niin continue
if( tarkennin( files[i].getName()
).compareToIgnoreCase(".jsp") != 0)
continue;
%>
…
<%
if( request.getParameter("all") ==
null )
{ // jos all parametria ei ole annettu, niin listataan tiedostojen nimet
out.print("<a
href=/tutor/viewSource.jsp?url=");
out.print(files[i].getName() +
">");
out.print("<h3>"+files[i].getName()+"</h3></a>");
}
else
{ //muutoin tulostetaan tiedoston nimi
out.println("<h3>" +
files[i].getName() + "</h3>");
//out.print("TEsti");
%> //ja incluudataan viewSource.jsp:n tulostus eli tiedoston sisältö
<jsp:include
page="/viewSource.jsp">
<jsp:param name="url"
value="<%=files[i].getName()%>"/>
</jsp:include>
<%
}
}
%>
…
<%! //declaration tagi, aliohjelma palauttaa tarkentimen
public String tarkennin(String file)
{
int i = file.lastIndexOf(".");
if( i == -1)
return "";
return file.substring(i, file.length() );
}
%>
String url =
request.getParameter("url");//url==tiedoston nimi
if
(url.indexOf("..") > -1)
throw new java.io.IOException("Relative
paths are not allowed");
File
realPath = new File(application.getRealPath(url));
%>
…
<%
FileInputStream
fis = null;
try {
BufferedReader reader;
reader = new BufferedReader(new
InputStreamReader(fis));
String line; //luetaan tiedostoa rivi kerrallaan
while ((line = reader.readLine()) !=
null) {
line = replace(line, "&",
"&"); //html muotoon
line = replace(line, "<",
"<");
line = replace(line, ">",
">");
out.println(line);
}
}
catch
(IOException e) {
out.println("IOException: " +
e.getMessage());
}
finally
{ if (fis != null) fis.close(); }
%>
</pre></body></html>
<%!//aliohjelmalla korvataan old osat replacement osalla
public
String replace(String s, String old, String replacement) {
int i = s.indexOf(old);
StringBuffer r = new StringBuffer();
if (i == -1) return s;
r.append(s.substring(0,i) + replacement);
if (i + old.length() < s.length())
r.append(replace(s.substring(i +
old.length(), s.length()),
old, replacement));
return r.toString();
} %>
[1] Active Server Pages
[2] Ks. http://www.php.net/
[3] Ks. Kappale ”Pavuista eli JavaBeaneistä”
[4] Tänne hakemistoon Tomcat kääntää JSP sivut servleteiksi.
[5] Debuggaaminen eli virheiden korjaaminen.
[6] Tarkemmin http://java.sun.com/products/javabeans/docs/spec.html