Skip to content

3. Introdução aos servlets Java e às páginas JSP

Neste capítulo, encontrar-se-ão vários exemplos de servlets e páginas JSP. Foram testados com o servidor Tomcat, que funciona na porta 8080. Ao seguir as ligações da página inicial, tem-se acesso a exemplos de servlets e páginas JSP. A maioria dos exemplos abaixo foi retirada dos exemplos do Tomcat. Para os testar, basta iniciar o Tomcat, aceder a URL http://localhost:8080 com um navegador e seguir o link dos servlets.

3.1. Servlets Java

3.1.1. Enviar um conteúdo HTML a um cliente Web

Analisamos o exemplo «Hello World» acima. O servlet é o seguinte:


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

Ao executar este servlet, obtém-se a seguinte visualização:

Image

É importante ter em conta os seguintes pontos:

  • é necessário importar classes específicas para os servlets:

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

A biblioteca javax.servlet nem sempre é fornecida de série com o JDK. Nesse caso, pode ser obtida diretamente no site da Sun.

  • Um servlet deriva da classe HttpServlet

public class HelloWorld extends HttpServlet {
  • Uma solicitação GET enviada ao servlet é processada pelo método doGet

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  • Da mesma forma, uma solicitação POST enviada ao servlet é processada pelo método doPost

    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  • O objeto HttpServletRequest request é o objeto que nos dá acesso ao pedido efetuado pelo cliente Web. A resposta do servlet será enviada através do objeto HttpServletResponse response
  • O objeto response permite-nos definir os cabeçalhos HTTP que serão enviados ao cliente. Por exemplo, o cabeçalho Content-type: text/html é aqui definido por:
        response.setContentType("text/html");
  • Para enviar a resposta ao cliente, o servlet utiliza um fluxo de saída que lhe é fornecido pelo objeto response:
        PrintWriter out = response.getWriter();
  • Assim que este fluxo de saída é obtido, o código HTML é gravado nele e, consequentemente, enviado ao cliente:
        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. Recuperar os parâmetros enviados por um cliente web

O exemplo seguinte mostra como um servlet pode recuperar parâmetros enviados pelo cliente web. Um formulário de introdução de dados:

Image

A resposta enviada pela servlet:

Image

O código-fonte do servlet é o seguinte:


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

}

De notar as seguintes novidades em relação ao exemplo anterior:

  • Os parâmetros enviados pelo navegador são recuperados da seguinte forma:

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

O método request.getParameter("nomParamètre") devolve o ponteiro null, caso o parâmetro nomParamètre não faça parte dos parâmetros enviados pelo cliente web.

  • O formulário especifica que o navegador deve enviar os parâmetros através do método POST
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
  • Os parâmetros recebidos serão processados pelo método doPost do servlet. Neste caso, este método limita-se a chamar o método doGet. Assim, este servlet processa os valores do formulário, quer sejam enviados por um GET ou por um POST.

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

3.1.3. Recuperar os cabeçalhos HTTP enviados por um cliente web

O servlet a seguir mostra como recuperar os cabeçalhos HTTP enviados pelo cliente web:

Image

O código-fonte do servlet é o seguinte:


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

Pontos a ter em conta:

  • É o objeto request e o seu método getHeaderNames que nos dão acesso aos cabeçalhos HTTP enviados pelo navegador sob a forma de uma enumeração:
        Enumeration e = request.getHeaderNames();
  • O método request.getHeader("entête") permite obter um cabeçalho HTTP específico. O exemplo acima apresenta alguns deles. É importante lembrar que os cabeçalhos aqui apresentados são enviados pelo navegador. O servidor também tem os seus próprios cabeçalhos HTTP, que, por vezes, reproduzem os do navegador. Os cabeçalhos HTTP enviados pelo navegador têm como objetivo informar o servidor sobre as capacidades do navegador.
Cabeçalho
Significado
User-Agent
identidade do navegador
Accept
os formatos MIME aceites pelo navegador. Assim, image/gif significa que o navegador sabe processar imagens no formato GIF
Host
no formato hote:port. Indica qual o servidor e qual a porta que o navegador pretende contactar.
Accept-Encoding
formato de codificação aceite pelo navegador para os documentos enviados pelo servidor. Assim, se um servidor tiver um documento na forma normal não comprimida e outro no formato comprimido gzip, e o navegador tiver indicado que sabe processar o formato gzip, então o servidor poderá enviar o documento no formato gzip para poupar largura de banda.
Accept-language
línguas aceites pelo navegador. Se um servidor dispuser do mesmo documento em várias línguas, enviará aquele cuja língua seja aceite pelo navegador.
Referer
o URL que foi solicitado pelo navegador
Connection
o modo de ligação solicitado pelo navegador. Keep-alive significa que o servidor não deve interromper a ligação após ter servido a página solicitada ao navegador. Se este último detetar que a página recebida contém, por exemplo, links para imagens, poderá efetuar novos pedidos ao servidor para as solicitar sem necessidade de criar uma nova ligação. Será então o navegador que tomará a iniciativa de encerrar a ligação assim que tiver recebido todos os elementos da página.

3.1.4. Obter informações do ambiente

O servlet a seguir mostra como aceder a informações do ambiente de execução do servlet. Algumas dessas informações são enviadas sob a forma de cabeçalhos HTTP pelo navegador e, por isso, podem ser recuperadas através do método anterior.

Image

O código do servlet é o seguinte:


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

As informações aqui apresentadas são obtidas através de vários métodos:

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

Segue-se uma lista de alguns dos métodos disponíveis e o seu significado:

método
significado
getServerName()
o nome do servidor Web
getServerPort()
a porta de trabalho do servidor Web
getMethod()
o método GET ou POST utilizado pelo navegador para efetuar a sua solicitação
getRemoteHost()
o nome do computador cliente a partir do qual o navegador efetuou a sua solicitação
getRemoteAddr()
o endereço IP dessa mesma máquina
getContentType()
o tipo de conteúdo enviado pelo navegador (cabeçalho HTTP Content-type)
getContentLength()
o número de caracteres enviados pelo navegador (cabeçalho HTTP Content-length)
getProtocol()
a versão do protocolo HTTP solicitada pelo navegador
getRequestURI()
o URI solicitado pelo navegador. Corresponde à parte do URL que se segue à identificação hote:port em http://hote:port/URI

3.1.5. Criar um servlet com JBuilder e implementá-lo com o Tomcat

Vamos agora descrever como criar e executar um servlet Java. Utilizaremos duas ferramentas: o JBuilder para compilar o servlet e o Tomcat para o executar. O Tomcat, por si só, poderia ser suficiente. No entanto, oferece capacidades de depuração limitadas. Retomamos o exemplo desenvolvido anteriormente, que apresenta os parâmetros recebidos pelo servidor. O servlet envia, em primeiro lugar, o seguinte formulário de introdução de dados:

Image

A resposta enviada pela servlet:

Image

O código-fonte da servlet é o seguinte:


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

}
  • Crie um projeto myRequestParamExample com o JBuilder e inclua nele o programa myRequestParamExample.java anterior.
  • Durante a compilação, poderá ocorrer o seguinte problema: o seu JBuilder não dispõe necessariamente da biblioteca javax.servlet necessária para a compilação dos servlets. Neste caso, terá de configurar o JBuilder para que utilize bibliotecas de classes adicionais. O procedimento está descrito nos anexos deste documento para o JBuilder 7. Reproduzimos aqui parte desse procedimento:
  • ativar a opção Tools/Configure JDKs ou (Options/Configurar JDK)

Image

Na secção «JDK Settings» acima, normalmente encontra-se no campo «Name» a versão 1.3.1. Se tiver uma versão mais recente do JDK, utilize o botão Change para indicar o diretório de instalação desta versão. Acima, foi indicado o diretório E:\Program Files\jdk14, onde estava instalado um JDK 1.4. A partir de agora, o JBuilder utilizará este JDK para as suas compilações e execuções. Na secção (Class, Source, Documentation) encontra-se a lista de todas as bibliotecas de classes que serão exploradas pelo JBuilder, neste caso as classes do JDK 1.4. As classes deste último não são suficientes para realizar desenvolvimento web em Java. Para adicionar outras bibliotecas de classes, utiliza-se o botão Add e indicam-se os ficheiros .jar adicionais que se pretende utilizar. Os ficheiros .jar são bibliotecas de classes. O Tomcat 4.x inclui todas as bibliotecas de classes necessárias para o desenvolvimento web. Estas encontram-se em <tomcat>\common\lib, sendo que <tomcat> é o diretório de instalação do Tomcat:

Image

Com o botão Add, vamos adicionar estas bibliotecas, uma a uma, à lista de bibliotecas exploradas pelo JBuilder:

Image

A partir de agora, é possível compilar programas Java em conformidade com a norma J2EE, nomeadamente os servlets Java. O JBuilder serve apenas para a compilação, sendo a execução posteriormente assegurada pelo Tomcat.

  • Agora pode compilar o programa myRequestParamExample.java e gerar o servlet myRequestParamExample.class. Onde colocar este servlet? Se a configuração inicial do Tomcat não tiver sido alterada, os ficheiros .class dos servlets devem ser colocados em <tomcat>\webapps\examples\WEB-INF\classes (Tomcat 4.x).
  • Verifique se o Tomcat está em execução e, num navegador, aceda ao URL http://localhost:8080/examples/servlet/myRequestParamExample:

Image

3.1.6. Exemplos

Para os exemplos que se seguem, utilizámos o método descrito anteriormente:

  • compilação do código-fonte XX.java do servlet com o JBuilder
  • implantação da servlet XX.class em <tomcat>\webapps\examples\WEB-INF\classes
  • Com o Tomcat em execução, aceder com um navegador à página URL http://localhost:8080/examples/servlet/XX

3.1.6.1. Geração dinâmica de formulários - 1

Tomamos como exemplo a geração de um formulário com apenas um controlo: uma lista. O conteúdo desta lista é construído dinamicamente com valores retirados de uma matriz. Na realidade, estes valores são frequentemente retirados de uma base de dados. O formulário é o seguinte:

Image

Se, no exemplo acima, introduzirmos Envoyer, obtemos a seguinte resposta:

Image

Repare-se que o código URL que gera a resposta é o mesmo que o que apresenta o formulário. Aqui temos um servlet que processa ele próprio a resposta ao formulário que enviou. Trata-se de um caso comum. O código HTML do formulário é o seguinte:

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

Note-se que os valores enviados pelo formulário são transmitidos através do método POST. O código HTML da resposta:

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

O código do servlet que gera este formulário e esta resposta é o seguinte:

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

public class gener1 extends HttpServlet{
    // variáveis de instância
    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{

      // indica-se ao cliente o tipo de documento enviado
      response.setContentType("text/html");
       // envia-se o formulário
      PrintWriter out=response.getWriter();
            // início
      out.println(HTML1);
            // menu suspenso
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            for (int i=0;i<valeurs.length;i++){
                out.println("<option>"+valeurs[i]+"</option>");
            }//para
            out.println("</select>");
            // fim do formulário
            out.println(HTML2+HTML3);
    }//GET

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

             // recuperamos a escolha do utilizador
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) doGet(request,response);

             // prepara-se a resposta
            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>";
             // indica-se ao cliente o tipo de documento enviado
            response.setContentType("text/html");
             // envia-se o formulário
            PrintWriter out=response.getWriter();
            out.println(réponse);
    }//POST
    }//classificar

O método doGet serve para gerar o formulário. Existe uma parte dinâmica que corresponde ao conteúdo da lista, conteúdo esse proveniente, neste caso, de uma tabela. O método doPost serve para gerar a resposta. Aqui, a única parte dinâmica é o valor da escolha feita pelo utilizador na lista do formulário. Este valor é obtido através de request.getParameter("cmbValeurs"), em que cmbValeurs é o nome HTML da lista:

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

Para concluir, é importante destacar os seguintes pontos:

  • o navegador envia os valores do formulário para o servlet que gerou o formulário, uma vez que a baliza <form> não possui o atributo <action>. Neste caso, o navegador envia os dados introduzidos no formulário para a URL que o forneceu.
  • A baliza <form> especifica que os dados do formulário devem ser enviados através do método POST. É por isso que estes valores são recuperados pelo método doPost do servlet.

3.1.6.2. Geração dinâmica de formulários - 2

Retomamos o exemplo anterior, alterando-o da seguinte forma. O formulário proposto continua a ser o mesmo:

Image

A resposta é diferente:

Image

Na resposta, é devolvido o formulário, com o número escolhido pelo utilizador indicado por baixo. Além disso, esse número é o que aparece como selecionado quando a lista é apresentada. O utilizador pode então escolher outro número:

Image

e, em seguida, executar Envoyer. Obtém a seguinte resposta:

Image

O código da servlet denominada gener2.java é o seguinte:

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

public class gener2 extends HttpServlet{
        // variáveis de instância
        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{

            // recupera-se a eventual escolha do utilizador
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

             // indica-se ao cliente o tipo de documento enviado
            response.setContentType("text/html");
             // envia-se o formulário
            PrintWriter out=response.getWriter();
            // início
            out.println(HTML1);
            // menu suspenso
            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>");
            }//para
            out.println("</select>");
            // continuação do formulário
            out.println(HTML2);
            if(! choix.equals("")){
                // exibe a escolha do utilizador
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//se
             // fim do formulário
            out.println(HTML3);
        }//GET

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

            // redireciona para GET
            doGet(request,response);
        }//POST
    }//classe

O método doGet faz tudo: cria o formulário que envia ao cliente e processa os valores que este devolve. Os pontos a ter em conta são os seguintes:

  • verifica-se se o parâmetro cmbValeurs tem um valor.
  • Se for esse o caso, ao criar o conteúdo da lista, compara-se cada elemento da mesma com a escolha do utilizador para atribuir o atributo selected ao elemento escolhido pelo utilizador: <option selected>elemento</option>. Além disso, exibe-se abaixo do formulário o valor da escolha.

3.1.6.3. Geração dinâmica de formulários - 3

Retomamos o mesmo problema que anteriormente, mas desta vez os valores são obtidos de uma base de dados. No nosso exemplo, trata-se da base MySQL:

  • a base de dados chama-se dbValeurs
  • O seu proprietário é admDbValeurs, cuja palavra-passe é mdpDbValeurs
  • A base de dados tem uma única tabela chamada tvaleurs
  • esta tabela tem apenas um campo inteiro chamado «valor»
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)

A base de dados MySQL dbValeurs foi disponibilizada por um controlador ODBC para MySQL. O seu nome DSN (Data Source Name) é odbc-valeurs. O código da servlet é o seguinte:

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

public class gener3 extends HttpServlet{
        // o título da página
        private final String title="Génération d'un formulaire";
         // a base de dados dos valores da lista
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
         // valores da lista
        private String[] valeurs=null;
         // mensagem de erro
        private String msgErreur=null;
        // código 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{

            // indica-se ao cliente o tipo de documento enviado
            response.setContentType("text/html");
             // fluxo de saída
            PrintWriter out=response.getWriter();

            // A inicialização do servlet decorreu corretamente?
            if (msgErreur!=null){
                 // ocorreu um erro — é gerada uma página de erro
                out.println("<html><head><title>"+title+"</title></head>");
                out.println("<body><h3>Application indisponible ("+msgErreur+
                                        ")</h3></body></html>");
                return;
            }//if

             // recuperamos a eventual escolha do utilizador
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

             // envia-se o formulário
             // início
            out.println(HTML1);
            // menu suspenso
            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>");
            // continuação do formulário
            out.println(HTML2);
            if(! choix.equals("")){
                // exibe a escolha do utilizador
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//se
             // fim do formulário
            out.println(HTML3);
        }//GET

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

            // redireciona para GET
            doGet(request,response);
        }//POST

        // inicialização do servlet
        public void init(){
             // preenche a matriz de valores a partir de uma base de dados ODBC
             // com o nome DSN: DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // ligação à base de dados ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // objeto Statement
                st=connexion.createStatement();
                 // execução da consulta SELECT para recuperar os valores
                rs=st.executeQuery("select valeur from Tvaleurs");
                // os valores são recuperados e colocados numa tabela dinâmica
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // o valor é registado na lista
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                 // transformação da lista em matriz
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                 // problema
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//inicializar
    }//classe

Os pontos importantes a ter em conta são os seguintes:

  1. Uma servlet pode ser inicializada por um método cuja assinatura deve ser public void init(). Este método só é executado no carregamento inicial da servlet
  2. Uma vez carregada, uma servlet permanece na memória permanentemente. Isto significa que, depois de ter atendido um cliente, não é descarregada. Assim, responde mais rapidamente aos pedidos dos clientes.
  3. Na nossa servlet, é necessário pesquisar uma lista de valores numa base de dados. Como esta lista não se altera ao longo do tempo, o método init é o momento ideal para a recuperar. Assim, a base de dados é acedida apenas uma vez pela servlet, no momento do seu carregamento inicial, e não em cada pedido de um cliente.
  4. O acesso a uma base de dados pode falhar. O método init do nosso servlet define uma mensagem de erro msgErreur em caso de falha. Esta mensagem é verificada no método doGet e, caso tenha ocorrido um erro doGet, é gerada uma página a sinalizá-lo.
  5. A implementação do método init utiliza um acesso clássico a uma base de dados com os controladores ODBC-JDBC. Se necessário, o leitor é convidado a rever os métodos de acesso às bases de dados JDBC.

Quando se executa o servlet e o servidor MySQL não tiver sido iniciado, é apresentada a seguinte página de erro:

Image

Se, agora, iniciarmos o servidor MySQL, obtemos a página:

Image

Se selecionarmos o número 6 e clicarmos em «Enviar»:

Image

3.1.6.4. Recuperar os valores de um formulário

Retomamos um exemplo já abordado, o do seguinte formulário web:

Image

O código HTML do formulário balises2.htm é o seguinte:

<html>

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

A baliza <form> do formulário foi definida da seguinte forma:

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

O navegador «enviará» os valores do formulário para o URL http://localhost:8080/examples/servlet/parameters, que é o URL de um servlet gerido pelo Tomcat e que apresenta os valores do formulário anterior. Se chamarmos diretamente a servlet parameters, obtemos os seguintes resultados:

Image

Se o formulário balises2.htm preenchido for este:

Image

e se premirmos o botão «Enviar» (do tipo submit), a servlet parameters é, desta vez, chamada com parâmetros. Em seguida, devolve a seguinte resposta:

Image

Nesta resposta, é possível verificar os valores introduzidos no formulário. O código da servlet é o seguinte:

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

public class parameters extends HttpServlet{
    // variáveis de instância
    String title="Récupération des paramètres d'un formulaire";

