5.3.1 Käyttöliittymän ohjelmointi
appleteilla
5.3.2 Käyttöliittymän ohjelmointi
JavaScriptillä
5.3.3 Tietojen validointi palvelinpuolella
Yhteenveto ensimmäisestä osasta
5.6 ServletContext ja
alustusparametrien käyttö
Huomautus sessioista JSP sivuissa
Web sovellus jakaantuu perinteisesti kolmeen osaan (MVC[1])
Näiden kolmen kerroksen erillään pitäminen on tärkeää varsinkin sovelluksen ylläpidon kannalta.
Javalla web sovellusta tehdessä on oleellista käyttää hyväkseen papuja (Java Beans[2]), joihin sovelluksen varsinainen äly eli toiminnallisuus koodataan. Näin toimiessa on helppoa muuttaa käyttöliittymää tai ohjelmalogiikkaa myöhemmin. Tämä mahdollistaa myös sen, että eri henkilöt tekevät käyttöliittymän ja toiset logiikan. Kannattaa huomata myös hyvin tehtyjen luokkien uudelleenkäytön mahdollisuus.
Eräs esimerkki papujen hyödyistä olisi valuuttojen käyttö. Mikäli ennen euroihin siirtymistä aiempi valuutta (Suomen markka) olisi koodattu suoraan käyttöliittymään, olisi kaikki nämä käyttöliittymän osat jouduttu koodaamaan uudestaan. Jos sovelluksessa olisi käytetty papua, olisi riittänyt koodata uudestaan get-metodi, joka välittää valuutan tiedot.
Tyypillisesti esimerkiksi tietokannasta haku tehdään seuraavasti papuja hyödyntäen
Pavuista lisää myöhemmin[3].
Tyypillisesti sovelluksen käsittelemä tieto on tietokannassa. Tietokanta voi sijaita samalla palvelimella kuin sovelluskin.
Javassa tietokantoja käsitellään JDBC[4] (Java Database Connectivity) API:n avulla.
Käyttöliittymä muodostetaan tyypillisesti html-sivuilla ja JSP sivuilla. Tyypillisesti käyttöliittymä on siis html-pohjainen, mutta tietyissä tapauksissa ohjelman logiikkaa voidaan siirtää asiakkaan puolelle.
Periaatteessa kaiken voisi tehdä palvelinpuolella, mutta on huomattavasti järkevämpää hoitaa osa toiminnasta asiakkaan selaimessa. Varsinkin asiakkaan antamat tiedot kannattaa tarkastaa ennen kuin ne lähetetään palvelimelle esimerkiksi talletettavaksi tietokantaan.
Tietojen tarkastamisessa asiakkaan puolella on käytännössä kaksi vaihtoehtoa (muita esim. flash[5]):
Appletteja[6] käyttämällä pystyt hyödyntämään Java kielen ominaisuuksia. Appletin rakentaminen on helppoa visuaalisten editorien kuten Borlandin Jbuilderin[7] avulla. Tosin applettien lataaminen on hidasta niiden suhteellisen ison koon takia.
Appletilla saat tosin tehtyä kohtuullisen helposti dynaamisia käyttöliittymiä, sillä ne voivat kommunikoida servlettien ja JSP sivujen kanssa (lähde [1]. sivu 228) käyttäen URLConnection[8] luokan oliota jonka avulla appletti saa syötteenä kaiken servletin standardi tulostuksen.
Esimerkkinä AppletServletApplet.html.
Kuva 1. Appletti, joka kutsuu servlettiä ja näyttää haun tulokset
Esimerkissä tarvittavat tiedostot:
Koodeihin ei aloittelijan kannata vielä tässä vaiheessa tarkemmin perehtyä.
Jos et saa esimerkkiä toimimaan, koita käyttäen sivua AppletServletAppletConverted.html. Sivu on käsitelty HTMLConverter[9] ohjelmalla, jonka avulla varmistetaan Java PlugIn:n käyttö. Toinen testattava asia on korvata localhost koneesi IP osoitteella[10].
Toinen tapa, jolla palvelimelta voidaan välittää tietoa appletille on appletin parametrit. Palvelin generoi html-sivun, johon appletti ladataan. Samalla generoidaan sivulle vaihteleva määrä parametreja.
Mielestäni
appletteja kannattaa käyttää vain jos tarvitaan todella näyttävää (jos html ja
skriptit eivät riitä) tai korkeampaa logiikkaa vaativaa käyttöliittymää.
Appleteissa on omat hyvät puolensa, mutta valitettavasti suurin haitta on
selainten tuki ja toiminta. Eri selaimet saattavat näyttää appletin erilailla
ja osassa selaimista appletti ei välttämättä toimi lainkaan. Muista aina
testata ohjelmasi usealla selaimella.
Scriptikielillä kuten JavaScriptillä[11] on näppärää ja helppoa (ei tosin kovin ohjelmoinnillisesti eleganttia) suorittaa pieniä tarkastuksia ja käyttöliittymän viilauksia tapahtumien sekä funktioiden avulla. Huomattavaa on, että tarkastus pitää suorittaa myös palvelimella, sillä asiakkaalla ei välttämättä ole JavaScript päällä selaimessa.
Seuraavassa pieni esimerkki, jossa tarkastetaan seuraavat asiat ennen kuin lomakkeen tiedot lähetetään palvelimelle. Palvelimella servletti näyttää annetut tiedot. Asiakkaan selaimella tarkastetaan seuraavat tiedot JavaScriptiä käyttäen:
Mikäli jokin näistä ei pidä paikkaansa, käytetään selaimen alert-metodia näyttämään virheilmoitus käyttäjälle. Väärät kentät myös voitaisiin tyhjentää (tässä oltava erittäin varovainen. Jos käyttäjä on kirjoittanut esim. 256 merkkiä ja raja on 255, niin häntä saattaa ottaa rajusti pannuun jos ohjelma tyhjentää kentän ja käyttäjä joutuu näpyttelemään tekstinsä uudestaan), scriptissä kenttien tyhjennys kommentteina.
<servlet>
<servlet-name>Jep</servlet-name>
<servlet-class>Jep</servlet-class>
</servlet>
Nyt testaa lomakkeen toimintaa osoitteessa tutor/tarkistus.htm. Testaa ensin kaikki väärät syötteet.
Painettuasi lähetä tiedot painiketta kutsutaan Jep servlettiä, joka tulostaa seuraavanlaisen HTML-sivun. Parametrit ja niiden arvot on laitettu taulukkoon (sivujen ulkonäöstä ei kannata välittää. Tästä ei ollut tarkoituskaan tulla käyttöliittymäopasta).
Kuva 2. Servletti tulostaa lomakkeen tiedot
Lomakkeen tietojen tarkistaminen on toki mahdollista myös JSP:llä ja servleteillä. Jos tiedot eivät ole oikein, tulostetaan käyttäjälle lomake uudestaan. HTTP:n tilattomuudesta johtuen tässä on huonona puolena se, että käyttäjä saa eteensä tyhjän lomakkeen ja joutuu kirjoittamaan kaiken uudestaan. Jos käytät tällaista tekniikkaa, niin pyri siihen, että tulostaessasi lomakkeen käyttäjälle uudestaan täytettäväksi, laita käyttäjän aiemmat syöttötiedot suoraan lomakkeelle valmiiksi.
Nyt kun olet saanut Tomcatin asennettua ja configuroitua, olet saanut toimintaan jsp-sivun sekä servletin, on aika pitää tauko. Seuraavassa osassa tutustutaan Tomcattiin tarkemmin.
Saat koko ensimmäisen osan websovelluksen toimintaan purkamalla tutor_osa1.zip paketin ja kopioimalla tutor hakemiston webapps\tutor hakemiston päälle. Paketti sisältää kokonaisuudessaan seuraavat tiedostot:
Lisäksi
Web sovellusten ohjelmointi Javalla on kohtuullisen helppoa ja selkeää. Servletit tarjoavat helpon tavan kommunikoida web asiakkaiden[12] (web clients), tyypillisesti selainten, kanssa. Servlettien ohjelmoijan ei tarvitse huolehtia verkkoyhteyksien luomisesta, pyyntöjen (request) kiinniottamisesta, protokollien eri versioista eikä vastauksen (response) ohjaamisesta oikealle asiakkaalle. Lisäksi kuten Javassa on tapana, servlettien muistiin lataaminen ja muistin vapauttaminen eivät ole ohjelmoijan rasitteena. Nämä kaikki yksityiskohdat hoitaa servlettimoottori (servlet engine, servlet container) kuten Tomcat.
Seuraavat esimerkit on tehty Java Servlet Development Kit (JSDK) versiolla 2.3, mutta esimerkit toimivat myös aiemmilla JSDK versioilla (testasin esimerkit myös versiolla 2.1).
Servlettien testaamiseen on olemassa hyvä työkalu ServletRunner[13] (JSDK:ssa mukana) minkä kanssa et tarvitse erillistä servlettimoottoria.
Tyypillisesti
servlettien elinkaari kattaa seuraavat vaiheet [1].
Oletetaan, että asiakkaan palveleminen kestää suhteellisen kauan, esim. doGet-metodin suoritus. Miten voidaan tällöin taata, että samanaikaisesti voidaan palvella muita asiakkaita? Kuten kappaleen otsikko vihjaa, vastaus on säikeet. Servletti on koodattava säieturvalliseksi (thread safe), sillä Servlet API ei sitä automaattisesti takaa. Tämä suoritetaan käytännössä viljelemällä synchronized termiä joko metodien otsikossa tai suoritettavien lauseiden yhteydessä.
Säieturvallinen koodi edellyttää luokan attribuuttien käytön suojaamista. Paikallisista muuttujista ei tarvitse olla huolissaan. Helppo mutta kustannustehoton tapa on synkronoida koko metodi seuraavasti
private synchronized void metodi ()
Tämä on hyväksyttävää lähinnä suoritukseltaan lyhyiden metodien yhteydessä tai jos kuitenkin lähes kaikki metodin koodi joudutaan synkronoimaan. Toinen tapa on synkronoida ainoastaan osia metodista seuraavasti.
private void metodi()
{
synchronized ( this ) {
//Nyt vain yksi säie voi suorittaa tämän koodilohkon yhtäaikaa
}
//koodia, joka ei ole synkronoitu
}
Voidaan myös käyttää SingleThreadModel-rajapintaa[18], jolloin taataan, ettei kaksi säiettä pääse yhtä aikaa samaan metodiin käsiksi, vaan odotetaan kunnes yksi asiakas on palveltu. Tässä on haittapuolena palvelun huomattava hidastuminen asiakasmäärän kasvaessa.
Lisää säikeistä ja synkronoinnista luettavissa esimerkiksi osoitteesta http://java.sun.com/docs/books/tutorial/essential/threads/
Alustusparametreilla voidaan välittää servleteille tietoja, joita ne tarvitsevat käynnistyessään ja joita on helppo muokata. Esimerkkinä mainittakoon tietokantayhteyksissä tarvittavat tiedot.
Oletetaan, että haluaisit asettaa jokaiselle sovelluksesi servletille ja JSP:lle jonkin yhteisen tiedon, tavallaan globaalin muuttujan. Miten tämän voisi toteuttaa? Yksi vaihtoehto olisi käyttää ServletContext-oliota, ja ladata sinne servlettiä käyttäen tarvittava data.
Esimerkissä katsotaan seuraavia asioita:
· ServletContexti-olion käyttäminen
· Servletin lataaminen palvelimen käynnistyksen yhteydessä
Esimerkissä käytetään kahta servlettiä. Ensimmäinen servletti LataaContextServlet.java on oleellinen, sillä se alustaa ServletContext-olioon tarvittavan tietorakenteen. Tärkeätä onkin, että tämä servletti ladataan muistiin Tomcatin käynnistyessä ja vieläpä ensimmäisenä. Tämä saadaan aikaan muokkaamalla web.xml tiedostoa seuraavasti[19]:
<servlet>
<servlet-name>LataaContextServlet</servlet-name>
<servlet-class>LataaContextServlet</servlet-class>
<init-param>
<param-name>titleBegin</param-name>
<param-value>WebApplication
tutoriaali - </param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Esimerkin saat toimintaan asettamalla servletit WEB-INF/classes hakemistoon, muokkaamalla web.xml tiedostoa ja käynnistämällä Tomcatin uudelleen (tai pelkän tutor WEBAPPin käyttäen Tomcatin manager-apuohjelmaa). Uudelleenkäynnistys aiheuttaa sen, että servletit initialisoidaan. Itse asiassa servletit initalisoidaan joka kerta automaattisesti, kun olet muokannut web.xml tiedostoa, ja kutsut jotakin servlettiä/JSP sivua (Jos testaat tämän, muista sulkea kaikki selainikkunat ettei selaimen välimuisti sotke testauksiasi). Käynnistyksen jälkeen suunnista osoitteeseen:
http://localhost/tutor/servlet/LueContextServlet
Tällöin huomaat, että selaimen otsikkorivillä lukee web.xml tiedostoon määritelty parametri, jonka LataaContextServlet on asettanut ServletContext-olioon. LueContextServlet puolestaan lukee arvon init-metodissa.
ServletContext-olion parametriolioiden asettaminen tapahtuu seuraavasti (ks. LataaContextServlet.java)
//luetaan alustusparametri, katso web.xml
String titleBegin =
this.getServletConfig().getInitParameter("titleBegin");
System.out.println("titleBegin ==" + titleBegin);
//asetetaan
servletcontextiin kaikkien servlettien ja JSP-sivujen saataville
this.getServletContext().setAttribute("titleBegin",
titleBegin);
ServletContext-oliosta ”lukeminen” eli parametriolioiden käyttöönotto tapahtuu seuraavasti (ks. LueContextServlet.java ja LueContext.jsp):
otsikonAlku = (String)
this.getServletContext().getAttribute("titleBegin");
Kaikki System.out virtaan kohdistettu tulostus on nähtävissä Tomcatin ikkunassa. Tämä on yksi keino tutkia servlettiesi suorituksia.
Tähän on kuitenkin yksi poikkeus: Servletin init-metodi. Itse varmistin, että servlettien init metodit todella tapahtuvat oikeaan aikaan luomalla molempien servlettien init-metodeissa tiedoston. Luotujen tiedostojen timestampistä selviää, milloin servlettien init-metodit tapahtuivat.
Jos init metodissasi on virheitä, saat seuraavan virheilmoituksen:
javax.servlet.ServletException: Cannot allocate
servlet instance for path /tutor/servlet/LataaContextServlet
Kun laitat omia kirjastojasi WEB_INF\lib hakemistoon, laita tiedostot jar-pakettiin, vaikkei tiedostoja olisi kuin yksi. Muuten kirjastoasi ei oteta käyttöön, eivätkä servletit, joissa kirjastoja käytetään, toimi. Toki voit laittaa yksittäiset class-tiedostot WEB-INF\ classes hakemistoon
HTTP protokollan tilattomasta luonteesta johtuen sessioiden käytön hallinta on lähes pakollista kaikissa web sovellusten tekemiseen soveltuvissa kielissä, niin myös Javassa.
Seuraavassa esimerkissä demonstroidaan sessioiden käyttö sekä servleteissä että JSP sivuissa.
Esimerkkinä on rakennettu ostoskori, jossa ostokset tallennetaan sessiotietoihin. Lisäksi esimerkki sisältää yhden servletin ja JSP sivun, jotka näyttävät session tietoja.
Aseta ennen esimerkin testaamista selaimestasi evästeet (cookies) pois päältä!
Osta yksi JavaBean sivulta http://localhost/tutor/ShoppingCart.html. Sen jälkeen sinulle näytetään korissasi olevat ostokset.
Kuva 3. Sessiotietojen käyttö ShoppingCart servletissä.
Testaa myös http://localhost/tutor/sessionTestaus.jsp, jossa on demonstroitu encodeURL metodin tärkeys myös JSP sivujen yhteydessä.
Jos olet asettanut evästeet (cookies) pois päältä, niin linkit joissa lukee sulkeissa ”ei encodedURL” eivät toimi oikein. Seuraavassa selitys tähän.
Normaalisti sessiotietojen tallennukseen käytetään evästeitä. Mikäli kuitenkin evästeet on poistettu käytöstä, pitää session id numero tallentaa URL osoitteeseen (tai lomakkeen piilotettuihin kenttiin). Tämän vuoksi luodessasi sovelluksia, jotka käsittelevät sessioita, pitää kaikki osoitteet käsitellä erityisellä metodilla nimeltä encodeURL.
Eri
asiakkaiden sessiotiedot erotellaan id-numerolla. Session id-numero pitää
olla joko asiakkaan selaimessa
evästeenä, URL-osoitteessa parametrina tai lomakkeen piilotettuna kenttänä. Id
numeron avulla palvelin tietää, kuka asiakas on kyseessä.
Metodeilla encodeURL ja encodeRedirectURL (käyttö kun haluat ohjata pyyntöjä eteenpäin) saat sessio id:n tallennettua osoitteeseen. Esimerkissä encodeURL metodia on käytetty seuraavasti
String URL =
"http://localhost/tutor/session.jsp";
String encodedURL = res.encodeURL( URL);
Sessio id:tä ei kuitenkaan tallenneta osoitteeseen kahdessa tapauksessa
Session luominen ja käyttö tapahtuu käyttämällä HttpServletRequest[20] luokan metodia getSession.
// otetaan session objekti, luodaan jos ei ole olemassa
HttpSession session = req.getSession(true);
Mikäli parametrilla on arvo true, sessio luodaan ellei sitä ole jo olemassa. Jos sessio on olemassa, se ainoastaan otetaan käyttöön. Mikäli parametrin arvo on false ja sessiota ei olemassa, palauttaa metodi arvon null.
Session attribuutteja käsitellään seuraavilla metodeilla (JSP 1.1 spesifikaatiossa)
·
putValue (String nimi, Object objekti) – asettaa uuden attribuutin nimen ja arvon
·
getValue (String nimi) – palauttaa attribuutin arvon ja null jos attribuuttia ei ole
Uudemmissa spesifikaatioissa suositellaan käytettäväksi setAttribute ja getAttribute metodeja. Metodien käyttö on täsmälleen samanlaista kuin niiden ”esi-isien” (esimerkeissä on käytetty molempia).
JSP
sivuihin sessioiden käyttö on ”sisään-rakennettu”, eli yhtenä JSP sivun
implisiittisenä objektina on session. Älä siis luo sessiota äläkä ota käyttöön
sessio objektia eksplisiittisesti, sillä objekti on olemassa automaattisesti.
Netscape Navigatorilla[21] sessioiden ja evästeiden testaaminen on huomattavasti helpompaa, koska evästeiden managerointi on hoidettu paremmin kuin Internet Explorerissa. Lisäksi Netscape näyttää tilarivillä linkissä olevan sessioID:n, IE[22] puolestaan ei. Itse asiassa en itse saanut IE:tä poistamaan evästeiden käyttöä käyttäessäni localhostia[23], vaikka kuinka yritin. Kielsin selaimen asetuksista evästeiden käytön, tyhjensin temporary internet files kansion ja suljin selaimen uudestaan. Silti sessiotiedot kyettiin lukemaan vaikkei linkkiä ollutkaan käsitelty encodeURL metodilla. Toiselta koneelta ajettuna IE:kin suostui toimimaan. Outoako?
Netscapella katsottaessa ShoppingCart servletin tulostaman html-sivun lähdekoodi on seuraavanlainen.
<html><head><title>Shopping Cart Contents</title></head><body>
<h1>Items currently in your cart</h1>
<hr>JavaBeanie Baby<br><hr><a HREF="../ShoppingCart.html">Back
to the shop</a><br>
<a HREF="../session.jsp;jsessionid=9F590CDBA347EB83CAD40EB31C530C2A">
sessiotiedot JSP </a>
<br>
<a HREF="../session.jsp">sessiotiedot
JSP(ei encodedURL)</a>
<br>
<a HREF="SessionInfoServlet;jsessionid=9F590CDBA347EB83CAD40EB31C530C2A">
sessiotiedot servlet</a>
<br>
<a HREF="SessionInfoServlet">sessiotiedot
servlet(ei encodedURL)</a>
<br>
</body></html>
Kuten huomaat, session id-numero on liitetty linkin osoitteeseen. IE:llä vastaavaa ei tapahtunut localhostia käytettäessä, joten olettaa saattaa, että kaikesta kielloista huolimatta IE käytti evästeitä sessiotiedon tallentamiseen.
[1] Ks.
http://java.sun.com/blueprints/patterns/MVC-detailed.html
[2] Spesifikaatio nähtävillä osoitteessa http://java.sun.com/products/javabeans/docs/spec.html
[3] Ks. kappale ” Pavuista eli JavaBeaneista”.
[4] Ks. http://java.sun.com/products/jdbc/
[5] Ks. http://www.macromedia.com/
[6] Ks. http://java.sun.com/applets/
[7] Ks. http://www.borland.com
[8] Ks.
http://java.sun.com/j2se/1.4.2/docs/api/java/net/URLConnection.html
[9] Ks.
http://java.sun.com/j2se/1.4.2/docs/guide/plugin/developer_guide/html_converter.html
[10] Windowsissa IP osoitteesi saat selville komennolla ipconfig.
[11] Selaimen ymmärtämä ohjelmointikieli. Huomaa, että eri selaimet ymmärtävät JavaScriptiä varsin vaihtelevasti.
[12] Asiakkaalla tarkoitetaan ohjelmaa, joka kommunikoi palvelimen kanssa
[13] Ks.
http://java.sun.com/developer/onlineTraining/Servlets/Fundamentals/magercises/ServletRunnerHosting/
[14] Yhteysallas, käytetään useasti esimerkiksi tietokantojen kanssa.
[15] Ks.
http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/servlet/http/HttpServlet.html
[16] Ks.
http://java.sun.com/j2ee/sdk_1.2.1/techdocs/api/javax/servlet/http/HttpServletRequest.html
[17] Ks.
http://java.sun.com/j2ee/sdk_1.2.1/techdocs/api/javax/servlet/http/HttpServletResponse.html
[18] Ks.
http://java.sun.com/j2ee/sdk_1.2.1/techdocs/api/javax/servlet/SingleThreadModel.html
[19] Huomaa erityisesti load-on-startup elementti
[20] Ks.
http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/servlet/http/HttpServletRequest.html
[21] Ks. http://www.netscape.com
[22] Microsoft Internet Explorer