Skip to content

3. Вступ до Java-сервлетів та сторінок JSP

У цьому розділі наведено різні приклади сервлетів та сторінок JSP. Вони були протестовані на сервері Tomcat, який працює на порту 8080. Перейшовши за посиланнями на головній сторінці, можна отримати доступ до прикладів сервлетів та сторінок JSP. Наведені нижче приклади здебільшого взяті з прикладів Tomcat. Щоб їх протестувати, достатньо запустити Tomcat, відкрити URL http://localhost:8080 у браузері та перейти за посиланням на сервлети.

3.1. Java-сервлети

3.1.1. Відправлення вмісту HTML веб-клієнту

Розглянемо наведений вище приклад «Hello World». Сервлет має такий вигляд:


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>");
    }
}

Під час виконання цього сервлету на екрані з'являється таке зображення:

Image

Слід звернути увагу на наступні моменти:

  • для сервлетів потрібно імпортувати спеціальні класи:

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

Бібліотека javax.servlet не завжди входить до стандартного комплекту JDK. У такому випадку її можна завантажити безпосередньо з веб-сайту Sun.

  • Сервлет є похідним від класу HttpServlet

public class HelloWorld extends HttpServlet {
  • Запит GET, надісланий до сервлету, обробляється методом doGet

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  • Аналогічно, запит POST, надісланий до сервлету, обробляється методом doPost

    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  • Об’єкт HttpServletRequest request — це об’єкт, який надає нам доступ до запиту, зробленого веб-клієнтом. Відповідь сервлету буде надана через об’єкт HttpServletResponse response
  • Об’єкт response дозволяє нам задати заголовки HTTP, які будуть надіслані клієнту. Наприклад, заголовок Content-type: text/html тут задається таким чином:
        response.setContentType("text/html");
  • Для відправлення відповіді клієнту сервлет використовує вихідний потік, який йому надає об’єкт response:
        PrintWriter out = response.getWriter();
  • Після отримання цього вихідного потоку у нього записується код HTML, після чого він надсилається клієнту:
        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. Отримання параметрів, надісланих веб-клієнтом

Наступний приклад демонструє, як сервлет може отримати параметри, надіслані веб-клієнтом. Форма введення даних:

Image

Відповідь, надіслана сервлетом:

Image

Вихідний код сервлета такий:


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);
    }

}

Відзначимо такі нововведення порівняно з попереднім прикладом:

  • Параметри, надіслані браузером, отримуються таким чином:

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

Метод request.getParameter("nomParamètre") повертає покажчик null, якщо параметр nomParamètre не входить до набору параметрів, надісланих веб-клієнтом.

  • У формі вказано, що браузер повинен надіслати параметри за допомогою методу POST
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
  • Отримані параметри будуть оброблені методом doPost сервлета. У цьому випадку цей метод просто викликає метод doGet. Таким чином, цей сервлет обробляє значення форми незалежно від того, чи були вони надіслані методом GET чи POST.

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

3.1.3. Отримання HTTP-заголовків, надісланих веб-клієнтом

Наведений нижче сервлет демонструє, як отримати заголовки HTTP, надіслані веб-клієнтом:

Image

Вихідний код сервлета такий:


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);
        }
    }
}

Важливі моменти:

  • Саме об’єкт request та його метод getHeaderNames надають нам доступ до заголовків HTTP, надісланих браузером у вигляді переліку:
        Enumeration e = request.getHeaderNames();
  • Метод request.getHeader("заголовок") дозволяє отримати конкретний HTTP-заголовок. У наведеному вище прикладі наведено декілька з них. Слід пам’ятати, що наведені тут заголовки надсилаються браузером. Сервер також має власні HTTP-заголовки, які іноді повторюють заголовки браузера. HTTP-заголовки, що надсилаються браузером, мають на меті інформувати сервер про можливості браузера.
Заголовок
Значення
User-Agent
ідентифікатор браузера
Accept
формати MIME, які підтримує браузер. Таким чином, image/gif означає, що браузер вміє обробляти зображення у форматі GIF
Host
у форматі hote:port. Вказує, з яким сервером і через який порт браузер хоче встановити з’єднання.
Accept-Encoding
формат кодування, який браузер приймає для документів, надісланих сервером. Отже, якщо сервер має документ у звичайному нестисненому вигляді та у форматі gzip, а браузер вказав, що вміє обробляти формат gzip, то сервер зможе надіслати документ у форматі gzip, щоб заощадити пропускну здатність.
Accept-language
мови, які підтримує браузер. Якщо сервер має один і той самий документ кількома мовами, він надішле той, мова якого підтримується браузером.
Referer
URL, який був запрошений браузером
Connection
режим з'єднання, запитуваний браузером. Keep-alive означає, що сервер не повинен розривати з'єднання після того, як надіслав запитувану сторінку браузеру. Якщо браузер виявить, що отримана сторінка містить, наприклад, посилання на зображення, він зможе надсилати нові запити до сервера, щоб завантажити їх, не створюючи нового з'єднання. Саме браузер візьме на себе ініціативу закрити з’єднання, коли отримає всі елементи сторінки.

3.1.4. Отримання інформації про середовище

Наведений нижче сервлет демонструє, як отримати інформацію про середовище виконання сервлета. Деякі з цих даних надсилаються браузером у вигляді HTTP-заголовків, тому їх можна отримати за допомогою описаного вище методу.

Image

Код сервлета такий:


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);
    }
}

Інформація тут отримується різними методами:

        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());

Нижче наведено перелік деяких доступних методів та їх значення:

метод
значення
getServerName()
назва веб-сервера
getServerPort()
робочий порт веб-сервера
getMethod()
метод GET або POST, який використовує браузер для відправлення запиту
getRemoteHost()
ім'я клієнтського комп'ютера, з якого браузер надіслав запит
getRemoteAddr()
адреса IP цього ж комп’ютера
getContentType()
тип вмісту, надісланого браузером (заголовок HTTP Content-type)
getContentLength()
кількість символів, надісланих браузером (заголовок HTTP Content-length)
getProtocol()
версія протоколу HTTP, яку запитує браузер
getRequestURI()
URI, запитуваний браузером. Відповідає частині URL, що розміщена після ідентифікатора hote:port у http://hote:port/URI

3.1.5. Створення сервлета з JBuilder та його розгортання за допомогою Tomcat

Тепер ми опишемо, як створити та запустити Java-сервлет. Ми використаємо два інструменти: JBuilder для компіляції сервлету та Tomcat для його виконання. Tomcat міг би вистачити сам по собі. Проте він має обмежені можливості налагодження. Ми повернемося до розробленого раніше прикладу, який виводить параметри, отримані сервером. Сервлет спочатку надсилає таку форму введення даних:

Image

Відповідь, надіслана сервлетом:

Image

Вихідний код сервлета такий:


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);
    }

}
  • Створіть проект myRequestParamExample за допомогою JBuilder і включіть до нього попередню програму myRequestParamExample.java.
  • Під час компіляції може виникнути така проблема: ваш файл JBuilder не обов’язково містить бібліотеку javax.servlet, необхідну для компіляції сервлетів. У цьому випадку вам потрібно налаштувати JBuilder так, щоб він використовував додаткові бібліотеки класів. Спосіб наведено в додатках до цього документа для JBuilder 7. Частково наводимо його тут:
  • увімкніть опцію Tools/Configure JDKs або (Options/Configure JDK)

Image

У розділі «Налаштування» (Settings) вище зазвичай у полі Name вказано версію JDK 1.3.1. Якщо у вас є новіша версія JDK, скористайтеся кнопкою Change, щоб вказати каталог, у якому вона встановлена. Вище було вказано каталог E:\Program Files\jdk14, де було встановлено JDK 1.4. Відтепер JBuilder використовуватиме цей JDK для своїх компіляцій та виконання. У розділі (Class, Source, Documentation) міститься список усіх бібліотек класів, які будуть аналізуватися JBuilder, у даному випадку — класи з JDK 1.4. Класів з цієї версії недостатньо для веб-розробки на Java. Щоб додати інші бібліотеки класів, використовується кнопка Add і вказуються додаткові файли .jar, які потрібно використовувати. Файли .jar — це бібліотеки класів. Tomcat 4.x містить усі бібліотеки класів, необхідні для веб-розробки. Вони знаходяться в <tomcat>\common\lib, де <tomcat> — це каталог інсталяції Tomcat:

Image

За допомогою кнопки Add ми додамо ці бібліотеки, одну за одною, до списку бібліотек, які сканує JBuilder:

Image

Відтепер можна компілювати Java-програми, що відповідають стандарту J2EE, зокрема Java-сервлети. JBuilder використовується лише для компіляції, а виконання згодом забезпечується Tomcat.

  • Тепер ви можете скомпілювати програму myRequestParamExample.java і створити сервлет myRequestParamExample.class. Куди розмістити цей сервлет? Якщо початкові налаштування Tomcat не змінювалися, файли .class сервлетів слід розмістити в папці <tomcat>\webapps\examples\WEB-INF\classes (Tomcat 4.x).
  • Переконайтеся, що Tomcat запущено, і за допомогою браузера відкрийте сторінку http://localhost:8080/examples/servlet/myRequestParamExample:

Image

3.1.6. Приклади

У наведених нижче прикладах ми використовували описаний раніше метод:

  • компіляція вихідного коду сервлета XX.java за допомогою JBuilder
  • розгортання сервлета XX.class у <tomcat>\webapps\examples\WEB-INF\classes
  • Запустити Tomcat, відкрити у браузері сторінку URL http://localhost:8080/examples/servlet/XX

3.1.6.1. Динамічне створення форми — 1

Як приклад розглянемо створення форми, що містить лише один елемент управління: список. Вміст цього списку формується динамічно на основі значень, взятих із масиву. Насправді ці значення часто беруться з бази даних. Форма має такий вигляд:

Image

Якщо для наведеного вище прикладу виконати Envoyer, отримаємо таку відповідь:

Image

