Skip to content

3. Wprowadzenie do serwletów Java i stron JSP

W niniejszym rozdziale przedstawiono różne przykłady serwletów i stron JSP. Zostały one przetestowane na serwerze Tomcat, który działa na porcie 8080. Korzystając z linków na stronie głównej, można uzyskać dostęp do przykładów serwletów i stron JSP. Poniższe przykłady pochodzą w większości z przykładów dostarczonych przez Tomcat. Aby je przetestować, wystarczy uruchomić Tomcat, wywołać stronę URL http://localhost:8080 w przeglądarce i kliknąć link do serwletów.

3.1. Serwlety Java

3.1.1. Wysyłanie treści HTML do klienta internetowego

Przyjrzyjmy się powyższemu przykładowi „Hello World”. Serwlet wygląda następująco:


import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorld extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello World!</h1>");
        out.println("</body>");
        out.println("</html>");
    }
}

Po uruchomieniu tego serwletu wyświetla się następujący obraz:

Image

Należy zwrócić uwagę na następujące kwestie:

  • należy zaimportować specjalne klasy dla serwletów:

import javax.servlet.*;
import javax.servlet.http.*;

Biblioteka javax.servlet nie zawsze jest dostarczana w standardowym pakiecie JDK. W takim przypadku można ją pobrać bezpośrednio ze strony firmy Sun.

  • Serwlet dziedziczy po klasie HttpServlet

public class HelloWorld extends HttpServlet {
  • Żądanie GET skierowane do serwletu jest przetwarzane przez metodę doGet

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  • Podobnie żądanie POST skierowane do serwletu jest przetwarzane przez metodę doPost

    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  • Obiekt HttpServletRequest request to obiekt, który zapewnia nam dostęp do żądania wysłanego przez klienta internetowego. Odpowiedź serwletu zostanie wysłana za pośrednictwem obiektu HttpServletResponse response
  • Obiekt response pozwala nam ustawić nagłówki HTTP, które zostaną wysłane do klienta. Na przykład nagłówek Content-type: text/html jest tutaj ustawiony za pomocą:
        response.setContentType("text/html");
  • Aby wysłać odpowiedź do klienta, serwlet wykorzystuje strumień wyjściowy dostarczany przez obiekt response:
        PrintWriter out = response.getWriter();
  • Po uzyskaniu tego strumienia wyjściowego kod HTML jest do niego zapisywany, a następnie wysyłany do klienta:
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello World!</h1>");
        out.println("</body>");
        out.println("</html>");

3.1.2. Pobieranie parametrów przesłanych przez klienta internetowego

Poniższy przykład pokazuje, w jaki sposób serwlet może pobrać parametry przesłane przez klienta internetowego. Formularz do wprowadzania danych:

Image

Odpowiedź wysłana przez serwlet:

Image

Kod źródłowy serwletu wygląda następująco:


import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class myRequestParamExample extends HttpServlet {

    String title="Récupération des paramètres d'un formulaire";

    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>" + title + "</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");
        out.println("<h3>" + title + "</h3>");
        String firstName = request.getParameter("firstname");
        String lastName = request.getParameter("lastname");
        if (firstName != null || lastName != null) {
            out.println("firstname= " + firstName + "<br>");
            out.println("lastname= " + lastName);
        } else {
            out.println("pas de paramètres");
        }
        out.println("<P>");
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
        out.println("firstname= <input type=text size=20 name=firstname>");
        out.println("<br>");
        out.println("lastname= <input type=text size=20 name=lastname>");
        out.println("<br>");
        out.println("<input type=submit>");
        out.println("</form>");
        out.println("</body>");
        out.println("</html>");
    }

    public void doPost(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        doGet(request, response);
    }

}

W porównaniu z poprzednim przykładem należy zwrócić uwagę na następujące zmiany:

  • Parametry wysyłane przez przeglądarkę są pobierane w następujący sposób:

        String firstName = request.getParameter("firstname");
        String lastName = request.getParameter("lastname");

Metoda request.getParameter("nomParamètre") zwraca wskaźnik null, jeśli parametr nomParamètre nie znajduje się wśród parametrów wysłanych przez klienta internetowego.

  • Formularz określa, że przeglądarka musi przesłać parametry za pomocą metody POST
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
  • Otrzymane parametry zostaną przetworzone przez metodę doPost serwletu. W tym przypadku metoda ta ogranicza się do wywołania metody doGet. W ten sposób serwlet ten przetwarza wartości z formularza, niezależnie od tego, czy zostały one przesłane przez metodę GET, czy przez metodę POST.

    public void doPost(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        doGet(request, response);
    }

3.1.3. Pobieranie nagłówków HTTP wysłanych przez klienta internetowego

Poniższy serwlet pokazuje, jak pobrać nagłówki HTTP wysłane przez klienta internetowego:

Image

Kod źródłowy serwletu wygląda następująco:


import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class RequestHeaderExample extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        Enumeration e = request.getHeaderNames();
        while (e.hasMoreElements()) {
            String name = (String)e.nextElement();
            String value = request.getHeader(name);
            out.println(name + " = " + value);
        }
    }
}

Uwagi:

  • To właśnie obiekt request i jego metoda getHeaderNames zapewniają nam dostęp do nagłówków HTTP wysyłanych przez przeglądarkę w postaci listy:
        Enumeration e = request.getHeaderNames();
  • Metoda request.getHeader("nagłówek") pozwala uzyskać konkretny nagłówek HTTP. Powyższy przykład przedstawia kilka z nich. Należy pamiętać, że przedstawione tutaj nagłówki są wysyłane przez przeglądarkę. Serwer również posiada własne nagłówki HTTP, które czasami powielają nagłówki przeglądarki. Nagłówki HTTP wysyłane przez przeglądarkę mają na celu poinformowanie serwera o możliwościach przeglądarki.
Nagłówek
Znaczenie
User-Agent
identyfikator przeglądarki
Accept
formaty MIME obsługiwane przez przeglądarkę. Zatem image/gif oznacza, że przeglądarka potrafi przetwarzać obrazy w formacie GIF
Host
w formacie hote:port. Wskazuje, z jakim urządzeniem i portem przeglądarka chce się połączyć.
Accept-Encoding
format kodowania akceptowany przez przeglądarkę dla dokumentów wysyłanych przez serwer. Jeśli więc serwer posiada dokument w normalnej, nieskompresowanej postaci oraz w formacie skompresowanym gzip, a przeglądarka wskazała, że potrafi przetwarzać format gzip, wówczas serwer będzie mógł wysłać dokument w formacie gzip, aby zaoszczędzić przepustowość.
Accept-language
języki obsługiwane przez przeglądarkę. Jeśli serwer dysponuje tym samym dokumentem w kilku językach, wyśle ten, którego język jest obsługiwany przez przeglądarkę.
Referer
URL, o który poprosiła przeglądarka
Connection
tryb połączenia żądany przez przeglądarkę. Keep-alive oznacza, że serwer nie powinien przerywać połączenia po dostarczeniu żądanej strony do przeglądarki. Jeśli przeglądarka wykryje, że otrzymana strona zawiera na przykład linki do obrazów, będzie mogła wysłać do serwera nowe żądania, aby je pobrać, bez konieczności nawiązywania nowego połączenia. To przeglądarka podejmie wówczas inicjatywę zamknięcia połączenia po otrzymaniu wszystkich elementów strony.

3.1.4. Pobieranie informacji o środowisku

Poniższy serwlet pokazuje, jak uzyskać dostęp do informacji o środowisku wykonania serwletu. Niektóre z nich są wysyłane przez przeglądarkę w postaci nagłówków HTTP i dzięki temu można je pobrać za pomocą poprzedniej metody.

Image

Kod serwletu wygląda następująco:


import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class RequestInfo extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>Request Information Example</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h3>Request Information Example</h3>");
        out.println("Method: " + request.getMethod());
        out.println("Request URI: " + request.getRequestURI());
        out.println("Protocol: " + request.getProtocol());
        out.println("PathInfo: " + request.getPathInfo());
        out.println("Remote Address: " + request.getRemoteAddr());
        out.println("</body>");
        out.println("</html>");
    }

  
    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        doGet(request, response);
    }
}

Informacje te uzyskano tutaj różnymi metodami:

        out.println("Method: " + request.getMethod());
        out.println("Request URI: " + request.getRequestURI());
        out.println("Protocol: " + request.getProtocol());
        out.println("PathInfo: " + request.getPathInfo());
        out.println("Remote Address: " + request.getRemoteAddr());

Poniżej znajduje się lista niektórych dostępnych metod wraz z ich znaczeniem:

metoda
znaczenie
getServerName()
nazwa serwera WWW
getServerPort()
port roboczy serwera WWW
getMethod()
metoda GET lub POST używana przez przeglądarkę do wysłania żądania
getRemoteHost()
nazwa komputera klienckiego, z którego przeglądarka wysłała żądanie
getRemoteAddr()
adres IP tego samego komputera
getContentType()
typ treści wysłanej przez przeglądarkę (nagłówek HTTP Content-type)
getContentLength()
liczba znaków wysłanych przez przeglądarkę (nagłówek HTTP Content-length)
getProtocol()
wersja protokołu HTTP żądana przez przeglądarkę
getRequestURI()
URI żądane przez przeglądarkę. Odpowiada części URL umieszczonej po identyfikatorze hote:port w http://hote:port/URI

3.1.5. Utworzenie serwletu z JBuilder i wdrożenie go za pomocą Tomcata

Opiszemy teraz, jak utworzyć i uruchomić serwlet Java. Wykorzystamy dwa narzędzia: JBuilder do kompilacji serwletu oraz Tomcat do jego uruchomienia. Sam Tomcat mógłby wystarczyć. Niemniej jednak oferuje on ograniczone możliwości debugowania. Wracamy do opracowanego wcześniej przykładu, który wyświetla parametry otrzymane przez serwer. Serwlet wysyła najpierw następujący formularz wprowadzania danych:

Image

Odpowiedź wysłana przez serwlet:

Image

Kod źródłowy serwletu wygląda następująco:


import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class myRequestParamExample extends HttpServlet {

    String title="Récupération des paramètres d'un formulaire";

    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>" + title + "</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");
        out.println("<h3>" + title + "</h3>");
        String firstName = request.getParameter("firstname");
        String lastName = request.getParameter("lastname");
        if (firstName != null || lastName != null) {
            out.println("firstname= " + firstName + "<br>");
            out.println("lastname= " + lastName);
        } else {
            out.println("pas de paramètres");
        }
        out.println("<P>");
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
        out.println("firstname= <input type=text size=20 name=firstname>");
        out.println("<br>");
        out.println("lastname= <input type=text size=20 name=lastname>");
        out.println("<br>");
        out.println("<input type=submit>");
        out.println("</form>");
        out.println("</body>");
        out.println("</html>");
    }

    public void doPost(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        doGet(request, response);
    }

}
  • Utwórz projekt myRequestParamExample w programie JBuilder i dołącz do niego poprzedni program myRequestParamExample.java.
  • Podczas kompilacji może pojawić się następujący problem: plik JBuilder niekoniecznie zawiera bibliotekę javax.servlet niezbędną do kompilacji serwletów. W takim przypadku należy skonfigurować JBuilder tak, aby korzystał z dodatkowych bibliotek klas. Sposób postępowania opisano w załącznikach do niniejszego dokumentu dla JBuilder 7. Częściowo przytaczamy go tutaj:
  • włącz opcję Tools/Configure JDKs lub (Options/Configure JDK)

Image

W sekcji „JDK Settings” powyżej w polu „Name” zazwyczaj znajduje się wersja JDK 1.3.1. Jeśli posiadasz nowszą wersję JDK, użyj przycisku Change, aby wskazać katalog instalacyjny tej wersji. Powyżej wskazano katalog E:\Program Files\jdk14, w którym zainstalowano wersję JDK 1.4. Od tej pory JBuilder będzie korzystać z tej wersji JDK do kompilacji i uruchamiania. W sekcji (Class, Source, Documentation) znajduje się lista wszystkich bibliotek klas, które będą przeszukiwane przez JBuilder – w tym przypadku są to klasy z JDK 1.4. Klasy z tej biblioteki nie wystarczają do tworzenia aplikacji internetowych w Javie. Aby dodać kolejne biblioteki klas, należy użyć przycisku Add i wskazać dodatkowe pliki .jar, z których chcemy skorzystać. Pliki .jar to biblioteki klas. Tomcat 4.x zawiera wszystkie biblioteki klas niezbędne do tworzenia aplikacji internetowych. Znajdują się one w katalogu <tomcat>\common\lib, gdzie <tomcat> to katalog instalacyjny Tomcata:

Image

Za pomocą przycisku Add dodamy te biblioteki, jedna po drugiej, do listy bibliotek przeszukiwanych przez JBuilder:

Image

Od tej chwili można kompilować programy Java zgodne ze standardem J2EE, w szczególności serwlety Java. JBuilder służy wyłącznie do kompilacji, natomiast wykonanie programu zapewnia później Tomcat.

  • Teraz można skompilować program myRequestParamExample.java i wygenerować serwlet myRequestParamExample.class. Gdzie umieścić ten serwlet? Jeśli początkowa konfiguracja serwera Tomcat nie została zmieniona, pliki serwletów .class należy umieścić w katalogu <tomcat>\webapps\examples\WEB-INF\classes (Tomcat 4.x).
  • Sprawdź, czy serwer Tomcat jest uruchomiony, a następnie w przeglądarce wywołaj stronę http://localhost:8080/examples/servlet/myRequestParamExample:

Image

3.1.6. Przykłady

W poniższych przykładach wykorzystaliśmy metodę opisaną powyżej:

  • kompilacja kodu źródłowego serwletu XX.java za pomocą JBuilder
  • wdrożenie serwletu XX.class w katalogu <tomcat>\webapps\examples\WEB-INF\classes
  • Po uruchomieniu serwera Tomcat należy w przeglądarce wywołać adres URL http://localhost:8080/examples/servlet/XX

3.1.6.1. Dynamiczne generowanie formularza – 1

Jako przykład weźmy generowanie formularza zawierającego tylko jeden element: listę. Zawartość tej listy jest tworzona dynamicznie na podstawie wartości pobranych z tablicy. W rzeczywistości wartości te są często pobierane z bazy danych. Formularz wygląda następująco:

Image

Jeśli w powyższym przykładzie wprowadzimy Envoyer, otrzymamy następującą odpowiedź:

Image

Warto zauważyć, że kod URL, który generuje odpowiedź, jest taki sam jak ten, który wyświetla formularz. Mamy tu do czynienia z serwletem, który samodzielnie przetwarza odpowiedź na formularz, który sam wysłał. Jest to częsty przypadek. Kod HTML formularza wygląda następująco:

<html>
    <head><title>Génération de formulaire</title></head>
    <body>
    <h3>Choississez un nombre</h3><hr>
    <form method="POST">
      <select name="cmbValeurs" size="1">
        <option>zéro</option>
        <option>un</option>
        <option>deux</option>
        <option>trois</option>
        <option>quatre</option>
        <option>cinq</option>
        <option>six</option>
        <option>sept</option>
        <option>huit</option>
        <option>neuf</option>
      </select>
      <input type="submit" value="Envoyer">
    </form>
    </body>
</html>

Należy zauważyć, że wartości przesyłane przez formularz są wysyłane metodą POST. Kod HTML w odpowiedzi:

<html>
    <head><title>Voici ma réponse</title></head>
  <body>
      Vous avez choisi le nombre<h2>neuf</h2>
  </body>
</html>

Kod serwletu generującego ten formularz i tę odpowiedź wygląda następująco:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class gener1 extends HttpServlet{
    // zmienne instancji
    private String title="Génération d'un formulaire";
    private final String[] valeurs={"zéro","un","deux","trois","quatre","cinq","six",
      "sept","huit","neuf"};
    private final String HTML1=
                "<html>" +
                  "<head>" +
                    "<title>Génération de formulaire</title>"+
                    "</head>" +
                  "<body>" +
                     "<h3>Choississez un nombre</h3>"+
                     "<hr>" +
                     "<form method=\"POST\">";
        private final String HTML2="<input type=\"submit\" value=\"Envoyer\">";
        private final String HTML3="</form>\n</body>\n</html>";

    // GET
    public void doGet(HttpServletRequest request,HttpServletResponse response)
          throws IOException, ServletException{

      // informuje się klienta o typie wysyłanego dokumentu
      response.setContentType("text/html");
       // wysyłamy formularz
      PrintWriter out=response.getWriter();
            // początek
      out.println(HTML1);
            // lista rozwijana
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            for (int i=0;i<valeurs.length;i++){
                out.println("<option>"+valeurs[i]+"</option>");
            }//dla
            out.println("</select>");
            // koniec formularza
            out.println(HTML2+HTML3);
    }//GET

         // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
          throws IOException, ServletException{

             // odbieramy wybór użytkownika
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) doGet(request,response);

             // przygotowuje się odpowiedź
            String réponse="<html><head><title>Voici ma réponse</title></head>";
            réponse+="<body>Vous avez choisi le nombre <h2>"+choix+"</h2></body></html>";
             // informujemy klienta o typie wysyłanego dokumentu
            response.setContentType("text/html");
             // wysyłamy formularz
            PrintWriter out=response.getWriter();
            out.println(réponse);
    }//POST
    }//klasyfikacja

Metoda doGet służy do generowania formularza. Zawiera ona część dynamiczną, którą stanowi zawartość listy, pochodząca w tym przypadku z tablicy. Metoda doPost służy do generowania odpowiedzi. W tym przypadku jedyną częścią dynamiczną jest wartość wyboru dokonanego przez użytkownika na liście w formularzu. Wartość tę uzyskuje się za pomocą metody request.getParameter("cmbValeurs"), gdzie cmbValeurs to nazwa listy HTML:

      <select name="cmbValeurs" size="1">

Na koniec należy zwrócić uwagę na następujące kwestie:

  • przeglądarka wysyła wartości z formularza do serwletu, który wygenerował formularz, ponieważ tag <form> nie posiada atrybutu <action>. W takim przypadku przeglądarka wysyła dane wprowadzone w formularzu do serwletu URL, który go udostępnił.
  • Tag <form> określa, że dane z formularza muszą być wysyłane za pomocą metody POST. Dlatego właśnie wartości te są pobierane przez metodę doPost serwletu.

3.1.6.2. Dynamiczne generowanie formularza – 2

Wracamy do poprzedniego przykładu, modyfikując go w następujący sposób. Proponowany formularz pozostaje ten sam:

Image

Czy odpowiedź jest inna:

Image

W odpowiedzi zwracany jest formularz, a wybrana przez użytkownika liczba jest podana poniżej. Ponadto liczba ta jest wyświetlana jako wybrana po wyświetleniu listy. Użytkownik może wówczas wybrać inną liczbę:

Image

a następnie wykonać Envoyer. Otrzymuje następującą odpowiedź:

Image

Kod serwletu o nazwie gener2.java wygląda następująco:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class gener2 extends HttpServlet{
        // zmienne instancji
        private String title="Génération d'un formulaire";
        private final String[] valeurs={"zéro","un","deux","trois","quatre","cinq","six",
            "sept","huit","neuf"};
        private final String HTML1=
                "<html>" +
                    "<head>" +
                        "<title>Génération de formulaire</title>"+
                    "</head>" +
                    "<body>" +
                         "<h3>Choisissez un nombre</h3>"+
                         "<hr>" +
                         "<form method=\"POST\">";
        private final String HTML2="<input type=\"submit\" value=\"Envoyer\"></form>\n";
        private final String HTML3="</body>\n</html>";

        // GET
        public void doGet(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // pobieramy ewentualny wybór użytkownika
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

             // informuje się klienta o typie wysłanego dokumentu
            response.setContentType("text/html");
             // wysyłamy formularz
            PrintWriter out=response.getWriter();
            // początek
            out.println(HTML1);
            // lista rozwijana
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            String selected="";
            for (int i=0;i<valeurs.length;i++){
                if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                out.println("<option "+selected+">"+valeurs[i]+"</option>");
            }//dla
            out.println("</select>");
            // kontynuacja formularza
            out.println(HTML2);
            if(! choix.equals("")){
                // wyświetlanie wyboru użytkownika
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//if
             // koniec formularza
            out.println(HTML3);
        }//GET

        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // przekierowanie do GET
            doGet(request,response);
        }//POST
    }//klasa

Metoda doGet wykonuje wszystkie czynności: tworzy formularz, który wysyła do klienta, oraz przetwarza wartości zwracane przez klienta. Należy zwrócić uwagę na następujące kwestie:

  • sprawdza się, czy parametr cmbValeurs ma wartość.
  • Jeśli tak, to podczas tworzenia zawartości listy porównuje się każdy jej element z wyborem użytkownika, aby przypisać atrybut selected do elementu wybranego przez użytkownika: <option selected>element</option>. Ponadto pod formularzem wyświetlana jest wartość dokonanej selekcji.

3.1.6.3. Dynamiczne generowanie formularza – 3

Powracamy do tego samego problemu, co poprzednio, ale tym razem wartości pobierane są z bazy danych. W naszym przykładzie jest to baza o nazwie MySQL:

  • baza nosi nazwę dbValeurs
  • jego właścicielem jest admDbValeurs, którego hasło brzmi mdpDbValeurs
  • baza danych zawiera jedną tabelę o nazwie tvaleurs
  • tabela ta zawiera tylko jedno pole typu całkowitego o nazwie „valeur”
E:\Program Files\EasyPHP\mysql\bin>mysql --database=dbValeurs --user=admDbValeurs --password=mdpDbVa
leurs

mysql> show tables;
+---------------------+
| Tables_in_dbValeurs |
+---------------------+
| tvaleurs            |
+---------------------+
1 row in set (0.00 sec)

mysql> describe tvaleurs;
+--------+---------+------+-----+---------+-------+
| Field  | Type    | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+-------+
| valeur | int(11) |      |     | 0       |       |
+--------+---------+------+-----+---------+-------+

mysql> select * from tvaleurs;
+--------+
| valeur |
+--------+
|      0 |
|      1 |
|      2 |
|      3 |
|      4 |
|      6 |
|      5 |
|      7 |
|      8 |
|      9 |
+--------+
10 rows in set (0.00 sec)

Baza danych o nazwie MySQL dbValeurs została udostępniona przez sterownik ODBC dla MySQL. Jej nazwa DSN (Data Source Name) to odbc-valeurs. Kod serwletu jest następujący:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
import java.util.*;

public class gener3 extends HttpServlet{
        // tytuł strony
        private final String title="Génération d'un formulaire";
         // baza danych wartości listy
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
         // wartości z listy
        private String[] valeurs=null;
         // komunikat o błędzie
        private String msgErreur=null;
        // kod HTML
        private final String HTML1=
                "<html>" +
                    "<head>" +
                        "<title>Génération de formulaire</title>"+
                    "</head>" +
                    "<body>" +
                         "<h3>Choisissez un nombre</h3>"+
                         "<hr>" +
                         "<form method=\"POST\">";
        private final String HTML2="<input type=\"submit\" value=\"Envoyer\"></form>\n";
        private final String HTML3="</body>\n</html>";

        // GET
        public void doGet(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // informacja dla klienta o typie wysłanego dokumentu
            response.setContentType("text/html");
             // strumień wyjściowy
            PrintWriter out=response.getWriter();

            // czy inicjalizacja serwletu przebiegła pomyślnie?
            if (msgErreur!=null){
                 // wystąpił błąd – generowana jest strona błędu
                out.println("<html><head><title>"+title+"</title></head>");
                out.println("<body><h3>Application indisponible ("+msgErreur+
                                        ")</h3></body></html>");
                return;
            }//if

             // pobieramy ewentualny wybór użytkownika
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

             // wysyłamy formularz
             // początek
            out.println(HTML1);
            // lista rozwijana
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            String selected="";
            for (int i=0;i<valeurs.length;i++){
                if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                out.println("<option "+selected+">"+valeurs[i]+"</option>");
            }//for
            out.println("</select>");
            // kontynuacja formularza
            out.println(HTML2);
            if(! choix.equals("")){
                // wyświetlanie wyboru użytkownika
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//if
             // koniec formularza
            out.println(HTML3);
        }//GET

        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // przekierowanie do GET
            doGet(request,response);
        }//POST

        // inicjalizacja serwletu
        public void init(){
             // wypełnia tablicę wartości na podstawie danych z bazy danych ODBC
             // o nazwie DSN: DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // połączenie z bazą danych ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // obiekt Statement
                st=connexion.createStatement();
                 // wykonanie zapytania SELECT w celu pobrania wartości
                rs=st.executeQuery("select valeur from Tvaleurs");
                // wartości zostały pobrane i umieszczone w tablicy dynamicznej
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // wartość jest zapisywana na liście
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                 // przekształcenie listy w tablicę
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                 // problem
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//inicjalizacja
    }//klasa

Należy zwrócić uwagę na następujące kwestie:

  1. Serwlet można zainicjować za pomocą metody o sygnaturze public void init(). Metoda ta jest wykonywana wyłącznie podczas pierwszego załadowania serwletu
  2. Po załadowaniu serwlet pozostaje w pamięci przez cały czas. Oznacza to, że po obsłużeniu klienta nie jest on wyładowywany. Dzięki temu szybciej odpowiada na żądania klientów.
  3. W naszym serwlecie należy wyszukać listę wartości w bazie danych. Ponieważ lista ta nie zmienia się w czasie, metoda init stanowi idealny moment na jej pobranie. Dzięki temu serwlet uzyskuje dostęp do bazy danych tylko raz, podczas jej pierwszego załadowania, a nie przy każdym żądaniu klienta.
  4. Dostęp do bazy danych może się nie powieść. Metoda init naszego serwletu generuje komunikat o błędzie msgErreur w przypadku niepowodzenia. Komunikat ten jest sprawdzany w metodzie doGet, a w przypadku wystąpienia błędu metoda doGet generuje stronę informującą o tym.
  5. Kod metody init wykorzystuje standardowy dostęp do bazy danych za pomocą sterowników ODBC-JDBC. W razie potrzeby czytelnik może zapoznać się z metodami dostępu do baz danych JDBC.

Gdy uruchamia się serwlet, a serwer MySQL nie został uruchomiony, wyświetla się następująca strona błędu:

Image

Jeśli teraz uruchomimy serwer MySQL, wyświetli się strona:

Image

Jeśli wybierzemy liczbę 6 i klikniemy „Wyślij”:

Image

3.1.6.4. Pobieranie wartości z formularza

Wróćmy do przykładu, z którym już się spotkaliśmy, a mianowicie do następującego formularza internetowego:

Image

Kod HTML formularza balises2.htm wygląda następująco:

<html>

  <head>
      <title>balises</title>
    <script language="JavaScript">
        function effacer(){
          alert("Vous avez cliqué sur le bouton Effacer");
      }//usunąć
        </script>
  </head>

  <body background="/images/standard.jpg">
...

    <form method="POST" action="http://localhost:8080/examples/servlet/parameters">

      <table border="0">
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
              <input type="radio" value="Oui" name="R1">Oui
              <input type="radio" name="R1" value="non" checked>Non
          </td>
        </tr>
        <tr>
          <td>Cases à cocher</td>
          <td>
              <input type="checkbox" name="C1" value="un">1
              <input type="checkbox" name="C2" value="deux" checked>2
              <input type="checkbox" name="C3" value="trois">3
          </td>
        </tr>
        <tr>
          <td>Champ de saisie</td>
          <td>
              <input type="text" name="txtSaisie" size="20" value="qqs mots">
          </td>
        </tr>
        <tr>
          <td>Mot de passe</td>
          <td>
              <input type="password" name="txtMdp" size="20" value="unMotDePasse">
          </td>
        </tr>
        <tr>
          <td>Boîte de saisie</td>
          <td>
               <textarea rows="2" name="areaSaisie" cols="20">
ligne1
ligne2
ligne3
</textarea>
          </td>
        </tr>
        <tr>
          <td>combo</td>
          <td>
              <select size="1" name="cmbValeurs">
                <option>choix1</option>
                <option selected>choix2</option>
                <option>choix3</option>
              </select>
          </td>
        </tr>
        <tr>
          <td>liste à choix simple</td>
          <td>
              <select size="3" name="lst1">
                <option selected>liste1</option>
                <option>liste2</option>
                <option>liste3</option>
                <option>liste4</option>
                <option>liste5</option>
              </select>
          </td>
        </tr>
        <tr>
          <td>liste à choix multiple</td>
          <td>
              <select size="3" name="lst2" multiple>
                <option selected>liste1</option>
                <option>liste2</option>
                <option selected>liste3</option>
                <option>liste4</option>
                <option>liste5</option>
              </select>
          </td>
        </tr>
        <tr>
          <td>bouton</td>
          <td>
              <input type="button" value="Effacer" name="cmdEffacer" onclick="effacer()">
          </td>
        </tr>
        <tr>
          <td>envoyer</td>
          <td>
              <input type="submit" value="Envoyer" name="cmdRenvoyer">
          </td>
        </tr>
        <tr>
          <td>rétablir</td>
          <td>
              <input type="reset" value="Rétablir" name="cmdRétablir">
          </td>
        </tr>
      </table>
      <input type="hidden" name="secret" value="uneValeur">
    </form>
  </body>
</html>

Tag <form> w formularzu został zdefiniowany w następujący sposób:

    <form method="POST" action="http://localhost:8080/examples/servlet/parameters">

Przeglądarka „wyśle” wartości formularza do serwletu URL http://localhost:8080/examples/servlet/parameters, który jest serwletem URL zarządzanym przez Tomcat i wyświetlającym wartości poprzedniego formularza. Jeśli wywołamy serwlet parameters bezpośrednio, otrzymamy następujące wyniki:

Image

Jeśli wprowadzony formularz balises2.htm wygląda następująco:

Image

i naciśniemy przycisk „Wyślij” (typu submit), tym razem wywoływany jest serwlet parameters wraz z parametrami. Zwraca on wówczas następującą odpowiedź:

Image

W odpowiedzi tej rzeczywiście znajdują się wartości wprowadzone w formularzu. Kod serwletu wygląda następująco:

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class parameters extends HttpServlet{
    // zmienne instancji
    String title="Récupération des paramètres d'un formulaire";

    private String getParameter(HttpServletRequest request, String contrôle){
      // zwraca wartość request.getParameter (kontrola) lub „” jeśli nie istnieje
      String valeur=request.getParameter(contrôle);
      if(valeur==null) return ""; else return valeur;
    }//getParameter

    // GET
    public void doGet(HttpServletRequest request,HttpServletResponse response)
        throws IOException, ServletException
    {
      // najpierw pobieramy parametry formularza
      String R1=getParameter(request,"R1");
      String C1=getParameter(request,"C1");
      String C2=getParameter(request,"C2");
      String C3=getParameter(request,"C3");
      String txtSaisie=getParameter(request,"txtSaisie");
      String txtMdp=getParameter(request,"txtMdp");
      String areaSaisie=getParameter(request,"areaSaisie");
      String[] lignes=areaSaisie.split("\\r\\n");
      String cmbValeurs=getParameter(request,"cmbValeurs");
      String lst1=getParameter(request,"lst1");
      String[] lst2=request.getParameterValues("lst2");
      String secret=getParameter(request,"secret");

      // podajemy treść dokumentu
        response.setContentType("text/html");
       // wysyłamy dokument
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>" + title + "</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");
        out.println("<h3>" + title + "</h3>");
        out.println("<hr>");
        out.println("<table border=\"1\">");
        out.println("<tr><td>R1</td><td>"+R1+"</td></tr>");
        out.println("<tr><td>C1</td><td>"+C1+"</td></tr>");
        out.println("<tr><td>C2</td><td>"+C2+"</td></tr>");
        out.println("<tr><td>C3</td><td>"+C3+"</td></tr>");
        out.println("<tr><td>txtSaisie</td><td>"+txtSaisie+"</td></tr>");
        out.println("<tr><td>txtMdp</td><td>"+txtMdp+"</td></tr>");
        for(int i=0;i<lignes.length;i++)
          out.println("<tr><td>areaSaisie["+i+"]</td><td>"+lignes[i]+"</td></tr>");
        out.println("<tr><td>cmbValeurs</td><td>"+cmbValeurs+"</td></tr>");
        out.println("<tr><td>lst1</td><td>"+lst1+"</td></tr>");
        if(lst2==null)
          out.println("<tr><td>lst2</td><td></td></tr>");
        else
          for(int i=0;i<lst2.length;i++)
            out.println("<tr><td>lst2</td><td>"+lst2[i]+"</td></tr>");
        out.println("<tr><td>secret</td><td>"+secret+"</td></tr>");
        out.println("</body>");
        out.println("</html>");
    }

     // POST
    public void doPost(HttpServletRequest request,HttpServletResponse response)
        throws IOException, ServletException
    {
       // przekierowuje do GET
      doGet(request,response);
    }
}

W tym kodzie wykorzystano techniki przedstawione wcześniej w innym przykładzie. Należy zwrócić uwagę na dwie kwestie:

  1. kontrolka lst2 jest listą z możliwością wielokrotnego wyboru, a zatem można zaznaczyć kilka elementów. Tak jest w naszym przykładzie, gdzie zaznaczono elementy liste1 i liste3. Wartości lst2 zostały przesłane przez przeglądarkę do serwera w postaci lst2=liste1&lst2=liste3. Serwlet Java może pobrać te wartości do tablicy za pomocą metody getParameterValues: w tym przypadku request.getParameterValues("lst2") zwraca tablicę zawierającą 2 ciągi znaków ["liste1","liste3"].
  2. Kontrolka areaSaisie jest wielowierszowym polem wprowadzania danych. Metoda request.getParameter("areaSaisie") zwraca zawartość tego pola w postaci pojedynczego ciągu znaków. Jeśli chcemy z tego ciągu znaków wyodrębnić poszczególne wiersze, z których się składa, możemy użyć funkcji split z klasy String. Poniższy kod
      String areaSaisie=getParameter(request,"areaSaisie");
      String[] lignes=areaSaisie.split("\\r\\n");

pobiera wiersze z pola wprowadzania danych. Wiersze te kończą się znakami \r\n (0D0A).

W celu przeprowadzenia testów:

  • zbudowaliśmy i skompilowaliśmy serwlet parameters wraz z JBuilder, tak jak wyjaśniono to wcześniej
  • umieściliśmy wygenerowaną klasę w katalogu <tomcat>\webapps\examples\WEB-INF\classes, gdzie <tomcat> to katalog instalacyjny serwera Tomcat.
  • uruchomiłem URL http://localhost:81/html/balises2.htm, którego kod został przedstawiony powyżej
  • wypełniłem formularz i kliknąłem przycisk Envoyer.

3.1.6.5. Pobieranie nagłówków HTTP od klienta internetowego

Wracamy do tego samego przykładu, co poprzednio, ale w odpowiedzi na żądanie klienta internetowego, który przesłał wartości z formularza, wysyłamy mu nagłówki HTTP, które przesłał jednocześnie. Wprowadzamy tylko jedną zmianę w naszym formularzu:

    <form method="GET" action="http://localhost:8080/examples/servlet/headers">

Wartości z formularza zostaną przesłane metodą GET do serwletu Java o nazwie headers, umieszczonego w katalogu <tomcat>\webapps\examples\WEB-INF\classes. Serwlet headers został skompilowany wraz z JBuilder:

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class headers extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
       // określa się rodzaj dokumentu
        response.setContentType("text/html");
       // uzyskuje się strumień zapisu
        PrintWriter out = response.getWriter();
      // wyświetlanie listy nagłówków HTTP
        Enumeration e = request.getHeaderNames();
        while (e.hasMoreElements()) {
            String name = (String)e.nextElement();
            String value = request.getHeader(name);
            out.println("<b>"+name + "</b> = " + value + "<br>");
        }
    }//GET

    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
      //GET
      doGet(request,response);
    }//POST
}

Wysyłamy zapytanie o URL http://localhost:81/html/balises2.htm i generujemy Envoyer bez modyfikacji formularza. Otrzymujemy następującą odpowiedź:

Image

Warto zwrócić uwagę na parametr URL obecny w polu Address przeglądarki, który pokazuje sposób (GET) użyty do przesłania parametrów. Powtórzmy ten sam przykład, zmieniając jednak sposób wysyłania parametrów (POST):

    <form method="POST" action="http://localhost:8080/examples/servlet/headers">

Otrzymujemy następującą nową odpowiedź:

Image

Warto zwrócić uwagę na nagłówki HTTP, content-type i content-length, charakterystyczne dla wysyłki za pomocą POST. Ponadto należy zauważyć, że w polu przeglądarki o numerze Address wartości z formularza już się nie wyświetlają.

3.2. Strony JSP

Strony JSP (Java Server Pages) stanowią inny sposób tworzenia aplikacji serwerowych. W rzeczywistości strony te JSP są przekształcane w serwlety przed ich wykonaniem, co oznacza, że mamy tu do czynienia z technologią serwletów. Strony JSP pozwalają lepiej uwydatnić strukturę wygenerowanych stron HTML. Poniżej przedstawiamy przykłady, z których niektóre są dostępne po kliknięciu linku JSP na stronie głównej serwera Tomcat:

3.2.1. Pobieranie informacji o środowisku

W tym miejscu powracamy do przykładu omówionego już w kontekście serwletu: wyświetlanie zmiennych środowiskowych serwletu. Jest to przykład snoop z zestawu przykładów JSP:

Image

Kod źródłowy strony JSP znajduje się w <tomcat>\jakarta-tomcat\examples\jsp\snp\snoop.jsp (Tomcat 3.x) lub <tomcat>\examples\jsp\snp\snoop.jsp (Tomcat 4.x)


<html>
<!--
  Copyright (c) 1999 The Apache Software Foundation.  All rights 
  reserved.
-->

  <body bgcolor="white">
    <h1> Request Information </h1>
    <font size="4">
      JSP Request Method: <%= request.getMethod() %>
      <br>
      Request URI: <%= request.getRequestURI() %>
      <br>
      Request Protocol: <%= request.getProtocol() %>
      <br>
      Servlet path: <%= request.getServletPath() %>
      <br>
      Path info: <%= request.getPathInfo() %>
      <br>
      Path translated: <%= request.getPathTranslated() %>
      <br>
      Query string: <%= request.getQueryString() %>
      <br>
      Content length: <%= request.getContentLength() %>
      <br>
      Content type: <%= request.getContentType() %>
      <br>
      Server name: <%= request.getServerName() %>
      <br>
      Server port: <%= request.getServerPort() %>
      <br>
      Remote user: <%= request.getRemoteUser() %>
      <br>
      Remote address: <%= request.getRemoteAddr() %>
      <br>
      Remote host: <%= request.getRemoteHost() %>
      <br>
      Authorization scheme: <%= request.getAuthType() %> 
      <hr>
      The browser you are using is <%= request.getHeader("User-Agent") %>
      <hr>
    </font>
  </body>
</html>

Należy zwrócić uwagę na następujące kwestie:

  • mamy tu kod, który bardzo przypomina HTML. Znajdują się w nim jednak tagi <%= wyrażenie %>, które są charakterystyczne dla języka JSP. Kompilator JSP zastępuje w tekście HTML cały tag wartością z expression.
  • W tym przykładzie wykorzystano metody obiektu Java request, który jest obiektem request, znanym już z omówienia serwletów. Jest to zatem obiekt HttpServletRequest. W ten sposób tag <%= request.getRemoteHost() %> zostanie zastąpiony w kodzie HTML nazwą komputera klienta sieciowego, który wysłał żądanie.
  • Ten sam efekt można osiągnąć za pomocą serwletu, ale w tym przypadku struktura strony internetowej jest bardziej przejrzysta.

3.2.2. Pobieranie parametrów przesłanych przez przeglądarkę

W tym miejscu powracamy do przykładu omówionego już w kontekście serwletu. W przeglądarce wyświetlany jest formularz:

Image

W odpowiedzi na powyższe żądanie przeglądarka otrzymuje następującą stronę:

Image

Kod strony JSP jest następujący:


<%
   // zmienne lokalne procedury głównej
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

<!-- kod HTML -->
<html>
  <head>
    <title><%= title %></title>
  </head>
  <body bgcolor="white">
    <h3><%= title %></h3>
    <%
      if (firstName != null || lastName != null) {
        out.println("firstname= " + firstName + "<br>");
        out.println("lastname= " + lastName);
      } else {
        out.println("pas de paramètres");
      }
    %>
    <P>
    <form method="POST">
      firstname= <input type="text" size="20" name="firstname">
      <br>
      lastname= <input type="text" size="20" name="lastname">
      <br>
      <input type="submit">
    </form>
  </body>
</html>
  • Oprócz znacznika <%= wyrażenie %>, który pojawił się już w poprzednim przykładzie, pojawia się nowy znacznik <% instrukcje Java; %>. Znacznik <% wprowadza kod Java. Kod ten kończy się wraz z napotkaniem znacznika zamykającego kod %>.
  • Cały powyższy kod (HTML + JSP) zostanie przekształcony w serwlet Java. Zostanie on umieszczony w jednej metodzie, zwanej metodą główną strony JSP. Dlatego też zmienne Java zadeklarowane na początku strony JSP są rozpoznawalne w innych fragmentach kodu JSP, które występują w kodzie HTML: zmienne te i fragmenty kodu będą częścią tej samej metody Java. Gdyby jednak nasz kod JSP zawierał metody, zmienne title, firstname i lastname nie byłyby w nim znane ze względu na izolację między metodami. Należałoby uczynić je zmiennymi globalnymi lub przekazać je jako parametry do metod. Wrócimy do tego później.
  • Aby włączyć elementy dynamiczne do kodu HTML, dostępne są dwie metody: <%= wyrażenie %> lub out.println(wyrażenie). Obiekt out jest strumieniem wyjściowym podobnym do tego o tej samej nazwie, który pojawia się w przykładach serwletów, ale nie jest tego samego typu: jest to obiekt JspWriter, a nie PrintWriter. Umożliwia on zapisywanie danych do strumienia HTML za pomocą metod print i println.
  • Strona JSP lepiej odzwierciedla strukturę wygenerowanej strony HTML niż odpowiadający jej serwlet.

3.2.3. Tagi JSP

Poniżej znajduje się lista znaczników, które można spotkać na stronie JSP, wraz z ich znaczeniem.

tag
znaczenie
<!-- komentarz -->
komentarz HTML. Jest wysyłany do klienta.
<%-- komentarz --%>
komentarz JSP. Nie jest wysyłany do klienta.
<%! déclarations, méthodes %>
Deklaruje zmienne globalne i metody. Zmienne będą dostępne we wszystkich metodach
<%= expression %>
wartość wyrażenia zostanie wstawiona na stronę HTML w miejsce tagu
<% code Java %>
zawiera kod Java, który będzie częścią głównej metody strony JSP
<%@ page attribut1=valeur1
attribut2=valeur2 … %>
ustala atrybuty dla strony JSP. Na przykład:
import="java.util.*,java.sql.*", aby określić biblioteki niezbędne dla strony JSP
extends="klasaNadrzędna" w celu utworzenia strony JSP jako pochodnej innej klasy

3.2.4. Obiekty domyślne JSP

W poprzednich przykładach natrafiliśmy na dwa niezadeklarowane obiekty: request i out. Są to dwa z obiektów, które są automatycznie definiowane w serwlecie, do której konwertowana jest strona JSP. Nazywamy je obiektami domyślnymi lub predefiniowanymi. Istnieją też inne, ale te są najczęściej używane wraz z obiektem response:

znaczenie
znaczenie
HttpServletRequest request
obiekt, za pomocą którego uzyskuje się dostęp do żądania klienta internetowego (getParameter, getParameterNames, getParameterValues)
HttpServletResponse response
obiekt, za pomocą którego można skonstruować odpowiedź serwera WWW dla klienta. Umożliwia ustawienie nagłówków HTTP, które mają zostać wysłane do klienta WWW.
JspWriter out
strumień wyjściowy, który pozwala nam wysłać kod HTML do klienta (print, println)

3.2.5. Przekształcenie strony JSP w serwlet

Wróćmy do kodu JSP z pliku myRequestParamExample.jsp:


<%
   // zmienne lokalne procedury głównej
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

<!-- kod HTML -->
<html>
  <head>
    <title><%= title %></title>
  </head>
  <body bgcolor="white">
    <h3><%= title %></h3>
    <%
      if (firstName != null || lastName != null) {
        out.println("firstname= " + firstName + "<br>");
        out.println("lastname= " + lastName);
      } else {
        out.println("pas de paramètres");
      }
    %>
    <P>
    <form method="POST">
      firstname= <input type="text" size="20" name="firstname">
      <br>
      lastname= <input type="text" size="20" name="lastname">
      <br>
      <input type="submit">
    </form>
  </body>
</html>

Gdy przeglądarka zażąda tej strony JSP od serwera Tomcat, ten przekształci ją w serwlet. Jeśli żądana strona URL to

http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp, Tomcat 4.x umieści wygenerowany serwlet w katalogu <tomcat>\work\localhost\examples\jsp\perso\intro:

Image

W tej nazwie znajduje się identyfikator URL http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp strony JSP. Jak widać powyżej, mamy dostęp do kodu Java serwletu wygenerowanego dla strony JSP. W naszym przykładzie wygląda on następująco:

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;


public class myRequestParamExample$jsp extends HttpJspBase {


    static {
    }
    public myRequestParamExample$jsp( ) {
    }

    private static boolean _jspx_inited = false;

    public final void _jspx_init() throws org.apache.jasper.runtime.JspException {
    }

    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

        JspFactory _jspxFactory = null;
        PageContext pageContext = null;
        HttpSession session = null;
        ServletContext application = null;
        ServletConfig config = null;
        JspWriter out = null;
        Object page = this;
        String  _value = null;
        try {

            if (_jspx_inited == false) {
                synchronized (this) {
                    if (_jspx_inited == false) {
                        _jspx_init();
                        _jspx_inited = true;
                    }
                }
            }
            _jspxFactory = JspFactory.getDefaultFactory();
            response.setContentType("text/html;charset=ISO-8859-1");
            pageContext = _jspxFactory.getPageContext(this, request, response,
                        "", true, 8192, true);

            application = pageContext.getServletContext();
            config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();


                   // zmienne lokalne procedury głównej
                  String title="Récupération des paramètres d'un formulaire";
                  String firstName = request.getParameter("firstname");
                  String lastName = request.getParameter("lastname");

                out.write("\r\n\r\n<!-- code HTML -->\r\n<html>\r\n  <head>\r\n    <title>");
                out.print( title );
                out.write("</title>\r\n  </head>\r\n  <body bgcolor=\"white\">\r\n    <h3>");
                out.print( title );
                out.write("</h3>\r\n    ");

                      if (firstName != null || lastName != null) {
                        out.println("firstname= " + firstName + "<br>");
                        out.println("lastname= " + lastName);
                      } else {
                        out.println("pas de paramètres");
                      }

                out.write("\r\n    <P>\r\n    <form method=\"POST\">\r\n      firstname= <input type=\"text\" size=\"20\" name=\"firstname\">\r\n      <br>\r\n      lastname= <input type=\"text\" size=\"20\" name=\"lastname\">\r\n      <br>\r\n      <input type=\"submit\">\r\n    </form>\r\n  </body>\r\n</html>\r\n");

        } catch (Throwable t) {
            if (out != null && out.getBufferSize() != 0)
                out.clearBuffer();
            if (pageContext != null) pageContext.handlePageException(t);
        } finally {
            if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
        }
    }
}

Wygenerowany kod jest dość złożony. Skupimy się jedynie na następujących kwestiach:

  • Główna metoda serwletu wygląda następująco:
    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

To właśnie ta metoda jest wywoływana przy uruchomieniu serwletu. Widać, że otrzymuje ona dwa parametry: żądanie request od klienta oraz obiekt response służący do wygenerowania odpowiedzi dla klienta internetowego.

  • W metodzie głównej deklarowany jest obiekt JspWriter o nazwie „out”, a następnie jest on inicjowany. To właśnie on umożliwi wysłanie kodu HTML do klienta za pomocą instrukcji out.print("codeHTML").
        JspWriter out = null;
...
            out = pageContext.getOut();
  • Kod Java

<%
   // zmienne lokalne procedury głównej
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

został w całości przeniesiony do głównej metody serwletu _jspService. To samo dotyczy całego kodu umieszczonego w tagach <%… %>

  • Kod HTML ze strony JSP jest przedmiotem instrukcji out.print("codeHTML") lub out.write(...). Na przykład

                out.write("</title>\r\n  </head>\r\n  <body bgcolor=\"white\">\r\n    <h3>");
  • W tym przykładzie nie ma innych metod poza metodą główną _jspService.

3.2.6. Metody i zmienne globalne strony JSP

Rozważmy następującą stronę JSP:


<%!
   // poprzedni znacznik rozpoczyna sekcję zmiennych i metod globalnych
   // ta część zostanie przejęta bez zmian do serwletu
  
   // zmienna globalna
  String prenom="inconnu";

   // metoda  
  private String sonChien(){
    return "milou";
  }//sonChien

   // kolejna metoda
  private void afficheAmi(JspWriter out) throws Exception{
    out.println("<p>Son ami s'appelle Haddock</p>");
  }//afficheAmi

   // koniec części globalnej serwletu
%>  

<%
   // poprzedni tag wskazuje, że poniższy kod zostanie zapisany
   // w głównej metodzie serwletu
  
   // zmienna lokalna dla metody głównej
  String nom="tintin";
%>


<%-- kod HTML --%>
<html>
  <head>
    <title>Page JSP</title>
  </head>
  <body>
    <center>
      <h2>Page JSP</h2>
      <p>Son nom est <%= nom %></p>
      <p>Son prénom est <%= prenom %></p>
      <p>Son chien s'appelle <%= sonChien() %></p>
      <%
         // imię jego przyjaciela
        afficheAmi(out);
      %>
    </center>
  </body>
</html>

Ta strona JSP generuje następującą stronę internetową:

Image

Przyjrzyjmy się, w jaki sposób generowane są cztery powyższe wiersze:


      <p>Son nom est <%= nom %></p>
      <p>Son prénom est <%= prenom %></p>
      <p>Son chien s'appelle <%= sonChien() %></p>
      <%
         // imię jego przyjaciela
        afficheAmi(out);
      %>

Powyższe wiersze znajdują się w tagu <%..%> i w związku z tym będą częścią głównej metody _jspService serwletu, który zostanie wygenerowany. W jaki sposób mają one dostęp do zmiennych nom, prenom oraz metod sonChien i afficheAmi?

nom (tintin)
jest zmienną lokalną dla głównej metody strony JSP i dlatego jest w niej rozpoznawalna
prenom (inconnu)
jest zmienną globalną strony JSP, a zatem znaną w metodzie głównej
sonChien (milou)
jest metodą publiczną strony JSP, a zatem dostępną z poziomu metody głównej
afficheAmi (Haddock)
jest metodą publiczną strony JSP, a zatem dostępną z poziomu metody głównej. Warto zauważyć, że obiekt out jest przekazywany jako parametr do tej metody. Jest to tutaj obowiązkowe. Obiekt out jest bowiem zadeklarowany i zainicjowany w metodzie głównej serwletu i nie jest zmienną globalną.

Przyjrzyjmy się teraz kodowi serwletu Java wygenerowanego na podstawie tej strony JSP, po usunięciu zbędnego kodu:

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;


public class tintin$jsp extends HttpJspBase {

          // poprzedni tag rozpoczyna sekcję zmiennych i metod globalnych
           // ta część zostanie przejęta bez zmian do serwletu

           // zmienna globalna
          String prenom="inconnu";

           // metoda  
          private String sonChien(){
            return "milou";
          }//sonChien

           // kolejna metoda
          private void afficheAmi(JspWriter out) throws Exception{
            out.println("<p>Son ami s'appelle Haddock</p>");
          }//afficheAmi

           // koniec części globalnej serwletu

    static {
    }
    public tintin$jsp( ) {
    }

    private static boolean _jspx_inited = false;

    public final void _jspx_init() throws org.apache.jasper.runtime.JspException {
    }

    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

        JspFactory _jspxFactory = null;
        PageContext pageContext = null;
        HttpSession session = null;
        ServletContext application = null;
        ServletConfig config = null;
        JspWriter out = null;
        Object page = this;
        String  _value = null;
        try {

            if (_jspx_inited == false) {
                synchronized (this) {
                    if (_jspx_inited == false) {
                        _jspx_init();
                        _jspx_inited = true;
                    }
                }
            }
            _jspxFactory = JspFactory.getDefaultFactory();
            response.setContentType("text/html;charset=ISO-8859-1");
            pageContext = _jspxFactory.getPageContext(this, request, response,
                        "", true, 8192, true);

            application = pageContext.getServletContext();
            config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();

                out.write("  \r\n\r\n");
                  // poprzedni tag wskazuje, że poniższy kod zostanie zapisany
                   // w głównej metodzie serwletu

                   // zmienna lokalna dla metody głównej
                  String nom="tintin";

                out.write("\r\n\r\n\r\n");
                out.write("\r\n<html>\r\n  <head>\r\n    <title>Page JSP</title>\r\n  </head>\r\n  <body>\r\n    <center>\r\n      <h2>Page JSP</h2>\r\n      <p>Son nom est ");
                out.print( nom );
                out.write("</p>\r\n      <p>Son prénom est ");
                out.print( prenom );
                out.write("</p>\r\n      <p>Son chien s'appelle ");
                out.print( sonChien() );
                out.write("</p>\r\n      ");

                        // imię jego przyjaciela
                        afficheAmi(out);

                out.write("\r\n    </center>\r\n  </body>\r\n</html>\r\n");
        } catch (Throwable t) {
            if (out != null && out.getBufferSize() != 0)
                out.clearBuffer();
            if (pageContext != null) pageContext.handlePageException(t);
        } finally {
            if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
        }
    }
}

Jak widać powyżej, kod Java, który znajdował się pomiędzy tagami JSP <%! .. %>, został przejęty w całości i nie stanowi części głównej metody _jspService serwletu. Zmienne zadeklarowane w tej części są zatem zmiennymi instancji, a więc globalnymi dla metod, i właśnie w tym miejscu można definiować metody inne niż _jspService.


  // ta część zostanie przejęta bez zmian w serwlecie

   // zmienna globalna
  String prenom="inconnu";

   // metoda
  private String sonChien(){
    return "milou";
  }//sonChien

   // kolejna metoda
  private void afficheAmi(JspWriter out) throws Exception{
    out.println("<p>Son ami s'appelle Haddock</p>");
  }//afficheAmi

   // koniec części globalnej serwletu

3.2.7. Wdrażanie i debugowanie stron JSP na serwerze Tomcat

Gdy chcemy utworzyć stronę JSP i korzystać z niej na serwerze Tomcat, pojawia się pytanie, gdzie umieścić tę stronę w drzewie katalogów serwera. Istnieją różne sposoby, do których jeszcze wrócimy. Na razie najprostszym rozwiązaniem jest umieszczenie strony JSP w folderze w drzewie katalogów <tomcat>\webapps\examples\jsp (Tomcat 4.x), gdzie <tomcat> jest katalogiem instalacyjnym serwera Tomcat. W ten sposób katalog URL z poprzedniego przykładu znajdował się pod adresem http://localhost:8080/examples/jsp/perso/tintin/tintin.jsp. Oznacza to, że strona o nazwie tintin.jsp znajdowała się w folderze <tomcat>\webapps\examples\jsp\perso\tintin.

Strona o nazwie JSP jest przekształcana w plik źródłowy Java, który następnie jest kompilowany przez Tomcat, gdy przeglądarka żąda strony URL należącej do strony JSP. Mogą pojawić się błędy kompilacji. Tomcat 4.x zgłasza je w odpowiedzi wysyłanej do przeglądarki. Wskazuje w szczególności wiersze pliku .java, które zawierają błędy. Błędy mogą mieć różne przyczyny:

  1. kod JSP na stronie jest błędny (na przykład błędy w użytych tagach JSP)
  2. błędny jest kod Java zawarty na stronie JSP

Pierwszą przyczynę można wyeliminować, sprawdzając kod strony JSP. Drugą przyczynę można wyeliminować, sprawdzając kod Java. Można to zrobić, kompilując bezpośrednio plik .java wygenerowany dla strony JSP za pomocą narzędzia takiego jak JBuilder, które oferuje bardziej zaawansowane możliwości debugowania niż Tomcat.

3.2.8. Przykłady

Powrócimy do omówionego już przykładu z serwletem, w którym użytkownik wybiera liczbę z listy, a serwer informuje go, jaką liczbę wybrał, jednocześnie zwracając tę samą listę z elementem wybranym przez użytkownika jako zaznaczonym:

Image

Aby stworzyć tę stronę, wykorzystaliśmy kod serwletu, który zmodyfikowaliśmy w następujący sposób:

  • zachowaliśmy bez zmian kod Java, który nie generował kodu HTML
  • kod Java, który generował kod HTML, został przekształcony w mieszankę kodu HTML i kodu JSP

W ten sposób otrzymujemy następującą stronę o nazwie JSP:



<%@ page import="java.sql.*, java.util.*" %>