    private String getParameter(HttpServletRequest request, String contrôle){
      // retorna o valor request.getParameter (verificação) ou "" se não existir
      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
    {
      // começa-se por recuperar os parâmetros do formulário
      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");

      // indica-se o conteúdo do documento
        response.setContentType("text/html");
       // envia-se o documento
        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
    {
       // redireciona para GET
      doGet(request,response);
    }
}

Neste código, reencontramos as técnicas apresentadas anteriormente noutro exemplo. É importante destacar dois pontos:

  1. o controlo lst2 é uma lista de seleção múltipla e, por isso, podem ser selecionados vários elementos. É o que acontece no nosso exemplo, em que os elementos liste1 e liste3 foram selecionados. Os valores de lst2 foram transmitidos pelo navegador ao servidor na forma lst2=liste1&lst2=liste3. O servlet Java pode recuperar esses valores numa matriz com o método getParameterValues: aqui, request.getParameterValues("lst2") fornece uma matriz com 2 cadeias de caracteres ["liste1","liste3"].
  2. O controlo areaSaisie é um campo de introdução de texto multilinha. request.getParameter("areaSaisie") fornece o conteúdo do campo na forma de uma única cadeia de caracteres. Se, nessa cadeia, se pretender recuperar as diferentes linhas que a compõem, poderá utilizar-se a função split da classe String. O código seguinte
      String areaSaisie=getParameter(request,"areaSaisie");
      String[] lignes=areaSaisie.split("\\r\\n");

recupera as linhas do campo de introdução de dados. Estas linhas terminam com os caracteres \r\n (0D0A).

Para realizar os testes, procedemos da seguinte forma:

  • construímos e compilámos o servlet parameters com o JBuilder, tal como foi explicado anteriormente
  • colocado a classe gerada em <tomcat>\webapps\examples\WEB-INF\classes, onde <tomcat> é o diretório de instalação do Tomcat.
  • acedeu ao URL http://localhost:81/html/balises2.htm, cujo código foi apresentado anteriormente
  • preenchi o formulário e cliquei no botão Envoyer.

3.1.6.5. Recuperar os cabeçalhos HTTP de um cliente web

Retomamos o mesmo exemplo anterior, mas, em resposta ao cliente web que enviou os valores do formulário, enviamos-lhe os cabeçalhos HTTP que ele enviou ao mesmo tempo. Introduzimos uma única alteração no nosso formulário:

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

Os valores do formulário serão enviados pelo método GET para um servlet Java denominado headers, localizado em <tomcat>\webapps\examples\WEB-INF\classes. O servlet headers foi criado e compilado juntamente com o 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
    {
       // define-se a natureza do documento
        response.setContentType("text/html");
       // obtém-se um fluxo de gravação
        PrintWriter out = response.getWriter();
      // exibição da lista de cabeçalhos 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
}

Solicita-se o URL http://localhost:81/html/balises2.htm e executa-se o Envoyer sem alterar o formulário. Obtém-se a seguinte resposta:

Image

Repare-se que o parâmetro URL está presente no campo Address do navegador, o que mostra a forma (GET) utilizada para transmitir os parâmetros. Retomamos o mesmo exemplo, mas alterando a forma de enviar os parâmetros (POST):

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

Obtém-se a seguinte resposta nova:

Image

Repare-se nos cabeçalhos HTTP, content-type e content-length, característicos de um envio efetuado por POST. Além disso, note-se que no campo Address do navegador, os valores do formulário já não aparecem.

3.2. Páginas JSP

As páginas JSP (Java Server Pages) constituem outra forma de escrever aplicações de servidor web. Na verdade, estas páginas JSP são convertidas em servlets antes de serem executadas, pelo que se recorre à tecnologia dos servlets. As páginas JSP permitem destacar melhor a estrutura das páginas HTML geradas. Apresentamos abaixo alguns exemplos, alguns dos quais estão acessíveis através do link JSP na página inicial do Tomcat:

3.2.1. Recuperar informações do ambiente

Retomamos aqui um exemplo já abordado com uma servlet: apresentar as variáveis de ambiente de uma servlet. Trata-se do exemplo snoop dos exemplos JSP:

Image

O código-fonte da página JSP encontra-se em <tomcat>\jakarta-tomcat\examples\jsp\snp\snoop.jsp (Tomcat 3.x) ou em <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>

Observam-se os seguintes pontos:

  • estamos perante um código que se assemelha muito ao HTML. No entanto, encontram-se nele as balizas <%= expressão %>, que são específicas da linguagem JSP. O compilador JSP substitui, no texto HTML, a totalidade da baliza pelo valor de expression.
  • Este exemplo utiliza os métodos do objeto Java request, que é o objeto request já abordado no estudo dos servlets. Trata-se, portanto, de um objeto HttpServletRequest. Assim, a tag <%= request.getRemoteHost() %> será substituída no código HTML pelo nome do computador do cliente web que efetuou a solicitação.
  • É possível chegar ao mesmo resultado com um servlet, mas, neste caso, a estrutura da página web é mais evidente.

3.2.2. Recuperar os parâmetros enviados pelo cliente web

Retomamos aqui o exemplo já analisado com uma servlet. É apresentado um formulário no navegador:

Image

Em resposta à solicitação acima, o navegador recebe a seguinte página:

Image

O código da página JSP é o seguinte:


<%
   // variáveis locais da rotina principal
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

<!-- código 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>
  • Embora se encontre a baliza <%= expressão %>, já vista no exemplo anterior, surge uma nova baliza: <% instruções Java; %>. A baliza <% introduz código Java. Este código termina ao encontrar a baliza de encerramento de código %>.
  • Todo o código anterior (HTML + JSP) será convertido num servlet Java. Será incluído num único método, denominado método principal da página JSP. É por isso que as variáveis Java declaradas no início da página JSP são reconhecidas nas outras partes do código JSP que se encontram espalhadas pelo código HTML: estas variáveis e partes de código farão parte do mesmo método Java. Mas se o nosso código JSP contivesse métodos, as variáveis title, firstname e lastname não seriam reconhecidas nesse código devido à isolação entre métodos. Seria necessário torná-las variáveis globais ou passá-las como parâmetros aos métodos. Voltaremos a este assunto.
  • Para incluir partes dinâmicas no código HTML, existem dois métodos possíveis: <%= expressão %> ou out.println(expressão). O objeto out é um fluxo de saída semelhante ao de mesmo nome encontrado nos exemplos de servlets, mas não é do mesmo tipo: trata-se de um objeto JspWriter e não de um PrintWriter. Permite escrever no fluxo HTML utilizando os métodos print e println.
  • A página JSP reflete melhor a estrutura da página HTML gerada do que o servlet equivalente.

3.2.3. As etiquetas JSP

Segue-se uma lista de tags que podem ser encontradas numa página JSP e o seu significado.

tag
significado
<!-- comentário -->
Comentário HTML. É enviado ao cliente.
<%-- comentário --%>
comentário JSP. Não é enviado ao cliente.
<%! déclarations, méthodes %>
Declara variáveis globais e métodos. As variáveis serão reconhecidas em todos os métodos
<%= expression %>
O valor da expressão será inserido na página HTML no lugar da baliza
<% code Java %>
contém código Java que fará parte do método principal da página JSP
<%@ page attribut1=valeur1
attribut2=valeur2 … %>
define os atributos para a página JSP. Por exemplo:
import="java.util.*,java.sql.*" para especificar as bibliotecas necessárias à página JSP
extends="umaClassePai" para fazer com que a página JSP derive de outra classe

3.2.4. Os objetos implícitos JSP

Nos exemplos anteriores, deparámo-nos com dois objetos não declarados: request e out. Estes são dois dos objetos que são definidos automaticamente no servlet no qual a página JSP é convertida. Chamamos-lhes objetos implícitos ou predefinidos. Existem outros, mas estes são os mais utilizados juntamente com o objeto response:

objeto
significado
HttpServletRequest request
o objeto a partir do qual se acede à solicitação do cliente Web (getParameter, getParameterNames, getParameterValues)
HttpServletResponse response
o objeto com o qual é possível construir a resposta do servidor Web ao seu cliente. Permite definir os cabeçalhos HTTP a enviar ao cliente Web.
JspWriter out
o fluxo de saída que nos permite enviar código HTML ao cliente (print, println)

3.2.5. A transformação de uma página JSP num servlet

Retomemos o código JSP de myRequestParamExample.jsp:


<%
   // variáveis locais do procedimento principal
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

<!-- código 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>

Quando o navegador solicita esta página JSP ao servidor Tomcat, este transforma-a num servlet. Se a página URL solicitada for

http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp, o Tomcat 4.x irá colocar o servlet gerado no diretório <tomcat>\work\localhost\examples\jsp\perso\intro:

Image

Neste nome, encontramos o «http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp» URL da página JSP. Como se pode ver acima, temos acesso ao código Java do servlet gerado para a página JSP. No nosso exemplo, é o seguinte:

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


                   // variáveis locais do procedimento principal
                  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);
        }
    }
}

O código gerado é bastante complexo. Vamos destacar apenas os seguintes pontos:

  • O método principal do servlet é o seguinte:
    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

É este método que é executado no início da servlet. Vemos que recebe dois parâmetros: o pedido request do cliente e um objeto response para gerar a resposta ao cliente web.

  • No método principal, é declarado e, em seguida, inicializado um objeto JspWriter denominado «out». É este objeto que permitirá enviar código HTML ao cliente através das instruções out.print("codeHTML").
        JspWriter out = null;
...
            out = pageContext.getOut();
  • O código Java

<%
   // variáveis locais do procedimento principal
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

foi reproduzido na íntegra no método principal _jspService do servlet. O mesmo se aplica a todo o código situado entre as balizas <%… %>

  • O código HTML da página JSP é alvo das instruções out.print("codeHTML") ou out.write(...). Por exemplo

                out.write("</title>\r\n  </head>\r\n  <body bgcolor=\"white\">\r\n    <h3>");
  • Neste exemplo, não existem outros métodos além do método principal _jspService.

3.2.6. Os métodos e variáveis globais de uma página JSP

Consideremos a seguinte página JSP:


<%!
   // a baliza anterior dá início à secção de variáveis e métodos globais
   // esta secção será reproduzida sem alterações no servlet
  
   // uma variável global
  String prenom="inconnu";

   // um método  
  private String sonChien(){
    return "milou";
  }//sonChien

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

   // fim da parte global do servlet
%>  

<%
   // a baliza anterior indica que o código que se segue será gravado
   // no método principal do servlet
  
   // variável local do método principal
  String nom="tintin";
%>


<%-- código 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>
      <%
         // o nome do seu amigo
        afficheAmi(out);
      %>
    </center>
  </body>
</html>

Esta página JSP gera a seguinte página Web:

Image

Vamos analisar como são geradas as quatro linhas acima:


      <p>Son nom est <%= nom %></p>
      <p>Son prénom est <%= prenom %></p>
      <p>Son chien s'appelle <%= sonChien() %></p>
      <%
         // o nome do seu amigo
        afficheAmi(out);
      %>

As linhas acima encontram-se dentro de uma baliza <%..%> e, por conseguinte, farão parte do método principal _jspService do servlet que será gerado. Como é que estas linhas têm acesso às variáveis nom, prenom e aos métodos sonChien e afficheAmi?