Слід зауважити, що код URL, який формує відповідь, є таким самим, як і той, що відображає форму. У цьому випадку сервлет самостійно обробляє відповідь на форму, яку він сам надіслав. Це типовий випадок. Код HTML форми має такий вигляд:

<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>

Слід зазначити, що значення, які надсилаються через форму, передаються методом POST. Код HTML у відповіді:

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

Код сервлета, який генерує цю форму та відповідь, виглядає так:

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

public class gener1 extends HttpServlet{
    // змінні екземпляра
    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{

      // клієнту вказується тип надісланого документа
      response.setContentType("text/html");
       // надсилається форма
      PrintWriter out=response.getWriter();
            // початок
      out.println(HTML1);
            // комбінований список
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            for (int i=0;i<valeurs.length;i++){
                out.println("<option>"+valeurs[i]+"</option>");
            }//для
            out.println("</select>");
            // кінець форми
            out.println(HTML2+HTML3);
    }//GET

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

             // отримуємо вибір користувача
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) doGet(request,response);

             // готуємо відповідь
            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>";
             // повідомляємо клієнту тип надісланого документа
            response.setContentType("text/html");
             // надсилаємо форму
            PrintWriter out=response.getWriter();
            out.println(réponse);
    }//POST
    }//класифікація

Метод doGet використовується для формування форми. У ній є динамічна частина — вміст списку, який у даному випадку береться з масиву. Метод doPost використовується для формування відповіді. Тут єдиною динамічною частиною є значення, обране користувачем у списку форми. Це значення отримується за допомогою request.getParameter("cmbValeurs"), де cmbValeurs — це ім’я списку HTML:

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

На завершення слід зазначити наступне:

  • браузер надсилає значення форми до сервлету, який її згенерував, оскільки тег <form> не має атрибута <action>. У цьому випадку браузер надсилає дані, введені у форму, до сервлету URL, який її надав.
  • тег <form> вказує, що дані форми мають надсилатися за допомогою методу POST. Саме тому ці значення отримуються методом doPost сервлета.

3.1.6.2. Динамічне формування форми — 2

Повернемося до попереднього прикладу, змінивши його наступним чином. Пропонована форма залишається незмінною:

Image

Чи відрізняється відповідь:

Image

У відповіді повертається форма, а під нею вказано число, обране користувачем. Крім того, саме це число відображається як обране під час показу списку. Тоді користувач може обрати інше число:

Image

а потім виконати Envoyer. Він отримає таку відповідь:

Image

Код сервлету з іменем gener2.java виглядає так:

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

public class gener2 extends HttpServlet{
        // змінні екземпляру
        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{

            // отримуємо можливий вибір користувача
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

             // повідомляємо клієнту тип надісланого документа
            response.setContentType("text/html");
             // надсилається форма
            PrintWriter out=response.getWriter();
            // початок
            out.println(HTML1);
            // комбінований список
            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>");
            }//для
            out.println("</select>");
            // продовження форми
            out.println(HTML2);
            if(! choix.equals("")){
                // відображається вибір користувача
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//якщо
             // кінець форми
            out.println(HTML3);
        }//GET

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

            // перенаправлення на GET
            doGet(request,response);
        }//POST
    }//клас

Метод doGet виконує всі операції: він формує форму, яку надсилає клієнту, та обробляє значення, які той повертає. Слід звернути увагу на наступне:

  • перевіряється, чи має параметр cmbValeurs значення.
  • якщо так, то під час формування вмісту списку кожен його елемент порівнюється з вибором користувача, щоб присвоїти атрибут selected елементу, обраному користувачем: <option selected>елемент</option>. Крім того, під формою відображається значення вибору.

3.1.6.3. Динамічне формування форми — 3

Ми повертаємося до тієї самої задачі, що й раніше, але цього разу значення беруться з бази даних. У нашому прикладі це база MySQL:

  • база даних називається dbValeurs
  • його власником є admDbValeurs, пароль якого — mdpDbValeurs
  • база даних містить єдину таблицю з назвою tvaleurs
  • ця таблиця має лише одне цілочисельне поле з назвою «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)

База даних MySQL dbValeurs була відкрита драйвером ODBC для MySQL. Її ім’я DSN (Data Source Name) — odbc-valeurs. Код сервлету такий:

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

public class gener3 extends HttpServlet{
        // заголовок сторінки
        private final String title="Génération d'un formulaire";
         // база даних значень списку
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
         // значення списку
        private String[] valeurs=null;
         // повідомлення про помилку
        private String msgErreur=null;
        // код 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{

            // клієнту вказується тип надісланого документа
            response.setContentType("text/html");
             // вихідний потік
            PrintWriter out=response.getWriter();

            // чи успішно пройшла ініціалізація сервлету?
            if (msgErreur!=null){
                 // сталася помилка — генерується сторінка помилки
                out.println("<html><head><title>"+title+"</title></head>");
                out.println("<body><h3>Application indisponible ("+msgErreur+
                                        ")</h3></body></html>");
                return;
            }//if

             // отримуємо можливий вибір користувача
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

             // відправляється форма
             // початок
            out.println(HTML1);
            // комбінований список
            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>");
            // продовження форми
            out.println(HTML2);
            if(! choix.equals("")){
                // відображається вибір користувача
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//якщо
             // кінець форми
            out.println(HTML3);
        }//GET

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

            // перенаправлення на GET
            doGet(request,response);
        }//POST

        // ініціалізація сервлета
        public void init(){
             // заповнює масив значень з бази даних ODBC
             // з іменем DSN: DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // підключення до бази даних ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // об'єкт Statement
                st=connexion.createStatement();
                 // виконання запиту SELECT для отримання значень
                rs=st.executeQuery("select valeur from Tvaleurs");
                // значення отримано та поміщено в динамічний масив
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // значення записується у список
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                 // перетворення списку в масив
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                 // проблема
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//ініціалізація
    }//клас

Слід звернути увагу на такі важливі моменти:

  1. Сервлет може бути ініціалізований методом, сигнатура якого повинна бути public void init(). Цей метод виконується лише під час початкового завантаження сервлета
  2. Після завантаження сервлет залишається в пам'яті постійно. Це означає, що після обслуговування клієнта він не вивантажується. Таким чином, він швидше відповідає на запити клієнтів.
  3. У нашому сервлеті потрібно знайти список значень у базі даних. Оскільки цей список не змінюється з часом, метод init є ідеальним моментом для його отримання. Таким чином, сервлет звертається до бази даних лише один раз — під час його початкового завантаження, а не при кожному запиті клієнта.
  4. Доступ до бази даних може завершитися невдачею. Метод init нашого сервлета встановлює повідомлення про помилку msgErreur у разі невдачі. Це повідомлення перевіряється в методі doGet, і якщо сталася помилка, doGet генерує сторінку, що повідомляє про неї.
  5. У коді методу init використовується стандартний доступ до бази даних за допомогою драйверів Odbc-Jdbc. За необхідності читачеві пропонується ознайомитися з методами доступу до баз даних JDBC.

Під час виконання сервлету, якщо сервер MySQL не запущено, з’являється така сторінка помилки:

Image

Якщо тепер запустити сервер MySQL, з’явиться така сторінка:

Image

Якщо вибрати число 6 і натиснути «Відправити»:

Image

3.1.6.4. Отримання значень з форми

Повернемося до прикладу, з яким ми вже стикалися, — до наступної веб-форми:

Image

Код HTML для форми balises2.htm виглядає так:

<html>

  <head>
      <title>balises</title>
    <script language="JavaScript">
        function effacer(){
          alert("Vous avez cliqué sur le bouton Effacer");
      }//видалити
        </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>

Тег <form> у формі було визначено наступним чином:

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

Браузер «відправить» значення форми на URL http://localhost:8080/examples/servlet/parameters, який є URL сервлета, що керується Tomcat і відображає значення попередньої форми. Якщо викликати сервлет parameters безпосередньо, отримаємо такі результати:

Image

Якщо введена форма balises2.htm має такий вигляд:

Image

і натиснути кнопку «Відправити» (типу submit), сервлет parameters цього разу викликається з параметрами. Тоді він повертає таку відповідь:

Image

У цій відповіді дійсно містяться значення, введені у форму. Код сервлету такий:

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

public class parameters extends HttpServlet{
    // змінні екземпляра
    String title="Récupération des paramètres d'un formulaire";