<%!

         // zmienne globalne aplikacji
         // tytuł strony
        private final String title="Génération d'un formulaire";
         // baza danych wartości listy
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
         // wartości z listy
        private String[] valeurs=null;
         // komunikat o błędzie
        private String msgErreur=null;
    
         // inicjalizacja strony JSP – wykonywana tylko raz
        public void jspInit(){
             // wypełnia tablicę wartości z bazy danych ODBC
             // o nazwie DSN: DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // połączenie z bazą danych ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // obiekt Statement
                st=connexion.createStatement();
                 // wykonanie zapytania SELECT w celu pobrania wartości
                rs=st.executeQuery("select valeur from Tvaleurs");
                // wartości zostały pobrane i umieszczone w tablicy dynamicznej
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // wartość jest zapisywana na liście
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                 // przekształcenie listy w tablicę
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                 // problem
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//inicjalizacja
   
%>

<%
    // code de _jspService exécuté à chaque requête cliente
   // czy wystąpił błąd podczas inicjalizacji strony JSP?
  if(msgErreur!=null){
%>
       <!-- kod HTML -->
    <html>
        <head>
          <title>Erreur</title>
      </head>
      <body>
          <h3>Application indisponible (<%= msgErreur %></h3>
      </body>
    </html>
<%
       // koniec jspService
    return;
  }//if
  
     // pobieramy ewentualny wybór użytkownika
    String choix=request.getParameter("cmbValeurs");
    if(choix==null) choix="";
%>
  
   <%-- brak błędu – kod HTML strony standardowej --%>
      <html>
        <head>
          <title><%= title %></title>
      </head>
      <body>
          <h3>Choisissez une valeur</h3>
          <form method="POST">
            <select name="cmbValeurs">
              <%
                 // dynamiczne wyświetlanie wartości
                          String selected="";
                          for (int i=0;i<valeurs.length;i++){
                              if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                              out.println("<option "+selected+">"+valeurs[i]+"</option>");
                          }//for
            %>
          </select>
          <input type="submit" value="Envoyer">
        </form>
<%
         // czy wybrano jakąś wartość?
                if(! choix.equals("")){
        // wyświetla się wybór użytkownika
%>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
<%        
                }//if
%>        
      </body>
    </html>

Należy zwrócić uwagę na następujące kwestie:

  • instrukcje import w serwlecie zostały objęte dyrektywą <% page import="..." %>
  • tag <%! ... %> otacza zmienne globalne i metody Java aplikacji
  • metoda init serwletu, która jest wykonywana tylko raz, w momencie ładowania serwletu, wywołuje dla strony JSP metodę jspInit. Obie te metody pełnią tę samą rolę. W związku z tym w całości przejęto tutaj kod metody init serwletu.
  • Zmienne instancji serwletu, które muszą być dostępne w wielu metodach, zostały skopiowane bez zmian. Są to przede wszystkim zmienne title, valeurs oraz msgErreur, które są następnie wykorzystywane w kodzie JSP.
  • Znaczniki <% ... %> otaczają kod Java, który zostanie włączony do metody _jspService wykonywanej w momencie nadejścia żądania od klienta.
  • Podobnie jak w przypadku serwletu, metoda _jspService rozpocznie się od sprawdzenia wartości zmiennej msgErreur, aby ustalić, czy należy wygenerować stronę błędu. W przypadku wystąpienia błędu generuje stronę błędu i kończy działanie (return).
  • Jeśli nie ma błędu, generuje formularz z listą wartości
  • następnie sprawdza, czy użytkownik wybrał liczbę; w takim przypadku wyświetla tę liczbę na wygenerowanej stronie

Co zyskaliśmy w porównaniu z serwletem? Bez wątpienia lepszy wgląd w wygenerowany kod HTML. Jednak wciąż pozostaje sporo kodu Java, który „zanieczyszcza” ten widok. Później omówimy inną metodę zwaną delegacją, dzięki której będziemy mogli umieścić większość kodu Java w serwlecie, a strona JSP zachowa jedynie kod HTML i JSP. W ten sposób wyraźnie oddzielamy część przetwarzającą od części prezentacyjnej.

3.3. Wdrażanie aplikacji internetowej na serwerze Tomcat

Przedstawimy teraz sposób wdrażania aplikacji internetowych Java na serwerze Tomcat. Chociaż poniższe informacje dotyczą konkretnie tego serwera, wdrożenie aplikacji internetowej Java w innym kontenerze J2EE będzie miało cechy zbliżone do tych, które zostaną teraz opisane.

3.3.1. Pliki konfiguracyjne server.xml i web.xml

Do tej pory, aby przetestować nasze serwlety i strony JSP, umieszczaliśmy

  • serwlety w folderze <tomcat>\webapps\examples\WEB-INF\classes. Były one wówczas dostępne poprzez adres http://localhost:8080/examples/servlet/nomServlet
  • strony JSP w drzewie katalogów <tomcat>\webapps\examples\jsp. Były one wówczas dostępne poprzez adres URL http://localhost:8080/examples/jsp/nomPageJSP

Nigdy nie wyjaśniliśmy, dlaczego tak było. Konfiguracja serwera Tomcat odbywa się w pliku tekstowym o nazwie server.xml, który znajduje się w folderze <tomcat>\conf:

Image

Ten plik tekstowy jest w rzeczywistości plikiem XML (eXtended Markup Language). Dokument XML jest dokumentem tekstowym zawierającym znaczniki, podobnie jak dokument HTML. Jednakże, podczas gdy znaczniki języka HTML są jasno zdefiniowane, znaczniki języka XML nie są. Zatem poniższy dokument jest dokumentem XML:

<personne>
    <prenom>Pierre</prenom>
  <nom>Lucas</nom>
  <age>28</age>
</personne>

Dokument XML to po prostu dokument „oznaczony tagami”, który jest zgodny z określonymi zasadami oznaczania:

  • tekst oznaczony w postaci <xx att1="val1" att2="val2" ....>tekst</xx>
  • tag może występować samodzielnie i mieć postać <xx att1="val1" att2="val2" ..../>

Pola „atti” nazywane są atrybutami znacznika xx, a pola „vali” to wartości powiązane z tymi atrybutami. Niektóre dokumenty HTML nie są poprawnymi dokumentami XML. Na przykład znacznik <br> nie jest prawidłowym znacznikiem. Aby był prawidłowy, powinien mieć postać <br/>, zgodnie z zasadą, że każdy znacznik musi być zamknięty. Wariant HTML o nazwie XHTML został stworzony w celu przekształcenia każdego dokumentu XHTML w poprawny dokument XML. Niektóre nowsze przeglądarki są w stanie wyświetlać pliki XML. Jeśli więc nazwemy dokument XML, przedstawiony w powyższym przykładzie, jako personne.xml i wyświetlimy go za pomocą IE6, otrzymamy następujący wynik:

Image

IE6 rozpoznaje tagi i nadaje im kolory. Rozpoznaje również strukturę dokumentu na podstawie tagów. Jeśli więc nazwiemy następujący dokument personne2.xml:

<personne><prenom>Pierre</prenom><nom>Lucas</nom><age>28</age></personne>

i wyświetlimy go za pomocą IE6, otrzymamy ten sam wynik:

Image

IE6 poprawnie rozpoznał strukturę i zawartość dokumentu. Cała zaleta dokumentu XML polega właśnie na tej właściwości: łatwo jest odtworzyć strukturę i zawartość dokumentu XML. Odbywa się to za pomocą programu zwanego parserem XML. Dokumenty XML stają się coraz bardziej powszechne w wymianie dokumentów w sieci. Weźmy na przykład komputer A, który ma wysłać dokument DOC do komputera B. Dokument DOC jest tworzony na podstawie informacji zawartych w bazie danych DB-A. Urządzenie B musi z kolei zapisać dokument DOC w bazie danych DB-B. Wymiana może przebiegać w następujący sposób:

  • maszyna A pobiera dane z bazy DB-A i umieszcza je w dokumencie tekstowym XML
  • dokument XML jest wysyłany do maszyny B przez sieć
  • maszyna B analizuje otrzymany dokument za pomocą parsera XML i pobiera zarówno strukturę, jak i dane (tak jak zrobił to IE6 w naszym przykładzie). Następnie może zapisać otrzymane dane w bazie DB-B

Nie będziemy się tutaj szerzej rozpisywać na temat języka XML, który zasługuje na osobną książkę.

W tym przypadku Tomcat jest skonfigurowany za pomocą pliku XML server.xml. Jeśli wyświetlimy go za pomocą IE6, otrzymamy złożony dokument. Skupimy się jedynie na następujących wierszach:

Image

W tym przypadku interesuje nas tag <Context ...>. Służy on do definiowania aplikacji internetowych. Należy zwrócić uwagę na dwa jego atrybuty:

  • path: nazwa aplikacji internetowej
  • docBase: to folder, w którym się znajduje. W tym przypadku jest to nazwa względna: examples. Względna względem jakiego folderu? Odpowiedź znajduje się również w pliku server.xml w następującym wierszu:

Image

Powyższy wiersz określa serwer WWW:

  • name: nazwa serwera WWW
  • appBase: katalog główny drzewa dokumentów, które serwer udostępnia. Ponownie mamy do czynienia z nazwą względną: webapps. Jest ona względna względem katalogu instalacyjnego serwera Tomcat <tomcat>. A zatem chodzi o folder <tomcat>\webapps.

Aplikacja internetowa „examples” przechowuje swoje dokumenty w folderze examples (por. docBase powyżej). Nazwa ta odnosi się do katalogu głównego drzewa katalogów serwera, c.a.d. <tomcat>\webapps. Jest to zatem katalog <tomcat>\webapps\examples. Przyjrzyjmy się bliżej temu katalogowi:

Image

Znajdujemy tam folder WEB-INF\classes, w którym umieściliśmy nasze serwlety w celu ich przetestowania. Folder WEB-INF zawiera plik o nazwie web.xml:

Image

Plik ten służy do konfiguracji aplikacji internetowej examples. Na razie nie będziemy zagłębiać się w szczegóły tego zbyt złożonego pliku. Skupimy się jedynie na kilku poniższych wierszach:

    <servlet>
      <servlet-name>
          servletToJsp
      </servlet-name>
      <servlet-class>
          servletToJsp
      </servlet-class>
    </servlet>

Tag <servlet> służy do zdefiniowania serwletu w ramach aplikacji internetowej. Przypomnijmy, że omawianą aplikacją internetową jest examples. Tag servlet zawiera tutaj dwa inne tagi:

  • <servlet-name>servletToJsp</servlet-name>: określa nazwę serwletu
  • <servlet-name>servletToJsp</servlet-name>: określa nazwę klasy, która ma zostać uruchomiona po wywołaniu serwletu. W tym przykładzie serwlet i jego klasa mają tę samą nazwę. Nie jest to jednak obowiązkowe.

W jaki sposób serwer Tomcat otrzymuje od przeglądarki żądanie dotyczące serwletu servletToJsp?

  • Przeglądarka wysyła żądanie dotyczące serwletu URL http://localhost:8080/examples/servlet/servletToJsp
  • Tomcat analizuje ścieżkę do serwletu /examples/servlet/servletToJsp. Interpretuje pierwszą część ścieżki /examples jako nazwę aplikacji internetowej i przeszukuje jej plik konfiguracyjny server.xml w poszukiwaniu lokalizacji, w której zapisano dokumenty tej aplikacji. Jak widzieliśmy wcześniej, znajduje się on w folderze <tomcat>\webapps\examples.
  • Tomcat wykorzystuje pozostałą część ścieżki serwletu, aby zlokalizować go w aplikacji internetowej examples. Ścieżka /servlet/servletToJsp wskazuje, że należy uruchomić serwlet o nazwie servletToJsp. Następnie Tomcat odczyta plik konfiguracyjny web.xml aplikacji examples, który znajdzie w katalogu <tomcat>\webapps\examples\WEB-INF. W tym pliku Tomcat stwierdzi, że serwlet servletToJsp jest powiązany z klasą Java o nazwie servletToJsp (patrz plik web.xml powyżej). Następnie program wyszuka tę klasę w folderze WEB-INF\classes aplikacji internetowej examples, c.a.d. w <tomcat>\webapps\examples\WEB-INF\classes i uruchomi ją.

Image

3.3.2. Przykład: wdrożenie aplikacji internetowej „lista”

Wykorzystujemy serwlet, który już wcześniej omawialiśmy i który wyświetlał użytkownikowi listę liczb, spośród których wybierał jedną. Następnie serwlet potwierdzał użytkownikowi wybraną liczbę:

Image

Jak widać w polu Address przeglądarki powyżej, plik klasy serwletu nosił nazwę gener3. Zgodnie z wcześniejszymi wyjaśnieniami:

  • URL /examples/servlet/gener3 wskazuje, że jest to serwlet o nazwie gener3 należący do aplikacji internetowej examples
  • w pliku web.xml aplikacji examples nie ma żadnej wzmianki o serwlecie o nazwie gener3. Jak więc Tomcat ją znalazł? Po przejrzeniu całego pliku web.xml nie potrafię udzielić pewnej odpowiedzi... Pytanie pozostaje otwarte...

Postanowiliśmy wdrożyć serwlet gener3.class pod nazwą lstValeurs w aplikacji internetowej o nazwie „liste”, znajdującej się w folderze E:\data\serge\Servlets\lstValeurs:

Image

Umieszczamy plik gener3.class w powyższym folderze WEB-INF\classes:

Image

Konfigurujemy aplikację internetową „liste”, dodając w pliku server.xml następujące wiersze powyżej tych, które definiują aplikację internetową manager:

                 <!-- Użytkownik: lstValeurs -->
                <Context path="/liste" docBase="e:/data/serge/servlets/lstValeurs" />

                <!-- Kontekst menedżera Tomcat -->
                <Context path="/manager" docBase="manager" debug="0" privileged="true" />
                <!-- Kontekst przykładów Tomcat -->
                <Context path="/examples" docBase="examples" debug="0" reloadable="true" crossContext="true">
........

Wiersz definiujący listę aplikacji wskazuje, że znajduje się ona w folderze e:/data/serge/servlets/lstValeurs. Teraz musimy zdefiniować plik web.xml tej aplikacji. Plik ten zdefiniuje jedyny serwlet aplikacji:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
</web-app>

Powyższy plik wskazuje, że serwlet o nazwie lstValeurs jest powiązany z plikiem klasy gener3.class. Plik web.xml należy utworzyć i zapisać w folderze WEB-INF aplikacji „liste”:

Image

Powyższy zrzut ekranu przedstawia folder src, w którym umieszczono plik źródłowy gener3.java. Folder ten może nie istnieć. Nie ma on żadnego znaczenia w niniejszym przykładzie. Jesteśmy gotowi do przeprowadzenia testów:

  • zatrzymaj i uruchom Tomcat, aby ponownie odczytał swój plik konfiguracyjny server.xml. Tutaj korzystamy z systemu Windows. W systemie Unix można wymusić na Tomcacie ponowne odczytanie pliku konfiguracyjnego bez konieczności jego zatrzymywania.
  • W przeglądarce wpisz adres URL http://localhost:8080/liste/servlet/lstValeurs

Image

Widać, że poprzedni plik URL zawiera słowo kluczowe servlet, podobnie jak wszystkie dotychczas używane pliki serwletów URL. Można to pominąć, przypisując w pliku web.xml aplikacji listy serwlet lstValeurs do szablonu URL (url-pattern):

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
</web-app>

W tagu <servlet-mapping> przypisujemy ścieżkę /valeurs do serwletu lstValeurs zdefiniowanego w poprzednich wierszach. Zapisujemy nowy plik web.xml i wywołujemy serwlet URL oraz http://localhost:8080/liste/valeurs:

Image

3.3.3. Wdrażanie publicznych stron aplikacji internetowej

Właśnie zapoznaliśmy się z wdrożeniem aplikacji internetowej składającej się z pojedynczego serwletu. Aplikacja internetowa może zawierać wiele elementów: serwlety, strony JSP, pliki HTML, aplety Java itp. Gdzie umieszcza się te elementy aplikacji? Jeśli <application> jest katalogiem aplikacji internetowej zdefiniowanym przez atrybut docBase aplikacji w pliku konfiguracyjnym Tomcata server.xml, to widzieliśmy, że serwlety umieszczono w katalogu <application>\WEB-INF\classes. Pozostałe elementy aplikacji można umieścić w dowolnym miejscu w drzewie katalogów <application>, z wyjątkiem katalogu WEB-INF. Rozważmy omówioną już wcześniej aplikację JSP listvaleurs.jsp:

Image

Ta strona JSP została zapisana w folderze <tomcat>\webapps\examples\jsp\perso\listvaleurs. Strona ta może stanowić element wcześniej wdrożonej aplikacji „liste”. Umieśćmy plik listvaleurs.jsp bezpośrednio w folderze tej aplikacji:

Image

Przypomnijmy sobie konfigurację aplikacji liste zawartą w pliku server.xml:

                <Context path="/liste" docBase="e:/data/serge/servlets/lstValeurs" />

Każdy plik URL, którego ścieżka zaczyna się od /liste, jest traktowany jako część aplikacji liste i będzie wyszukiwany w wskazanym folderze. Wywołajmy plik URL http://localhost:8080/liste/listvaleurs.jsp za pomocą przeglądarki:

Image

Otrzymaliśmy oczekiwaną stronę JSP.

3.3.4. Parametry inicjalizacji serwletu

Widzieliśmy, że serwlet jest konfigurowany za pomocą pliku <application>\WEB-INF\web.xml, gdzie <application> to katalog aplikacji internetowej, do której należy. W pliku tym można zawrzeć parametry inicjalizacji serwletu. Wróćmy do naszego serwletu lstValeurs z aplikacji internetowej liste, którego plik konfiguracyjny wyglądał następująco:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
</web-app>

Klasą powiązaną z serwletem jest klasa gener3. W jej kodzie źródłowym znajduje się definicja kilku stałych:

public class gener3 extends HttpServlet{
        // tytuł strony
        private final String title="Génération d'un formulaire";
         // baza danych wartości listy
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";

Przypomnijmy znaczenie czterech stałych zdefiniowanych powyżej:

title
tytuł dokumentu HTML wygenerowanego przez serwlet
DSNValeurs
nazwa bazy danych DSN, z której serwlet pobiera dane
admDbValeurs
nazwa użytkownika posiadającego uprawnienia do odczytu w powyższej bazie danych
mdpDbvaleurs
jego hasło

Jeśli administrator bazy danych DSNValeurs zmieni hasło użytkownika admDbValeurs, kod źródłowy serwletu musi zostać zmodyfikowany i ponownie skompilowany. Nie jest to zbyt wygodne. Plik konfiguracyjny serwletu web.xml oferuje nam alternatywę, umożliwiając zdefiniowanie parametrów inicjalizacji serwletu za pomocą tagu <init-param>:

    <init-param>
        <param-name>...</param-name>
        <param-value>...</param-value>
    </init-param>
<param-name>
umożliwia zdefiniowanie nazwy parametru
<param-value>
określa wartość przypisaną do poprzedniego parametru

Serwlet ma dostęp do swoich parametrów inicjalizacyjnych za pomocą następujących metod:

[Servlet].getServletConfig()
metoda klasy Servlet, od której wywodzi się klasa HttpServlet wykorzystywana do programowania stron internetowych. Zwraca obiekt ServletConfig, który zapewnia dostęp do parametrów konfiguracyjnych serwletu.
[ServletConfig].getInitParameter("paramètre")
metoda klasy ServletConfig, która zwraca wartość parametru inicjalizacyjnego „paramètre”

Konfigurujemy aplikację liste przy użyciu następującego nowego pliku web.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
    <servlet>
      <servlet-name>lstValeurs2</servlet-name>
    <servlet-class>gener5</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
    <init-param>
        <param-name>DSNValeurs</param-name>
      <param-value>odbc-valeurs</param-value>
    </init-param>
    <init-param>
        <param-name>admDbValeurs</param-name>
      <param-value>admDbValeurs</param-value>
    </init-param>
    <init-param>
        <param-name>mdpDbValeurs</param-name>
      <param-value>mdpDbValeurs</param-value>
    </init-param>   
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
      <servlet-name>lstValeurs2</servlet-name>
    <url-pattern>/valeurs2</url-pattern>
  </servlet-mapping>
</web-app>

W aplikacji liste definiujemy drugą serwletę o nazwie lstValeurs2 powiązaną z plikiem klasy gener5. Ten ostatni został umieszczony w katalogu <application>\WEB-INF\classes:

Image

Serwlet lstValeurs2 ma cztery parametry inicjalizacyjne: title, DSNValeurs, admDbValeurs, mdpDbValeurs. Ponadto dla serwletu zdefiniowano alias /valeurs2 za pomocą tagu <servlet-mapping>. W związku z tym serwlet lstValeurs2 w aplikacji liste będzie dostępny poprzez URL i http://localhost:8080/liste/valeurs2.

Kod źródłowy serwletu został zmodyfikowany w następujący sposób, aby pobrać parametry inicjalizacyjne serwletu:

public class gener5 extends HttpServlet{
    // tytuł strony
    private String title=null;
    // baza danych wartości listy
    private String DSNValeurs=null;
    private String admDbValeurs=null;
    private String mdpDbValeurs=null;
...............

         // inicjalizacja serwletu
        public void init(){
             // pobieramy parametry inicjalizacji serwletu
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");
            DSNValeurs=config.getInitParameter("DSNValeurs");
            admDbValeurs=config.getInitParameter("admDbValeurs");
            mdpDbValeurs=config.getInitParameter("mdpDbValeurs");

             //Czy pobrano wszystkie parametry?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // wypełnianie tablicy wartości na podstawie bazy danych ODBC
             // o nazwie DSN: DSNvaleurs
...............

Aby przetestować serwlet, należy ponownie uruchomić Tomcat, aby uwzględnił nowy plik konfiguracyjny web.xml aplikacji liste. W przeglądarce należy wywołać stronę URL serwletu http://localhost:8080/liste/valeurs2:

Image

Jeśli w pliku web.xml brakuje jednego z parametrów inicjalizacyjnych wymaganych przez serwlet, wyświetla się następująca strona:

Image

3.3.5. Parametry inicjalizacyjne aplikacji internetowej

W powyższym przykładzie tylko serwlet lstValeurs2 ma dostęp do parametrów title, DSNValeurs, admDbValeurs, mdpDbValeurs. Można sobie wyobrazić, że inny serwlet tej samej aplikacji, liste, potrzebuje danych z tej samej bazy danych, z której korzysta serwlet lstValeurs2. W takim przypadku należałoby ponownie zdefiniować parametry DSNValeurs, admDbValeurs, mdpDbValeurs w sekcji konfiguracyjnej pliku web.xml nowego serwletu. Innym rozwiązaniem jest zdefiniowanie parametrów wspólnych dla kilku serwletów na poziomie aplikacji, a nie na poziomie poszczególnych serwletów. Nowy plik web.xml aplikacji przyjmuje następującą postać:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <context-param>
      <param-name>DSNValeurs</param-name>
    <param-value>odbc-valeurs</param-value>
  </context-param>
  <context-param>
      <param-name>admDbValeurs</param-name>
    <param-value>admDbValeurs</param-value>
  </context-param>
  <context-param>
      <param-name>mdpDbValeurs</param-name>
    <param-value>mdpDbValeurs</param-value>
  </context-param>   

    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
    <servlet>
      <servlet-name>lstValeurs3</servlet-name>
    <servlet-class>gener6</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
      <servlet-name>lstValeurs3</servlet-name>
    <url-pattern>/valeurs3</url-pattern>
  </servlet-mapping>
</web-app>

Nowy serwlet nosi nazwę lstValeurs3, jest powiązany z plikiem klasy gener6 i został przypisany do aliasu /valeurs3 (mapowanie serwletów). Parametr title jest jedynym parametrem, który został zachowany w definicji serwletu. Pozostałe parametry zostały umieszczone w konfiguracji aplikacji w tagach <context-param>. Tag ten służy do definiowania informacji właściwych dla aplikacji, a nie dla konkretnego serwletu lub strony JSP. W jaki sposób serwlet Java uzyskuje dostęp do tych parametrów, często nazywanych parametrami kontekstowymi? Metody dostępne w celu uzyskania informacji kontekstowych są bardzo podobne do tych stosowanych w celu uzyskania parametrów inicjalizacyjnych właściwych dla serwletu:

[Servlet].getServletContext()
metoda klasy Servlet, od której wywodzi się klasa HttpServlet wykorzystywana w programowaniu internetowym. Zwraca obiekt ServletContext, zapewniający dostęp do parametrów konfiguracyjnych aplikacji
[ServletContext].getInitParameter("paramètre")
metoda klasy ServletContext, która zwraca wartość parametru inicjalizacyjnego „paramètre”

Klasa gener6.java wprowadza jedynie następujące zmiany w kodzie Java klasy gener5.java używanej wcześniej:

             // pobieramy parametry inicjalizacyjne serwletu
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");

            ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");

             //– czy pobrano wszystkie parametry?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // wypełniamy tablicę wartości na podstawie danych z bazy danych ODBC
             // o nazwie DSN: DSNvaleurs
...............

Parametr title właściwy dla serwletu jest uzyskiwany za pośrednictwem obiektu ServletConfig. Trzy pozostałe parametry zdefiniowane na poziomie aplikacji są uzyskiwane za pośrednictwem obiektu ServletContext. Kompilujemy tę klasę i umieszczamy ją, podobnie jak pozostałe, w katalogu <application>\WEB-INF\classes:

Image

Uruchamiamy ponownie serwer Tomcat, aby uwzględnił nowy plik aplikacji web.xml, a następnie wywołujemy obiekty URL i http://localhost:8080/liste/valeurs3:

Image

3.3.6. Parametry inicjalizacji strony JSP

Widzieliśmy już, jak zdefiniować parametry inicjalizacyjne dla serwletu lub aplikacji internetowej. Czy można zrobić to samo dla strony JSP? Wróćmy do początku kodu strony listvaleurs.jsp, którą już analizowaliśmy:

<%@ page import="java.sql.*, java.util.*" %>

<%!
         // zmienne globalne aplikacji
         // tytuł strony
        private final String title="Génération d'un formulaire";
         // baza danych wartości listy
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
.........

W pliku web.xml aplikacji zdefiniowano cztery stałe: title, DSNValeurs, admDbValeurs oraz mdpDbValeurs. Stałe DSNValeurs, admDbValeurs i mdpDbValeurs zostały teraz zdefiniowane na poziomie aplikacji, dlatego można założyć, że strona JSP należąca do tej aplikacji będzie miała do nich dostęp. Tak właśnie jest. Wiemy, że strona JSP zostanie przekształcona w serwlet. Będzie on miał dostęp do kontekstu za pośrednictwem metody getServletContext(). Bardziej skomplikowany jest przypadek stałej title. Zdefiniowaliśmy ją bowiem na poziomie serwletu, a nie na poziomie aplikacji, w następujący sposób:

    <servlet>
      <servlet-name>lstValeurs3</servlet-name>
    <servlet-class>gener6</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
  </servlet>

W przypadku strony JSP powyższa składnia nie ma już zastosowania, ponieważ nie ma już pojęcia pliku klasy. Składnia konfiguracji strony JSP jest jednak bardzo zbliżona do składni serwletu. Wygląda ona następująco:

    <servlet>
      <servlet-name>JSPlstValeurs</servlet-name>
    <jsp-file>/listvaleurs2.jsp</jsp-file>
...
  </servlet>

W rzeczywistości strona JSP jest traktowana jako serwlet, któremu nadaje się nazwę (servlet-name). Zamiast powiązać z tym serwletem plik klasy, powiązuje się z nim plik źródłowy strony JSP, który ma zostać wykonany (jsp-file). W ten sposób kilka poprzednich wierszy definiuje serwlet o nazwie JSPlstvaleurs powiązany ze stroną JSP /listvaleurs2.jsp. Ścieżka /listvaleurs2.jsp jest mierzona względem katalogu głównego aplikacji. Tak więc w przypadku naszej aplikacji liste plik listvaleurs2.jsp znajdowałby się w folderze docBase (por. server.xml) aplikacji liste:

Image

Konfiguracja strony JSP będzie wyglądała następująco w pliku web.xml aplikacji:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <context-param>
      <param-name>DSNValeurs</param-name>
    <param-value>odbc-valeurs</param-value>
  </context-param>
  <context-param>
      <param-name>admDbValeurs</param-name>
    <param-value>admDbValeurs</param-value>
  </context-param>
  <context-param>
      <param-name>mdpDbValeurs</param-name>
    <param-value>mdpDbValeurs</param-value>
  </context-param>   
.......      
    <servlet>
      <servlet-name>JSPlstValeurs</servlet-name>
    <jsp-file>/listvaleurs2.jsp</jsp-file>
    <init-param>
        <param-name>JSPtitle</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
  </servlet>
..........
  <servlet-mapping>
      <servlet-name>JSPlstValeurs</servlet-name>
    <url-pattern>/jspvaleurs</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
........
</web-app>

Strona JSP listvaleurs2.jsp znajduje się w katalogu głównym aplikacji liste i jest powiązana z nazwą serwletu JSPlstValeurs (nazwa serwletu), która z kolei jest powiązana z aliasem /jspvaleurs (mapowanie serwletu). W ten sposób nasza strona JSP będzie dostępna poprzez URL http://localhost:8080/liste/jspvaleurs.

Początkowa strona JSP (listvaleurs.jsp) zostaje zmodyfikowana na listvaleurs2.jsp i pobiera swoje cztery parametry inicjalizacyjne w metodzie jspInit():

<%!
         // globalne zmienne aplikacji
         // tytuł strony
        private String title=null;
        // baza danych wartości listy
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
         // wartości z listy
        private String[] valeurs=null;
         // komunikat o błędzie
        private String msgErreur=null;

         // inicjalizacja strony JSP – wykonywana tylko raz
        public void jspInit(){

             // pobieranie parametrów inicjalizacyjnych serwletu
      ServletConfig config=getServletConfig();
            title=config.getInitParameter("JSPtitle");
      ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");

             //Czy pobrano wszystkie parametry?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // wypełnianie tablicy wartości na podstawie danych z bazy danych ODBC
             // o nazwie DSN: DSNvaleurs
..............

Strona JSP pobiera swoje parametry inicjalizacyjne w taki sam sposób jak serwlety. Poprzedni plik jest zapisany w katalogu głównym aplikacji internetowej liste:

Image

Serwer Tomcat jest ponownie uruchamiany, aby wymusić ponowne wczytanie nowego pliku konfiguracyjnego aplikacji web.xml. Następnie można wywołać URL http://localhost:8080/liste/jspvaleurs:

Image

3.3.7. Współpraca serwletów i stron JSP w ramach aplikacji internetowej

Gdy klient wysyła żądanie do serwera WWW, odpowiedź może być generowana przez wiele serwletów i stron JSP. Dotychczas odpowiedź była generowana przez pojedynczy serwlet lub stronę JSP. Zauważyliśmy, że strona JSP zapewnia lepszą czytelność struktury generowanego dokumentu HTML. Zawiera ona jednak zazwyczaj również dużo kodu Java. Sytuację tę można poprawić, umieszczając

  • w jednym lub kilku serwletach umieścić kod Java, który nie generuje kodu HTML w odpowiedzi
  • na stronach JSP, a kod generujący różne dokumenty HTML wysyłane w odpowiedzi do klienta

Można w ten sposób liczyć na lepsze rozdzielenie kodu Java od kodu HTML. Zastosujemy tę nową strukturę do naszej aplikacji liste: serwlet Java lstValeurs4 będzie odpowiedzialny za odczytanie wartości z bazy danych podczas uruchamiania, a następnie za analizę żądań klientów. W zależności od wyniku analizy żądanie klienta zostanie przekierowane na stronę błędu erreur.jsp lub na stronę wyświetlającą listę liczb liste.jsp. Aplikacja liste będzie zatem składać się z serwletu oraz dwóch stron JSP.

W jaki sposób serwlet może przekazać żądanie otrzymane od klienta do innego serwletu lub do strony JSP? Wykorzystamy następujące metody:

[ServletContext].getRequestDispatcher(
String url)
metoda klasy ServletContext, która zwraca obiekt RequestDispatcher. Parametr url to nazwa serwletu URL, do którego chcemy przekazać żądanie klienta. Przekazanie żądania może nastąpić wyłącznie w obrębie tej samej aplikacji. Dlatego parametr url jest ścieżką względną w strukturze katalogów tej aplikacji.
[RequestDispatcher].forward
(ServletRequest request,
 ServletResponse response)
metoda interfejsu RequestDispatcher, która przekazuje do poprzedniej metody URL żądanie klienta request oraz obiekt response, który ma zostać wykorzystany do przygotowania odpowiedzi.
[ServletRequest].setAttribute(String nom, Object obj)
gdy serwlet lub strona JSP przekazuje żądanie do innego serwletu lub strony JSP, zazwyczaj musi przekazać jej informacje inne niż samo żądanie klienta – informacje wynikające z własnej obróbki tego żądania. Metoda setAttribute klasy ServletRequest umożliwia dodawanie atrybutów do obiektu request klienta w formie przypominającej słownik par (atrybut, wartość), gdzie attribut to nazwa atrybutu, a valeur to dowolny obiekt reprezentujący jego wartość.
[ServletRequest].getAttribute(
String attribut)
pozwala pobrać wartości atrybutów z zapytania. Metoda ta będzie wykorzystywana przez serwlet lub stronę JSP, do której przekazano zapytanie w celu uzyskania informacji w nim zawartych.

Serwlet odpowiedzialny za przetwarzanie formularza zostanie skonfigurowany w następujący sposób w pliku web.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <context-param>
      <param-name>DSNValeurs</param-name>
    <param-value>odbc-valeurs</param-value>
  </context-param>
  <context-param>
      <param-name>admDbValeurs</param-name>
    <param-value>admDbValeurs</param-value>
  </context-param>
  <context-param>
      <param-name>mdpDbValeurs</param-name>
    <param-value>mdpDbValeurs</param-value>
  </context-param>   

............
    <servlet>
      <servlet-name>lstValeurs4</servlet-name>
    <servlet-class>gener7</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
    <init-param>    
        <param-name>JSPerreur</param-name>
      <param-value>/erreur.jsp</param-value>
    </init-param>      
    <init-param>      
        <param-name>JSPliste</param-name>
      <param-value>/liste.jsp</param-value>
    </init-param>      
    <init-param>      
        <param-name>URLservlet</param-name>
      <param-value>/liste/valeurs4</param-value>
    </init-param>      
  </servlet>
...........
  <servlet-mapping>
      <servlet-name>lstValeurs4</servlet-name>
    <url-pattern>/valeurs4</url-pattern>
  </servlet-mapping>
.......
</web-app>

Serwlet lstValeurs4 będzie miał cztery własne parametry inicjalizacyjne:

title
tytuł dokumentu HTML, który ma zostać wygenerowany
JSPerreur
URL strony błędu JSP
JSPliste
URL ze strony JSP zawierającej listę liczb
URLservlet
URL powiązany z atrybutem „action” formularza wyświetlanego na stronie JSPliste. Ten URL będzie odpowiadał serwletowi lstValeurs4

Serwlet będzie miał alias /valeurs4 (mapowanie serwletu) i będzie zatem dostępny poprzez URL http://localhost:8080/liste/valeurs4. Jest ona powiązana z plikiem klasy gener7.java, którego pełny kod źródłowy wygląda następująco:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
import java.util.*;

public class gener7 extends HttpServlet{
        // tytuł strony
        private String title=null;
        // baza danych wartości listy
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
         // strony wyświetlania JSP
        private String JSPerreur=null;
        private String JSPliste=null;
        // serwlet URL
        private String URLservlet=null;
        // wartości z listy
        private String[] valeurs=null;
         // komunikat o błędzie
        private String msgErreur=null;

        // -----------------------------------------------------------------
        // GET
        public void doGet(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // w atrybutach żądania umieszczamy msgErreur,title
            request.setAttribute("msgErreur",msgErreur);
            request.setAttribute("title",title);
            request.setAttribute("URLservlet",URLservlet);

             // czy wystąpił błąd podczas ładowania serwletu?
            if(msgErreur!=null){
                 // przekierowujemy do strony błędu JSP
                getServletContext().getRequestDispatcher(JSPerreur).forward(request,response);
                 // koniec
                return;
            }

             // nie wystąpił błąd
             // umieszczamy listę wartości w atrybutach żądania
            request.setAttribute("valeurs",valeurs);

             // pobieramy ewentualny wybór użytkownika
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";
            request.setAttribute("choix",choix);

             // przekazujemy kontrolę do strony JSP wyświetlającej listę
            getServletContext().getRequestDispatcher(JSPliste).forward(request,response);
            // koniec
            return;
        }//GET

        // -----------------------------------------------------------------
        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

             // odsyła do GET
            doGet(request,response);
        }//POST

        // -----------------------------------------------------------------
         // inicjalizacja serwletu
        public void init(){

             // pobieranie parametrów inicjalizacji serwletu
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");
            JSPerreur=config.getInitParameter("JSPerreur");
            JSPliste=config.getInitParameter("JSPliste");
            URLservlet=config.getInitParameter("URLservlet");

            ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");


             //Czy pobrano wszystkie parametry?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null || JSPerreur==null || JSPliste==null || URLservlet==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // wypełnianie tablicy wartości na podstawie danych z bazy danych ODBC
             // o nazwie DSN: DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // połączenie z bazą ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // obiekt Statement
                st=connexion.createStatement();
                 // wykonanie zapytania SELECT w celu pobrania wartości
                rs=st.executeQuery("select valeur from Tvaleurs");
                // wartości zostały pobrane i umieszczone w tablicy dynamicznej
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // wartość jest zapisywana na liście
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                 // przekształcenie listy w tablicę
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                 // problem
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//inicjalizacja
    }//klasa

Nowością w tej klasie jest przekazywanie żądania klienta do strony JSPerreur w przypadku błędu, a w przeciwnym razie do strony JSPliste. Klasa ta nie generuje samodzielnie odpowiedzi. Zadanie to realizują strony JSP, JSPerreur oraz JSPliste. Wcześniej serwlet dodawał atrybuty (setAttribute) do żądania klienta:

  • komunikat o błędzie msgErreur w przypadku wystąpienia błędu na stronie JSPerreur
  • wartości (valeurs)) do wyświetlenia, wartość wybrana (choix) przez użytkownika, tytuł (title) formularza, URL (URLservlet) atrybutu action formularza dla strony JSPliste

Klasa ta jest kompilowana i umieszczana w klasach aplikacji:

Image

Strona JSP wyświetlająca komunikat o błędzie jest skonfigurowana w następujący sposób:

    <servlet>
      <servlet-name>JSPerreur</servlet-name>
    <jsp-file>/erreur.jsp</jsp-file>
    <init-param>
        <param-name>mainServlet</param-name>
      <param-value>/valeurs4</param-value>
    </init-param>
  </servlet>
.........
<servlet-mapping>
      <servlet-name>JSPerreur</servlet-name>
    <url-pattern>/JSPerreur</url-pattern>
  </servlet-mapping>

Plik JSP powiązany ze stroną błędu nosi nazwę erreur.jsp i znajduje się w katalogu głównym aplikacji:

Image

Ma on alias /JSPerreur, co sprawia, że jest dostępny poprzez adres http://localhost:8080/liste/JSPerreur. Posiada parametr inicjalizacyjny o nazwie mainServlet, którego wartością jest alias głównego serwletu opisanego powyżej. Należy zauważyć, że alias ten odnosi się do katalogu głównego aplikacji liste,; w przeciwnym razie mielibyśmy /liste/valeurs4.. Kod strony erreur.jsp jest następujący:

<%
     // kod z _jspService
   // pobieramy parametr inicjalizacyjny mainServlet
  String servletListValeurs=config.getInitParameter("mainServlet");
   // pobieramy atrybut msgErreur
  String msgErreur=(String)request.getAttribute("msgErreur");
   // czy atrybut jest prawidłowy?
  if(msgErreur!=null){
%>
       <!-- kod HTML -->
    <html>
        <head>
          <title>Erreur</title>
      </head>
      <body>
          <h3>Application indisponible (<%= msgErreur %>)</h3>
      </body>
    </html>
<%
    } else { // nieprawidłowy atrybut msgErreur – powrót do głównego serwletu
%>
    <jsp:forward page="<%= servletListValeurs %>" />  
<%    
  }
%>  

Ta strona powinna być wywołana przez poprzedni serwlet, który powinien przekazać jej atrybut msgErreur. Nic jednak nie stoi na przeszkodzie, aby wywołać ją bezpośrednio, jeśli znamy jej atrybut URL. Ponadto, jeśli okaże się, że brakuje atrybutu msgErreur, żądanie przekazujemy do głównego serwletu. W tym przypadku używamy tagu charakterystycznego dla stron JSP, którego składnia jest następująca:

<jsp:forward page="URL" />

gdzie URL to identyfikator URL serwletu, do którego przekazywane jest żądanie klienta. Jeśli atrybut msgErreur jest obecny, wyświetlana jest strona błędu.

Strona JSP wyświetlająca listę liczb jest skonfigurowana w następujący sposób:

    <servlet>
      <servlet-name>JSPliste</servlet-name>
    <jsp-file>/liste.jsp</jsp-file>
    <init-param>
        <param-name>mainServlet</param-name>
      <param-value>/valeurs4</param-value>
    </init-param>
.........
  <servlet-mapping>
      <servlet-name>JSPliste</servlet-name>
    <url-pattern>/JSPliste</url-pattern>
  </servlet-mapping>

Plik JSP powiązany ze stroną błędu nosi nazwę liste.jsp i znajduje się w katalogu głównym aplikacji:

Image

Serwlet ma alias /JSPliste, dzięki czemu jest dostępny pod adresem http://localhost:8080/liste/JSPliste. Posiada parametr inicjalizacyjny o nazwie mainServlet, którego wartością jest alias głównego serwletu. Kod strony liste.jsp wygląda następująco:

   <%-- strona wyświetlająca listę wartości --%>
  <%
       // kod jspService
       // pobieramy parametr inicjalizacyjny
      String servletListValeurs=config.getInitParameter("mainServlet");

     // pobieranie atrybutów żądania pochodzącego z głównego serwletu
    String title=(String) request.getAttribute("title");
    String[] valeurs=(String[]) request.getAttribute("valeurs");
    String choix=(String) request.getAttribute("choix");
    String URLservlet=(String) request.getAttribute("URLservlet");

     // czy atrybuty są prawidłowe?
    if(title==null || valeurs==null || choix==null){
         // występuje nieprawidłowy atrybut – przekazujemy kontrolę do serwletu
   %>
   <jsp:forward page="<%= servletListValeurs %>" />
    <%
    }//if
  %>

  <%-- kod HTML --%>  
      <html>
        <head>
          <title><%= title %></title>
      </head>
      <body>
          <h3>Choisissez une valeur</h3>
          <form method="POST" action="<%= URLservlet %>">
            <select name="cmbValeurs">
              <%
                 // dynamiczne wyświetlanie wartości
                          String selected="";
                          for (int i=0;i<valeurs.length;i++){
                              if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                              out.println("<option "+selected+">"+valeurs[i]+"</option>");
                          }//for
            %>
          </select>
          <input type="submit" value="Envoyer">
        </form>
                <%
             // czy wybrano jakąś wartość?
                    if(! choix.equals("")){
                // wyświetla się wybór użytkownika
                %>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
                <%        
                    }//if
                %>        
      </body>
    </html>

Ta strona działa tak samo jak strona erreur.jsp. Powinna być zwykle wywoływana przez serwlet /liste/valeurs4 i otrzymywać atrybuty title, valeurs oraz choix. Jeśli brakuje jednego z tych parametrów, kontrola przechodzi do serwletu URLservlet (/liste/valeurs4). Jeśli wszystkie parametry są obecne, wyświetlana jest lista liczb oraz liczba wybrana przez użytkownika, o ile taką wybrał.

Jeśli wywołamy serwlet główny URL, otrzymamy następujący wynik:

Image

z następującym kodem źródłowym (View/Source):

<html>
    <head>
      <title>Génération d'un formulaire</title>
  </head>
  <body>
      <h3>Choisissez une valeur</h3>
      <form method="POST" action="/liste/valeurs4">
        <select name="cmbValeurs">
          <option >0</option>
        <option >1</option>
        <option >2</option>
        <option >3</option>
        <option >4</option>
        <option >6</option>
        <option >5</option>
        <option >7</option>
        <option >8</option>
        <option >9</option>
      </select>
      <input type="submit" value="Envoyer">
    </form>

  </body>
</html>

Niniejszy dokument HTMl został wygenerowany przez stronę JSP liste.jsp. Widać, że atrybuty title, valeurs, URLservlet zostały poprawnie pobrane.

Podsumowując współpracę serwletów i stron JSP, zauważamy, że strony JSP są tutaj bardzo krótkie i pozbawione kodu Java, który nie przyczynia się bezpośrednio do tworzenia odpowiedzi HTML. Dzięki temu struktura wygenerowanych dokumentów jest bardziej przejrzysta.

3.4. Cykl życia serwletów i stron JSP

3.4.1. Cykl życia

Zajmujemy się tutaj cyklem życia serwletów. Cyklem życia stron JSP jest on ściśle powiązany. Rozważmy serwlet wywołany po raz pierwszy. Serwer WWW tworzy wówczas instancję klasy i ładuje ją do pamięci. Następnie serwlet ten obsługuje żądanie. Po zakończeniu tej operacji serwlet nie jest usuwany z pamięci. Pozostaje w pamięci, aby obsługiwać kolejne żądania, co pozwala zoptymalizować czas odpowiedzi serwera. Zostanie usunięta z pamięci, gdy upłynie wystarczająco długi czas, w którym nie obsłużyła żadnych nowych żądań. Czas ten można zazwyczaj skonfigurować w ustawieniach serwera WWW.

Gdy serwlet znajduje się w pamięci, może obsługiwać wiele żądań jednocześnie. Serwer WWW tworzy jeden wątek na każde żądanie, a wszystkie one korzystają z tej samej instancji serwletu:

Wszystkie powyższe wątki współdzielą zmienne instancji serwletu. Konieczna może być synchronizacja wątków, aby uniknąć uszkodzenia danych serwletu. Wrócimy do tego później.

Podczas ładowania serwletu wykonywana jest określona metoda serwletu:

public void init() throws ServletException{
}

W przypadku strony JSP jest to metoda


  public void jspInit(){
  }

. Oto przykład strony JSP wykorzystującej metodę jspInit:

<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>

<%!
   // zmienne i metody globalne strony JSP

   // zmienna instancji
  int compteur;

   // metoda zwiększająca licznik  
  public int getCompteur(){
     // inkrementujemy licznik
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
     // przywraca się go
    return compteur;
  }

   // metoda wykonywana podczas pierwszego załadowania strony
  public void jspInit(){
     // inicjowanie licznika
    compteur=100;
  }
%>

Poprzednia strona JSP ustawia licznik w pliku jspInit na wartość 100. Każde kolejne żądanie skierowane do serwletu zwiększa tę wartość, a następnie wyświetla ją:

Za pierwszym razem:

Image

Za drugim razem:

Image

Jak widać powyżej, między dwoma żądaniami serwlet nie został zwolniony, w przeciwnym razie przy drugim żądaniu licznik miałby wartość 101. Gdy serwlet zostaje zwolniony, metoda

public void destroy(){
}

jest wykonywana, o ile istnieje. W przypadku stron JSP jest to metoda


  public void jspDestroy(){
  }

W tych metodach można na przykład zamknąć połączenia z bazami danych, które zostały otwarte w odpowiadających im metodach init.

3.4.2. Synchronizacja żądań z serwletem

Wróćmy do poprzedniej strony JSP, która zwiększa licznik i zwraca jego wartość do klienta internetowego. Załóżmy, że występują 2 równoczesne żądania. W celu ich wykonania tworzone są wówczas dwa wątki, które będą korzystać z tej samej instancji serwletu, a więc w tym przypadku z tego samego licznika. Przypomnijmy kod, który zwiększa licznik:


  public int getCompteur(){
     // zwiększamy licznik
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
     // zwracamy go
    return compteur;
  }

Inkrementacja licznika została celowo zapisana w sposób nieprecyzyjny. Załóżmy, że wykonanie obu wątków przebiega w następujący sposób:

 
  1. w momencie T1 uruchamia się wątek TH1. Odczytuje on licznik (=145) z myCompteur, po czym zostaje przerwany i traci dostęp do procesora. Nie zdążył więc zwiększyć wartości w wątku myCompteur i skopiować nowej wartości do wątku compteur.
  2. W momencie T2 uruchamiany jest wątek TH2. Odczytuje on licznik (=145) z myCompteur, po czym zostaje przerwany i traci dostęp do procesora. Należy zauważyć, że oba wątki mają różne zmienne myCompteur. Dzielą jedynie zmienne instancji, czyli te, które są globalne dla metod.
  3. W momencie T3 wątek TH1 przejmuje kontrolę i kończy działanie. Zwraca zatem 146 do swojego klienta.
  4. W momencie T4 wątek TH2 przejmuje kontrolę i kończy działanie. On również zwraca 146 do swojego klienta, podczas gdy powinien był zwrócić 147.

Mamy tu do czynienia z problemem synchronizacji wątków. Kiedy wątek TH1 chce zwiększyć licznik, należy uniemożliwić innym wątkom wykonanie tej samej operacji. Aby uwidocznić ten problem, przepisujemy stronę JSP w następujący sposób:

<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>

<%!
   // zmienne i metody globalne strony JSP

   // zmienna instancji
  int compteur;

   // metoda zwiększania licznika  
  public int getCompteur(){
     // odczyt licznika
    int myCompteur=compteur;
     // zatrzymuje się na 10 sekund
    try{
      Thread.sleep(10000);
    }catch (Exception ignored){}
     // zwiększanie wartości licznika
    compteur=myCompteur+1;
     // zwracamy go
    return compteur;
  }

   // metoda wykonywana przy pierwszym załadowaniu strony
  public void jspInit(){
     // inicjowanie licznika
    compteur=100;
  }
%>

W tym przypadku wymusiliśmy zatrzymanie wątku 10 sekund po odczytaniu licznika. Powinien on zatem zwolnić procesor, a inny wątek będzie mógł z kolei odczytać licznik, który nie został zwiększony. Kiedy wysyłamy zapytania za pomocą przeglądarki, nie widzimy żadnej różnicy poza 10-sekundowym oczekiwaniem na wynik.

Image

Teraz, jeśli otworzymy dwa okna przeglądarki i wyślemy dwa żądania w wystarczająco krótkim odstępie czasu:

Image

Image

Otrzymujemy tę samą wartość licznika. Problem ten można lepiej uwidocznić za pomocą klienta programowego, a nie ręcznego, jakim jest przeglądarka. Poniżej znajduje się klient napisany w języku Perl, wywoływany w następujący sposób:

program URL N

gdzie

URL to identyfikator serwletu liczącego URL

N to liczba żądań, które mają zostać wysłane do tej serwletu

Oto wyniki uzyskane dla 5 żądań, które wyraźnie pokazują problem złej synchronizacji wątków: wszystkie otrzymują tę samą wartość licznika.


DOS>java clientCompteurJSP http://localhost:8080/examples/jsp/perso/compteur/compteur2.jsp 5
Compteur=121
Compteur=121
Compteur=121
Compteur=121
Compteur=121

Kod klienta Java wygląda następująco.

import java.net.*;
import java.util.regex.*;
import java.io.*;

public class clientCompteurJSP {

    public static void main(String[] params){

         // dane
        String syntaxe="Syntaxe : pg URL nbAppels";

         // sprawdzanie parametrów
        if(params.length!=2){
            System.err.println(syntaxe);
            System.exit(1);
        }//if
         // URL
        URL urlCompteur=null;
        try{
            urlCompteur=new URL(params[0]);
            String query=urlCompteur.getQuery();
            if(query!=null) throw new Exception();
        }catch (Exception ex){
            System.err.println(syntaxe);
            System.err.println("URL ["+params[0]+" incorrecte");
            System.exit(2);
        }//try-catch
         // liczba wywołań
        int nbAppels=0;
        try{
            nbAppels=Integer.parseInt(params[1]);
            if(nbAppels<=0) throw new Exception();
        }catch(Exception ex){
            System.err.println(syntaxe);
            System.err.println("Nombre d'appels ["+params[1]+" incorrect");
            System.exit(3);
        }//try-catch

         // parametry są poprawne – można nawiązać połączenia z URL
        try{
            getCompteurs(urlCompteur,nbAppels);
        }catch(Exception ex){
            System.err.println(syntaxe);
            System.err.println("L'erreur suivante s'est produite : "+ex.getMessage());
            System.exit(4);
        }//try-catch
    }//main

    private static void getCompteurs (URL urlCompteur, int nbAppels)
            throws Exception {
         // wywołuje nbAppels w URL urlCompteur
         // za każdym razem wyświetla wartość licznika zwróconą przez serwer WWW


         // z urlCompteur pobierane są informacje niezbędne do połączenia z serwerem podatkowym
        String path=urlCompteur.getPath();
        if(path.equals("")) path="/";
        String host=urlCompteur.getHost();
        int port=urlCompteur.getPort();
        if(port==-1) port=urlCompteur.getDefaultPort();

         // wykonywane są wywołania do URL
        Socket[] clients=new Socket[nbAppels];
        for(int i=0;i<nbAppels;i++){
             // nawiązuje się połączenie z serwerem
            clients[i]=new Socket(host,port);
             // tworzy się strumień zapisu do serwera
            PrintWriter OUT=new PrintWriter(clients[i].getOutputStream(),true);
             // żądamy URL – wysyłamy nagłówki HTTP
            OUT.println("GET " + path + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            OUT.println("Connection: close");
            OUT.println("");
        }//dla

         // dane lokalne
        String réponse=null;                        // odpowiedź serwera
         // szukany wzorzec w odpowiedzi HTML z serwera
        Pattern modèleCompteur=Pattern.compile("^\\s*Compteur= (\\d+)");
         // szablon prawidłowej odpowiedzi
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // wynik porównania z wzorcem
        Matcher résultat=null;

        for(int i=0;i<nbAppels;i++){
             // każdy klient odczytuje odpowiedź przesłaną mu przez serwer

             // tworzy się strumienie wejścia-wyjścia klienta TCP
            BufferedReader IN=new BufferedReader(new InputStreamReader(clients[i].getInputStream()));

             // odczytuje się pierwszy wiersz odpowiedzi
            réponse=IN.readLine();
             // porównuje się wiersz HTTP z wzorcem prawidłowej odpowiedzi
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                 // wystąpił błąd dotyczący URL
                throw new Exception("Client n° " + i + " - Le serveur a répondu : URL ["+ urlCompteur + "] inconnue");
            }//if

             // odczytuje się odpowiedź aż do końca nagłówków
            while((réponse=IN.readLine())!=null && ! réponse.equals("")){
            }//while

             // kończy się sekcja nagłówków HTTP – przechodzimy do kodu HTML
             // w celu pobrania wartości licznika
            boolean compteurTrouvé=false;
            while((réponse=IN.readLine())!=null){
                 // porównujemy wiersz z wzorcem licznika
                if(! compteurTrouvé){
                    résultat=modèleCompteur.matcher(réponse);
                    if(résultat.find()){
                         // znaleziono licznik
                        System.out.println("Compteur="+résultat.group(1));
                        compteurTrouvé=true;
                    }//if
                }//if
            }//while

             // koniec
            clients[i].close();
        }//for
    }//getCompteurs

}//klasa

Wyjaśnijmy powyższy kod:

  • program przyjmuje dwa parametry:
    • identyfikator URL strony JSP licznika
    • liczba klientów, których należy utworzyć dla tego URL
  • program zaczyna więc od sprawdzenia poprawności parametrów: czy rzeczywiście są dwa, czy pierwszy jest składniowo zgodny z formatem URL, a drugi jest liczbą całkowitą większą od 0. Aby sprawdzić, czy URL jest poprawne pod względem składniowym, wykorzystuje się klasę URL oraz jej konstruktor URL (String), który tworzy obiekt URL na podstawie ciągu znaków, takiego jak http://istia.univ-angers.fr. Jeśli ciąg znaków nie jest składniowo poprawnym obiektem typu URL, zgłaszany jest wyjątek. Pozwala to na sprawdzenie poprawności pierwszego parametru.
  • Po sprawdzeniu parametrów kontrola przechodzi do procedury getCompteurs. Procedura ta utworzy klientów nbAppels, z których wszyscy połączą się jednocześnie (lub niemal jednocześnie) z serwerem URL urlCompteur.
  • Port oraz maszyna, z którą klienci mają się połączyć, są pobierane z URL, urlCompteur oraz [URL].Funkcja getHost() pozwala uzyskać nazwę komputera, a funkcja [URL].getPort() pozwala uzyskać numer portu.
  • Pierwsza pętla umożliwia każdemu klientowi:
    • połączyć się z serwerem WWW
    • wysłać do niego żądanie o URL urlCompteur

W tej pętli klient nie czeka na odpowiedź serwera. Chodzi bowiem o to, aby serwer otrzymywał żądania niemal jednocześnie.

  • Druga pętla pozwala każdemu klientowi na odebranie i przetworzenie odpowiedzi wysłanej przez serwer. Przetwarzanie polega na znalezieniu w odpowiedzi wiersza zawierającego wartość licznika i wyświetleniu tej wartości.

Aby rozwiązać problem wskazany wcześniej (ten sam licznik wysyłany do pięciu klientów), musimy zsynchronizować wątki usługi liczenia na tym samym obiekcie przed wejściem do sekcji krytycznej odczytu i aktualizacji licznika. Nowa strona JSP wygląda następująco:


<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>

<%!
   // zmienne i metody globalne strony JSP
  
   // zmienne instancji
  int compteur;
  Object verrou=new Object();
  
   // metoda zwiększania licznika  
  public int getCompteur(){
  
     // synchronizacja sekcji krytycznej
    synchronized(verrou){
       // odczyt licznika
      int myCompteur=compteur;
       // zatrzymujemy się na 10 sekund
      try{
        Thread.sleep(10000);
      }catch (Exception ignored){}
       // zwiększamy licznik
      compteur=myCompteur+1;
    }//zsynchronizowano
     // przywraca się
    return compteur;
  }//getCompteur

   // metoda wykonywana podczas pierwszego załadowania strony
  public void jspInit(){
     // inicjowanie licznika
    compteur=100;
  }
%>

Po uruchomieniu otrzymujemy następujące wyniki:

dos>c:\perl\bin\perl.exe client2.pl http://localhost:8080/examples/jsp/perso/compteur/compteur3.jsp 5
    Compteur= 104
    Compteur= 106
    Compteur= 105
    Compteur= 107
    Compteur= 108

Dokumentacja wskazuje, że serwer WWW może czasami utworzyć kilka instancji tego samego serwletu. W takim przypadku powyższa synchronizacja przestaje działać, ponieważ zmienna verrou jest lokalna dla jednej instancji i nie jest zatem znana pozostałym instancjom. To samo dotyczy zmiennej compteur. Aby nadać im charakter globalny dla wszystkich instancji, należy zapisać:


// zmienna klasy
  static int compteur;
  static Object verrou=new Object();

Pozostała część kodu pozostaje bez zmian.