nom (tintin)
é uma variável local do método principal da página JSP e, por isso, conhecida nesse método
prenom (inconnu)
é uma variável global da página JSP e, por isso, é reconhecida no método principal
sonChien (milou)
é um método público da página JSP e, por isso, acessível a partir do método principal
afficheAmi (Haddock)
é um método público da página JSP e, por isso, acessível a partir do método principal. Note-se que o objeto out é passado como parâmetro ao método. Isto é obrigatório neste caso. Com efeito, o objeto out é declarado e inicializado no método principal do servlet e não é uma variável global.

Vejamos agora o código do servlet Java gerado a partir desta página JSP, depois de removido o código desnecessário:

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 {

          // a baliza anterior dá início à secção de variáveis e métodos globais
           // esta parte será reproduzida sem alterações no servlet

           // uma variável global
          String prenom="inconnu";

           // um método  
          private String sonChien(){
            return "milou";
          }//sonChien

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

           // fim da parte global do servlet

    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");
                  // a baliza anterior indica que o código que se segue será gravado
                   // no método principal do servlet

                   // variável local do método principal
                  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      ");

                        // o nome do seu amigo
                        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);
        }
    }
}

Como se pode ver acima, o código Java que se encontrava entre as balizas JSP <%! .. %> foi reproduzido na íntegra e não faz parte do método principal _jspService do servlet. As variáveis declaradas nesta parte são, portanto, variáveis de instância, ou seja, globais para os métodos, e é também aqui que se podem definir métodos diferentes do _jspService.


  // esta parte será reproduzida sem alterações no servlet

   // uma variável global
  String prenom="inconnu";

   // um método
  private String sonChien(){
    return "milou";
  }//sonChien

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

   // fim da parte global do servlet

3.2.7. Implantação e depuração das páginas JSP no servidor Tomcat

Quando se pretende criar uma página JSP e utilizá-la com o servidor Tomcat, surge a questão de onde colocar a página na estrutura de diretórios do servidor. Existem várias formas de o fazer, às quais voltaremos mais tarde. Por enquanto, a mais simples é colocar a página JSP numa pasta da árvore de diretórios <tomcat>\webapps\examples\jsp (Tomcat 4.x), sendo que <tomcat> é o diretório de instalação do Tomcat. Assim, o URL do exemplo anterior era http://localhost:8080/examples/jsp/perso/tintin/tintin.jsp. Isto significa que a página tintin.jsp estava na pasta <tomcat>\webapps\examples\jsp\perso\tintin.

Uma página JSP é convertida num ficheiro-fonte Java, que é posteriormente compilado pelo Tomcat quando a página JSP (URL) é solicitada por um navegador. Podem ocorrer erros de compilação. O Tomcat 4.x sinaliza-os na sua resposta ao navegador. Indica, nomeadamente, as linhas do ficheiro .java que estão erradas. Os erros podem ter várias origens:

  1. o código JSP da página está incorreto (erros nas tags JSP utilizadas, por exemplo)
  2. o código Java incluído na página JSP está incorreto

A primeira causa pode ser eliminada verificando o código JSP da página. A segunda pode ser eliminada verificando o código Java. Isto pode ser feito compilando diretamente o ficheiro .java gerado para a página JSP com uma ferramenta como o JBuilder, que oferece funcionalidades de depuração mais avançadas do que as do Tomcat.

3.2.8. Exemplos

Retomamos o exemplo já abordado com um servlet em que um utilizador escolhe um número numa lista e o servidor indica-lhe qual o número que escolheu, devolvendo-lhe simultaneamente a mesma lista com o elemento selecionado pelo utilizador:

Image

Para criar esta página, recuperámos o código do servlet e alterámo-lo da seguinte forma:

  • mantivemos inalterado o código Java que não produzia o código HTML
  • o código Java que produzia o código HTML foi transformado numa combinação do código HTML com o código JSP

Obteve-se então a seguinte página JSP:



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