    private String getParameter(HttpServletRequest request, String contrôle){
      // повертає значення request.getParameter (перевірка) або "" якщо вона не існує
      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
    {
      // спочатку отримуємо параметри форми
      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");

      // вказуємо вміст документа
        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>");
        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
    {
       // перенаправляє на GET
      doGet(request,response);
    }
}

У цьому коді використовуються техніки, представлені раніше в іншому прикладі. Слід звернути увагу на два моменти:

  1. елемент управління lst2 є списком із можливістю множинного вибору, отже, можна вибрати кілька елементів. Саме так і сталося в нашому прикладі, де було вибрано елементи liste1 та liste3. Значення lst2 були передані браузером на сервер у вигляді lst2=liste1&lst2=liste3. Java-сервлет може отримати ці значення у вигляді масиву за допомогою методу getParameterValues: тут request.getParameterValues("lst2") повертає масив із 2 символьних рядків ["liste1","liste3"].
  2. Елемент управління areaSaisie є багаторядковим полем введення. request.getParameter("areaSaisie") повертає вміст поля у вигляді єдиного символьного рядка. Якщо з нього потрібно витягти окремі рядки, що його складають, можна скористатися функцією split класу String. Наступний код
      String areaSaisie=getParameter(request,"areaSaisie");
      String[] lignes=areaSaisie.split("\\r\\n");

витягує рядки з поля введення. Ці рядки закінчуються символами \r\n (0D0A).

Для проведення тестів ми:

  • збудували та скомпілювали сервлет parameters разом із JBuilder, як було описано раніше
  • розмістили згенерований клас у <tomcat>\webapps\examples\WEB-INF\classes, де <tomcat> — це каталог інсталяції Tomcat.
  • запросив URL http://localhost:81/html/balises2.htm, код якого наведено вище
  • заповнив форму та натиснув кнопку Envoyer.

3.1.6.5. Отримання заголовків HTTP від веб-клієнта

Ми використовуємо той самий приклад, що й раніше, але у відповідь веб-клієнту, який надіслав значення форми, ми надсилаємо йому заголовки HTTP, які він надіслав одночасно. Ми вносимо лише одну зміну до нашої форми:

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

Значення з форми будуть надіслані методом GET до Java-сервлета з назвою headers, розташованого в <tomcat>\webapps\examples\WEB-INF\classes. Сервлет headers було створено та скомпільовано разом із 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
    {
       // визначається тип документа
        response.setContentType("text/html");
       // отримується потік запису
        PrintWriter out = response.getWriter();
      // відображення списку заголовків 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
}

Запитуємо URL http://localhost:81/html/balises2.htm і отримуємо Envoyer без зміни форми. Отримуємо таку відповідь:

Image

Зверніть увагу на значення URL, яке міститься в полі Address браузера і показує спосіб (GET), який використовується для передачі параметрів. Візьмемо той самий приклад, але змінимо спосіб надсилання параметрів (POST):

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

Отримуємо таку нову відповідь:

Image

Зверніть увагу на заголовки HTTP, content-type та content-length, характерні для відправлення за допомогою POST. Крім того, слід зазначити, що в полі Address браузера значення форми більше не відображаються.

3.2. Сторінки JSP

Сторінки JSP (Java Server Pages) — це ще один спосіб написання веб-серверних додатків. Фактично ці сторінки JSP перед виконанням перетворюються на сервлети, і тут знову застосовується технологія сервлетів. Сторінки JSP дозволяють краще висвітлити структуру згенерованих сторінок HTML. Нижче наведено приклади, деякі з яких можна переглянути, перейшовши за посиланням JSP на головній сторінці Tomcat:

3.2.1. Отримання інформації про середовище

Тут ми повернемося до прикладу, який вже розглядали у контексті сервлету: відображення змінних середовища сервлету. Це приклад snoop із набору прикладів JSP:

Image

Вихідний код сторінки JSP знаходиться в <tomcat>\jakarta-tomcat\examples\jsp\snp\snoop.jsp (Tomcat 3.x) або <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>

Звернемо увагу на наступні моменти:

  • ми маємо код, який дуже схожий на HTML. Однак у ньому містяться теги <%= expression %>, що є характерними для мови JSP. Компілятор JSP замінює в тексті HTML весь тег на значення expression.
  • У цьому прикладі використовуються методи об’єкта Java request, який є об’єктом request, з яким ми вже знайомилися під час вивчення сервлетів. Отже, це об’єкт HttpServletRequest. Таким чином, тег <%= request.getRemoteHost() %> буде замінено в коді HTML на ім’я комп’ютера веб-клієнта, який надіслав запит.
  • Такого самого результату можна досягти за допомогою сервлета, але тут структура веб-сторінки є більш зрозумілою.

3.2.2. Отримання параметрів, надісланих веб-клієнтом

Тут ми повернемося до прикладу, який вже розглядали з використанням сервлета. У браузері відображається форма:

Image

У відповідь на цей запит браузер отримує таку сторінку:

Image

Код сторінки JSP такий:


<%
   // локальні змінні головної процедури
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

<!-- код 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>
  • Якщо ми бачимо тег <%= вираз %>, який вже зустрічався в попередньому прикладі, то з’являється новий тег <% інструкції Java; %>. Тег <% вводить код Java. Цей код закінчується при зустрічі з тегом закриття коду %>.
  • Весь попередній код (HTML + JSP) буде перетворено на Java-сервлет. Він буде розміщений в єдиному методі, який називається головним методом сторінки JSP. Саме тому змінні Java, оголошені на початку сторінки JSP, доступні в інших фрагментах коду JSP, що розкидані по коду HTML: ці змінні та фрагменти коду стануть частиною одного й того самого методу Java. Але якби наш код JSP містив методи, змінні title, firstname та lastname не були б у ньому доступні через ізольованість методів. Їх потрібно було б зробити глобальними змінними або передати як параметри методам. Ми повернемося до цього пізніше.
  • Щоб включити динамічні елементи в код HTML, можна скористатися двома методами: <%= вираз %> або out.println(вираз). Об’єкт out — це потік виводу, аналогічний однойменному, що зустрічається у прикладах сервлетів, але не того самого типу: це об’єкт JspWriter, а не PrintWriter. Він дозволяє записувати в потік HTML за допомогою методів print та println.
  • Сторінка JSP краще відображає структуру згенерованої сторінки HTML, ніж еквівалентний сервлет.

3.2.3. Теги JSP

Ось перелік тегів, які можна зустріти на сторінці JSP, та їхнє значення.

тег
значення
<!-- коментар -->
коментар HTML. Надсилається клієнту.
<%-- коментар --%>
коментар JSP. Не надсилається клієнту.
<%! déclarations, méthodes %>
Оголошує глобальні змінні та методи. Змінні будуть доступні у всіх методах
<%= expression %>
значення виразу буде вставлено на сторінку HTML замість тегу
<% code Java %>
містить код Java, який стане частиною головного методу сторінки JSP
<%@ page attribut1=valeur1
attribut2=valeur2 … %>
визначає атрибути для сторінки JSP. Наприклад:
import="java.util.*,java.sql.*" для вказання бібліотек, необхідних для сторінки JSP
extends="uneClasseParent" — щоб сторінка JSP успадковувалася від іншого класу

3.2.4. Неявні об’єкти JSP

У попередніх прикладах ми зустріли два неоголошені об’єкти: request та out. Це два з об’єктів, які автоматично визначаються у сервлеті, в який перетворюється сторінка JSP. Їх називають неявними або попередньо визначеними об’єктами. Існують й інші, але найчастіше їх використовують разом з об’єктом response:

об’єкт
значення
HttpServletRequest request
об’єкт, через який здійснюється доступ до запиту веб-клієнта (getParameter, getParameterNames, getParameterValues)
HttpServletResponse response
об’єкт, за допомогою якого можна сформувати відповідь веб-сервера своєму клієнту. Дозволяє задати заголовки HTTP, які будуть надіслані веб-клієнту.
JspWriter out
вихідний потік, що дозволяє нам надсилати код HTML клієнту (print, println)

3.2.5. Перетворення сторінки JSP у сервлет

Повернемося до коду JSP з myRequestParamExample.jsp:


<%
   // локальні змінні головної процедури
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

<!-- код 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>

Коли браузер запитує цю сторінку JSP у сервера Tomcat, той перетворює її на сервлет. Якщо запитувана сторінка URL має вигляд

http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp, Tomcat 4.x розмістить згенерований сервлет у каталозі <tomcat>\work\localhost\examples\jsp\perso\intro:

Image

У цій назві міститься URL http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp сторінки JSP. Вище ми бачимо, що маємо доступ до Java-коду сервлету, згенерованого для сторінки JSP. У нашому прикладі він виглядає так:

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();


                   // локальні змінні головної процедури
                  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);
        }
    }
}

Згенерований код є досить складним. Ми зупинимося лише на таких моментах:

  • Головний метод сервлету виглядає так:
    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

Саме цей метод запускається при запуску сервлета. Бачимо, що він отримує два параметри: запит request від клієнта та об’єкт response для формування відповіді веб-клієнту.

  • У головному методі оголошується та ініціалізується об’єкт JspWriter out. Саме він дозволить надіслати код HTML клієнту за допомогою інструкцій out.print("codeHTML").
        JspWriter out = null;
...
            out = pageContext.getOut();
  • Код Java

<%
   // локальні змінні головної процедури
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

був повністю перенесений до головного методу _jspService сервлета. Те саме стосується всього коду, розміщеного в тегах <%… %>

  • Код HTML зі сторінки JSP є об’єктом інструкцій out.print("codeHTML") або out.write(...). Наприклад

                out.write("</title>\r\n  </head>\r\n  <body bgcolor=\"white\">\r\n    <h3>");
  • У цьому прикладі немає інших методів, окрім головного методу _jspService.

3.2.6. Методи та глобальні змінні сторінки JSP

Розглянемо таку сторінку JSP:


<%!
   // попередній тег починає розділ глобальних змінних та методів
   // ця частина буде перенесена без змін у сервлет
  
   // глобальна змінна
  String prenom="inconnu";

   // метод  
  private String sonChien(){
    return "milou";
  }//sonChien

   // ще один метод
  private void afficheAmi(JspWriter out) throws Exception{
    out.println("<p>Son ami s'appelle Haddock</p>");
  }//afficheAmi

   // кінець глобальної частини сервлета
%>  

<%
   // попередній тег вказує, що наступний код буде збережено
   // у головному методі сервлета
  
   // локальна змінна головного методу
  String nom="tintin";
%>


<%-- код 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>
      <%
         // ім’я його друга
        afficheAmi(out);
      %>
    </center>
  </body>
</html>

Ця сторінка JSP генерує таку веб-сторінку:

Image

Давайте розглянемо, як генеруються чотири наведені вище рядки:


      <p>Son nom est <%= nom %></p>
      <p>Son prénom est <%= prenom %></p>
      <p>Son chien s'appelle <%= sonChien() %></p>
      <%
         // ім'я його друга
        afficheAmi(out);
      %>

Наведені вище рядки знаходяться в тезі <%..%> і, отже, стануть частиною головного методу _jspService сервлету, який буде згенеровано. Як вони отримують доступ до змінних nom, prenom та методів sonChien і afficheAmi?

nom (tintin)
є локальною змінною головного методу сторінки JSP і, отже, відома в ньому
prenom (inconnu)
є глобальною змінною сторінки JSP і, отже, відома в головному методі
sonChien (milou)
є публічним методом сторінки JSP і, отже, доступний з головного методу
afficheAmi (Haddock)
є публічним методом сторінки JSP і, отже, доступний з головного методу. Зверніть увагу, що об’єкт out передається як параметр методу. Це є обов’язковим. Адже об’єкт out оголошується та ініціалізується в головному методі сервлету і не є глобальною змінною.

Тепер розглянемо код Java-сервлета, згенерованого на основі цієї сторінки JSP, після видалення непотрібного коду:

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 {

          // попередній тег починає розділ глобальних змінних та методів
           // ця частина буде скопійована без змін у сервлет

           // глобальна змінна
          String prenom="inconnu";

           // метод  
          private String sonChien(){
            return "milou";
          }//sonChien

           // ще один метод
          private void afficheAmi(JspWriter out) throws Exception{
            out.println("<p>Son ami s'appelle Haddock</p>");
          }//afficheAmi

           // кінець глобальної частини сервлета

    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");
                  // попередній тег вказує, що наступний код буде збережено
                   // у головному методі сервлета

                   // локальна змінна головного методу
                  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      ");

                        // ім'я його друга
                        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);
        }
    }
}

З наведеного вище видно, що Java-код, який містився між тегами JSP <%! .. %>, був перенесений повністю і не є частиною головного методу _jspService сервлета. Отже, змінні, оголошені в цій частині, є змінними екземпляру, тобто глобальними для методів, і саме тут можна визначати методи, відмінні від _jspService.


  // ця частина буде використана без змін у сервлеті

   // глобальна змінна
  String prenom="inconnu";

   // метод
  private String sonChien(){
    return "milou";
  }//sonChien

   // ще один метод
  private void afficheAmi(JspWriter out) throws Exception{
    out.println("<p>Son ami s'appelle Haddock</p>");
  }//afficheAmi

   // кінець глобальної частини сервлета

3.2.7. Розгортання та налагодження сторінок JSP на сервері Tomcat

Коли потрібно створити сторінку JSP і використовувати її на сервері Tomcat, постає питання, де саме розмістити цю сторінку в дереві каталогів сервера. Існують різні способи, до яких ми ще повернемося. Наразі найпростішим є розміщення сторінки JSP у папці в дереві каталогів <tomcat>\webapps\examples\jsp (Tomcat 4.x), де <tomcat> — це каталог інсталяції Tomcat. Отже, URL з попереднього прикладу знаходився за адресою http://localhost:8080/examples/jsp/perso/tintin/tintin.jsp. Це означає, що сторінка tintin.jsp знаходилася у папці <tomcat>\webapps\examples\jsp\perso\tintin.

Сторінка JSP перекладається у вихідний файл Java, який потім компілюється Tomcat, коли браузер запитує файл URL зі сторінки JSP. Можуть виникнути помилки компіляції. Tomcat 4.x повідомляє про них у своїй відповіді браузеру. Зокрема, він вказує рядки файлу .java, які містять помилки. Помилки можуть мати різні причини:

  1. код JSP на сторінці містить помилки (наприклад, помилки у тегах jsp, що використовуються)
  2. код Java, включений у сторінку JSP, містить помилки

Першу причину можна усунути, перевіривши код JSP на сторінці. Другу причину можна усунути, перевіривши код Java. Це можна зробити, безпосередньо скомпілювавши файл .java, згенерований для сторінки JSP, за допомогою такого інструменту, як JBuilder, який пропонує більш розширені можливості налагодження, ніж Tomcat.

3.2.8. Приклади

Повернемося до прикладу, який ми вже розглядали, із сервлетом, де користувач вибирає число зі списку, а сервер повідомляє йому, яке саме число він вибрав, одночасно повертаючи той самий список із вибраним елементом, який обрав користувач:

Image

Для створення цієї сторінки ми взяли код сервлета та змінили його наступним чином:

  • ми залишили без змін Java-код, який не генерував код HTML
  • код Java, який генерував код HTML, було перетворено на комбінацію коду HTML та коду JSP

У результаті отримуємо таку сторінку JSP:



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