<%!

         // variáveis globais da aplicação
         // o título da página
        private final String title="Génération d'un formulaire";
         // a base de dados dos valores da lista
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
         // valores da lista
        private String[] valeurs=null;
         // mensagem de erro
        private String msgErreur=null;
    
         // inicialização da página JSP — executada apenas uma vez
        public void jspInit(){
             // preenche a tabela de valores a partir de uma base de dados ODBC
             // com o nome DSN: DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // ligação à base de dados ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // objeto Statement
                st=connexion.createStatement();
                 // execução da consulta SELECT para recuperar os valores
                rs=st.executeQuery("select valeur from Tvaleurs");
                // os valores são recuperados e colocados numa tabela dinâmica
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // o valor é registado na lista
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                 // transformação da lista em tabela
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                 // problema
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//inicialização
   
%>

<%
     // código de _jspService executado em cada pedido do cliente
   // ocorreu algum erro durante a inicialização da página JSP?
  if(msgErreur!=null){
%>
       <!-- código HTML -->
    <html>
        <head>
          <title>Erreur</title>
      </head>
      <body>
          <h3>Application indisponible (<%= msgErreur %></h3>
      </body>
    </html>
<%
       // fim de jspService
    return;
  }//se
  
     // recuperamos a eventual escolha do utilizador
    String choix=request.getParameter("cmbValeurs");
    if(choix==null) choix="";
%>
  
   <%-- sem erros - código HTML da página normal --%>
      <html>
        <head>
          <title><%= title %></title>
      </head>
      <body>
          <h3>Choisissez une valeur</h3>
          <form method="POST">
            <select name="cmbValeurs">
              <%
                 // visualização dinâmica dos valores
                          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>");
                          }//para
            %>
          </select>
          <input type="submit" value="Envoyer">
        </form>
<%
         // foi selecionado algum valor?
                if(! choix.equals("")){
        // exibe-se a escolha do utilizador
%>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
<%        
                }//if
%>        
      </body>
    </html>

É importante destacar os seguintes pontos:

  • as instruções import do servlet foram alvo de uma diretiva <% page import="..." %>
  • a baliza <%! ... %> engloba as variáveis globais e os métodos Java da aplicação
  • o método init do servlet, que é executado uma única vez, no momento do carregamento do servlet, é chamado para uma página JSP: jspInit. Estes dois métodos têm a mesma função. Por isso, reproduzimos aqui na íntegra o código do método init do servlet.
  • As variáveis de instância da servlet, aquelas que devem ser conhecidas em vários métodos, foram reproduzidas na íntegra. Trata-se essencialmente das variáveis title, valeurs e msgErreur, que são posteriormente utilizadas no código JSP.
  • As balizas <% ... %> delimitam o código Java que será incluído no método _jspService, executado no momento de um pedido de um cliente.
  • Tal como no caso do servlet, o método _jspService começará por verificar o valor da variável msgErreur para determinar se deve gerar uma página de erro. Se houver um erro, gera a página de erro e termina (return).
  • Se não houver erro, gera o formulário com a lista de valores
  • feito isto, verifica se o utilizador selecionou um número; nesse caso, apresenta esse número na página gerada

O que ganhámos em relação ao servlet? Sem dúvida, uma melhor visão do código HTML gerado. Mas ainda há muito código Java que «polui» esta visão. Veremos mais tarde outro método chamado «delegação», em que poderemos colocar a maior parte do código Java numa servlet, ficando a página JSP apenas com o código HTML e JSP. Desta forma, separamos claramente a parte de processamento da parte de apresentação.

3.3. Implantação de uma aplicação web no servidor Tomcat

Apresentamos agora a forma de implementar aplicações web Java com o servidor Tomcat. Embora o que se vai referir seja específico deste servidor, a implementação de uma aplicação web Java num outro contentor J2EE apresentará características semelhantes às que serão descritas a seguir.

3.3.1. Os ficheiros de configuração server.xml e web.xml

Até agora, para testar os nossos servlets e páginas JSP, colocámos

  • os servlets na pasta <tomcat>\webapps\examples\WEB-INF\classes. Estavam então acessíveis através do URL http://localhost:8080/examples/servlet/nomServlet
  • as páginas JSP na árvore de diretórios <tomcat>\webapps\examples\jsp. Na altura, eram acessíveis através do URL http://localhost:8080/examples/jsp/nomPageJSP

Nunca explicámos por que razão era assim. A configuração do servidor Tomcat é feita num ficheiro de texto chamado server.xml, que se encontra na pasta <tomcat>\conf:

Image

Este ficheiro de texto é, na verdade, um ficheiro XML (eXtended Markup Language). Um documento XML é um documento de texto que contém balizas, tal como um documento HTML. No entanto, enquanto as balizas da linguagem HTML estão bem definidas, as da linguagem XML não o estão. Assim, o documento seguinte é um documento XML:

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

Um documento XML é simplesmente um documento «marcado» que segue determinadas regras de marcação:

  • um texto marcado com a forma <xx att1="val1" att2="val2" ....>texto</xx>
  • uma baliza pode estar sozinha e ter o formato <xx att1="val1" att2="val2" ..../>

Os campos «atti» são denominados atributos da baliza «xx» e os campos «vali» são os valores associados a esses atributos. Alguns documentos HTML não são documentos XML válidos. Por exemplo, a baliza HTML <br> não é uma baliza XML válida. Para ser válida, deveria ser escrita como <br/>, de modo a respeitar a regra que exige que todas as balizas XML sejam fechadas. Foi criada uma variante de HTML, denominada XHTML, para que qualquer documento XHTML se torne um documento XML válido. Alguns dos navegadores mais recentes são capazes de visualizar ficheiros XML. Assim, se designarmos como personne.xml o documento XML apresentado no exemplo acima e o visualizarmos com o IE6, obtemos a seguinte visualização:

Image

O IE6 reconhece as balizas e destaca-as a cores. Reconhece também a estrutura do documento graças às balizas. Assim, se designarmos como personne2.xml o seguinte documento:

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

e o visualizarmos com o IE6, obtemos a mesma apresentação:

Image

O IE6 reconheceu corretamente a estrutura e o conteúdo do documento. Todo o interesse do documento XML reside nesta propriedade: é fácil recuperar a estrutura e o conteúdo de um documento XML. Isto é feito com um programa denominado analisador XML. Os documentos XML tendem a tornar-se a norma nas trocas de documentos na Web. Consideremos uma máquina A que tem de enviar um documento DOC para uma máquina B. O documento DOC é construído a partir das informações contidas numa base de dados DB-A. A máquina B, por sua vez, deve armazenar o documento DOC numa base de dados DB-B. A troca poderá ocorrer da seguinte forma:

  • a máquina A recupera os dados da base de dados DB-A e encapsula-os num documento de texto XML
  • o documento XML é enviado para a máquina B através da rede
  • a máquina B analisa o documento recebido com um analisador XML e recupera tanto a estrutura como os dados (tal como fez o IE6 no nosso exemplo). Pode então armazenar os dados recebidos na base de dados DB-B

Não nos alongaremos mais sobre a linguagem XML, que merece um livro só para si.

Aqui, portanto, o Tomcat é configurado pelo ficheiro XML server.xml. Se o visualizarmos com o IE6, obtemos um documento complexo. Vamos apenas debruçar-nos sobre as seguintes linhas:

Image

É a baliza <Context ...> que nos interessa aqui. Serve para definir aplicações web. Há dois dos seus atributos a destacar:

  • path: é o nome da aplicação web
  • docBase: é a pasta na qual se encontra. Aqui, trata-se de um nome relativo: examples. Relativo a que pasta? A resposta encontra-se também no ficheiro server.xml, na linha seguinte:

Image

A linha acima define o servidor web:

  • name: nome do servidor web
  • appBase: raiz da árvore de documentos que distribui. Mais uma vez, temos um nome relativo: webapps. Este é relativo ao diretório de instalação do servidor Tomcat <tomcat>. Portanto, trata-se da pasta <tomcat>\webapps.

A aplicação web «examples» tem os seus documentos na pasta examples (ver docBase acima). Este nome é relativo à raiz da árvore web do servidor, c.a.d. <tomcat>\webapps. Trata-se, portanto, da pasta <tomcat>\webapps\examples. Vamos analisar esta pasta mais de perto:

Image

Encontramos aqui a pasta WEB-INF\classes, na qual guardámos os nossos servlets para os testar. A pasta WEB-INF contém um ficheiro chamado web.xml:

Image

Este ficheiro serve para configurar a aplicação web examples. Por enquanto, não vamos entrar em pormenores sobre este ficheiro, que é demasiado complexo. Vamos apenas debruçar-nos sobre as seguintes linhas:

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

A baliza <servlet> serve para definir um servlet no âmbito de uma aplicação web. Recorde-se aqui que a aplicação web em questão é a examples. A baliza servlet contém aqui duas outras balizas:

  • <servlet-name>servletToJsp</servlet-name>: define o nome do servlet
  • <servlet-name>servletToJsp</servlet-name>: define o nome da classe a executar quando a servlet é solicitada. Neste exemplo, a servlet e a sua classe têm o mesmo nome. Isto não é obrigatório.

Como é que a servlet servletToJsp é solicitada por um navegador ao servidor Tomcat?

  • O navegador solicita a servlet URL http://localhost:8080/examples/servlet/servletToJsp
  • O Tomcat analisa o caminho da servlet /examples/servlet/servletToJsp. Interpreta a primeira parte do caminho, /examples, como o nome de uma aplicação web e procura no seu ficheiro de configuração, server.xml, onde os documentos dessa aplicação foram armazenados. Como vimos anteriormente, é na pasta <tomcat>\webapps\examples.
  • O Tomcat utiliza o resto do caminho do servlet para localizar este na aplicação web examples. Este caminho /servlet/servletToJsp indica que deve executar o servlet com o nome servletToJsp. O Tomcat irá então ler o ficheiro de configuração web.xml da aplicação examples, que irá encontrar em <tomcat>\webapps\examples\WEB-INF. Neste ficheiro, irá verificar que o servlet servletToJsp está associado à classe Java servletToJsp (ver ficheiro web.xml acima). Em seguida, irá procurar essa classe na pasta WEB-INF\classes da aplicação web examples, c.a.d. em <tomcat>\webapps\examples\WEB-INF\classes e irá executá-la.

Image

3.3.2. Exemplo: implementação da aplicação web «lista»

Retomamos um servlet já estudado, que apresentava ao utilizador uma lista de números, entre os quais este escolhia um. O servlet confirmava-lhe, em seguida, o número que tinha escolhido:

Image

Como mostra o campo Address do navegador acima, o ficheiro de classe do servlet chamava-se gener3. De acordo com as explicações dadas anteriormente:

  • o URL /examples/servlet/gener3 indica que se trata de uma servlet chamada gener3 da aplicação web examples
  • no ficheiro web.xml da aplicação examples, não se encontra nada que faça referência a uma servlet gener3. Como é que o Tomcat a encontrou, então? Depois de ter percorrido todo o ficheiro web.xml, não consigo responder com certeza... A questão permanece em aberto...

Decidimos implementar a servlet gener3.class com o nome lstValeurs numa aplicação web chamada «liste», localizada na pasta E:\data\serge\Servlets\lstValeurs:

Image

Colocamos o ficheiro gener3.class na pasta WEB-INF\classes acima:

Image

Configuramos a aplicação web «liste» adicionando ao ficheiro server.xml as seguintes linhas, acima das que definem a aplicação web manager:

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

                <!-- Contexto do Tomcat Manager -->
                <Context path="/manager" docBase="manager" debug="0" privileged="true" />
                <!-- Contexto de exemplos do Tomcat -->
                <Context path="/examples" docBase="examples" debug="0" reloadable="true" crossContext="true">
........

A linha que define a aplicação indica que esta se encontra na pasta e:/data/serge/servlets/lstValeurs. Temos agora de definir o ficheiro web.xml desta aplicação. Este ficheiro irá definir o único servlet da aplicação:

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

O ficheiro acima indica que o servlet denominado lstValeurs está associado ao ficheiro de classe gener3.class. Este ficheiro web.xml deve ser criado e guardado na pasta WEB-INF da aplicação «lista»:

Image

A captura de ecrã acima mostra uma pasta src na qual foi colocado o ficheiro de origem gener3.java. Esta pasta pode não existir. Não tem qualquer utilidade na presente demonstração. Estamos prontos para realizar os testes:

  • encerre e inicie o Tomcat para que este recarregue o seu ficheiro de configuração server.xml. Aqui estamos a utilizar o Windows. No Unix, é possível forçar o Tomcat a recarregar o seu ficheiro de configuração sem o encerrar.
  • Com um navegador, aceda a URL http://localhost:8080/liste/servlet/lstValeurs

Image

Vê-se que o URL anterior contém a palavra-chave servlet, tal como todos os URL de servlets utilizados até agora. É possível prescindir dela associando, no ficheiro web.xml da aplicação «liste», o servlet lstValeurs a um modelo de 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>

Na baliza <servlet-mapping>, associamos o caminho /valores ao servlet lstValeurs definido nas linhas anteriores. Guardamos o novo ficheiro web.xml e solicitamos o URL http://localhost:8080/liste/valeurs:

Image

3.3.3. Implantação das páginas públicas de uma aplicação web

Acabámos de ver a implementação de uma aplicação web composta por um único servlet. Uma aplicação web pode ter vários componentes: servlets, páginas JSP, ficheiros HTML, applets Java, etc. Onde se colocam estes elementos da aplicação? Se <application> for a pasta da aplicação web definida pelo atributo docBase da aplicação no ficheiro de configuração do Tomcat server.xml, vimos que os servlets eram colocados em <application>\WEB-INF\classes. Os restantes elementos da aplicação podem ser colocados em qualquer local da árvore de pastas <application>, exceto na pasta WEB-INF. Consideremos a aplicação JSP listvaleurs.jsp já analisada:

Image

Esta página JSP tinha sido guardada na pasta <tomcat>\webapps\examples\jsp\perso\listvaleurs. Esta página poderia ser um componente da aplicação «liste» implementada anteriormente. Coloquemos o ficheiro listvaleurs.jsp diretamente na pasta desta aplicação:

Image

Recorde-se a configuração da aplicação liste no ficheiro server.xml:

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

Qualquer URL que comece pelo caminho /liste é considerado parte da aplicação liste e será procurado na pasta indicada. Vamos aceder ao URL http://localhost:8080/liste/listvaleurs.jsp com um navegador:

Image

Conseguimos, de facto, a página JSP esperada.

3.3.4. Parâmetros de inicialização de um servlet

Vimos que um servlet é configurado pelo ficheiro <application>\WEB-INF\web.xml, em que <application> é a pasta da aplicação web à qual pertence. É possível incluir neste ficheiro parâmetros de inicialização da servlet. Voltemos à nossa servlet lstValeurs da aplicação web liste, cujo ficheiro de configuração era o seguinte:

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

A classe associada ao servlet é a classe gener3. No código-fonte desta classe, encontram-se as definições de algumas constantes:

public class gener3 extends HttpServlet{
        // o título da página
        private final String title="Génération d'un formulaire";
         // a base de dados dos valores da lista
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";

Recorde-se o significado das quatro constantes definidas acima:

title
título do documento HTML gerado pelo servlet
DSNValeurs
nome DSN da base de dados ODBC onde o servlet vai buscar os dados
admDbValeurs
nome de um utilizador com direitos de leitura na base de dados anterior
mdpDbvaleurs
a sua palavra-passe

Se o administrador da base de dados DSNValeurs alterar a palavra-passe do utilizador admDbValeurs, o código-fonte do servlet terá de ser alterado e recompilado. Isto não é muito prático. O ficheiro de configuração da servlet web.xml oferece-nos uma alternativa, permitindo a definição de parâmetros de inicialização da servlet com a baliza <init-param>:

    <init-param>
        <param-name>...</param-name>
        <param-value>...</param-value>
    </init-param>
<param-name>
permite definir o nome do parâmetro
<param-value>
define o valor associado ao parâmetro anterior

A servlet tem acesso aos seus parâmetros de inicialização através dos seguintes métodos:

[Servlet].getServletConfig()
método da classe Servlet, da qual deriva a classe HttpServlet utilizada na programação web. Devolve um objeto ServletConfig que dá acesso aos parâmetros de configuração do servlet.
[ServletConfig].getInitParameter("paramètre")
método da classe ServletConfig que devolve o valor do parâmetro de inicialização «paramètre»

Configuramos a aplicação liste com o seguinte novo ficheiro 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>

Na aplicação liste, definimos um segundo servlet denominado lstValeurs2, associado ao ficheiro de classe gener5. Este último foi colocado em <application>\WEB-INF\classes:

Image

O servlet lstValeurs2 tem quatro parâmetros de inicialização: title, DSNValeurs, admDbValeurs, mdpDbValeurs. Além disso, foi definido o alias /valeurs2 para a servlet através da baliza <servlet-mapping>. Assim, a servlet lstValeurs2 da aplicação liste estará acessível através de URL e http://localhost:8080/liste/valeurs2.

O código-fonte do servlet foi alterado da seguinte forma para recuperar os parâmetros de inicialização do servlet:

public class gener5 extends HttpServlet{
    // o título da página
    private String title=null;
    // a base de dados dos valores da lista
    private String DSNValeurs=null;
    private String admDbValeurs=null;
    private String mdpDbValeurs=null;
...............

         // inicialização do servlet
        public void init(){
             // recuperam-se os parâmetros de inicialização do servlet
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");
            DSNValeurs=config.getInitParameter("DSNValeurs");
            admDbValeurs=config.getInitParameter("admDbValeurs");
            mdpDbValeurs=config.getInitParameter("mdpDbValeurs");

             //foram recuperados todos os parâmetros?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // preenchimento da tabela de valores a partir de uma base de dados ODBC
             // com o nome DSN: DSNvaleurs
...............

Para testar o servlet, é necessário reiniciar o Tomcat para que este tenha em conta o novo ficheiro de configuração web.xml da aplicação liste. Através de um navegador, acede-se à página URL da servlet http://localhost:8080/liste/valeurs2:

Image

Se algum dos parâmetros de inicialização necessários para o servlet estiver em falta no ficheiro web.xml, obtém-se a seguinte página:

Image

3.3.5. Parâmetros de inicialização de uma aplicação web

No exemplo anterior, apenas o servlet lstValeurs2 tem acesso aos parâmetros title, DSNValeurs, admDbValeurs e mdpDbValeurs. É possível imaginar que outra servlet da mesma aplicação, a liste, necessite dos dados da mesma base de dados que a servlet lstValeurs2 utiliza. Nesse caso, seria necessário redefinir os parâmetros DSNValeurs, admDbValeurs e mdpDbValeurs na secção de configuração do ficheiro web.xml da nova servlet. Outra solução consiste em definir os parâmetros comuns a várias servlets ao nível da aplicação e não mais ao nível das servlets. O novo ficheiro web.xml da aplicação passa a ter o seguinte conteúdo:

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

O novo servlet chama-se lstValeurs3, está associado ao ficheiro de classe gener6 e foi associado ao alias /valeurs3 (servlet-mapping). O parâmetro title é o único parâmetro que foi mantido na definição do servlet. Os restantes foram colocados na configuração da aplicação, em tags <context-param>. Esta baliza serve para definir informações específicas da aplicação e não de um servlet ou de uma página JSP em particular. Como é que o servlet Java acede a estes parâmetros, frequentemente designados por parâmetros de contexto? Os métodos disponíveis para obter as informações de contexto são muito semelhantes aos utilizados para obter os parâmetros de inicialização específicos de um servlet:

[Servlet].getServletContext()
método da classe Servlet, da qual deriva a classe HttpServlet utilizada na programação web. Devolve um objeto ServletContext que dá acesso aos parâmetros de configuração da aplicação
[ServletContext].getInitParameter("paramètre")
método da classe ServletContext que devolve o valor do parâmetro de inicialização «paramètre»

A classe gener6.java introduz apenas as seguintes alterações no código Java da classe gener5.java utilizada anteriormente:

             // recuperam-se os parâmetros de inicialização do servlet
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");

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

             //: foram recuperados todos os parâmetros?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // preenche-se a tabela de valores a partir de uma base de dados ODBC
             // com o nome DSN: DSNvaleurs
...............

O parâmetro title específico do servlet é obtido através de um objeto ServletConfig. Os outros três parâmetros definidos ao nível da aplicação são, por sua vez, obtidos através de um objeto ServletContext. Compilamos esta classe e colocamo-la, tal como as outras, em <application>\WEB-INF\classes:

Image

Reiniciamos o Tomcat para que este tenha em conta o novo ficheiro web.xml da aplicação e solicitamos o URL e o http://localhost:8080/liste/valeurs3:

Image

3.3.6. Parâmetros de inicialização de uma página JSP

Já vimos como definir parâmetros de inicialização para um servlet ou uma aplicação web. Será possível fazer o mesmo para uma página JSP? Voltemos ao início do código da página listvaleurs.jsp já analisada:

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

<%!
         // variáveis globais da aplicação
         // o título da página
        private final String title="Génération d'un formulaire";
         // a base de dados dos valores da lista
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
.........

Encontram-se as quatro constantes title, DSNValeurs, admDbValeurs e mdpDbValeurs definidas no ficheiro web.xml da aplicação. As constantes DSNValeurs, admDbValeurs e mdpDbValeurs foram agora definidas ao nível da aplicação e, por isso, podemos supor que uma página JSP que faça parte desta aplicação terá acesso a elas. É esse o caso. Sabemos que a página JSP será convertida num servlet. Este terá acesso ao contexto através do método getServletContext(). Mais delicado é o caso da constante title. Com efeito, definimo-la ao nível do servlet e não ao nível da aplicação, da seguinte forma:

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

Para a página JSP, a sintaxe anterior já não é adequada, uma vez que já não existe o conceito de ficheiro de classe. No entanto, a sintaxe de configuração de uma página JSP é muito semelhante à de um servlet. É a seguinte:

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

Na verdade, uma página JSP é equiparada a um servlet ao qual se atribui um nome (servlet-name). Em vez de associar um ficheiro de classe a este servlet, associa-se o ficheiro fonte da página JSP a ser executado (jsp-file). Assim, as linhas anteriores definem um servlet denominado JSPlstvaleurs associado à página JSP /listvaleurs2.jsp. O caminho /listvaleurs2.jsp é medido em relação à raiz da aplicação. Assim, no caso da nossa aplicação liste, o ficheiro listvaleurs2.jsp estaria na pasta docBase (ver server.xml) da aplicação liste:

Image

A configuração da página JSP será a seguinte no ficheiro web.xml da aplicação:

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

A página JSP listvaleurs2.jsp está localizada na raiz da aplicação liste e associada ao nome do servlet JSPlstValeurs (servlet-name), que, por sua vez, está associado ao alias /jspvaleurs (servlet-mapping). Assim, a nossa página JSP ficará acessível através de URL http://localhost:8080/liste/jspvaleurs.

A página inicial JSP, listvaleurs.jsp, é alterada para listvaleurs2.jsp e recupera os seus quatro parâmetros de inicialização no método jspInit():

<%!
         // variáveis globais da aplicação
         // o título da página
        private String title=null;
        // a base de dados dos valores da lista
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
         // valores da lista
        private String[] valeurs=null;
         // mensagem de erro
        private String msgErreur=null;

         // inicialização da página JSP — executada apenas uma vez
        public void jspInit(){

             // recuperam-se os parâmetros de inicialização do servlet
      ServletConfig config=getServletConfig();
            title=config.getInitParameter("JSPtitle");
      ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");

             //foram recuperados todos os parâmetros?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // preenche a tabela de valores a partir de uma base de dados ODBC
             // com o nome DSN: DSNvaleurs
..............

A página JSP recupera os seus parâmetros de inicialização da mesma forma que os servlets. O ficheiro anterior é guardado na raiz da aplicação web liste:

Image

O servidor Tomcat é reiniciado para forçá-lo a recarregar o novo ficheiro de configuração web.xml da aplicação. É então possível aceder à página URL http://localhost:8080/liste/jspvaleurs:

Image

3.3.7. Colaboração entre servlets e páginas JSP numa aplicação web

Quando um cliente faz uma solicitação a um servidor Web, a resposta pode ser elaborada por vários servlets e páginas JSP. Até agora, a resposta era elaborada por um único servlet ou página JSP. Vimos que a página JSP proporcionava uma melhor legibilidade da estrutura do documento HTML gerado. No entanto, ela também contém, em geral, muito código Java. É possível melhorar esta situação colocando

  • num ou em vários servlets o código Java que não gera o código HTML da resposta
  • nas páginas JSP, o código de geração dos diferentes documentos HTML enviados em resposta ao cliente

Desta forma, espera-se melhorar a separação entre o código Java e o código HTML. Vamos aplicar esta nova estrutura à nossa aplicação liste: um servlet Java lstValeurs4 será responsável por ler, no arranque, os valores da base de dados e, em seguida, analisar os pedidos dos clientes. Dependendo do resultado dessa análise, o pedido do cliente será redirecionado para uma página de erro erreur.jsp ou para a página de exibição da lista de números liste.jsp. A aplicação liste será, portanto, composta por um servlet e por duas páginas JSP.

Como é que um servlet pode passar o pedido que recebeu de um cliente para outro servlet ou para uma página JSP? Iremos utilizar os seguintes métodos:

[ServletContext].getRequestDispatcher(
String url)
método da classe ServletContext que devolve um objeto RequestDispatcher. O parâmetro url é o nome do URL para o qual se pretende transmitir a solicitação do cliente. Esta transferência da solicitação só pode ser efetuada no âmbito de uma mesma aplicação. Por isso, o parâmetro url é um caminho relativo à árvore de diretórios web dessa aplicação.
[RequestDispatcher].forward
(ServletRequest request,
 ServletResponse response)
método da interface RequestDispatcher que transmite ao método anterior URL o pedido request do cliente e o objeto response que deve ser utilizado para elaborar a resposta.
[ServletRequest].setAttribute(String nom, Object obj)
quando um servlet ou uma página JSP encaminha uma solicitação para outro servlet ou página JSP, normalmente precisa de lhe passar outras informações além da mera solicitação do cliente, informações resultantes do seu próprio processamento da solicitação. O método setAttribute da classe ServletRequest permite adicionar atributos ao objeto request do cliente sob uma forma semelhante a um dicionário de pares (atributo, valor), em que attribut é o nome do atributo e valeur é um objeto qualquer que representa o valor desse atributo.
[ServletRequest].getAttribute(
String attribut)
permite recuperar os valores dos atributos de um pedido. Este método será utilizado pelo servlet ou pela página JSP a quem foi encaminhado um pedido, para obter as informações que nele foram adicionadas.

O servlet responsável pelo processamento do formulário será configurado da seguinte forma no ficheiro 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>

A servlet lstValeurs4 terá quatro parâmetros de inicialização próprios:

title
o título do documento HTML a gerar
JSPerreur
o URL da página de erro JSP
JSPliste
o URL da página JSP que apresenta a lista de números
URLservlet
o URL associado ao atributo «action» do formulário apresentado pela página JSPliste. Este URL será o da servlet lstValeurs4

A servlet terá o alias /valeurs4 (servlet-mapping) e será, portanto, acessível através do URL http://localhost:8080/liste/valeurs4. Está associada ao ficheiro de classe gener7.java, cujo código-fonte completo é o seguinte:

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

public class gener7 extends HttpServlet{
        // o título da página
        private String title=null;
        // a base de dados dos valores da lista
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
         // as páginas de visualização JSP
        private String JSPerreur=null;
        private String JSPliste=null;
        // o URL do servlet
        private String URLservlet=null;
        // valores da lista
        private String[] valeurs=null;
         // mensagem de erro
        private String msgErreur=null;

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

            // coloca-se «msgErreur,title» nos atributos da solicitação
            request.setAttribute("msgErreur",msgErreur);
            request.setAttribute("title",title);
            request.setAttribute("URLservlet",URLservlet);

             // Ocorreu algum erro ao carregar o servlet?
            if(msgErreur!=null){
                 // redireciona para uma página de erro JSP
                getServletContext().getRequestDispatcher(JSPerreur).forward(request,response);
                 // fim
                return;
            }

             // não ocorreu qualquer erro
             // coloca-se a lista de valores nos atributos da solicitação
            request.setAttribute("valeurs",valeurs);

             // recupera-se a eventual escolha do utilizador
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";
            request.setAttribute("choix",choix);

             // passa-se o controlo para a página JSP de apresentação da lista
            getServletContext().getRequestDispatcher(JSPliste).forward(request,response);
            // fim
            return;
        }//GET

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

             // redireciona para GET
            doGet(request,response);
        }//POST

        // -----------------------------------------------------------------
         // inicialização do servlet
        public void init(){

             // recuperam-se os parâmetros de inicialização do servlet
            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");


             //foram recuperados todos os parâmetros?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null || JSPerreur==null || JSPliste==null || URLservlet==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // preenche a tabela de valores a partir de uma base de dados ODBC
             // com o nome DSN: DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // ligação à base de dados ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // objeto Statement
                st=connexion.createStatement();
                 // execução da consulta SELECT para recuperar os valores
                rs=st.executeQuery("select valeur from Tvaleurs");
                // os valores são recuperados e colocados numa tabela dinâmica
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // o valor é registado na lista
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                 // transformação da lista em tabela
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                 // problema
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//inicializar
    }//classe

A novidade nesta classe é o encaminhamento do pedido do cliente para a página JSPerreur em caso de erro e para a página JSPliste nos restantes casos. A classe não elabora ela própria a resposta. São as páginas JSP, JSPerreur e JSPliste que se encarregam disso. Anteriormente, o servlet adicionava atributos (setAttribute) à solicitação do cliente:

  • uma mensagem de erro msgErreur em caso de erro na página JSPerreur
  • os valores (valeurs) a apresentar, o valor selecionado (choix) pelo utilizador, o título (title) do formulário, o URL (URLservlet) do atributo «action» do formulário para a página JSPliste

Esta classe é compilada e colocada nas classes da aplicação:

Image

A página JSP, que apresenta uma mensagem de erro, está configurada da seguinte forma:

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

O ficheiro JSP associado à página de erro chama-se erreur.jsp e encontra-se na raiz da aplicação:

Image

Tem como alias /JSPerreur, o que o torna acessível através do http://localhost:8080/liste/JSPerreur URL. Possui um parâmetro de inicialização denominado mainServlet, cujo valor é o alias do servlet principal descrito anteriormente. Note-se que este alias é relativo à raiz da aplicação liste,; caso contrário, teríamos /liste/valeurs4.. O código da página erreur.jsp é o seguinte:

<%
     // código de _jspService
   // recupera-se o parâmetro de inicialização mainServlet
  String servletListValeurs=config.getInitParameter("mainServlet");
   // recupera-se o atributo msgErreur
  String msgErreur=(String)request.getAttribute("msgErreur");
   // atributo válido?
  if(msgErreur!=null){
%>
       <!-- código HTML -->
    <html>
        <head>
          <title>Erreur</title>
      </head>
      <body>
          <h3>Application indisponible (<%= msgErreur %>)</h3>
      </body>
    </html>
<%
    } else { // atributo msgErreur inválido — regresso ao servlet principal
%>
    <jsp:forward page="<%= servletListValeurs %>" />  
<%    
  }
%>  

Esta página deve, normalmente, ser chamada pela servlet anterior, que deve passar-lhe o atributo msgErreur. No entanto, nada impede que seja chamada diretamente, caso se conheça o seu URL. Além disso, se se verificar que o atributo msgErreur está ausente, a solicitação é encaminhada para o servlet principal. Aqui, utiliza-se uma baliza específica das páginas JSP, cuja sintaxe é:

<jsp:forward page="URL" />

onde URL é o URL do servlet para o qual se transfere a solicitação do cliente. Se o atributo msgErreur estiver presente, é apresentada a página de erro.

A página JSP, que apresenta a lista de números, está configurada da seguinte forma:

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

O ficheiro JSP associado à página de erro chama-se liste.jsp e encontra-se na raiz da aplicação:

Image

O alias do servlet é /JSPliste, o que o torna acessível através do http://localhost:8080/liste/JSPlisteURL. Possui um parâmetro de inicialização denominado mainServlet, cujo valor é o alias do servlet principal. O código da página liste.jsp é o seguinte:

   <%-- página de visualização da lista de valores --%>
  <%
       // código de jspService
       // recupera-se o parâmetro de inicialização
      String servletListValeurs=config.getInitParameter("mainServlet");

     // recuperam-se os atributos da solicitação proveniente do servlet principal
    String title=(String) request.getAttribute("title");
    String[] valeurs=(String[]) request.getAttribute("valeurs");
    String choix=(String) request.getAttribute("choix");
    String URLservlet=(String) request.getAttribute("URLservlet");

     // atributos válidos?
    if(title==null || valeurs==null || choix==null){
         // existe um atributo inválido — passamos o controlo para a servlet
   %>
   <jsp:forward page="<%= servletListValeurs %>" />
    <%
    }//if
  %>

  <%-- código HTML --%>  
      <html>
        <head>
          <title><%= title %></title>
      </head>
      <body>
          <h3>Choisissez une valeur</h3>
          <form method="POST" action="<%= URLservlet %>">
            <select name="cmbValeurs">
              <%
                 // exibição dinâmica dos valores
                          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>
                <%
             // foi selecionado algum valor?
                    if(! choix.equals("")){
                // exibe-se a escolha do utilizador
                %>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
                <%        
                    }//if
                %>        
      </body>
    </html>

Esta página funciona da mesma forma que a página erreur.jsp. Normalmente, deve ser chamada pelo servlet /liste/valeurs4 e receber os atributos title, valeurs e choix. Se algum destes parâmetros estiver em falta, o controlo é passado para a servlet URLservlet (/liste/valeurs4). Se todos os parâmetros estiverem presentes, é apresentada a lista de números, bem como o número escolhido pelo utilizador, caso este tenha selecionado algum.

Se for solicitado o URL da servlet principal, obtém-se o seguinte resultado:

Image

com o código-fonte seguinte (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>

Este documento HTMl foi gerado pela página JSP liste.jsp. Verifica-se que os atributos title, valeurs e URLservlet foram efetivamente recuperados.

Para concluir sobre a colaboração entre servlets e páginas JSP, observamos que as páginas JSP são, neste caso, muito curtas e não contêm código Java que não contribua diretamente para a criação da resposta HTML. A estrutura dos documentos gerados torna-se, assim, mais visível.

3.4. Ciclo de vida dos servlets e das páginas JSP

3.4.1. O ciclo de vida

Aqui, estamos interessados no ciclo de vida dos servlets. O das páginas JSP decorre deste. Consideremos um servlet chamado pela primeira vez. É então criada uma instância da classe pelo servidor Web e carregada na memória. Esta irá, então, atender ao pedido. Feito isto, o servlet não é descarregado da memória. Permanece na memória para atender a outras solicitações, de modo a otimizar os tempos de resposta do servidor. Será descarregada quando tiver decorrido um período de tempo suficientemente longo sem que tenha atendido a novas solicitações. Este período é, geralmente, configurável no próprio servidor Web.

Enquanto estiver na memória, a servlet pode atender a várias solicitações simultaneamente. O servidor Web cria um thread por solicitação, e todos utilizam a mesma instância da servlet:

Todas as threads acima partilham as variáveis da instância do servlet. Pode ser necessário sincronizar as threads para evitar a corrupção dos dados do servlet. Voltaremos a este assunto mais tarde.

Ao carregar uma servlet, é executado um método específico da servlet:

public void init() throws ServletException{
}

Para uma página JSP, trata-se do método


  public void jspInit(){
  }

que é executado. Aqui está um exemplo de uma página JSP que utiliza o método jspInit:

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

<%!
   // variáveis e métodos globais da página JSP

   // variável de instância
  int compteur;

   // método para incrementar o contador  
  public int getCompteur(){
     // incrementa-se o contador
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
     // redefine-se
    return compteur;
  }

   // o método executado no carregamento inicial da página
  public void jspInit(){
     // inicialização do contador
    compteur=100;
  }
%>

A página anterior JSP inicializa um contador em jspInit com o valor 100. Qualquer pedido subsequente ao servlet incrementa e, em seguida, apresenta o valor desse contador:

Na primeira vez:

Image

Na segunda vez:

Image

Como se pode ver acima, entre as duas solicitações, o servlet não foi descarregado; caso contrário, o contador estaria em 101 na segunda solicitação. Quando o servlet é descarregado, o método

public void destroy(){
}

é executado, caso exista. Para as páginas JSP, é o método


  public void jspDestroy(){
  }

Nestes métodos, é possível, por exemplo, encerrar ligações a bases de dados, ligações essas que terão sido abertas nos métodos init correspondentes.

3.4.2. Sincronização de pedidos com um servlet

Voltemos à página JSP anterior, que incrementa um contador e o devolve ao cliente web. Suponhamos que haja duas solicitações simultâneas. São então criadas duas threads para as executar, threads que irão utilizar a mesma instância de servlet e, portanto, neste caso, o mesmo contador. Recordemos o código que incrementa o contador:


  public int getCompteur(){
     // incrementa-se o contador
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
     // retornamos
    return compteur;
  }

O incremento do contador foi deliberadamente escrito de forma desajeitada. Suponhamos que a execução das duas threads ocorra da seguinte forma:

 
  1. no momento T1, a thread TH1 é executada. Ela lê o contador (=145) em myCompteur, depois é interrompida e perde o controlo do processador. Por isso, não teve tempo de incrementar myCompteur e de copiar o novo valor para compteur.
  2. No momento em que T2 está a ser executado, o thread TH2 é executado. Este lê o contador (=145) em myCompteur, sendo depois interrompido e perdendo o processador. Note-se que as duas threads têm variáveis myCompteur diferentes. Partilham apenas as variáveis de instância, ou seja, as que são globais aos métodos.
  3. No momento T3, o thread TH1 retoma o controlo e termina. Assim, devolve 146 ao seu cliente.
  4. No momento T4, o thread TH2 retoma o controlo e termina. Também ele devolve 146 ao seu cliente, quando deveria ter devolvido 147.

Temos aqui um problema de sincronização de threads. Quando TH1 pretende incrementar o contador, seria necessário impedir que qualquer outra thread o fizesse também. Para evidenciar este problema, reescrevemos a página JSP da seguinte forma:

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

<%!
   // variáveis e métodos globais da página JSP

   // variável de instância
  int compteur;

   // método para incrementar o contador  
  public int getCompteur(){
     // lê-se o contador
    int myCompteur=compteur;
     // pausa de 10 segundos
    try{
      Thread.sleep(10000);
    }catch (Exception ignored){}
     // incrementa-se o contador
    compteur=myCompteur+1;
     // retornar o valor
    return compteur;
  }

   // o método executado no carregamento inicial da página
  public void jspInit(){
     // inicializar contador
    compteur=100;
  }
%>

Aqui, forçámos o thread a parar 10 segundos após ter lido o contador. Assim, este deverá perder o controlo do processador e outro thread poderá, por sua vez, ler um contador que não foi incrementado. Quando fazemos pedidos com um navegador, não notamos qualquer diferença, a não ser a espera de 10 segundos antes de obtermos o resultado.

Image

Agora, se abrirmos duas janelas do navegador e fizermos duas solicitações com um intervalo de tempo suficientemente curto entre elas:

Image

Image

Obtemos o mesmo valor do contador. Podemos destacar melhor o problema com um cliente programado, em vez de um cliente manual como é o caso do navegador. Segue-se um cliente em Perl que é chamado da seguinte forma:

programa URL N

onde

URL é o URL do servlet de contagem

N é o número de pedidos a efetuar a esta servlet

Eis os resultados obtidos para 5 pedidos, que ilustram bem o problema da má sincronização das threads: todos obtêm o mesmo valor do contador.


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

O código do cliente Java é o seguinte.

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

public class clientCompteurJSP {

    public static void main(String[] params){

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

         // verificação dos parâmetros
        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
         // número de chamadas
        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

         // os parâmetros estão corretos — é possível estabelecer as ligações ao 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 {
         // executa o nbAppels no URL urlCompteur
         // exibe sempre o valor do contador devolvido pelo servidor web


         // retira-se de urlCompteur as informações necessárias para a ligação ao servidor fiscal
        String path=urlCompteur.getPath();
        if(path.equals("")) path="/";
        String host=urlCompteur.getHost();
        int port=urlCompteur.getPort();
        if(port==-1) port=urlCompteur.getDefaultPort();

         // efetuam-se as chamadas para o URL
        Socket[] clients=new Socket[nbAppels];
        for(int i=0;i<nbAppels;i++){
             // e estabelece-se a ligação ao servidor
            clients[i]=new Socket(host,port);
             // cria-se um fluxo de gravação para o servidor
            PrintWriter OUT=new PrintWriter(clients[i].getOutputStream(),true);
             // solicita-se o URL - envio dos cabeçalhos HTTP
            OUT.println("GET " + path + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            OUT.println("Connection: close");
            OUT.println("");
        }//para

         // dados locais
        String réponse=null;                        // resposta do servidor
         // o padrão procurado na resposta HTML do servidor
        Pattern modèleCompteur=Pattern.compile("^\\s*Compteur= (\\d+)");
         // o modelo de uma resposta correta
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // o resultado da comparação com o modelo
        Matcher résultat=null;

        for(int i=0;i<nbAppels;i++){
             // cada cliente lê a resposta que o servidor lhe envia

             // criam-se os fluxos de entrada e saída do cliente TCP
            BufferedReader IN=new BufferedReader(new InputStreamReader(clients[i].getInputStream()));

             // lê-se a primeira linha da resposta
            réponse=IN.readLine();
             // compara-se a linha HTTP com o modelo da resposta correta
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                 // existe um problema com URL
                throw new Exception("Client n° " + i + " - Le serveur a répondu : URL ["+ urlCompteur + "] inconnue");
            }//se

             // lê-se a resposta até ao fim dos cabeçalhos
            while((réponse=IN.readLine())!=null && ! réponse.equals("")){
            }//enquanto

             // as cabeçalhas terminaram HTTP - passamos ao código HTML
             // para recuperar o valor do contador
            boolean compteurTrouvé=false;
            while((réponse=IN.readLine())!=null){
                 // comparamos a linha com o modelo do contador
                if(! compteurTrouvé){
                    résultat=modèleCompteur.matcher(réponse);
                    if(résultat.find()){
                         // contador encontrado
                        System.out.println("Compteur="+résultat.group(1));
                        compteurTrouvé=true;
                    }//se
                }//se
            }//enquanto

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

}//classe

Vamos explicar o código anterior:

  • o programa aceita dois parâmetros:
    • o URL da página JSP do contador
    • o número de clientes a criar para este URL
  • o programa começa, portanto, por verificar a validade dos parâmetros: se existem efetivamente dois, se o primeiro se assemelha sintaticamente a um URL e se o segundo a um número inteiro >0. Para verificar se o URL está sintaticamente correto, utiliza-se a classe URL e o seu construtor URL (String), que cria um objeto URL a partir de uma cadeia de caracteres como http://istia.univ-angers.fr. É lançada uma exceção se a cadeia de caracteres não for um URL sintaticamente válido. Isto permite-nos verificar a validade do primeiro parâmetro.
  • Depois de verificados os parâmetros, o controlo é passado para a rotina getCompteurs. Este procedimento irá criar clientes nbAppels, os quais se irão ligar todos ao mesmo tempo (ou quase) ao URL urlCompteur.
  • A porta e a máquina à qual os clientes devem ligar-se são obtidas a partir do URL urlCompteur: [URL].getHost() permite obter o nome do computador e [URL].getPort() permite obter a porta.
  • Um primeiro ciclo permite que cada cliente:
    • ligar-se ao servidor web
    • solicitar-lhe o URL urlCompteur

Neste ciclo, o cliente não aguarda a resposta do servidor. Com efeito, o objetivo é fazer com que o servidor receba pedidos quase simultâneos.

  • Um segundo ciclo permite que cada cliente receba e processe a resposta enviada pelo servidor. O processamento consiste em encontrar na resposta a linha que contém o valor do contador e exibi-la.

Para resolver o problema identificado anteriormente (o mesmo contador enviado aos cinco clientes), é necessário sincronizar os threads do serviço de contagem num único objeto antes de entrar na secção crítica de leitura e atualização do contador. A nova página JSP é a seguinte:


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

<%!
   // variáveis e métodos globais da página JSP
  
   // variáveis de instância
  int compteur;
  Object verrou=new Object();
  
   // método para incrementar o contador  
  public int getCompteur(){
  
     // sincroniza-se a secção crítica
    synchronized(verrou){
       // lê-se o contador
      int myCompteur=compteur;
       // pausa de 10 segundos
      try{
        Thread.sleep(10000);
      }catch (Exception ignored){}
       // incrementa-se o contador
      compteur=myCompteur+1;
    }//sincronizado
     // é devolvido
    return compteur;
  }//getCompteur

   // o método executado no carregamento inicial da página
  public void jspInit(){
     // inicializar contador
    compteur=100;
  }
%>

Ao executar, obtêm-se então os seguintes resultados:

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

A documentação indica que o servidor Web pode, por vezes, criar várias instâncias de um mesmo servlet. Neste caso, a sincronização anterior deixa de funcionar, uma vez que a variável verrou é local a uma instância e, por isso, não é conhecida pelas outras instâncias. O mesmo se aplica à variável compteur. Para torná-las globais para todas as instâncias, escrever-se-á:


// variável de classe
  static int compteur;
  static Object verrou=new Object();

O resto do código não se altera.