<%!

         // глобальні змінні додатка
         // заголовок сторінки
        private final String title="Génération d'un formulaire";
         // база даних значень списку
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
         // значення списку
        private String[] valeurs=null;
         // повідомлення про помилку
        private String msgErreur=null;
    
         // ініціалізація сторінки JSP — виконується лише один раз
        public void jspInit(){
             // заповнює масив значень з бази даних ODBC
             // з іменем DSN: DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // підключення до бази даних ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // об'єкт Statement
                st=connexion.createStatement();
                 // виконання запиту SELECT для отримання значень
                rs=st.executeQuery("select valeur from Tvaleurs");
                // значення отримано та поміщено в динамічний масив
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // значення записується у список
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                 // перетворення списку в масив
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                 // проблема
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//ініціалізація
   
%>

<%
     // код з _jspService, що виконується при кожному запиті клієнта
   // чи сталася помилка під час ініціалізації сторінки JSP?
  if(msgErreur!=null){
%>
       <!-- код HTML -->
    <html>
        <head>
          <title>Erreur</title>
      </head>
      <body>
          <h3>Application indisponible (<%= msgErreur %></h3>
      </body>
    </html>
<%
       // кінець jspService
    return;
  }//if
  
     // отримуємо можливий вибір користувача
    String choix=request.getParameter("cmbValeurs");
    if(choix==null) choix="";
%>
  
   <%-- помилки немає — код HTML звичайної сторінки --%>
      <html>
        <head>
          <title><%= title %></title>
      </head>
      <body>
          <h3>Choisissez une valeur</h3>
          <form method="POST">
            <select name="cmbValeurs">
              <%
                 // динамічне відображення значень
                          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>");
                          }//для
            %>
          </select>
          <input type="submit" value="Envoyer">
        </form>
<%
         // чи було обрано якесь значення?
                if(! choix.equals("")){
        // відображається вибір користувача
%>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
<%        
                }//if
%>        
      </body>
    </html>

Звернімо увагу на наступні моменти:

  • інструкції import сервлета були обгорнуті директивою <% page import="..." %>
  • тег <%! ... %> охоплює глобальні змінні та методи Java додатка
  • метод init сервлета, який виконується лише один раз під час завантаження сервлета, для сторінки JSP викликається як jspInit. Ці два методи виконують одну й ту саму роль. Тому тут повністю повторено код методу init сервлета.
  • Змінні екземпляру сервлета, які повинні бути доступними в декількох методах, були скопійовані без змін. В основному це змінні title, valeurs та msgErreur, які потім використовуються в коді JSP.
  • Теги <% ... %> охоплюють код Java, який буде включено до методу _jspService, що виконується під час надходження запиту від клієнта.
  • Як і у випадку з сервлетом, метод _jspService спочатку перевірить значення змінної msgErreur, щоб визначити, чи потрібно генерувати сторінку помилки. Якщо помилка є, вона генерує сторінку помилки та зупиняється (return).
  • Якщо помилки немає, вона генерує форму зі списком значень
  • після цього вона перевіряє, чи вибрав користувач число; у цьому випадку вона відображає це число на згенерованій сторінці

Що ми виграли порівняно з сервлетом? Безсумнівно, кращий вигляд згенерованого коду HTML. Але все ще залишається багато коду Java, який «забруднює» цей вигляд. Пізніше ми розглянемо інший метод, який називається делегуванням, де ми зможемо розмістити основну частину коду Java у сервлеті, а сторінка JSP збереже лише код HTML та JSP. Таким чином ми чітко відокремлюємо частину обробки від частини візуалізації.

3.3. Розгортання веб-додатку на сервері Tomcat

Тепер ми розглянемо, як розгортати веб-додатки на Java за допомогою сервера Tomcat. Хоча наведена нижче інформація стосується саме цього сервера, розгортання веб-додатка на Java в іншому контейнері J2EE матиме особливості, схожі на ті, що будуть описані далі.

3.3.1. Конфігураційні файли server.xml та web.xml

До цього моменту для тестування наших сервлетів та сторінок JSP ми розміщували

  • сервлети в папку <tomcat>\webapps\examples\WEB-INF\classes. Тоді до них можна було отримати доступ через URL http://localhost:8080/examples/servlet/nomServlet
  • сторінки JSP у дереві <tomcat>\webapps\examples\jsp. Тоді до них можна було отримати доступ через URL http://localhost:8080/examples/jsp/nomPageJSP

Ми ніколи не пояснювали, чому це так. Налаштування сервера Tomcat здійснюється у текстовому файлі під назвою server.xml, який знаходиться у папці <tomcat>\conf:

Image

Цей текстовий файл насправді є файлом XML (eXtended Markup Language). Документ XML — це текстовий документ, що містить теги, так само як і документ HTML. Однак, хоча теги мови HTML чітко визначені, теги мови XML чітко визначені не є. Отже, наступний документ є документом XML:

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

Документ XML — це просто «розмічений» документ, який відповідає певним правилам розмітки:

  • текст, розмічений у формі <xx att1="val1" att2="val2" ....>текст</xx>
  • тег може бути окремим і мати вигляд <xx att1="val1" att2="val2" ..../>

Поля «atti» називаються атрибутами тегу «xx», а поля «vali» — значеннями, пов’язаними з цими атрибутами. Деякі документи HTML не є валідними документами XML. Наприклад, тег HTML <br> не є валідним тегом XML. Щоб він був валідним, його слід записати як <br/>, щоб дотриматися правила, згідно з яким кожен тег XML повинен бути закритий. Був створений варіант HTML під назвою XHTML, щоб перетворити будь-який документ XHTML на дійсний документ XML. Деякі сучасні веб-браузери здатні відображати файли XML. Отже, якщо назвати personne.xml документ XML, наведений у прикладі вище, і переглянути його за допомогою IE6, ми отримаємо таке зображення:

Image

IE6 розпізнає теги та виділяє їх кольором. Він також розпізнає структуру документа завдяки тегам. Отже, якщо назвати personne2.xml наступний документ:

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

і переглянемо його за допомогою IE6, отримаємо таке саме відображення:

Image

IE6 правильно розпізнав структуру та зміст документа. Уся цінність документа XML полягає саме в цій властивості: легко відтворити структуру та зміст документа XML. Це здійснюється за допомогою програми, яка називається парсером XML. Документи XML поступово стають стандартом у обміні документами в Інтернеті. Розглянемо комп’ютер А, який повинен надіслати документ DOC на комп’ютер Б. Документ DOC формується на основі інформації, що міститься в базі даних DB-A. Машина B, у свою чергу, повинна зберегти документ DOC у базі даних DB-B. Обмін може відбуватися наступним чином:

  • машина А отримує дані з бази DB-A і інкапсулює їх у текстовий документ XML
  • документ XML надсилається до машини B через мережу
  • машина B аналізує отриманий документ за допомогою парсера XML та витягує з нього як структуру, так і дані (як це зробив IE6 у нашому прикладі). Після цього вона може зберегти отримані дані в базі DB-B

Ми не будемо детальніше зупинятися на мові XML, яка заслуговує на окрему книгу.

Отже, у цьому випадку Tomcat налаштовується за допомогою файлу XML server.xml. Якщо переглянути його за допомогою IE6, ми отримаємо складний документ. Ми зупинимося лише на таких рядках:

Image

Нас тут цікавить тег <Context ...>. Він слугує для визначення веб-додатків. Слід звернути увагу на два його атрибути:

  • path — це назва веб-додатка
  • docBase — це папка, у якій вона знаходиться. Тут вказано відносне ім’я: examples. Відносне до якої папки? Відповідь також міститься у файлі server.xml у наступному рядку:

Image

У наведеному вище рядку визначено веб-сервер:

  • name: ім’я веб-сервера
  • appBase: корінь дерева документів, які він розповсюджує. Знову маємо відносне ім’я: webapps. Воно є відносним до каталогу встановлення сервера Tomcat <tomcat>. Отже, мова йде про папку <tomcat>\webapps.

Веб-додаток examples зберігає свої документи у папці examples (див. docBase вище). Ця назва є відносною до кореня веб-дерева сервера, c.a.d. <tomcat>\webapps. Отже, це папка <tomcat>\webapps\examples. Давайте детальніше розглянемо цю папку:

Image

Тут ми бачимо папку WEB-INF\classes, у якій ми розмістили наші сервлети для їхнього тестування. Папка WEB-INF містить файл із назвою web.xml:

Image

Цей файл слугує для налаштування веб-додатку examples. Наразі ми не будемо заглиблюватися в деталі цього файлу, оскільки він є надто складним. Ми просто зупинимося на таких кількох рядках:

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

Тег <servlet> використовується для визначення сервлета в рамках веб-додатку. Нагадаємо, що веб-додаток, про який йдеться, має назву examples. Тег servlet містить тут ще два теги:

  • <servlet-name>servletToJsp</servlet-name>: визначає ім’я сервлета
  • <servlet-name>servletToJsp</servlet-name>: визначає ім’я класу, який має виконуватися при запиті на сервлет. У цьому прикладі сервлет і його клас мають однакову назву. Це не є обов’язковим.

Як сервер Tomcat отримує запит на сервлет servletToJsp від браузера?

  • браузер надсилає запит на сервер Tomcat щодо сервлета URL: http://localhost:8080/examples/servlet/servletToJsp
  • Tomcat аналізує шлях до сервлету /examples/servlet/servletToJsp. Він інтерпретує першу частину шляху /examples як ім’я веб-додатку та шукає у своєму конфігураційному файлі server.xml, де зберігаються документи цього додатка. Як ми вже бачили раніше, це папка <tomcat>\webapps\examples.
  • Tomcat використовує решту шляху до сервлету, щоб знайти його у веб-додатку examples. Цей шлях /servlet/servletToJsp вказує, що він повинен запустити сервлет із назвою servletToJsp. Потім Tomcat прочитає файл конфігурації web.xml для додатка examples, який він знайде в <tomcat>\webapps\examples\WEB-INF. У цьому файлі він виявить, що сервлет servletToJsp пов’язаний із класом Java servletToJsp (див. файл web.xml вище). Потім він шукатиме цей клас у папці WEB-INF\classes веб-додатку examples, c.a.d. у <tomcat>\webapps\examples\WEB-INF\classes і виконає його.

Image

3.3.2. Приклад: розгортання веб-додатку «список»

Ми повернемося до сервлета, який ми вже розглядали раніше і який показував користувачеві список чисел, з яких він обирав одне. Потім сервлет підтверджував йому число, яке він обрав:

Image

Як показано у полі Address браузера вище, файл класу сервлета називався gener3. Згідно з поясненнями, наведеними раніше:

  • URL /examples/servlet/gener3 вказує, що це сервлет із назвою gener3 у веб-додатку examples
  • у файлі web.xml додатка examples не знайдемо нічого, що вказувало б на сервлет gener3. Як же тоді Tomcat її знайшов? Переглянувши весь файл web.xml, я не можу відповісти з упевненістю... Питання залишається відкритим...

Ми вирішили розгорнути сервлет gener3.class під іменем lstValeurs у веб-додатку під назвою «liste», розташованому в папці E:\data\serge\Servlets\lstValeurs:

Image

Ми розміщуємо файл gener3.class у папці WEB-INF\classes, зазначеній вище:

Image

Налаштовуємо веб-додаток «liste», додавши у файл server.xml такі рядки над тими, що визначають веб-додаток manager:

                 <!-- Особисте: lstValeurs -->
                <Context path="/liste" docBase="e:/data/serge/servlets/lstValeurs" />

                <!-- Контекст Tomcat Manager -->
                <Context path="/manager" docBase="manager" debug="0" privileged="true" />
                <!-- Контекст прикладів Tomcat -->
                <Context path="/examples" docBase="examples" debug="0" reloadable="true" crossContext="true">
........

Рядок, що визначає список додатків, вказує, що він знаходиться в папці e:/data/serge/servlets/lstValeurs. Тепер нам потрібно визначити файл 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>
</web-app>

Вищезазначений файл вказує, що сервлет із назвою lstValeurs пов'язаний із файлом класу gener3.class. Цей файл web.xml потрібно створити та зберегти в папці WEB-INF додатка «liste»:

Image

На знімку екрана вище показано папку src, у яку було поміщено вихідний файл gener3.java. Ця папка може не існувати. У даній демонстрації вона не відіграє жодної ролі. Ми готові до тестування:

  • зупиніть і запустіть Tomcat, щоб він перечитав свій файл конфігурації server.xml. Тут ми працюємо в Windows. У Unix можна змусити Tomcat перечитати файл конфігурації, не зупиняючи його.
  • У браузері введіть адресу URL http://localhost:8080/liste/servlet/lstValeurs

Image

Видно, що попередній URL містить ключове слово servlet, як і всі URL сервлетів, що використовувалися до цього часу. Цього можна уникнути, пов’язавши у файлі web.xml додатка «liste» сервлет lstValeurs із шаблоном 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>

У тезі <servlet-mapping> ми пов'язуємо шлях /valeurs із сервлетом lstValeurs, визначеним у попередніх рядках. Зберігаємо новий файл web.xml і викликаємо URL та http://localhost:8080/liste/valeurs:

Image

3.3.3. Розгортання публічних сторінок веб-додатку

Ми щойно розглянули розгортання веб-додатка, що складається з одного сервлета. Веб-додаток може містити безліч компонентів: сервлети, сторінки JSP, файли HTML, Java-аплети тощо. Де розміщуються ці елементи додатка? Якщо <application> — це папка веб-додатка, визначена атрибутом docBase додатка у файлі конфігурації Tomcat server.xml, то ми бачили, що сервлети розміщуються в <application>\WEB-INF\classes. Інші елементи додатка можна розміщувати будь-де в дереві папок <application>, за винятком папки WEB-INF. Розглянемо вже вивчену програму JSP listvaleurs.jsp:

Image

Ця сторінка JSP була збережена в папці <tomcat>\webapps\examples\jsp\perso\listvaleurs. Ця сторінка може бути компонентом раніше розгорнутого додатка «liste». Помістимо файл listvaleurs.jsp безпосередньо в папку цього додатка:

Image

Нагадаємо конфігурацію додатка liste у файлі server.xml:

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

Будь-який файл URL, що починається зі шляху /liste, вважається частиною програми liste і буде шукатися у вказаній папці. Відкриємо файл URL http://localhost:8080/liste/listvaleurs.jsp у браузері:

Image

Ми дійсно отримали очікувану сторінку JSP.

3.3.4. Параметри ініціалізації сервлета

Ми бачили, що сервлет налаштовується за допомогою файлу <application>\WEB-INF\web.xml, де <application> — це папка веб-додатку, до якого він належить. У цей файл можна включити параметри ініціалізації сервлета. Повернемося до нашого сервлета lstValeurs із веб-додатку liste, конфігураційний файл якого мав такий вигляд:

<?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>

Клас, пов'язаний із сервлетом, — це клас gener3. У його вихідному коді міститься визначення кількох констант:

public class gener3 extends HttpServlet{
        // заголовок сторінки
        private final String title="Génération d'un formulaire";
         // база даних значень списку
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";

Нагадаємо значення чотирьох констант, визначених вище:

title
заголовок документа HTML, згенерованого сервлетом
DSNValeurs
назва DSN бази даних ODBC, з якої сервлет отримує дані
admDbValeurs
ім’я користувача, який має право на читання даних з попередньої бази
mdpDbvaleurs
його пароль

Якщо адміністратор бази даних DSNValeurs змінить пароль користувача admDbValeurs, вихідний код сервлету потрібно буде змінити та перекомпілювати. Це не дуже зручно. Файл конфігурації сервлета web.xml пропонує нам альтернативу, дозволяючи визначати параметри ініціалізації сервлета за допомогою тегу <init-param>:

    <init-param>
        <param-name>...</param-name>
        <param-value>...</param-value>
    </init-param>
<param-name>
дозволяє задати ім'я параметра
<param-value>
визначає значення, пов'язане з попереднім параметром

Сервлет має доступ до своїх параметрів ініціалізації за допомогою таких методів:

[Servlet].getServletConfig()
метод класу Servlet, від якого походить клас HttpServlet, що використовується для веб-програмування. Повертає об’єкт ServletConfig, що надає доступ до параметрів конфігурації сервлету.
[ServletConfig].getInitParameter("paramètre")
метод класу ServletConfig, який повертає значення параметра ініціалізації «paramètre»

Ми налаштовуємо додаток liste за допомогою такого нового файлу 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>

У додатку liste ми визначаємо другий сервлет під назвою lstValeurs2, пов'язаний із файлом класу gener5. Останній розміщено в <application>\WEB-INF\classes:

Image

Сервлет lstValeurs2 має чотири параметри ініціалізації: title, DSNValeurs, admDbValeurs, mdpDbValeurs. Крім того, для сервлета за допомогою тегу <servlet-mapping> було визначено псевдонім /valeurs2. Отже, до сервлету lstValeurs2 у додатку liste можна буде отримати доступ через URL та http://localhost:8080/liste/valeurs2.

Вихідний код сервлета було змінено наступним чином для отримання параметрів ініціалізації сервлета:

public class gener5 extends HttpServlet{
    // заголовок сторінки
    private String title=null;
    // база даних значень списку
    private String DSNValeurs=null;
    private String admDbValeurs=null;
    private String mdpDbValeurs=null;
...............

         // ініціалізація сервлету
        public void init(){
             // отримано параметри ініціалізації сервлета
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");
            DSNValeurs=config.getInitParameter("DSNValeurs");
            admDbValeurs=config.getInitParameter("admDbValeurs");
            mdpDbValeurs=config.getInitParameter("mdpDbValeurs");

             //чи отримано всі параметри?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // заповнення масиву значень з бази даних ODBC
             // з назвою DSN: DSNvaleurs
...............

Щоб протестувати сервлет, потрібно перезапустити Tomcat, щоб він врахував новий файл конфігурації web.xml додатка liste. У браузері потрібно завантажити URL із сервлета http://localhost:8080/liste/valeurs2:

Image

Якщо у файлі web.xml відсутній хоча б один із параметрів ініціалізації, необхідних для сервлета, відображається така сторінка:

Image

3.3.5. Параметри ініціалізації веб-додатку

У наведеному вище прикладі лише сервлет lstValeurs2 має доступ до параметрів title, DSNValeurs, admDbValeurs, mdpDbValeurs. Можна уявити, що інший сервлет того самого додатка liste потребує даних із тієї самої бази даних, яку використовує сервлет lstValeurs2. У такому разі потрібно перезадати параметри DSNValeurs, admDbValeurs, mdpDbValeurs у розділі конфігурації файлу web.xml нового сервлета. Іншим рішенням є визначення параметрів, спільних для декількох сервлетів, на рівні додатка, а не на рівні окремих сервлетів. Новий файл 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>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>

Новий сервлет має назву lstValeurs3, пов’язаний із файлом класу gener6 і йому було присвоєно псевдонім /valeurs3 (servlet-mapping). Параметр title — єдиний параметр, який було збережено у визначенні сервлета. Решту параметрів було розміщено в конфігурації додатка у тегах <context-param>. Цей тег використовується для визначення інформації, що стосується саме додатка, а не конкретного сервлета чи сторінки JSP. Як Java-сервлет отримує доступ до цих параметрів, які часто називають параметрами контексту? Методи, доступні для отримання інформації про контекст, дуже схожі на ті, що використовуються для отримання параметрів ініціалізації, характерних для конкретного сервлета:

[Servlet].getServletContext()
метод класу Servlet, від якого походить клас HttpServlet, що використовується для веб-програмування. Повертає об’єкт ServletContext, що надає доступ до параметрів конфігурації додатка
[ServletContext].getInitParameter("paramètre")
метод класу ServletContext, який повертає значення параметра ініціалізації «paramètre»

Клас gener6.java вносить лише такі зміни до Java-коду gener5.java, що використовувався раніше:

             // отримуємо параметри ініціалізації сервлету
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");

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

             //чи всі параметри отримано?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // заповнюємо масив значень з бази даних ODBC
             // з назвою DSN: DSNvaleurs
...............

Параметр title, властивий сервлету, отримується через об’єкт ServletConfig. Три інші параметри, визначені на рівні додатка, отримуються через об’єкт ServletContext. Ми компілюємо цей клас і розміщуємо його, як і інші, у папці <application>\WEB-INF\classes:

Image

Перезапускаємо Tomcat, щоб він врахував новий файл web.xml додатка, і запитуємо URL та http://localhost:8080/liste/valeurs3:

Image

3.3.6. Параметри ініціалізації сторінки JSP

Ми розглянули, як задати параметри ініціалізації для сервлета або веб-додатка. Чи можна зробити те саме для сторінки JSP? Повернімося до початку коду вже розглянутої сторінки listvaleurs.jsp:

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

<%!
         // глобальні змінні додатка
         // заголовок сторінки
        private final String title="Génération d'un formulaire";
         // база даних значень списку
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
.........

Чотири константи title, DSNValeurs, admDbValeurs та mdpDbValeurs визначені у файлі web.xml програми. Константи DSNValeurs, admDbValeurs та mdpDbValeurs тепер визначені на рівні додатка, тому можна припустити, що сторінка JSP, яка є частиною цього додатка, матиме до них доступ. Так і є. Ми знаємо, що сторінка JSP буде перетворена на сервлет. Цей сервлет матиме доступ до контексту через метод getServletContext(). Більш складним є випадок з константою title. Адже ми визначили її на рівні сервлету, а не на рівні додатка, таким чином:

    <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>

Для сторінки JSP попередній синтаксис більше не підходить, оскільки поняття файлу класу більше не існує. Однак синтаксис конфігурації сторінки JSP дуже схожий на синтаксис сервлета. Він виглядає так:

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

Фактично, сторінка JSP розглядається як сервлет, якому присвоюється ім’я (servlet-name). Замість того, щоб пов’язувати з цим сервлетом файл класу, до нього прив’язується вихідний файл сторінки JSP, що має бути виконаний (jsp-file). Таким чином, кілька попередніх рядків визначають сервлет із назвою JSPlstvaleurs, пов’язаний зі сторінкою JSP /listvaleurs2.jsp. Шлях /listvaleurs2.jsp вимірюється відносно кореневого каталогу додатка. Отже, у випадку нашого додатка liste файл listvaleurs2.jsp знаходитиметься в папці docBase (див. server.xml) додатка liste:

Image

Конфігурація сторінки JSP у файлі 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>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>

Сторінка JSP listvaleurs2.jsp розміщена в кореневому каталозі додатка liste і пов’язана з іменем сервлета JSPlstValeurs (servlet-name), який, у свою чергу, пов’язаний з псевдонімом /jspvaleurs (servlet-mapping). Таким чином, наша сторінка JSP буде доступна через URL http://localhost:8080/liste/jspvaleurs.

Початкова сторінка JSP (listvaleurs.jsp) змінюється на listvaleurs2.jsp і отримує свої чотири параметри ініціалізації в методі jspInit():

<%!
         // глобальні змінні додатка
         // заголовок сторінки
        private String title=null;
        // база даних значень списку
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
         // значення списку
        private String[] valeurs=null;
         // повідомлення про помилку
        private String msgErreur=null;

         // ініціалізація сторінки JSP — виконується лише один раз
        public void jspInit(){

             // отримано параметри ініціалізації сервлету
      ServletConfig config=getServletConfig();
            title=config.getInitParameter("JSPtitle");
      ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");

             //чи всі параметри отримано?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // заповнює масив значень з бази даних ODBC
             // з назвою DSN: DSNvaleurs
..............

Сторінка JSP отримує свої параметри ініціалізації так само, як і сервлети. Попередній файл зберігається в кореневому каталозі веб-додатку liste:

Image

Сервер Tomcat перезапускається, щоб змусити його перечитати новий файл конфігурації web.xml додатка. Після цього можна звернутися до URL http://localhost:8080/liste/jspvaleurs:

Image

3.3.7. Взаємодія сервлетів та сторінок JSP у веб-додатку

Коли клієнт надсилає запит до веб-сервера, відповідь може формуватися кількома сервлетами та сторінками JSP. Дотепер відповідь формувалася лише одним сервлетом або однією сторінкою JSP. Ми побачили, що сторінка JSP забезпечує кращу читабельність структури згенерованого документа HTML. Однак вона, як правило, також містить багато коду Java. Ситуацію можна покращити, розмістивши

  • в один або кілька сервлетів Java-код, який не генерує код HTML у відповіді
  • на сторінки JSP — код, що генерує різні документи HTML, які надсилаються клієнту у відповідь

Таким чином можна сподіватися на покращення розділення коду Java та коду HTML. Ми застосуємо цю нову структуру до нашого додатка liste: сервлет Java lstValeurs4 буде відповідати за зчитування значень із бази даних під час запуску, а потім — за аналіз запитів клієнтів. Залежно від результату аналізу, запит клієнта буде перенаправлено на сторінку помилки erreur.jsp або на сторінку з переліком чисел liste.jsp. Отже, додаток liste складатиметься з сервлета та двох сторінок JSP.

Як сервлет може передати запит, отриманий від клієнта, іншому сервлету або сторінці JSP? Ми використаємо такі методи:

[ServletContext].getRequestDispatcher(
String url)
метод класу ServletContext, який повертає об’єкт RequestDispatcher. Параметр url — це ім’я URL, якому потрібно передати запит клієнта. Така передача запиту можлива лише в межах одного додатка. Тому параметр url є відносним шляхом до веб-дерева цього додатка.
[RequestDispatcher].forward
(ServletRequest request,
 ServletResponse response)
метод інтерфейсу RequestDispatcher, який передає попередньому методу URL запит request від клієнта та об’єкт response, який має бути використаний для формування відповіді.
[ServletRequest].setAttribute(String nom, Object obj)
коли сервлет або сторінка JSP передає запит іншому сервлету або сторінці JSP, їй, як правило, потрібно передати цьому сервлету або сторінці не лише запит клієнта, а й іншу інформацію, отриману в результаті власної обробки запиту. Метод setAttribute класу ServletRequest дозволяє додавати атрибути до об’єкта request клієнта у формі, що нагадує словник пар (атрибут, значення), де attribut — це ім’я атрибута, а valeur — будь-який об’єкт, що представляє його значення.
[ServletRequest].getAttribute(
String attribut)
дозволяє отримати значення атрибутів запиту. Цей метод буде використовуватися сервлетом або сторінкою JSP, якій було передано запит, для отримання інформації, що була додана до нього.

Сервлет, відповідальний за обробку форми, буде налаштований у файлі 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>

Сервлет lstValeurs4 матиме чотири власні параметри ініціалізації:

title
заголовок документа HTML, що має бути згенерований
JSPerreur
URL сторінки помилки JSP
JSPliste
URL зі сторінки JSP, що містить список чисел
URLservlet
URL, пов'язана з атрибутом action форми, представленої на сторінці JSPliste. Ця URL буде належати сервлету lstValeurs4

Сервлет матиме псевдонім /valeurs4 (servlet-mapping) і, отже, буде доступний через URL http://localhost:8080/liste/valeurs4. Вона пов’язана з файлом класу gener7.java, повний вихідний код якого наведено нижче:

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

public class gener7 extends HttpServlet{
        // заголовок сторінки
        private String title=null;
        // база даних значень списку
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
         // сторінки відображення JSP
        private String JSPerreur=null;
        private String JSPliste=null;
        // сервлет URL
        private String URLservlet=null;
        // значення списку
        private String[] valeurs=null;
         // повідомлення про помилку
        private String msgErreur=null;

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

            // в атрибути запиту вноситься msgErreur,title
            request.setAttribute("msgErreur",msgErreur);
            request.setAttribute("title",title);
            request.setAttribute("URLservlet",URLservlet);

             // Чи сталася помилка під час завантаження сервлету?
            if(msgErreur!=null){
                 // передаємо управління на сторінку помилки JSP
                getServletContext().getRequestDispatcher(JSPerreur).forward(request,response);
                 // кінець
                return;
            }

             // помилки не сталося
             // список значень додається до атрибутів запиту
            request.setAttribute("valeurs",valeurs);

             // отримуємо можливий вибір користувача
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";
            request.setAttribute("choix",choix);

             // передаємо управління сторінці JSP для відображення списку
            getServletContext().getRequestDispatcher(JSPliste).forward(request,response);
            // кінець
            return;
        }//GET

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

             // перенаправляє на GET
            doGet(request,response);
        }//POST

        // -----------------------------------------------------------------
         // ініціалізація сервлета
        public void init(){

             // отримано параметри ініціалізації сервлета
            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");


             //чи всі параметри отримано?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null || JSPerreur==null || JSPliste==null || URLservlet==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // заповнення масиву значень з бази даних ODBC
             // з назвою DSN: DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // підключення до бази даних ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // об'єкт Statement
                st=connexion.createStatement();
                 // виконання запиту SELECT для отримання значень
                rs=st.executeQuery("select valeur from Tvaleurs");
                // значення отримано та поміщено в динамічний масив
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // значення записується у список
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                 // перетворення списку в масив
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                 // проблема
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//ініціалізація
    }//клас

Нововведенням у цьому класі є перенаправлення запиту клієнта на сторінку JSPerreur у разі помилки, а в іншому випадку — на сторінку JSPliste. Клас сам не формує відповідь. Цим займаються сторінки JSP, JSPerreur та JSPliste. Раніше сервлет додавав атрибути (setAttribute) до запиту клієнта:

  • повідомлення про помилку msgErreur у разі помилки на сторінці JSPerreur
  • значення (valeurs)), що має відображатися, значення (choix), обране користувачем, заголовок (title) форми, URL (URLservlet) атрибута action форми для сторінки JSPliste

Цей клас компілюється та додається до класів додатка:

Image

Сторінка JSP, що відображає повідомлення про помилку, налаштована наступним чином:

    <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>

Файл JSP, пов'язаний із сторінкою помилки, називається erreur.jsp і знаходиться в кореневому каталозі додатка:

Image

Він має псевдонім /JSPerreur, що робить його доступним через URL http://localhost:8080/liste/JSPerreur. Вона має параметр ініціалізації з назвою mainServlet, значенням якого є псевдонім головного сервлету, описаного вище. Слід зауважити, що цей псевдонім є відносним до кореневого каталогу додатка liste,; інакше ми б мали /liste/valeurs4.. Код сторінки erreur.jsp є таким:

<%
     // код з _jspService
   // отримуємо параметр ініціалізації mainServlet
  String servletListValeurs=config.getInitParameter("mainServlet");
   // отримуємо атрибут msgErreur
  String msgErreur=(String)request.getAttribute("msgErreur");
   // атрибут є дійсним?
  if(msgErreur!=null){
%>
       <!-- код HTML -->
    <html>
        <head>
          <title>Erreur</title>
      </head>
      <body>
          <h3>Application indisponible (<%= msgErreur %>)</h3>
      </body>
    </html>
<%
    } else { // атрибут msgErreur недійсний — повернення до головного сервлету
%>
    <jsp:forward page="<%= servletListValeurs %>" />  
<%    
  }
%>  

Ця сторінка зазвичай має викликатися попереднім сервлетом, який повинен передати їй атрибут msgErreur. Однак ніщо не заважає викликати її безпосередньо, якщо відомий її URL. Також, якщо виявляється, що атрибут msgErreur відсутній, запит передається до головного сервлету. Тут використовується тег, характерний для сторінок JSP, синтаксис якого такий:

<jsp:forward page="URL" />

де URL — це URL сервлета, якому передається запит клієнта. Якщо атрибут msgErreur присутній, відображається сторінка помилки.

Сторінка JSP, що відображає список чисел, налаштована наступним чином:

    <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>

Файл JSP, пов'язаний із сторінкою помилки, називається liste.jsp і знаходиться в кореневому каталозі додатка:

Image

Сервлет має псевдонім /JSPliste, завдяки чому до нього можна отримати доступ через URL http://localhost:8080/liste/JSPliste. Вона має параметр ініціалізації з назвою mainServlet, значенням якого є псевдонім головного сервлета. Код сторінки liste.jsp такий:

   <%-- сторінка відображення списку значень --%>
  <%
       // код jspService
       // отримуємо параметр ініціалізації
      String servletListValeurs=config.getInitParameter("mainServlet");

     // отримуємо атрибути запиту, що надходить від головного сервлету
    String title=(String) request.getAttribute("title");
    String[] valeurs=(String[]) request.getAttribute("valeurs");
    String choix=(String) request.getAttribute("choix");
    String URLservlet=(String) request.getAttribute("URLservlet");

     // атрибути є допустимими?
    if(title==null || valeurs==null || choix==null){
         // є недійсний атрибут — передаємо управління сервлету
   %>
   <jsp:forward page="<%= servletListValeurs %>" />
    <%
    }//if
  %>

  <%-- код HTML --%>  
      <html>
        <head>
          <title><%= title %></title>
      </head>
      <body>
          <h3>Choisissez une valeur</h3>
          <form method="POST" action="<%= URLservlet %>">
            <select name="cmbValeurs">
              <%
                 // динамічне відображення значень
                          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>
                <%
             // чи було обрано значення?
                    if(! choix.equals("")){
                // відображається вибір користувача
                %>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
                <%        
                    }//if
                %>        
      </body>
    </html>

Ця сторінка працює так само, як сторінка erreur.jsp. Зазвичай її має викликати сервлет /liste/valeurs4, який передає їй атрибути title, valeurs та choix. Якщо один із цих параметрів відсутній, управління передається сервлету URLservlet (/liste/valeurs4). Якщо всі параметри присутні, відображається список чисел, а також число, обране користувачем, якщо він його обрав.

Якщо викликати сервлет URL з головного сервлету, отримаємо такий результат:

Image

з таким вихідним кодом (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>

Цей документ HTMl було створено на сторінці JSP liste.jsp. Бачимо, що атрибути title, valeurs, URLservlet були успішно вилучені.

Підсумовуючи співпрацю сервлетів та сторінок JSP, можна зауважити, що сторінки JSP тут дуже короткі й не містять Java-коду, який не бере безпосередньої участі у формуванні відповіді HTML. Таким чином, структура згенерованих документів стає більш наочною.

3.4. Життєвий цикл сервлетів та сторінок JSP

3.4.1. Життєвий цикл

Тут ми розглядаємо життєвий цикл сервлетів. Життєвий цикл сторінок JSP випливає з нього. Розглянемо сервлет, який викликається вперше. У цьому випадку веб-сервер створює екземпляр класу та завантажує його в пам’ять. Потім він обробляє запит. Після цього сервлет не вивантажується з пам’яті. Вона залишається там для обслуговування інших запитів з метою оптимізації часу відгуку сервера. Її буде вивантажено, коли мине достатньо тривалий проміжок часу, протягом якого вона не обслуговувала нових запитів. Цей час, як правило, можна налаштувати в параметрах веб-сервера.

Перебуваючи в пам’яті, сервлет може обробляти кілька запитів одночасно. Веб-сервер створює по одному потоку на кожен запит, і всі вони використовують один і той самий екземпляр сервлета:

Усі наведені вище потоки спільно використовують змінні екземпляра сервлета. Може виникнути необхідність синхронізації потоків, щоб уникнути пошкодження даних сервлета. Ми повернемося до цього пізніше.

Під час завантаження сервлета виконується певний метод сервлета:

public void init() throws ServletException{
}

Для сторінки JSP це метод


  public void jspInit(){
  }

. Ось приклад сторінки JSP, яка використовує метод jspInit:

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

<%!
   // глобальні змінні та методи сторінки JSP

   // змінна екземпляра
  int compteur;

   // метод для інкременту лічильника  
  public int getCompteur(){
     // збільшуємо лічильник
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
     // повертається
    return compteur;
  }

   // метод, що виконується під час початкового завантаження сторінки
  public void jspInit(){
     // ініціалізація лічильника
    compteur=100;
  }
%>

Попередня сторінка JSP ініціалізує лічильник у jspInit, присвоюючи йому значення 100. Кожен наступний запит до сервлету збільшує значення цього лічильника, а потім відображає його:

Перший раз:

Image

Вдруге:

Image

З наведеного вище добре видно, що між двома запитами сервлет не був вивантажений, інакше під час другого запиту лічильник мав би значення 101. Коли сервлет вивантажується, метод

public void destroy(){
}

виконується, якщо він існує. Для сторінок JSP це метод


  public void jspDestroy(){
  }

У цих методах можна, наприклад, закрити з’єднання з базами даних, які були відкриті у відповідних методах init.

3.4.2. Синхронізація запитів до сервлету

Повернімося до попередньої сторінки JSP, яка інкрементує лічильник і повертає його значення веб-клієнту. Припустимо, що надходять 2 одночасні запити. Тоді для їх виконання створюються два потоки, які використовуватимуть один і той самий екземпляр сервлету, а отже, і один і той самий лічильник. Нагадаємо код, який інкрементує лічильник:


  public int getCompteur(){
     // збільшуємо лічильник
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
     // повертаємо його
    return compteur;
  }

Збільшення лічильника було навмисно написано неточно. Припустимо, що виконання двох потоків відбувається наступним чином:

 
  1. у момент часу T1 виконується потік TH1. Він зчитує значення лічильника (=145) у myCompteur, після чого його переривають і він втрачає доступ до процесора. Отже, він не встиг збільшити значення myCompteur і скопіювати нове значення в compteur.
  2. У момент часу T2 виконується потік TH2. Він зчитує лічильник (=145) з myCompteur, після чого його переривають і він втрачає доступ до процесора. Слід зауважити, що обидва потоки мають різні змінні myCompteur. Вони спільно використовують лише змінні екземпляру — ті, що є глобальними для методів.
  3. У момент часу T3 потік TH1 знову отримує контроль і завершується. Отже, він повертає 146 своєму клієнту.
  4. У момент часу T4 потік TH2 знову отримує контроль і завершується. Він також повертає своєму клієнту 146, хоча мав би повернути 147.

Тут маємо проблему синхронізації потоків. Коли TH1 хоче збільшити лічильник, потрібно запобігти тому, щоб будь-який інший потік зробив те саме. Щоб проілюструвати цю проблему, перепишемо сторінку JSP наступним чином:

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

<%!
   // глобальні змінні та методи сторінки JSP

   // змінна екземпляра
  int compteur;

   // метод для інкременту лічильника  
  public int getCompteur(){
     // зчитуємо лічильник
    int myCompteur=compteur;
     // зупинка на 10 секунд
    try{
      Thread.sleep(10000);
    }catch (Exception ignored){}
     // збільшуємо лічильник
    compteur=myCompteur+1;
     // повертаємо його
    return compteur;
  }

   // метод, що виконується під час початкового завантаження сторінки
  public void jspInit(){
     // ініціалізація лічильника
    compteur=100;
  }
%>

Тут ми змусили потік зупинитися через 10 секунд після зчитування лічильника. Таким чином, він повинен втратити доступ до процесора, і інший потік зможе у свою чергу зчитати лічильник, який не був інкрементований. Коли ми робимо запити за допомогою браузера, ми не бачимо жодної різниці, окрім 10-секундного очікування перед отриманням результату.

Image

Тепер, якщо ми відкриємо два вікна браузера і зробимо два запити з достатньо коротким проміжком часу:

Image

Image

Ми отримуємо те саме значення лічильника. Ми можемо краще проілюструвати проблему за допомогою програмного клієнта, а не ручного, яким є браузер. Далі наведено Perl-клієнт, який викликається наступним чином:

програма URL N

де

URL — це URL сервлета підрахунку

N — кількість запитів, які потрібно надіслати до цього сервлету

Ось результати, отримані для 5 запитів, які добре ілюструють проблему неправильної синхронізації потоків: усі вони отримують однакове значення лічильника.


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

Код клієнта Java виглядає наступним чином.

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

public class clientCompteurJSP {

    public static void main(String[] params){

         // дані
        String syntaxe="Syntaxe : pg URL nbAppels";

         // перевірка параметрів
        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
         // кількість викликів
        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

         // параметри правильні — можна встановлювати з’єднання з 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 {
         // виконує nbAppels на URL urlCompteur
         // щоразу відображає значення лічильника, повернуте веб-сервером


         // з urlCompteur вилучаються дані, необхідні для підключення до податкового сервера
        String path=urlCompteur.getPath();
        if(path.equals("")) path="/";
        String host=urlCompteur.getHost();
        int port=urlCompteur.getPort();
        if(port==-1) port=urlCompteur.getDefaultPort();

         // виконуються виклики до URL
        Socket[] clients=new Socket[nbAppels];
        for(int i=0;i<nbAppels;i++){
             // здійснюється підключення до сервера
            clients[i]=new Socket(host,port);
             // створюється потік запису на сервер
            PrintWriter OUT=new PrintWriter(clients[i].getOutputStream(),true);
             // запитується URL — надсилання заголовків HTTP
            OUT.println("GET " + path + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            OUT.println("Connection: close");
            OUT.println("");
        }//для

         // локальні дані
        String réponse=null;                        // відповідь сервера
         // шаблон, який шукали у відповіді HTML від сервера
        Pattern modèleCompteur=Pattern.compile("^\\s*Compteur= (\\d+)");
         // шаблон правильної відповіді
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // результат порівняння з шаблоном
        Matcher résultat=null;

        for(int i=0;i<nbAppels;i++){
             // кожен клієнт зчитує відповідь, яку йому надсилає сервер

             // створюються потоки вводу-виводу клієнта TCP
            BufferedReader IN=new BufferedReader(new InputStreamReader(clients[i].getInputStream()));

             // зчитується перший рядок відповіді
            réponse=IN.readLine();
             // порівнюється рядок HTTP із шаблоном правильної відповіді
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                 // виникає проблема з URL
                throw new Exception("Client n° " + i + " - Le serveur a répondu : URL ["+ urlCompteur + "] inconnue");
            }//якщо

             // зчитуємо відповідь до кінця заголовків
            while((réponse=IN.readLine())!=null && ! réponse.equals("")){
            }//поки

             // заголовки закінчилися HTTP — переходимо до коду HTML
             // для отримання значення лічильника
            boolean compteurTrouvé=false;
            while((réponse=IN.readLine())!=null){
                 // порівнюємо рядок із шаблоном лічильника
                if(! compteurTrouvé){
                    résultat=modèleCompteur.matcher(réponse);
                    if(résultat.find()){
                         // лічильник знайдено
                        System.out.println("Compteur="+résultat.group(1));
                        compteurTrouvé=true;
                    }//if
                }//якщо
            }//while

             // завершено
            clients[i].close();
        }//for
    }//getCompteurs

}//клас

Роз'яснимо попередній код:

  • програма приймає два параметри:
    • URL сторінки JSP лічильника
    • кількість клієнтів, яких потрібно створити для цього URL
  • отже, програма спочатку перевіряє правильність параметрів: чи їх дійсно два, чи перший синтаксично відповідає URL, а другий — цілому числу >0. Щоб перевірити, чи URL є синтаксично правильним, використовується клас URL та його конструктор URL (String), який створює об’єкт URL на основі такого рядка символів: http://istia.univ-angers.fr. Якщо рядок не є синтаксично правильним URL, генерується виняток. Це дозволяє нам перевірити правильність першого параметра.
  • Після перевірки параметрів управління передається процедурі getCompteurs. Ця процедура створить клієнтів nbAppels, які всі підключаться одночасно (або майже одночасно) до URL urlCompteur.
  • Порт та машина, до яких повинні підключитися клієнти, беруться з URL та urlCompteur: [URL].getHost() дозволяє отримати ім’я комп’ютера, а [URL].getPort() — порт.
  • Перший цикл дозволяє кожному клієнту:
    • підключитися до веб-сервера
    • запитати у нього URL urlCompteur

У цьому циклі клієнт не чекає на відповідь сервера. Адже ми хочемо змусити сервер отримувати запити майже одночасно.

  • другий цикл дозволяє кожному клієнту отримати та обробити відповідь, яку надсилає йому сервер. Обробка полягає у пошуку в відповіді рядка, що містить значення лічильника, та його відображенні.

Щоб вирішити проблему, на яку було вказано раніше (один і той самий лічильник надсилається п’ятьом клієнтам), нам потрібно синхронізувати потоки служби підрахунку за одним об’єктом перед входом у критичну ділянку читання та оновлення лічильника. Нова сторінка JSP виглядає так:


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

<%!
   // глобальні змінні та методи сторінки JSP
  
   // змінні екземпляра
  int compteur;
  Object verrou=new Object();
  
   // метод для інкременту лічильника  
  public int getCompteur(){
  
     // синхронізується критичний фрагмент
    synchronized(verrou){
       // зчитування лічильника
      int myCompteur=compteur;
       // зупинка на 10 секунд
      try{
        Thread.sleep(10000);
      }catch (Exception ignored){}
       // збільшуємо лічильник
      compteur=myCompteur+1;
    }//синхронізовано
     // повертається
    return compteur;
  }//getCompteur

   // метод, що виконується під час початкового завантаження сторінки
  public void jspInit(){
     // ініціалізація лічильника
    compteur=100;
  }
%>

Під час виконання отримуємо такі результати:

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

У документації зазначено, що веб-сервер іноді може створювати кілька екземплярів одного й того самого сервлету. У цьому випадку попередня синхронізація більше не працює, оскільки змінна verrou є локальною для одного екземпляра і, отже, невідома іншим екземплярам. Те саме стосується змінної compteur. Щоб зробити їх глобальними для всіх екземплярів, слід написати:


// змінна класу
  static int compteur;
  static Object verrou=new Object();

Решта коду залишається без змін.