Sei sulla pagina 1di 177

UNIVERSIDADE FEDERAL DE VIÇOSA

DEPARTAMENTO DE INFORMÁTICA

JAVA NA PRÁTICA
Volume II

Alcione de Paiva Oliveira


Vinícius Valente Maciel

2002
Java na Prática – Volume II 1

Sumário
Capítulo I - Concorrência ....................................... 3
CRIANDO THREADS EM JAVA ..................................................................................................... 5
Criando threads por meio da interface Runnable ............................................................ 8
A CLASSE THREAD ..................................................................................................................... 9
Hierarquia ............................................................................................................................ 9
Construtores ......................................................................................................................... 9
Métodos .............................................................................................................................. 10
Variáveis públicas .............................................................................................................. 11
CICLO DE VIDA DOS THREADS ................................................................................................. 12
sleep(), yield(), join(), destroy(), stop(), suspend() e resume(). ................ 13
DAEMON THREADS .................................................................................................................. 17
INFLUÊNCIA DO SISTEMA OPERACIONAL NO COMPORTAMENTO DOS THREADS....................... 18
Forma de escalonamento de threads.................................................................................. 19
Relacionamento entre os níveis de prioridades definidas na linguagem Java e os níveis de
prioridades definidas nos Sistemas Operacionais.............................................................. 20
COMPARTILHAMENTO DE MEMÓRIA E SINCRONIZAÇÃO .......................................................... 22
Atomicidade de Instruções e Sincronização do Acesso à Sessões Críticas ........................ 25
Comunicação entre Threads: wait() e notify() ................................................................... 31
Capítulo II - Animação ......................................... 46
Capítulo III - Programação em rede ........................... 50
CONCEITOS SOBRE PROTOCOLOS USADOS NA INTERNET......................................................... 50
TCP..................................................................................................................................... 52
UDP.................................................................................................................................... 52
IDENTIFICAÇÃO DE HOSTS (Número IP)...................................................................... 53
Identificação de Processos (Portas)................................................................................... 54
PROGRAMAÇÃO EM REDE COM JAVA ....................................................................................... 54
Comunicação Básica Entre Aplicações.............................................................................. 55
Comunicação Sem Conexão (UDP) ................................................................................... 60
Comunicação por meio de URL ......................................................................................... 63
Capítulo IV – Computação Distribuída (RMI) ................... 69
CRIANDO NOSSA AGENDA DISTRIBUÍDA ................................................................................... 69
Implementar interface do objeto remoto ............................................................................ 69
Capítulo V - Acesso a Banco de Dados ......................... 71
MODELOS DE ACESSO A SERVIDORES ...................................................................................... 71
TIPOS DE DRIVERS JDBC......................................................................................................... 72
Obtendo os Drivers JDBC.................................................................................................. 74
PREPARANDO UM BANCO DE DADOS ....................................................................................... 74
Configurando o ODBC....................................................................................................... 77
EXEMPLO INICIAL .................................................................................................................... 78
Carregando o Driver.......................................................................................................... 79
Estabelecendo a conexão ................................................................................................... 79
Criando e Executando Comandos ...................................................................................... 81
RECUPERANDO VALORES......................................................................................................... 82
TRANSAÇÕES E NÍVEL DE ISOLAMENTO ................................................................................... 83
Transação........................................................................................................................... 83

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 2
Níveis de isolamento........................................................................................................... 85
PREPARED STATEMENTS .......................................................................................................... 87
PROCEDIMENTOS ARMAZENADOS (STORED PROCEDURES)...................................................... 88
AGENDA ELETRÔNICA VERSÃO JDBC ..................................................................................... 90
Capítulo VI Servlets e JSP ................................... 97
SERVLETS ................................................................................................................................ 97
Applets X Servlets............................................................................................................... 98
CGI X Servlets .................................................................................................................... 99
A API SERVLET ....................................................................................................................... 99
Exemplo de Servlet ........................................................................................................... 101
COMPILANDO O SERVLET....................................................................................................... 102
Instalando o Tomcat......................................................................................................... 103
PREPARANDO PARA EXECUTAR O SERVLET ............................................................................ 106
Compilando o Servlet ....................................................................................................... 106
Criando uma aplicação no Tomcat .................................................................................. 107
EXECUTANDO O SERVLET ...................................................................................................... 108
Invocando diretamente pelo Navegador........................................................................... 108
Invocando em uma página HTML .................................................................................... 109
Diferenças entre as requisições GET e POST .................................................................. 109
CONCORRÊNCIA ..................................................................................................................... 110
OBTENDO INFORMAÇÕES SOBRE A REQUISIÇÃO .................................................................... 112
LIDANDO COM FORMULÁRIOS................................................................................................ 114
LIDANDO COM COOKIES......................................................................................................... 115
LIDANDO COM SESSÕES ......................................................................................................... 118
JSP......................................................................................................................................... 122
PHP X JSP ....................................................................................................................... 123
ASP X JSP ........................................................................................................................ 124
Primeiro exemplo em JSP ................................................................................................ 124
Executando o arquivo JSP................................................................................................ 125
Objetos implícitos............................................................................................................. 126
Tags JSP........................................................................................................................... 127
Comentários ..................................................................................................................... 130
Diretivas ........................................................................................................................... 131
Extraindo Valores de Formulários................................................................................... 133
Criando e Modificando Cookies....................................................................................... 134
Lidando com sessões ........................................................................................................ 136
O Uso de JavaBeans......................................................................................................... 138
REENCAMINHANDO OU REDIRECIONANDO REQUISIÇÕES ....................................................... 146
UMA ARQUITETURA PARA COMÉRCIO ELETRÔNICO ............................................................... 148
Tipos de aplicações na WEB ............................................................................................ 148
Arquitetura MVC para a Web .......................................................................................... 148
Agenda Web: Um Exemplo de uma aplicação Web usando a arquitetura MVC.............. 151
Capítulo VII Perguntas Frequentes ........................... 171
Bibliografia ................................................ 172
Links ....................................................... 173
Índice ...................................................... 175

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 3

Capítulo I - Concorrência
Um sistema operacional é dito concorrente se permite que mais que uma
tarefa seja executada ao mesmo tempo. Na prática a concorrência real ou
paralelismo só é possível se o hardware subjacente possui mais de um
processador. No entanto, mesmo em computadores com apenas um processador
é possível obter um certo tipo de concorrência fazendo com o processador
central execute um pouco de cada tarefa por vez, dando a impressão de que as
tarefas estão sendo executadas simultaneamente.

Dentro da nomenclatura empregada, uma instância de um programa em


execução é chamada de processo. Um processo ocupa um espaço em memória
principal para o código e para as variáveis transientes (variáveis que são
eliminadas ao término do processo). Cada processo possui pelo menos uma linha
de execução (Thread). Para ilustrarmos o que é uma linha de execução suponha
um determinado programa prog1. Ao ser posto em execução é criado um
processo, digamos A, com uma área de código e uma área de dados e é iniciada
a execução do processo a partir do ponto de entrada. A instrução inicial assim
como as instruções subsequentes formam uma linha de execução do processo A.
Portanto, um thread nada mais é que uma sequência de instruções que está em
execução de acordo com que foi determinado pelo programa. O estado corrente
da linha de execução é representada pela instrução que está sendo executada. A
figura IX.1 mostra a relação entre estes elementos.

arquivo prog1 Memória Principal

Área de código 101001101


101001101 Linha de execução
010110010 010110010
010101100 010101100
(thread)
100011101 100011101
Processo
Área de dados
100101010
101001010

Figura IX.1 – Relação entre Programa, Processo e Thread.

É possível existir mais de uma linha de execução em um único processo.


Cada linha de execução pode também ser vista como um processo, com a

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 4
diferença que enquanto cada processo possui sua área de código e dados
separada de outros processos, os threads em um mesmo processo compartilham
o código e a área de dados. O que distingue um thread de outro em um mesmo
processo é a instrução corrente e uma área de pilha usada para armazenar o
contexto da sequência de chamadas de cada thread. Por isso os threads também
são chamados de processos leves (light process). A figura IX.2 mostra
esquematicamente a diferença entre processos e threads.

Memória Processo
A
10100
01001
B
11001 1010001001110
10100
01010 0101010101010
01001
1010 11001 1010000101101
1010100010101
Thread 1
0111 01010

1010
Código 0101011011001
0101010100101
0111 0101010010101 Thread 2
C 0101001000000
1010101010101
10100
01001
11001
10100111001 Área de pilha
01010101010
01010 10101010101 do thread2
1010
Dados 01010101000
11110101010
0111
Área de pilha do thread1

Figura IX.2 – (a) Processos; (b) Threads.

Sistemas monotarefas e monothreads como o DOS possuem apenas um


processo em execução em um determinado instante e apenas um thread no
processo. Sistemas multitarefas e monothreads como o Windows 3.1 permitem
vários processos em execução e apenas um thread por processo. Sistemas
multitarefas e multithread como o Solaris, OS/2, Linux e Windows 95/98/NT
permitem vários processos em execução e vários threads por processo.
Como os threads em um mesmo processo possuem uma área de dados em
comum, surge a necessidade de controlar o acesso a essa área de dados, de modo
que thread não leia ou altere dados no momento que estão sendo alterados por
outro thread. A inclusão de instruções para controlar o acesso a áreas
compartilhadas torna o código mais complexo do que o código de processos
monothreads.
Uma pergunta pode surgir na mente do leitor: se a inclusão de mais de
um thread torna o código mais complexo porque razão eu deveria projetar
código multithread. Processos com vários threads podem realizar mais de uma

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 5
tarefa simultaneamente. São úteis na criação de processos servidores, criação de
animações e no projeto de interfaces com o usuário que não ficam travadas
durante a execução de alguma função. Por exemplo, imagine um processo
servidor a espera de requisições de serviços, podemos projetá-lo de modo que ao
surgir uma solicitação de um serviço por um processo cliente ele crie um thread
para atender a solicitação enquanto volta a esperar a requisição de novos
serviços. Com isto os processos clientes não precisam esperar o término do
atendimento de alguma solicitação para ter sua requisição atendida.
O mesmo pode ser dito em relação ao projeto de interfaces com o
usuário. O processo pode criar threads para executar as funções solicitadas pelo
usuário, enquanto aguarda novas interações. Caso contrário, a interface ficaria
impedida de receber novas solicitações enquanto processa a solicitação corrente,
o que poderia causar uma sensação de travamento ao usuário.
Outra aplicação para processos multithread é a animação de interfaces.
Nesse caso cria-se um ou mais threads para gerenciar as animações enquanto
outros threads cuidam das outras tarefas como por exemplo entrada de dados.
A rigor todas as aplicações acima como outras aplicações de processos
multithread podem ser executados por meio de processos monothreads. No
entanto, o tempo gasto na mudança de contexto entre processos na maioria dos
sistemas operacionais é muito mais lenta que a simples alternância entre threads,
uma vez que a maior parte das informações contextuais são compartilhadas pelos
threads de um mesmo processo.

Mudança de Contexto (task switch)


É o conjunto de operações necessárias para gravar o estado atual do processo corrente e
recuperar o estado de outro processo de modo a torná-lo o processo corrente.

Mesmo que você não crie mais de um thread todo processo Java possui
vários threads: thread para garbage collection, thread para monitoramento de
eventos, thread para carga de imagens, etc.

Criando threads em Java


Processos Multithread não é uma invenção da linguagem Java. É possível
criar processos multithread com quase todas as linguagens do mercado, como
C++, e Object Pascal. No entanto Java incorporou threads ao núcleo básico da
linguagem tornado desta forma mais natural o seu uso. Na verdade o uso de

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 6
threads está tão intimamente ligado a Java que é quase impossível escrever um
programa útil que não seja multithread.

A classe Thread agrupa os recursos necessários para a criação de um


thread. A forma mais simples de se criar um thread é criar uma classe derivada
da classe Thread. Por exemplo:

class MeuThread extends Thread {


...
}

É preciso também sobrescrever o método run() da classe Thread. O


método run() é o ponto de entrada do thread, da mesma forma que o método
main() é ponto de entrada de uma aplicação. O exemplo IX.1 mostra uma
classe completa.

public class MeuThread extends Thread {


String s;
public MeuThread (String as) {
super();
s = new String(as);
}
public void run() {
for (int i = 0; i < 5; i++)
System.out.println(i+” “+s);
System.out.println("FIM! "+s);
}
}

Exemplo IX.1 – Subclasse da classe Thread.

No exemplo IX.1foi inserido um atributo para identificar o thread, apesar


de existir formas melhores de se nomear um thread como veremos mais adiante.
O método run() contém o código que será executado pelo thread. No exemplo
IX.1 o thread imprime cinco vezes o atributo String. Para iniciar a execução de
um thread cria-se um objeto da classe e invoca-se o método start() do
objeto. O método start() cria o thread e inicia sua execução pelo método
run(). Se o método run() for chamado diretamente nenhum thread novo será
criado e o método run() será executado no thread corrente. O exemplo IX.2
mostra uma forma de se criar um thread usando a classe definida no exemplo
IX.1.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 7
public class TesteThread1 {
public static void main (String[] args) {
new MeuThread("Linha1").start();
}
}

Exemplo IX.2 – Criação de um Thread.

No exemplo acima apenas um thread, além do principal é criado. Nada


impede que sejam criados mais objetos da mesma classe para disparar um
número maior de threads. O exemplo IX.3 mostra a execução de dois threads
sobre dois objetos de uma mesma classe.

public class TesteThread2 {


public static void main (String[] args) {
new MeuThread("Linha1").start();
new MeuThread("Linha2").start();
}
}

Exemplo IX.3 – Criação de dois Threads.

Cada thread é executado sobre uma instância da classe e, por


consequência, sobre uma instância do método run(). A saída gerada pela
execução do exemplo IX.3 depende do sistema operacional subjacente. Uma
saída possível é a seguinte:

0 Linha2
0 Linha1
1 Linha2
1 Linha1
2 Linha2
2 Linha1
3 Linha2
3 Linha1
4 Linha2
4 Linha1
FIM! Linha2
FIM! Linha1

A saída acima mostra que os threads executam intercaladamente. No


entanto, em alguns sistemas operacionais os threads do exemplo IX.3
executariam um após o outro. A relação entre a sequência de execução e o
sistema operacional e dicas de como escrever programas multithread com

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 8
sequência de execução independente de plataforma operacional serão em uma
seção mais adiante neste mesmo capítulo.

Criando threads por meio da interface Runnable

Algumas vezes não é possível criar uma subclasse da classe Thread


porque a classe já deriva outra classe, por exemplo a classe Applet. Outras
vezes, por questões de pureza de projeto o projetista não deseja derivar a classe
Thread simplesmente para poder criar um thread uma vez que isto viola o
significado da relação de classe-subclasse. Para esses casos existe a interface
Runnable. A interface Runnable possui apenas um método para ser
implementado: o método run(). Para criar um thread usando a interface
Runnable é preciso criar um objeto da classe Thread, passando para o
construtor uma instância da classe que implementa a interface. Ao invocar o
método start() do objeto da classe Thread, o thread criado, inicia sua
execução no método run() da instância da classe que implementou a interface.
O exemplo IX.4 mostra a criação de um thread usando a interface Runnable.

public class TesteThread2 implements Runnable


{
private String men;
public static void main(String args[])
{
TesteThread2 ob1 = new TesteThread2 (“ola”);
Thread t1 = new Thread(ob1);
t1.start();
}
public TesteThread2 (String men) {this.men=men;}
public void run()
{
for(;;) System.out.println(men);
}
}

Exemplo IX.4 – Criação de um thread por meio da interface Runnable.

Note que agora ao invocarmos o método start() o thread criado


iniciará a execução sobre o método run() do objeto passado como parâmetro, e
não sobre o método run() do objeto Thread.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 9
Nada impede que seja criado mais de um thread executando sobre o
mesmo objeto:

Thread t1 = new Thread(ob1);


Thread t2 = new Thread(ob1);

Neste caso alguns cuidados devem ser tomados, uma vez que existe o
compartilhamento das variáveis do objeto por dois threads. Os problemas que
podem advir de uma situação como esta serão tratados mais adiante.

A classe Thread

A classe Thread é extensa, possuindo vários construtores, métodos e


variáveis públicas. Aqui mostraremos apenas os mais usados.

Hierarquia

A classe Thread deriva diretamente da classe Object.

java.lang.Object

java.lang.Thread

Construtores
Construtor Descrição
Thread(ThreadGroup g, String nome) Cria um novo thread com o nome
especificado dentro do grupo g.
Thread(Runnable ob, String nome) Cria um novo thread para executar
sobre o objeto ob, com o nome
especificado.
Thread(ThreadGroup g, Runnable ob, Cria um novo thread para executar
String nome) sobre o objeto ob, dentro do grupo
g, com o nome especificado.
Thread(String nome) Cria um novo thread com o nome
especificado.
Thread() Cria um novo thread com o nome
default.
Thread(Runnable ob) Cria um novo thread para executar

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 10
sobre o objeto ob.
Thread(ThreadGroup g, Runnable ob) Cria um novo thread para executar
sobre o objeto ob, dentro do grupo
g.

Tabela IX.1 – Construtores da classe Thread.

A tabela X.1 mostra os principais construtores da classe Thread.


Podemos notar que é possível nomear os threads e agrupá-los. Isto é útil para
obter a referência de threads por meio do seu nome.

Métodos
Método Descrição
currentThread() Retorna uma referência para o thread corrente em
execução.
destroy() Destroi o thread sem liberar os recursos.
dumpStack() Imprime a pilha de chamadas do thread corrente.
enumerate(Thread[] v) Copia para o array todos os thread ativos no
grupo do thread.
getName() Obtém o nome do thread.
getPriority() Obtém a prioridade do thread.
getThreadGroup() Retorna o grupo do thread.
resume() Reassume a execução de um thread previamente
suspenso.
run() Se o thread foi construído usando um objeto
Runnable separado então o método do objeto
Runnable é chamado. Caso contrário nada
ocorre.
setName(String name) Muda o nome do thread.
setPriority(int newPriority) Muda a prioridade do thread.
sleep(long millis) Suspende o thread em execução o número de
milisegundos especificados.
sleep(long millis, int Suspende o thread em execução o número de
nanos) milisegundos mais o número de nanosegundos
especificados.
start() Inicia a execução do thread. A máquina virtual
chama o método run() do thread.
stop() Força o encerramento do thread.
suspend() Suspende a execução de um thread.
yield() Faz com que o thread corrente interrompa
permitindo que outro thread seja executado.

Tabela IX.2 – Métodos da classe Thread.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 11

A tabela X.2 apresenta os principais métodos do classe Thread. Alguns


métodos muito usados nas versões anteriores do SDK1.2 foram depreciados na
versão atual por serem considerados inseguros ou com tendência a causarem
deadlock . Os métodos depreciados são: stop(), suspend(), resume() e
destroy().

Deadlock
Travamento causado pela espera circular de recursos em um conjunto de threads. O
travamento por deadlock mais simples é o abraço mortal onde um thread A espera que um thread
B libere um recurso, enquanto que o thread B só libera o recurso esperado por A se obter um
recurso mantido por A. Desta forma os dois threads são impedidos indefinidamente de
prosseguir.

Existem alguns métodos da classe Object que são importantes para o


controle dos threads. O leitor pode estar se perguntando porque métodos
relacionados threads estão na superclasse Object que é “mãe” de todas as
classe em Java. A razão disso é que esses métodos lidam com um elemento
associado a todo objeto e que é usado para promover o acesso exclusivo aos
objetos. Esse elemento é chamado de monitor. Na seção que aborda a
sincronização os monitores serão discutidos mais detalhadamente. Os métodos
herdados relacionados com controle dos threads estão descritos na tabela IX.3.

Método Descrição
notify() Notifica um thread que está esperando sobre
um objeto.
notifyAll() Notifica todos os threads que está esperando
sobre um objeto.
wait() Espera para ser notificado por outro thread.
wait(long timeout, int nanos) Espera para ser notificado por outro thread.
wait(long timeout) Espera para ser notificado por outro thread.

Tabela IX.3 – Métodos da classe Object relacionados com threads.

Variáveis públicas
As variáveis públicas da classe Thread definem valores máximo, mínimo
e default para a prioridade de execução dos threads. Java estabelece dez valores
de prioridade. Como essas prioridades são relacionadas com as prioridades do

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 12
ambiente operacional depende da implementação máquina virtual e pode
influenciar no resultado final da execução do programa. Mais adiante
abordaremos a influência do ambiente operacional na execução de programas
multithread.

Variável Descrição
static final int MAX_PRIORITY A prioridade máxima que um thread pode ter.
static final int MIN_PRIORITY A prioridade mínima que um thread pode ter.
static final int NORM_PRIORITY A prioridade default associado a um thread.

Tabela IX.4 – Variáveis públicas.

Ciclo de Vida dos Threads


Um thread pode possuir quatro estados conforme mostra a figura IX.3.
Podemos observar que uma vez ativo o thread alterna os estados em execução e
suspenso até que passe para o estado morto. A transição de um estado para
outro pode ser determinada por uma chamada explícita a um método ou devida a
ocorrência de algum evento a nível de ambiente operacional ou de programa.

Estados Ativos

thread em
Execução

thread thread
novo morto
thread
suspenso

Figura IX.3 – Estados de um thread.

A transição de um thread do estado novo para algum estado ativo é


sempre realizada pela invocação do método start() do objeto Thread. Já as
transições do estado em execução para o estado suspenso e vice-versa e desses
para o estado morto podem ser disparadas tanto pela invocação de variados
métodos como pela ocorrência de eventos. O exemplo IX.5 mostra as ocorrência
de transição em um código.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 13

public class TesteThread3 extends Thread


{
public TesteThread3 (String str) {super(str);}
public void run()
{
for (int i = 0; i < 10; i++)
{
System.out.println(i + " " + getName());
try {
// Comando para suspender o thread por
// 1000 milisegundos (1 segundo)
// Transição do estado em execução para o
// estado suspenso
sleep(1000);
} catch (InterruptedException e) {}
// Evento: fim do tempo de suspensão
// Transição do estado em suspenso para o
// estado em execução
}
System.out.println("FIM! " + getName());
// Evento: fim da execução do thread
// Transição do estado ativo suspenso para o
// estado morto
}
public static void main(String args[])
{
TesteThread3 t1 = new TesteThread3(args[0]);
t1.start(); // Transição para um estado ativo
}
}

Exemplo IX.5 – Alguns comandos e eventos que acarretam transição de


estados.

sleep(), yield(), join(), destroy(), stop(), suspend() e


resume().

Agora que vimos os estados que podem ser assumidos por um thread em
seu ciclo de vida vamos examinar mais detalhadamente alguns dos métodos
responsáveis pela mudança de estado de um thread.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 14
sleep()

O método sleep()é um método estático e possui as seguintes


interfaces:

static void sleep(long ms) throws InterruptedException


ou
static void sleep(long ms, int ns) throws InterruptedException

Onde ms é um valor em milisegundos e ns é um valor em nanosegundos.


O método sleep() faz com que o thread seja suspenso por um
determinado tempo, permitindo que outros threads sejam executados. Como o
método pode lançar a exceção InterruptedException, é preciso envolver a
chamada em um bloco try/catch ou propagar a exceção. O exemplo IX.6
define uma espera mínima de 100 milisegundos entre cada volta do loop. Note
que o tempo de suspensão do thread pode ser maior que o especificado, uma vez
que outros threads de maior ou mesmo de igual prioridade podem estar sendo
executados no momento em que expira o tempo de suspensão solicitado.

public class ThreadComYield extends Thread {


String s;
public ThreadComYield(String as) {
super();
s = new String(as);
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(i+” “+s);
try{
Thread.sleep(100);
catch(InterruptedException e){}
}
System.out.println("FIM! "+s);
}
}

Exemplo IX.6 – Uso do método sleep().

Outro problema com o sleep() é que a maioria dos Sistemas


Operacionais não suportam resolução de nanosegundos. Mesmo a resolução a
nível de unidade de milisegundo não é suportada pela maioria dos SOs. No caso
do SO não suportar a resolução de tempo solicitada, o tempo será arredondado
para a nível de resolução suportado pela plataforma operacional.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 15

yield()

O método yield() é um método estático com a seguinte interface:

static void yield()

Uma chamada ao método yield() faz com que o thread corrente libere
automaticamente a CPU para outro thread de mesma prioridade. Se não houver
nenhum outro thread de mesma prioridade aguardando, então o thread corrente
mantém a posse da CPU. O exemplo IX.7 altera o exemplo IX.1 de modo a
permitir que outros threads de mesma prioridade sejam executados a cada volta
do loop.

Public class ThreadComYield extends Thread {


String s;
public ThreadComYield(String as) {
super();
s = new String(as);
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(i+” “+s);
Thread.yield();
}
System.out.println("FIM! "+s);
}
}

Exemplo IX.7 – Uso do método yield().

join()

O método join() é um método de instância da classe Thread e é


utilizado quando existe a necessidade do thread corrente esperar pela término da
execução de outro thread. As versões do método join() são as seguintes:

public final void join();


public final void join(long millisecond);
public final void join(long millisecond, int nanosecond);

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 16
Na primeira versão o thread corrente espera indefinidamente pelo
encerramento da execução do segundo thread. Na segunda e terceira versão o
thread corrente espera pelo término da execução do segundo thread até no
máximo um período de tempo prefixado. O exemplo IX.8 mostra o como usar o
método join().

class ThreadComJoin extends Thread {


String s;
public ThreadComJoin(String as) {
super();
s = new String(as);
}
public void run() {
for (int i = 0; i < 10; i++)
System.out.println(i+” “+s);
System.out.println("Fim do thread!");
}
}

public class TestaJoin


{
public static void main(String args[])
{
ThreadComJoin t1 = new ThreadComJoin(args[0]);
t1.start(); // Transição para um estado ativo
t1.join(); // Espera pelo término do thread
System.out.println("Fim do programa!");
}
}

Exemplo IX.8 – Uso do método join().

stop(), suspend(), resume() e destroy()

A partir da versão 1.2 do SDK os métodos stop(), suspend(), and


resume() tornaram-se deprecated uma vez que a utilização desses métodos
tendia a gerar erros. No entanto, devido a grande quantidade de código que ainda
utiliza estes método, acreditamos que seja importante mencioná-los.
O método stop() é um método de instância que encerra a execução do
thread ao qual pertence. Os recursos alocados ao thread são liberados. É
recomendável substituir o método stop() pelo simples retorno do método
run().

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 17
O método suspend() é um método de instância que suspende a
execução do thread ao qual pertence. Nenhum recurso é liberado, inclusive os
monitores que possuir no momento da suspensão (os monitores serão vistos mais
adiante e servem para controlar o acesso à variáveis compartilhadas). Isto faz
com que o método suspend() tenda a ocasionar deadlocks. O método
resume() é um método de instância que reassume a execução do thread ao qual
pertence. Os métodos suspend() e resume() devem ser substituídos
respectivamente pelos métodos wait() e notify(), como veremos mais
adiante.
O método destroy() é um método de instância que encerra a execução
do thread ao qual pertence. Os recursos alocados ao thread não são liberados.
Não é um método deprecated mas é recomendável substituí-lo pelo simples
retorno do método run().

Daemon Threads
Daemon threads são threads que rodam em background com a função de
prover algum serviço mas não fazem parte do propósito principal do programa.
Quando só existem threads do tipo daemon o programa é encerrado. Um
exemplo de daemon é o thread para coleta de lixo.
Um thread é definido como daemon por meio do método de instância
setDaemon(). Para verificar se um thread é um daemon é usado o método de
instância isDaemon(). O exemplo IX.9 mostra o como usar esses métodos.

import java.io.*;
class ThreadDaemon extends Thread
{
public ThreadDaemon()
{
setDaemon(true);
start();
}
public void run()
{
for(;;) yield();
}
}

public class TestaDaemon


{

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 18
public static void main(String[] args)
{
Thread d = new ThreadDaemon();
System.out.println("d.isDaemon() = " +
d.isDaemon());
BufferedReader stdin = new BufferedReader(
new InputStreamReader(System.in));
System.out.println("Digite qualquer coisa");
try
{
stdin.readLine();
} catch(IOException e) {}
}
}

Exemplo IX.9 – Uso dos métodos relacionados com daemons.

No exemplo IX.9 o método main() da classe TestaDaemon cria um


objeto da classe ThreadDaemon. O construtor da classe ThreadDaemon
define o thread como daemon por meio do método setDaemon() e inicia a
execução do thread. Como é apenas um thread de demonstração o método run()
da classe ThreadDaemon não faz nada, apenas liberando a posse da CPU toda
vez que a adquire. Após a criação da instância da classe ThreadDaemon no
método main() é testado se o thread criado é um daemon, utilizando para esse
fim o método isDaemon(). Depois disso o programa simplesmente espera o
usuário pressionar a tecla <enter>. O programa termina logo em seguida ao
acionamento da tecla, mostrando dessa forma que o programa permanece ativo
apenas enquanto existem threads não daemons ativos.

Influência do Sistema Operacional no Comportamento


dos Threads
Apesar da linguagem Java prometer a construção de programas
independentes de plataforma operacional, o comportamento dos threads pode ser
fortemente influenciado pelo sistema operacional subjacente. Portanto, o
programador deve tomar alguns cuidados se deseja construir programas que
funcionem da mesma forma, independente do ambiente onde está sendo
executado.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 19
Alguns sistemas operacionais não oferecem suporte a execução de
threads. Neste caso, cada processo possui apenas um thread. Mesmo em sistemas
operacionais que oferecem suporte a execução de múltiplos threads por processo
o projetista da máquina virtual pode optar por não usar o suporte nativo a
threads. Deste modo, é responsabilidade da máquina virtual criar um ambiente
multithread. Threads implementados desta forma, a nível de usuário, são
chamados de green-threads.
As influências da plataforma operacional podem agrupadas em dois
tipos:

1) Forma de escalonamento de threads. O ambiente pode adotar um


escalonamento não preemptivo ou preemptivo. No escalonamento
não preemptivo (também chamado de cooperativo) um thread em
execução só perde o controle da CPU (Central Processing Unit) se a
liberar voluntariamente ou se necessitar de algum recurso que ainda
não está disponível. Já no escalonamento preemptivo, além das
formas acima um thread pode perde o controle da CPU por eventos
externos, como o fim do tempo máximo definido pelo ambiente para
a execução contínua de um thread (fatia de tempo) ou porque um
thread de mais alta prioridade está pronto para ser executado.
Exemplos de sistemas operacionais não preemptivos são Windows
3.1 e IBM OS/2. Exemplos de sistemas operacionais preemptivos são
Windows 95/98/NT e Linux, QNX, e muitos outros. Alguns sistemas
operacionais adotam uma abordagem híbrida, suportando tanto o
modelo cooperativo como o preemptivo, como o Solaris da Sun.

2) Relacionamento entre os níveis de prioridades definidas na


linguagem Java e os níveis de prioridades definidas nos Sistemas
Operacionais. Em um SO preemptivo um thread de uma
determinada prioridade perde a posse da CPU para um thread de
prioridade mais alta que esteja pronto para ser executado. A
linguagem Java prevê dez níveis de prioridades que podem ser
atribuídas aos threads. No entanto, cada SO possui um número de
prioridades diferente e o mapeamento das prioridades da linguagem
Java para as prioridades do SO subjacente pode influenciar o
comportamento do programa.

Forma de escalonamento de threads

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 20
A especificação da máquina virtual Java determina que a forma de
escalonamento de threads seja preemptiva. Portanto, mesmo em ambiente
operacionais cooperativos a máquina virtual deve garantir um escalonamento
preemptivo. No entanto, um escalonamento preemptivo não obriga a preempção
por fim de fatia de tempo. Podemos ter um escalonamento preemptivo onde um
thread de mais alta prioridade interrompe o thread que tem a posse da CPU mas
não existe preempção por fim de fatia de tempo. Um escalonamento onde
threads de mesma prioridade intercalam a posse da CPU por força do fim da
fatia de tempo é chamado de escalonamento Round-Robin. A especificação da
máquina virtual Java não prevê o escalonamento Round-Robin, mas também não
o descarta, abrindo uma possibilidade de implementações distintas de máquina
virtual e introduzindo o não determinismo na execução de programas
multithread. Por exemplo, o exemplo IX.3 poderia ter uma saída distinta da
apresentada anteriormente caso seja executado por uma máquina virtual que não
implementa o escalonamento Round-Robin. Nesse caso a saída seria a seguinte:

0 Linha2
1 Linha2
2 Linha2
3 Linha2
4 Linha2
FIM! Linha2
0 Linha1
1 Linha1
2 Linha1
3 Linha1
4 Linha1
FIM! Linha1

Neste caso, se o programador deseja que a execução de threads se


processe de forma alternada, independentemente da implementação da máquina
virtual, então é necessário que ele insira código para a liberação voluntária da
CPU. Isso pode ser feito com o método yield() ou com o método sleep().

Relacionamento entre os níveis de prioridades definidas na


linguagem Java e os níveis de prioridades definidas nos Sistemas
Operacionais.
Como já dissemos a linguagem Java prevê dez níveis de prioridades que
podem ser atribuídas aos threads. Na verdade são onze prioridades, mas a
prioridade nível 0 é reservada para threads internos. As prioridades atribuídas

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 21
aos threads são estáticas, ou seja não se alteram ao longo da vida do thread, a
não ser que por meio de chamadas a métodos definidos para esse propósito. A
classe thread possui variáveis públicas finais com valores de prioridade
predefinidos, como mostrado na tabela IX.4. No entanto, os sistemas
operacionais podem possuir um número maior ou menor de níveis de
prioridades. Vamos citar um exemplo: o MSWindows 95/98/NT. Este sistema
possui apenas sete níveis de prioridades e estes sete níveis devem ser mapeados
para os onze níveis de prioridades especificados em Java. Cada máquina virtual
fará este mapeamento de modo diferente, porém a implementação comum é
mostrada na tabela IX.5.

Prioridades Java Prioridades MSWindows


0 THREAD_PRIORITY_IDLE
1(Thread.MIN_PRIORITY) THREAD_PRIORITY_LOWEST
2 THREAD_PRIORITY_LOWEST
3 THREAD_PRIORITY_BELOW_NORMAL
4 THREAD_PRIORITY_BELOW_NORMAL
5(Thread.NORM_PRIORITY) THREAD_PRIORITY_NORMAL
6 THREAD_PRIORITY_ABOVE_NORMAL
7 THREAD_PRIORITY_ABOVE_NORMAL
8 THREAD_PRIORITY_HIGHEST
9 THREAD_PRIORITY_HIGHEST
10(Thread.MAX_PRIORITY) THREAD_PRIORITY_TIME_CRITICAL

Tabela IX.5 –Mapeamento das prioridades de Java para MSWindows.

Note que nesta implementação níveis de prioridades diferentes em Java


serão mapeados para um mesmo nível de prioridade em MSWindows. Isto pode
levar a resultados inesperados caso o programador projete uma aplicação
esperando, por exemplo, que um thread de prioridade 4 irá interromper um
thread de prioridade 3. Para evitar este tipo de problema o programador pode
adotar dois tipos de abordagem:

1) utilizar, se for possível, apenas as prioridades Thread.MIN_PRIORITY,


Thread.NORM_PRIORITY e Thread.MAX_PRIORITY para atribuir prioridades
aos threads; ou

2) não se basear em níveis de prioridades para definir o escalonamento de


threads, utilizando, alternativamente, primitivas de sincronização que serão
abordadas na próxima seção.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 22

Compartilhamento de Memória e Sincronização


Como já foi dito, mais de um thread pode ser criado sobre um mesmo
objeto. Neste caso cuidado especiais devem ser tomados, uma vez que os threads
compartilham as mesmas variáveis e problemas podem surgir se um thread está
atualizando uma variável enquanto outro thread está lendo ou atualizando a
mesma variável. Este problema pode ocorrer mesmo em threads que executam
sobre objetos distintos, já que os objetos podem possuir referências para um
mesmo objeto. O exemplo IX.8 mostra a execução de dois threads sobre um
mesmo objeto. O nome do thread é usado para que o thread decida que ação
tomar. O thread de nome “um” ontem um número de 0 a 1000 gerado
aleatoriamente e o coloca na posição inicial de um array de dez posições. As
outras posições do array são preenchidas com os nove números inteiros
seguintes ao número inicial. O thread de nome “dois” imprime o conteúdo do
vetor. Obviamente o programa é apenas ilustrativo, não possuindo aplicação
prática. A intenção inicial do projetista é obter na tela sequências de dez
números inteiros consecutivos iniciados aleatoriamente. No entanto, como os
dois threads compartilham o mesmo objeto e não existe qualquer sincronismo
entre sí, é pouco provável que o projetista obtenha o resultado esperado.

public class CalcDez implements Runnable


{
private int vetInt[];

public CalcDez () {vetInt=new int[10]; }


public void run()
{
if (Thread.currentThread().getName().equals(”um”))
for (;;)
{
vetInt[0] = (int)(Math.random() * 1000);
for (int i=1;i<10;i++) vetInt[i]= vetInt[0]+i;
}
else
for (;;)
{
System.out.println(“Serie iniciada por”+
vetInt[0]);
for (int i=1;i<10;i++)
System.out.println(vetInt[i]+ “ “);
}

}
public static void main(String args[])
{

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 23
CalcDez ob = new CalcDez();
Thread t1 = new Thread(ob,”um”);
Thread t2 = new Thread(ob,”dois”);
t1.start();
t2.start();
}
}

Exemplo IX.8 – Dois threads executando sobre o mesmo objeto.

Se a máquina virtual não implementar um escalonamento Round-Robin


apenas um thread será executado, visto que os dois threads possuem a mesma
prioridade.
Já no caso da máquina virtual implementar um escalonamento Round-
Robin a alternância da execução dos threads produzirá resultados imprevisíveis.
Um trecho de uma das saídas possíveis pode ser visto na figura IX.4. Ele foi
obtido em Pentium 100MHz executando a máquina virtual da Sun, versão 1.2,
sob o sistema operacional MSWindows 95.

258
259
Serie iniciada por573
574
575
576
577
578
579
580
581
582
Serie iniciada por80
81
82
Figura IX.4 – Saída do exemplo IX.8.

Podemos notar as sequências estão misturadas, mostrando que cada


thread interrompe o outro no meio da execução da tarefa especificada. O mesmo
problema pode mesmo em threads que executam sobre objetos diferentes,
bastando que cada thread possua referência para um mesmo objeto. O exemplo
IX.9 mostra a execução de dois threads sobre objetos distintos.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 24

class Compartilhada
{
private int vetInt[];
public Compartilhada() {vetInt=new int[10];}
public void setVal()
{
for (;;)
{
vetInt[0] = (int)(Math.random() * 1000);
for (int i=1;i<10;i++) vetInt[i]= vetInt[0]+i;
}
}
public int getVal(int i) {return vetInt [i];}
}

public class CalcDez2 extends Thread


{
private Compartilhada obj;
private int tipo;

public CalcDez2 (Compartilhada aObj, int aTipo)


{ obj = aObj; tipo = aTipo;}
public void run()
{
for (;;)
if (tipo==1) obj.setVal();
else
{
System.out.println(“Serie iniciada por”+
obj.getVal(0));
for (int i=1;i<10;i++)
System.out.println(obj.getVal(i)+ “ “);
}

}
public static void main(String args[])
{
Compartilhada obj = new Compartilhada();
CalcDez2 t1 = new CalcDez2(obj,1);
CalcDez2 t2 = new CalcDez2(obj,2);
t1.start();
t2.start();
}
}

Exemplo IX.9 – Dois threads executando sobre objetos distintos.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 25

É importante que o leitor não confunda o exemplo IX.9 com o exemplo


IX.8 achando que nos dois exemplos os dois threads executam sobre o mesmo
objeto, uma vez que a etapa da criação dos threads é bem parecida. No entanto,
no exemplo IX.9 foi declarada uma subclasse da classe Thread e não uma
classe que implementa a interface Runnable. Apesar de parecer que no
exemplo IX.9 ambos os threads executaram sobre um mesmo objeto da classe
Compartilhada que é passado como argumento, na verdade cada thread
executará sobre sua própria instância da classe CalcDez2, sendo que o objeto
da classe Compartilhada é referenciado pelos dois threads. O
comportamento do código do exemplo IX.9 é semelhante ao do exemplo IX.8,
com a diferença que no primeiro a sequência de inteiros é encapsulado pelo
objeto da classe Compartilhada.
Este tipo de situação, onde o resultado de uma computação depende da
forma como os threads são escalonados, é chamada de condições de corrida
(Race Conditions). É um problema a ser evitado uma vez que o programa passa
a ter um comportamento não determinístico.

Atomicidade de Instruções e Sincronização do Acesso à Sessões


Críticas
A condição de corrida ocorre porque os acesso à áreas de memória
compartilhada não é feita de forma atômica, e nem de forma exclusiva. Por
forma atômica queremos dizer que o acesso é feito por meio de várias instruções
e pode ser interrompido por outro thread antes que toda as instruções que
compõem o acesso sejam executadas. Por forma exclusiva queremos dizer que
um thread podem consultar/atualizar um objeto durante a consulta/atualização do
mesmo objeto por outros threads. Poucas operações são atômicas em Java. Em
geral, as atribuições simples, com exceção dos tipos long e double, são
atômicas, de forma que o programador não precisa se preocupar em ser
interrompido no meio de uma operação de atribuição. No entanto, no caso de
operações mais complexas sobre variáveis compartilhadas é preciso que o
programador garanta o acesso exclusivo a essas variáveis. Os trechos de código
onde é feito o acesso às variáveis compartilhadas são chamados de Seções
Críticas ou Regiões Críticas.
Uma vez determinada uma região crítica como garantir o acesso
exclusivo? A linguagem Java permite que o programador garanta o acesso
exclusivo por meio utilizando o conceito de monitor. O conceito de monitor foi
proposto por C. A. R. Hoare em 1974 e pode ser encarado como um objeto que

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 26
garante a exclusão mútua na execução dos procedimentos a ele associados. Ou
seja, apenas um procedimento associado ao monitor pode ser executado em um
determinado momento. Por exemplo, suponha que dois procedimentos A e B
estão associados a um monitor. Se no momento da invocação do procedimento A
algum o procedimento B estiver sendo executando o processo ou thread que
invocou o procedimento A fica suspenso até o término da execução do
procedimento B. Ao término do procedimento B o processo que invocou o
procedimento A é “acordado” e sua execução retomada.
O uso de monitores em Java é uma variação do proposto por Hoare. na
linguagem Java todo objeto possui um monitor associado. Para facilitar o
entendimento podemos encarar o monitor como um detentor de um “passe”.
Todo thread pode pedir “emprestado” o passe ao monitor de um objeto antes de
realizar alguma computação. Como o monitor possui apenas um passe, apenas
um thread pode adquirir o passe em um determinado instante. O passe tem que
ser devolvido para o monitor para possibilitar o empréstimo do passe a outro
thread. A figura IX.5 ilustra essa analogia.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 27

Monitor de x Monitor de x
passe !

Objeto x Objeto x

thread t1 !
thread t2

Instante 1: o thread t1 solicita Instante 2: o thread t2 solicita


o passe ao monitor o passe ao monitor
do objeto x. do objeto x e é
bloqueado.

Monitor de x Monitor de x
!

Objeto x Objeto x

Instante 3: o thread t1 libera Instante 4: o thread t2 recebe


o passe. o passe do monitor
do objeto x.

Figura IX.5 – Uma possível sequência na disputa de dois threads pela


autorização de um monitor.

Nos resta saber como solicitar o passe ao monitor. Isto é feito por meio
da palavra chave synchronized. Existem duas formas de se usar a palavra
chave synchronized: na declaração de métodos e no início de blocos. O
exemplo IX.10 mostra duas versões da classe FilaCirc que implementa uma
fila circular de valores inteiros: uma com métodos synchronized e outra
com blocos synchronized. Um objeto desta classe pode ser compartilhado
por dois ou mais threads para implementar o exemplo clássico de concorrência
do tipo produtor/consumidor.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 28

a) Versão com métodos b) Versão com blocos


synchronized synchronized
class FilaCirc class FilaCirc
{ {
private final int TAM = 10; private final int TAM = 10;
private int vetInt[]; private int vetInt[];
private int inicio, total; private int inicio, total;

public FilaCirc() public FilaCirc()


{ {
vetInt=new int[TAM]; vetInt=new int[TAM];
inicio=0; inicio=0;
total =0; total =0;
} }
public synchronized void public void addElement(int v)
addElement(int v) throws Exception throws Exception
{ {
if (total == TAM) throw new synchronized(this) {
Exception("Fila cheia!"); if (total == TAM) throw new
vetInt[(inicio+total)%TAM] = v; Exception("Fila cheia!");
total++; vetInt[(inicio+total)%TAM] = v;
} total++;
public synchronized int getElement() }
throws Exception }
{ public int getElement()
if (total == 0 ) throw new throws Exception
Exception("Fila vazia!"); {
int temp = vetInt[inicio]; synchronized(this) {
inicio = (++inicio)%TAM; if (total == 0 ) throw new
total--; Exception("Fila vazia!");
return temp; int temp = vetInt[inicio];
} inicio = (++inicio)%TAM;
} total--;
}
return temp;
}
}

Exemplo IX.10 – Duas versões de uma classe que implementa uma fila circular
de inteiros.

A palavra chave synchronized na frente dos métodos de instância


significa que o método será executado se puder adquirir o monitor do objeto a
quem pertence o método1. Caso contrário o thread que invocou o método será
suspenso até que possa adquirir o monitor. Este forma de sincronização é
abordada no exemplo IX.10.a. Portanto, se algum thread chamar algum método
de um objeto da classe FilaCirc nenhum outro thread que compartilha o
mesmo objeto poderá executar um método do objeto até que o método chamado
1
Não usaremos mais a analogia com a aquisição do passe do monitor. Ela foi usada apenas para
facilitar o entendimento do leitor. Quando se trata de monitores os termos mais usados são:
“adquirir o monitor” e “liberar o monitor”.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 29
pelo primeiro thread termine. Caso outro thread invoque um método do mesmo
objeto ficará bloqueado até que possa adquirir o monitor.
O leitor pode estar se perguntando sobre a necessidade de sincronizar os
métodos da classe FilaCirc uma vez que ocorrem apenas atribuições simples
a elementos individuais de um vetor e as atribuições de inteiros são atômicas. De
fato o problema ocorre não na atribuição dos elementos e sim na indexação do
array. Por exemplo, a instrução

inicio = (++inicio)%TAM;

do método getElement() não é atômica. Suponha que a os métodos da


classe FilaCirc não são sincronizados e que as variáveis inicio e total
possuem os valores 9 e 1 respectivamente. Suponha também que thread invocou
o método getElement() e foi interrompido na linha de código mostrada
acima após o incremento da variável inicio mas antes da conclusão da linha
de código. Nesse caso o valor de inicio é 10. Se neste instante outro thread
executar o método getElement() do mesmo objeto ocorrerá uma exceção
IndexOutOfBoundsException ao atingir a linha de código

int temp = vetInt[inicio];

Se alterarmos a linha de código para

inicio = (inicio+1)%TAM;

evitaremos a exceção, mas não evitaremos o problema de retornar mais de uma


vez o mesmo elemento. Por exemplo, se um thread for interrompido no mesmo
local do caso anterior, outro thread pode obter o mesmo elemento, uma vez que
os valores de inicio e total não foram alterados. Na verdade o número de
situações problemáticas, mesmo para esse exemplo pequeno, é enorme e
perderíamos muito tempo se tentássemos descreve-las em sua totalidade.
Em alguns casos pode ser indesejável sincronizar todo um método, ou
pode-se desejar adquirir o monitor de outro objeto, diferente daquele a quem
pertence o método. Isto pode ser feito usando a palavra chave synchronized
na frente de blocos. Este forma de sincronização é mostrada no exemplo
IX.10.b. Neste modo de usar a palavra-chave synchronized é necessário
indicar o objeto do qual tentara-se adquirir o monitor. Caso o monitor seja
adquirido o bloco é executado, caso contrário o thread é suspenso até que possa
adquirir o monitor. O monitor é liberado no final do bloco.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 30
No exemplo IX.10.b o monitor usado na sincronização é o do próprio
objeto do método, indicado pela palavra chave this. Qualquer outro objeto
referenciável no contexto poderia ser usado. O que importa que os grupos de
threads que possuem áreas de código que necessitam de exclusão mútua usem o
mesmo objeto.
No exemplo IX.10 não existe vantagem da forma de implementação a)
sobre a forma de implementação b) ou vice-versa. Isso ocorre principalmente
quando os métodos são muito pequenos ou não realizam computações muito
complexas. No entanto, se o método for muito longo ou levar muito tempo para
ser executado, sincronizar todo o método pode “travar” em demasia a execução
da aplicação. Nesses casos, a sincronização somente das seções críticas é mais
indicada. Outra vantagem da segunda forma de sincronização é a liberdade no
uso de monitores qualquer objeto referenciável. Isto permite a implementação
sincronizações mais complexas como veremos mais adiante.
O exemplo IX.11 mostra como pode ser usado um objeto da classe
FilaCirc.

public class TestaFilaCirc extends Thread {


private FilaCirc obj;
private int tipo;

public TestaFilaCirc (FilaCirc aObj, int aTipo)


{ obj = aObj; tipo = aTipo;}
public void run() {
for (;;)
try {
if (tipo==1){
int i = (int)(Math.random() * 1000);
System.out.println("Elemento gerado:"+i);
obj.addElement(i);
}
else System.out.println("Elemento obtido:"+obj.getElement());
} catch(Exception e) {System.out.println(e.getMessage());}
}
public static void main(String args[]) {
FilaCirc obj = new FilaCirc();
TestaFilaCirc t1 = new TestaFilaCirc(obj,1);
TestaFilaCirc t2 = new TestaFilaCirc(obj,2);
t1.start();
t2.start();
}
}

Exemplo IX.11 – Uso da fila circular de inteiros.

Um trecho possível da saída obtida na execução do programa do exemplo


IX.11 seria o seguinte:
...
Elemento obtido:154

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 31
Elemento gerado:725
Fila vazia!
Elemento gerado:801
Elemento obtido:725
Elemento gerado:204
Elemento obtido:801
...

É importante observar que o monitor em Java por si só não implementa a


exclusão mútua. Ele é apenas um recurso que pode ser usado pelo programador
para implementar o acesso exclusivo à variáveis compartilhadas. Cabe ao
programador a responsabilidade pela uso adequado deste recurso. Por exemplo
se o programador esquecer de sincronizar um bloco ou método que necessita de
exclusão mútua, de nada adianta ter sincronizado os outros métodos ou blocos.
O thread que executar o trecho não sincronizado não tentará adquirir o monitor,
e portanto de nada adianta os outros threads terem o adquirido.
Outro ponto que é importante chamar a atenção é ter o cuidado de usar a
palavra chave synchronized com muito cuidado. A sincronização custa
muito caro em se tratando de ciclos de CPU. A chamada de um método
sincronizado é por volta de 10 vezes mais lenta do que a chamada de um método
não sincronizado. Por essa razão use sempre a seguinte regra: não sincronize o
que não for preciso.

Comunicação entre Threads: wait() e notify()


O exemplo IX.10 não é um modelo de uma boa implementação de
programa. O thread que adiciona elementos à fila tenta adicionar um elemento à
cada volta do laço de iteração mesmo que a fila esteja cheia. Por outro lado, o
thread que retira os elementos da fila tenta obter um elemento a cada volta do
laço de iteração mesmo que a fila esteja vazia. Isto é um desperdício de tempo
de processador e pode tornar o programa bastante ineficiente.
Alguém poderia pensar em uma solução onde o thread testaria se a
condição desejada para o processamento ocorre. Caso a condição não ocorra o
thread poderia executar o método sleep() para ficar suspenso por algum
tempo para depois testar novamente a condição. O thread procederia desta forma
até que a condição fosse satisfeita. Este tipo de procedimento economizaria
alguns ciclos de CPU, evitando que a tentativa incessante de executar o
procedimento mesmo quando não há condições. O nome desta forma de ação,

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 32
onde o procedimento a cada intervalo de tempo pré-determinado testa se uma
condição é satisfeita é chamado de espera ocupada (pooling ou busy wait).
No entanto, existem alguns problemas com este tipo de abordagem.
Primeiramente, apesar da economia de ciclos de CPU ainda existe a
possibilidade de ineficiência, principalmente se o tempo não for bem ajustado.
Se o tempo for muito curto ocorrerá vários testes inúteis. Se for muito longo, o
thread ficará suspenso além do tempo necessário. Porém, mais grave que isto é
que o método sleep() faz com que o thread libere o monitor. Portanto, se o
trecho de código for uma região sincronizada, como é o caso do exemplo IX.10,
de nada adiantará o thread ser suspenso. O thread que é capaz de realizar a
computação que satisfaz a condição esperada pelo primeiro thread ficará
impedido de entrar na região crítica, ocorrendo assim um deadlock: o thread que
detém o monitor espera que a condição seja satisfeita e o thread que pode
satisfazer a condição não pode prossegui porque não pode adquirir o monitor.
O que precisamos é um tipo de comunicação entre threads que
comunique que certas condições foram satisfeitas. Além disso, é preciso que, ao
esperar por determinada condição, o thread libere o monitor. Esta forma de
interação entre threads é obtido em Java com o uso dos métodos de instância
wait(), notify() e notifyAll(). Como vimos anteriormente, esses
métodos pertencem à classe Object e não à classe Thread. Isto ocorre porque
esses métodos atuam sobre os monitores, que são objetos relacionados a cada
instância de uma classe Java e não sobre os threads.
Ao invocar o método wait() de um objeto o thread é suspenso e
inserido em uma fila do monitor do objeto, permanecendo na fila até receber
uma notificação. Cada monitor possui sua própria fila. Ao invocar o método
notify() de um objeto, um thread que está na fila do monitor do objeto é
notificado. Ao invocar o método notifyAll() de um objeto, todos os threads
que estão na fila do monitor do objeto são notificados.
A única exigência é que esses métodos sejam invocados em um thread
que detenham a posse do monitor do objeto a que pertencem. Essa exigência faz
sentido uma vez que eles sinalizam a threads que esperam na fila desses
monitores. Devido a essa exigência a invocação desses métodos ocorre em
métodos ou blocos sincronizados. O exemplo IX.12 mostra as formas mais
comuns de chamadas desses métodos.
Note que o thread deve possuir o monitor do objeto ao qual pertence o
método. Por isso, nos exemplo IX.12 b e c, o objeto sincronizado no bloco é o
mesmo que invoca os métodos notify() e notifyAll().

a) b)
class X class Y

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 33
{ {
... X ob;
public synchronized int ma() ...
{ public int mb()
... {
// Espera uma condição ...
while(!cond) wait(); synchronized (ob)
// Prossegue com a {
// condição satisfeita // Notifica algum thread
... ob.notify();
} ...
... }
} ...
}

c)
class Z
{
X ob;
...
public int mc()
{
...
synchronized (ob)
{
// Notifica todos os threads que esperam na fila
// do monitor de ob
ob.notifyAll();
...
}
...
}

Exemplo IX.12 – Exemplos de chamadas dos métodos wait(), notify() e


notifyAll().

Outra observação importante é que o thread que invoca o método


wait() o faz dentro de um laço sobre a condição de espera. Isto ocorre porque
apesar de ter sido notificado isto não assegura que a condição está satisfeita. O
thread pode ter sido notificado por outra razão ou entre a notificação e a
retomada da execução do thread a condição pode ter sido novamente alterada.
Uma vez notificado o thread não retoma imediatamente a execução. É
preciso primeiro retomar a posse do monitor que no momento da notificação
pertence ao thread que notificou. Mesmo após a liberação do monitor nada

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 34
garante que o thread notificado ganhe a posse do monitor. Outros threads podem
ter solicitado a posse do monitor e terem preferência na sua obtenção.
O exemplo IX.12 mostra apenas um esquema para uso dos métodos para
notificação. O exemplo IX.13 é uma versão do exemplo IX.10a que usa os
métodos de notificação para evitar problemas como a espera ocupada. O
exemplo IX.11 pode ser usado sem modificações para testar essa versão.

class FilaCirc
{
private final int TAM = 10;
private int vetInt[];
private int inicio, total;

public FilaCirc()
{
vetInt=new int[TAM];
inicio=0;
total =0;
}
public synchronized void addElement(int v) throws Exception
{
while (total == TAM) wait();
vetInt[(inicio+total)%TAM] = v;
total++;
notify();
}
public synchronized int getElement() throws Exception
{
while (total == 0 ) wait();
int temp = vetInt[inicio];
inicio = (++inicio)%TAM;
total--;
notify();
return temp;
}
}

Exemplo IX.13 – Classe que implementa uma fila circular de inteiros com
notificação.

A necessidade de se testar a condição em loop pode ser observada na


figura IX.6 que mostra a evolução da execução de três threads sobre objetos que
compartilham uma instância da classe FilaCirc. O thread 3 executa o método
addElement(), no entanto, em virtude da condição total==TAM é
obrigado a invocar o método wait() e esperar uma notificação. O próximo

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 35
thread a assumir a CPU é o thread 1 que executa o método getElement(), que
estabelece a condição total<TAM e executa um notify(). No entanto, o
próximo thread a assumir a CPU é o thread 2 e não o thread 3. O thread 2
executa o método addElement(), o qual estabelece novamente a condição
total==TAM. Quando o thread 3 assumi novamente a CPU, uma vez que foi
notificado, testa a condição e invoca novamente o método wait() para esperar
a condição favorável à execução. Caso não testasse a condição em um loop o
thread 3 tentaria inserir um elemento em uma fila cheia.
O método notify() não indica que evento ocorreu. No caso do
exemplo IX.13 existem dois tipos de eventos (a fila não está cheia e a fila não
está vazia), no entanto, podemos observar que não existe a possibilidade de um
thread ser notificado em decorrência de um evento diferente do que está
aguardando.

Método: addElement()
condição: total == TAM
thread 3

thread 2
Método: addElement()
condição: total < TAM

thread 1
Método: getElement()

Tempo
Executando Esperando CPU Esperando notificação

Figura IX.6 – Uma possível sequência na execução de três threads.

Porém, existem alguns casos mais complexos onde podem existir vários
threads aguardando em um mesmo monitor mas esperando por evento diferentes.
Neste caso podemos usar o notifyAll() para notificar todos os threads que
esperam em um único monitor que um evento ocorreu. Cada thread, a medida
que fosse escalado, testaria se ocorreu condição para a execução e em caso
positivo prosseguiria na execução e em caso contrário voltaria a aguardar no
monitor.
O exemplo IX.14 mostra o código de um gerenciador de mensagens. Ele
é responsável por receber mensagens destinadas à vários threads. As mensagens

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 36
de cada thread são colocadas em uma fila implementada por um objeto da classe
Vector. Cada fila é por sua vez colocada em uma tabela hash onde a chave é
um nome associado ao thread a que as mensagens se destinam. As filas são
criadas na primeira tentativa de acesso, tanto na leitura quanto no
armazenamento. Não existe bloqueio devido à fila cheia, uma vez que as filas
são implementadas por objetos da classe Vector que crescem conforme a
necessidade. Portanto, o único evento que necessita ser notificado é a chegada de
alguma mensagem. Como todos os threads aguardam sobre o mesmo monitor é
usado o método notifyAll() para notificar todos os threads.

import java.util.*;

class GerenteMen {
private Hashtable tamMen;

public GerenteMen() {tamMen=new Hashtable(); }

// Método para adicionar uma mensagem à fila de


// um destinatário
public synchronized void addMen(String dest, String men){
if (dest==null || men==null) return;
Vector listaMen = (Vector) tamMen.get(dest);
if (listaMen==null) listaMen = new Vector();
listaMen.addElement(men);
tamMen.put(dest, listaMen);
notifyAll();
}

// Método para obtenção da mensagem


public synchronized String getMen(String dest)
throws Exception
{
if (dest==null) return null;
Vector listaMen = (Vector) tamMen.get(dest);

// Se não existe a fila para esse thread cria uma vazia


if (listaMen==null)
{
listaMen = new Vector();
tamMen.put(dest, listaMen);
}
// A fila está vazia, portanto thread deve esperar
// a chegada de mensagens
while(listaMen.size()==0) wait();
String temp = (String) listaMen.firstElement();

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 37
// A mensagem é removida da fila
listaMen.removeElementAt(0);
return temp;
}
}

Exemplo IX.14 – Gerenciador de mensagens.

O exemplo IX.15 mostra como pode ser usado o gerente de filas do


exemplo IX.14. Devido o uso da classe ThreadGroup assim como vários de
seus métodos, resolvemos numerar as linhas de código do exemplo IX.15 para
melhor podermos explicar o seu funcionamento.
1 class Receptor extends Thread
2 {
3 private GerenteMen ger;
4 public Receptor(ThreadGroup tg, String nome, GerenteMen aGer)
5 {
6 super(tg,nome);
7 ger = aGer;
8 }
9 public void run() {
10 String nome = Thread.currentThread().getName();
11 for (;;)
12 try {
13 String men = ger.getMen(nome);
14 if (men.equals("fim")) return;
15 System.out.println(nome+">Mensagem recebida:"+men);
16 } catch(Exception e) {System.out.println(e.getMessage());}
17 }
18 }
19
20 class Gerador extends Thread
21 {
22 private GerenteMen ger;
23 public Gerador(ThreadGroup tg, String nome, GerenteMen aGer)
24 {
25 super(tg,nome);
26 ger = aGer;
27 }
28 public void run() {
29 String nome = Thread.currentThread().getName();
30 ThreadGroup tg = Thread.currentThread().getThreadGroup();
31 Thread[] tl=null;
32 for (int i=0;i<100;i++)
33 {
34 if (tl==null || tl.length!=tg.activeCount())
35 tl= new Thread[tg.activeCount()];
36 tg.enumerate(tl);
37 int n = (int)(Math.random() * 1000)%tl.length;
38 if (tl[n]!= Thread.currentThread())
39 {
40 System.out.println(nome+">Mensagem enviada para "+
41 tl[n].getName()+":mensagem "+i);
42 ger.addMen(tl[n].getName(),"mensagem "+i);
43 }

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 38
44 }
45 tl= new Thread[tg.activeCount()];
46 tg.enumerate(tl);
47 for (int i=0;i<tl.length;i++)
48 if (tl[i]!= Thread.currentThread())
49 ger.addMen(tl[i].getName(),"fim");
50 }
51 }
52
53 public class TestaGerenteMen {
54 public static void main(String args[])throws Exception {
55 GerenteMen ger = new GerenteMen();
56 ThreadGroup tg = new ThreadGroup("tg");
57 Receptor r1 = new Receptor(tg,"r_um",ger);
58 Receptor r2 = new Receptor(tg,"r_dois",ger);
59 Gerador g = new Gerador(tg,"g",ger);
60 r1.start();
61 r2.start();
62 g.start();
63 }
64 }

Exemplo IX.15 – Uso do gerenciador de filas.

Um objeto da classe ThreadGroup agrupa um conjunto de threads.


Um ThreadGroup pode possuir como membros outros objetos da
ThreadGroup formando assim uma árvore onde todos os grupos, exceto o
primeiro possui um grupo pai. O objetivo de se agrupar os threads em conjuntos
é facilitar a sua manipulação. No caso do exemplo IX.15 usaremos esse
agrupamento para poder acessar cada thread.
As linhas 1 a 18 definem a classe que será usada para criação de objetos
receptores de mensagens. Na linha 3 é declarada a variável que irá referenciar
um objeto do tipo GerenteMen. As linhas 4 a 8 contém o código do único
construtor da classe. Ele recebe uma referência para o grupo de threads ao qual
deve se associar, o nome que deve ser atribuído ao thread e a referência ao
gerente de filas. Na linha 6 os primeiros dois parâmetros são passados ao
construtor da superclasse. Na linha 7 a referência ao gerente de filas é atribuída à
variável da instância. As linhas 9 a 17 contém o código do método run() que é
o método de entrada do thread. Na linha 10 é invocado o método

Thread.currentThread().getName();

para se obter o nome do thread corrente. O nome do thread é usado para


referenciar a fila de mensagens do thread. Entre as linhas 11 e 16 é executado
um laço infinito onde o thread recebe e imprime as mensagens recebidas. Na
linha 14 o thread testa se a mensagem recebida é igual a “fim”. Neste caso o
thread encerra sua execução.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 39
As linhas 20 a 51 definem a classe que será usada para criação do objeto
gerador de mensagens. Este exemplo foi projetado para lidar com apenas um
thread gerador de mensagem. Modificações devem ser realizadas para tratar de
aplicações com mais de um thread gerador de mensagens. Na linha 22 é
declarada a variável que irá referenciar um objeto do tipo GerenteMen. As
linhas 23 a 27 contém o código do único construtor da classe. Ele recebe uma
referência para o grupo de threads ao qual deve se associar, o nome que deve ser
atribuído ao thread e a referência ao gerente de filas. Na linha 25 os primeiros
dois parâmetros são passados ao construtor da superclasse. Na linha 26 a
referência ao gerente de filas é atribuída à variável da instância. As linhas 28 a
50 contém o código do método run() que é o método de entrada do thread. Na
linha 29 é obtido o nome do thread corrente que será usado na impressão de
mensagens. Na linha 30 o método

Thread.currentThread().getThreadGroup();

obtém uma referência para o grupo de threads ao qual pertence o thread corrente.
Na linha 31 é declarada uma variável que irá referenciar um vetor contendo
referências a todos os threads ativos do grupo. Entre as linhas 32 e 44 é
executado um laço com 100 iterações que produz e armazena as mensagens. Na
linha 34 é realizado um teste para a verificação da necessidade de criar o vetor
que irá conter as referências para os threads ativos. Ele deve ser criado a
primeira vez e toda vez que a capacidade do vetor for diferente do número de
threads ativos do grupo. O tamanho do vetor é determinado pelo método de
instância activeCount() da classe ThreadGroup. A linha 36 contém o
código

tg.enumerate(tl);

que atribui as referencias aos threads no vetor. O comando

int n = (int)(Math.random()*1000)%tl.length;

da linha 37 calcula um número que será usado para acessar o thread dentro do
vetor de referências. O teste da linha 38 impede que seja enviada uma mensagem
para o próprio gerador. Essas mensagens são descartadas. As linhas 40 a 42
tratam da impressão e envio da mensagem construída. Já fora da iteração, as
linhas 45 a 49 tratam da do envio da mensagem “fim” para todos os threads
receptores, o que fará com que encerrem sua execução.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 40
As linhas 53 a 64 definem a classe que será usada como ponto de entrada
da aplicação. Ela é responsável pela criação dos objetos e disparos dos threads.
No exemplo, além do thread gerador apenas dois threads receptores são criados.
É interessante notar que não é preciso indicara para o thread gerador as
referências para os threads receptores. Elas são obtidas dinamicamente por meio
do grupo de threads.
Um trecho possível da saída obtida na execução do programa do exemplo
IX.15 seria o seguinte:

...
g>Mensagem enviada para r_dois:mensagem 88
r_um>Mensagem recebida:mensagem 87
g>Mensagem enviada para r_dois:mensagem 90
r_dois>Mensagem recebida:mensagem 88
g>Mensagem enviada para r_um:mensagem 91
r_dois>Mensagem recebida:mensagem 90
g>Mensagem enviada para r_um:mensagem 93
r_um>Mensagem recebida:mensagem 91
g>Mensagem enviada para r_um:mensagem 95
r_um>Mensagem recebida:mensagem 93
g>Mensagem enviada para r_um:mensagem 96
r_um>Mensagem recebida:mensagem 95
g>Mensagem enviada para r_um:mensagem 97
r_um>Mensagem recebida:mensagem 96
g>Mensagem enviada para r_dois:mensagem 99
r_um>Mensagem recebida:mensagem 97
r_dois>Mensagem recebida:mensagem 99
Pressione qualquer tecla para continuar . . .

Otimizando a Programação Multithread

Existe um problema óbvio com a abordagem do exemplo IX.14: a


mensagem é dirigida a apenas um thread mas todos serão notificados,
sobrecarregando o sistema, uma vez que todos os threads precisaram testar se a
mensagem é destinada a eles. Para contornar esses problema é necessário
vislumbrar uma forma de notificar apenas o thread destinatário.
Essa solução pode ser obtida se cada thread esperar em um monitor de
um objeto diferente. Não importa o tipo do objeto desde que seja referenciável
pelo thread receptor e pelo thread que irá armazenar a mensagem. Um candidato
natural é a fila de mensagem de cada thread. Existe uma fila para cada thread e o

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 41
thread que armazena a mensagem tem acesso a todas a filas por meio da tabela
hash. O exemplo IX.16 mostra uma versão do exemplo IX.14 que utiliza esta
técnica para criar uma aplicação multi-thread mais otimizada.

import java.util.*;

class GerenteMen {
private Hashtable tamMen;

public GerenteMen() {tamMen=new Hashtable(); }

// Método para adicionar uma mensagem à fila de


// um destinatário
public void addMen(String dest, String men){
if (dest==null || men==null) return;
Vector listaMen = (Vector) tamMen.get(dest);
if (listaMen==null) listaMen = new Vector();
synchronized (listaMen)
{
listaMen.addElement(men);
tamMen.put(dest, listaMen);
listaMen.notify();
};
}

// Método para obtenção da mensagem


public String getMen(String dest)
throws Exception
{
if (dest==null) return null;
Vector listaMen = (Vector) tamMen.get(dest);

// Se não existe a fila para esse thread cria uma vazia


if (listaMen==null)
{
listaMen = new Vector();
tamMen.put(dest, listaMen);
}
// A fila está vazia, portanto thread deve esperar
while(listaMen.size()==0)
synchronized (listaMen) {listaMen.wait();}
String temp = (String) listaMen.firstElement();

// A mensagem é removida da fila


listaMen.removeElementAt(0);
return temp;
}
}

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 42

Exemplo IX.16 – Gerenciador de mensagens otimizado.

Note que os métodos wait() e notify() invocados pertencem à fila


relacionada com cada thread. O exemplo IX.15 pode ser usado sem
modificações para testar essa versão.

Criando outros mecanismos de sincronização

Existem várias propostas de primitivas de sincronização. Dentre as mais


comuns podemos citar os semáforos, mutex, variáveis condicionais, monitores e
encontros (rendevouz). Cada uma dessas primitivas é mais adequada a um
determinado propósito. A implementação de monitores na linguagem Java,
juntamente com os métodos wait() e notify() que formam um tipo de
variáveis condicionais podem ser combinadas para implementar muitas dessas
outras primitivas, de modo a atender objetivos específicos. Para exemplificar
essa possibilidade mostraremos como implementar um semáforo usando as
primitivas de sincronização da linguagem Java.
Um semáforo é uma variável inteira sobre a qual pode-se realizar as
seguintes operações:

Operação Descrição
inicializar Um valor inteiro maior ou igual a zero é atribuído ao semáforo.
P Se o semáforo é maior que zero, o semáforo é decrementado. Caso
contrário, o thread é suspenso até que o semáforo contenha um
valor maior que zero.
V Incrementa o semáforo e acorda os threads que estiverem
bloqueados na fila de espera do semáforo.

Tabela IX.6 –Operações sobre um semáforo.

Semáforo é um mecanismo de sincronização muito utilizado quando


existe a necessidade de comunicação entre dois ou mais processos, como no caso
de sistemas do tipo produtor/consumidor. Por exemplo, suponha dois processos
onde um coloca mensagens em buffer e outro retira as mensagens. Os processos
podem usar dois semáforos para sincronizar o acesso ao buffer de mensagens:
um para controlar a entrada na região crítica e outro para contar o número de
mensagens. A figura IX.xx mostra os esquemas dos processos. A implementação
dos semáforos em Java pode ser visto no exemplo IX.xx e o uso dos semáforos

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 43
em uma situação como a ilustrada pela figura IX.xx pode ser visto no exemplo
IX.xx.

Produtor Consumidor

s=1; n=0;
início loop início loop
Produz mensagem P(n) // Verifica se existe mensagens
P(s) // Verifica se pode entrar na região P(s) // Verifica se pode entrar na região
// crítica //crítica
Coloca mensagem no buffer Retira mensagem
V(n) // incrementa no, de mensagens V(s) // sai da região crítica
V(s) // sai da região crítica Consome Mensagem
fim loop fim loop

Figura IX.xx – Comunicação entre processos usando semáforos.

public class Semaforo


{
private int cont;

public Semaforo(){cont =0;}


public Semaforo(int i){cont =i;}

public synchronized void P() throws InterruptedException


{
while(cont <=0) this.wait();
cont--;
}

public synchronized void V()


{
cont++;
notifyAll();
}
}

Exemplo IX.XX – Implementação de um Semáforo.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 44

import java.util.Vector;

class Consumidor extends Thread


{
private Vector buff;
private Semaforo s,n;

public Consumidor(Vector aBuff, Semaforo as, Semaforo an)


{
super();
buff = aBuff; s = as; n = an;
}
public void run()
{
for (;;)
try
{
n.p(); // Verifica se existe mensagens
s.p(); // Verifica se pode entrar na região crítica
String men = (String)buff.firstElement();
buff.removeElementAt(0);
s.v();
if (men.equals("fim")) return;
System.out.println("Mensagem recebida:"+men);
} catch(Exception e)
{System.out.println(e.getMessage());}
}
}

class Produtor extends Thread


{
private Vector buff;
private Semaforo s,n;
public Produtor(Vector aBuff, Semaforo as, Semaforo an)
{
super();
buff = aBuff; s = as; n = an;
}
public void run()
{
for (int i=0;i<11;i++)
{
try
{
s.p();// Verifica se pode entrar na região crítica
if (i<10)
{
buff.addElement(""+i);

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 45
System.out.println("Mensagem enviada: "+ i);
}
else buff.addElement("fim");
n.v(); // incrementa o número de mensagens
s.v(); // abandona a região crítica
Thread.yield();
} catch(Exception e)
{System.out.println(e.getMessage());}
}
}
}

public class TestaSemaforo


{
public static void main(String args[])throws Exception
{
Vector buff = new Vector();
Semaforo s = new Semaforo(1);
Semaforo n = new Semaforo(0);
Produtor t1 = new Produtor(buff,s,n);
Consumidor t2 = new Consumidor (buff,s,n);
t1.start();
t2.start();
}
}

Exemplo IX.XX – Uso de semáforos por dois threads.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 46

Capítulo II - Animação
Animação é exibir uma figura que muda com o tempo. No momento o
suporte a animação da API central do Java é limitado. Espera-se para o final de
1997 uma API que dê suporte avançado para animação. A animação pode ser
controlado por um thread que é executado em um certo intervalo pré-definido.

Exemplo básico de animação “in-place”.

import java.awt.*; import java.applet.Applet;

public class exemplo10 extends Applet implements Runnable


{
Image imgs[];
int ind=0;
Thread t1;

public void init()


{imgs = initImgs(); t1=new Thread(this);
t1.start();}

public void paint(Graphics g)


{g.draw.Image(imgs[ind],0,0,this);}

public void start() {


if (t1 == null) { t1 = new Thread(this);
t1.start();}
}

public void stop() {


if (t1 != null) {t1.stop();t1 = null;}
}

public void run() {


while (true){
try {Thread.sleep(100};}
catch(InterruptedException ex){}
repaint();
ind=++ind % imgs.length;
}
}
}

Problemas com o exemplo

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 47

Ele não permite interromper a animação. O repaint() chama o


update() default que repinta todo o fundo, o que causa “flicker” na animação.
Existe um problema relacionado com a integridade da variável ind. A variável é
atualizada pelo Thread t1 (run) e lida pelo Thread update (paint).

permitir interromper a animação.

boolean pause = false;

public boolean mouseDown(Event e, int x, int y)


{
if (pause) {t1.resume();}
else {t1.suspend();}
pause = !pause;
return true;
}

Eliminar o “flicker”

Default

public void update(Graphics g)


{
g.setColor(getBackground());
g.fillRect(0,0,width, height);
g.setColor(getForeground());
paint(g);
}

Mudança

public void update(Graphics g) {paint(g);}

Eliminando conflitos

public synchronized void paint(Graphics g)


{
g.draw.Image(imgs[ind],0,0,this);
}

public synchronized void mudaInd()

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 48
{
ind = ++ind % imgs.length;
}

Copiando a figura

public void drawStickFigure (Graphics g, int nX, int nY)


{
g.drawOval (nX + 10, nY + 20, 20, 40);
g.drawLine (nX + 20, nY + 60, nX + 20, nY + 100);
g.drawLine (nX + 10, nY + 70, nX + 30, nY + 70);
g.drawLine (nX + 10, nY + 150, nX + 20, nY + 100);
g.drawLine (nX + 20, nY + 100, nX + 30, nY + 150);
}
public void paint (Graphics g, Applet Parent)
{
if (bFirstTime) {
bFirstTime = false;
drawStickFigure (g, nX, nY);
}
else { g.copyArea (nX, nY, 35, 155, 5, 0);}
}

Double-buffer

offScreenImage = createImage (nWidth, nHeight);


offScreenGraphic = offScreenImage.getGraphics();
...
offScreenGraphic.setColor (Color.lightGray);
offScreenGraphic.fillRect (0, 0,nWidth, nHeight);
offScreenGraphic.setColor (Color.black);
...
offScreenGraphic. drawOval(10,10,20,20);
...
g.drawImage (offScreenImage, 0, 0, this);

Ticker-Tape

class TextScrolling extends AnimationObject


{
String pcMessage; // The message
int nXPos; // The location of the message
int nYPos; // The location of the message
int nAppletWidth; // The width of the applet
int nMessageWidth; // The width of the message

public TextScrolling (String pcMsg, int nWide)

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 49
{
pcMessage = pcMsg;
nAppletWidth = nWide;
nMessageWidth = -1;
nYPos = -1;
nXPos = 0;
}

public void paint (Graphics g, Applet parent)


{
if (nYPos < 0)
{
nYPos = (g.getFontMetrics ()).getHeight ();

char pcChars [];


pcChars = new char [pcMessage.length() + 2];
pcMessage.getChars(0, pcMessage.length()- 1, pcChars, 0);
nMessageWidth = (g.getFontMetrics ()).charsWidth
(pcChars, 0, pcMessage.length());
}

g.drawString (pcMessage, nXPos, nYPos);


}
public void clockTick ()
{
if (nMessageWidth < 0) return;

// Move Right
nXPos -= 10;
if (nXPos < -nMessageWidth)
nXPos = nAppletWidth - 10;
}
public void run()
{
int ndx = 0;

Thread.currentThread().setPriority
(Thread.MIN_PRIORITY);

while (size().width > 0 && size().height > 0


&& kicker != null)
{
AnimatedObjects[0].clockTick ();
repaint();
try {Thread.sleep(nSpeed);}
catch (InterruptedException e){}
}
}

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 50

Capítulo III - Programação em rede


Diferentemente das linguagens mais populares atualmente, Java foi
projetada na era da Internet, e por isso mesmo ferramentas para comunicação
dentro da Grande Rede foram incorporadas à linguagem desde a sua concepção.
Classes para manipulação de URLs e dos protocolos que constituem a Internet
fazem parte do núcleo básico da linguagem. Isto facilita muito a tarefa de
desenvolver aplicações que para a Internet ou outras redes que fazem uso do
mesmo conjunto de protocolos. Esta é uma das principais forças da linguagem
Java. De modo a entendermos como desenvolver aplicações em rede com Java é
importante o compreensão de alguns conceitos básicos sobre protocolos de
comunicação.

Conceitos Sobre Protocolos Usados na Internet


Um protocolo de comunicação é um conjunto de formatos e regras
usadas para transmitir informação. Computadores distintos devem obedecer
estas regras e formatos de modo que se possam comunicar. Podemos encarar o
protocolo como a definição de uma linguagem comum de modo a possibilitar a
comunicação entre diferentes entidades.

Visando diminuir a complexidade de implementação e uso do protocolo,


ele é divido e organizado em forma de camadas de protocolos, onde a camada
relativamente inferior na pilha a outra estabelece as regras para a camada
superior sobre a utilização de seus serviços. As camadas inferiores fornecem
serviços mais básicos de transmissão de dados, enquanto que as camadas
superiores oferecem serviços de mais alto nível. Esta forma de organização
hierárquica de protocolos é também chamada de pilha de protocolos.

A principal pilha de protocolo sobre o qual a Internet se organiza é o


TCP/IP. Por simplicidade chamaremos a pilha de protocolos TCP/IP apenas
como protocolo TCP/IP ou TCP/IP. A figura XI.1 mostra como se organizam
alguns dos protocolos que fazem parte do TCP/IP.

A camada física é a responsável pela transporte efetivo dos dados sobre o


meio físico. A camada de rede é responsável pela interface lógica entre os
computadores. A camada de transporte provê transferência de dados a nível de

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 51
serviço, e a camada de aplicação provê comunicação a nível de processos ou
aplicações. Exemplos de protocolos a nível de aplicação são: FTP, usado para
transferência de arquivos; HTTP, usado para transmissão de páginas Web;
TELNET provê capacidade de log-on remoto; SMTP provê serviços básicos de
correio eletrônico; SNMP usado para gerência da rede; e MIME que é uma
extensão do SMTP para lidar com mensagens com conteúdos diversos.

MIME

Camada de Aplicação FTP HTTP SMTP TELNET SNMP

Camada de Transporte TCP UDP

Camada de rede IP

Camada física Ethernet, X.25, Token Ring

FTP - File Transfer Protocol SMTP - Simple Mail Transfer Protocol


HTTP - Hypertext Transfer Protocol SNMP - Simple Network Management Protocol
IP - Internet Protocol TCP - Transmission Control Protocol
MIME - Multi-purpose Internet Mail Extensions UDP - User Datagram Protocol

Figura XI.1 –Alguns protocolos da pilha TCP/IP.

No processo de transmissão de dados sobre uma rede TCP/IP os dados


são divididos em grupos chamados de pacotes. Cada camada adiciona um alguns
dados a mais no início de cada pacote para permitir que o pacote chegue ao
destino. Os dados adicionados são chamados de headers.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 52

Dados
Aplicação

TCP Dados Transporte


Header

IP TCP Dados Rede


Header Header

Ethernet IP TCP Dados Física


Header Header Header

Figura XI.2 –Headers adicionados a cada camada de protocolo.

Na camada de transporte existem dois protocolos que fazem uso do


protocolo IP: o protocolo TCP/IP e o UDP.

TCP
O protocolo TCP é um protocolo orientado a conexão que provê um
fluxo confiável de dados entre dois computadores. Por protocolo orientado a
conexão queremos dizer que é estabelecido um canal de comunicação ponto-a-
ponto onde os dados podem trafegar em ambas as direções. O TCP garante que
os dados enviados em uma ponta cheguem ao destino, na mesma ordem que
foram enviados. Caso contrário, um erro é reportado. Protocolos como HTTP,
FTP e TELNET exigem um canal de comunicação confiável e a ordem de
recebimento dos dados é fundamental para o sucesso dessas aplicações.

UDP
No entanto, nem todas as aplicações necessitam destas características do
protocolo TCP e o processamento adicional exigido para garantir a
confiabilidade e a ordenação dos dados podem inviabilizá-las. Para esses casos
existe o protocolo de transporte UDP. UDP é um protocolo para envio de
pacotes independentes de dados, chamados de datagramas, de um computador a
outro, sem garantias sobre a chegada dos pacotes. O protocolo UDP não é
orientado a conexão.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 53

IDENTIFICAÇÃO DE HOSTS (Número IP)


Cada computador conectado a uma rede TCP/IP é chamado de Host e é
identificado por um único número de 32 bits, denominado de número IP. O
número IP é representado por quatro grupos de 8 bits, limitando desta forma o
valor numérico de cada grupo ao valor máximo de 255. Um exemplo de número
IP é 200.65.18.70. Uma vez que é muito difícil lembrar e atribuir significado a
números existe uma forma alternativa de identificar os computadores da rede por
meio de nomes. Um ou mais computadores da rede fazem o papel de
resolvedores de nomes, mantendo bases de dados que associam o nomes do
Hosts à seus números IP. Desta forma é possível um computador comunicar com
outro computador por meio do nome lógico e não por meio do número IP. A
figura XI.3 ilustra a comunicação entre dois computadores. A Internet é
representada como uma nuvem devido a complexidade da rede.

Host meucomp.com.br Host outrocomp.edu


IP: 200.18.46.12 IP: 205.50.30.75

Figura XI.3 –Representação da comunicação entre dois computadores.

O representação dos nomes dos computadores na Internet é feita por


substrings separadas por ‘.’ e obedecem uma regra de nomeação que define que
o primeiro substring representa o nome da máquina, e os restantes representa o
domínio onde está inserida a máquina. Um domínio é um agrupamento de
computadores que pertencem a uma instituição, órgão, empresa, ou uma
organização qualquer. Assim, no exemplo da figura XI.3 o computador
meucomp pertence ao domínio com.br. No Brasil a FAPESP (Fundação de
Amparo à Pesquisa do Estado de São Paulo) responsável pela gerência dos
nomes dos domínios na Internet.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 54
Identificação de Processos (Portas)
A comunicação entre dois processos em uma rede TCP/IP é assimétrica,
no sentido um processo faz o papel de servidor, oferecendo um serviço e outro
faz o papel de cliente do serviço. Em um único Host vários processos podem
estar fazendo o papel de servidor, oferecendo serviços através de um único meio
físico. Portanto, é preciso uma forma de identificar as servidores em um mesmo
Host. Isto é feito por meio da associação de um número inteiro, chamado de
porta, ao processo servidor. Essa associação é feita pelo processo assim que é
carregado, por meio de uma chamada ao sistema. O número da porta pode variar
de 1 a 65535, no entanto os números de 1 a 1023 são reservados para serviços
conhecidos como FTP e HTTP. O programador não deve usar estas portas a não
ser que esteja implementando algum desses serviços. Nos ambientes Unix as
portas que vão de 6000 a 6999 são usadas pelo gerenciador de Interfaces X
Windows e 2000 a 2999 por outros serviços, como o NFS. Nestes ambientes,
estas faixas de números de portas também devem ser evitadas. A tabela XI.1
mostra o número da porta de alguns dos serviços mais conhecidos.

Protocolo Porta
HTTP 80
echo 7
FTP 20,21
SMTP 25
Finger 79
Daytime 13
pop3 110

Tabela XI.1 – Número das portas dos principais serviços.

Uma vez associado a uma porta o serviço pode ser acessado por uma
aplicação cliente, bastando para isso que ela indique o nome do Host e o número
da porta ao se comunicar.

Programação em Rede com Java

O pacote java.net contém as classes e interfaces usadas para


programação de sistemas em rede com Java. As classes podem ser enquadradas
em três categorias:

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 55

1. Classes para comunicação básica em rede. Tratam da comunicação


em baixo nível entre aplicações. Outros protocolos podem ser
implementados usando como base esta comunicação básica.
2. Classes para comunicação dentro da Web. Estas classes provêem
facilidades para acessar conteúdos por meio de URLs.
3. Classes para tratamento dos formatos estendidos da Web. Utilizadas
para tratar novos protocolos e tipos MIME.

Comunicação Básica Entre Aplicações

As classes Socket, ServerSocket, DatagramSocket,


DatagramPacket e InetAddress, fornecem os métodos necessários para
a comunicação básica entre dois processos. A tabela XI.2 descreve sucintamente
cada uma das classes.

Classe Descrição
Socket Provê um socket cliente para comunicação orientada à
conexão via protocolo TCP.
ServerSocket Provê um socket servidor para comunicação orientada à
conexão via protocolo TCP.
DatagramSocket Provê um socket UDP para comunicação não orientada
à conexão.
DatagramPacket Representa um datagrama que pode ser enviado usando
DatagramSocket.
InetAddress Representa os dados de um Host (Nome e endereço IP)

Tabela XI.2 – Classes para comunicação básica.

As classes Socket e ServerSocket são utilizadas para comunicação


orientada à conexão, enquanto que as classes DatagramSocket e
DatagramPacket são utilizadas para comunicação não orientada à conexão.

Comunicação orientada à conexão (cliente)

Para comunicar via protocolo TCP é preciso que a aplicação cliente crie
um objeto Socket. É preciso passar o nome ou número IP do Host e o número
da porta onde o servidor está esperando as solicitações de serviço. A Classe
Socket possui os métodos getInputStream() e

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 56
getOutputStream(), que são usados para obter Streams associados ao
Socket. Deste modo, a transmissão de dados via Socket é idêntica à leitura e
escrita em arquivos via Streams. O exemplo XI.1 mostra o código de um cliente
que acessa um servidor de Daytime. O serviço de Daytime é disponibilizado nas
plataformas UNIX e é acessado via porta 13. Sua função enviar, aos processos
clientes, uma linha de texto contendo a data e a hora corrente .

import java.io.*;
import java.net.*;

public class ClienteData {


public static void main(String[] args) throws IOException
{
Socket socket = null;
BufferedReader in = null;

try {
socket = new Socket(args[0], 13);
in = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
}catch (UnknownHostException e)
{System.err.println("Não achou o host:"+args[0]);
System.exit(1);}
catch (IOException e)
{System.err.println("Erro de I/O."+e.getMessage());
System.exit(1);}

System.out.println("Data: " + in.readLine());


}

in.close();
socket.close();
}
}

Exemplo XI.1 – Cliente para o serviço de Daytime.

No programa do exemplo XI.1 o usuário precisa passar o nome do Host


servidor pela linha de comando. Você pode testar este programa mesmo que seu
computador não esteja conectado em uma rede, desde que o protocolo TCP/IP
esteja instalado. É que você pode passar como parâmetro o nome do seu
computador ou, alternativamente, o nome localhost, ainda o número IP
127.0.0.0. O nome localhost e o número IP127.0.0.0 sempre identificam o
computador local. Note que após a criação do objeto Socket, o método
getInputStream() é chamado e o objeto retornado é envolvido por um

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 57
objeto BufferedReader de modo a comunicar dados via rede da mesma forma
que é realizada uma operação de E/S. Ao se terminar a operação é preciso fechar
tanto a instância BufferedReader quanto o objeto Socket. A instância da Classe
de E/S sempre deve ser fechada primeiro.

Os Hosts que utilizam as variações do sistema operacional UNIX


possuem o serviço de Daytime, no entanto, outros sistemas operacionais podem
não implementar este serviço. O programador pode resolver este problema
implentando ele mesmo um servidor de Daytime. A próxima seção mostrará
como isto pode ser feito.

Comunicação orientada à conexão (servidor)

Para criar um processo servidor é preciso associá-lo à uma porta. Isto é


feito ao se criar uma instância da classe ServerSocket. Se estamos criando um
novo serviço é preciso associar a uma porta com valor maior que 1023. Se
estamos implementando um serviço já estabelecido é preciso obedecer as
especificações definidas para o serviço. O exemplo XI.2 mostra o código de um
servidor do serviço Daytime. Como não queremos substituir o serviço padrão de
Daytime utilizaremos o número de porta 5013 no lugar do número 13. Para se
testar este servidor com o programa cliente do exemplo X.1 é preciso alterar o
número da porta no código do cliente.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 58

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

public class ServerData {


public static void main(String[] args) throws IOException
{
SeverSocket ssocket = null;
Socket socket = null;
BufferedWriter out = null;

ssocket = new SeverSocket (5013,5);


for(;;)
{
socket = ssocket.accept();
out = new BufferedWriter (new OuputStreamWriter (
socket.getOuputStream()));
out.write((new Date()).toString()+”\n”);
}

out.close();
socket.close();
}
}

Exemplo XI.2 – Servidor de Daytime.

Ao criar uma instância da classe ServerSocket o programador pode


indicar o tamanho da fila de solicitações de conexão. As conexões são colocadas
na fila até que o servidor possa atende-las. Se chegar alguma conexão e não
houver espaço na fila a conexão será recusada. No nosso exemplo passamos
como parâmetro o valor 5. Após criar o objeto ServerSocket o servidor deve
indicar que está disposto a receber conexões. Isto é feito por meio da execução
do método accept() do objeto ServerSocket. Ao executar este método o
processo passa para o estado bloqueado até que alguma conexão seja solicitada.
Quando a conexão é solicitada o método accept() retorna um objeto Socket
igual ao do processo cliente, que será usado para obter os Streams onde será
efetuada a comunicação. O Stream obtido do objeto Socket é encapsulado em
objeto BufferedWriter que será encarregado de enviar a cadeia de caracteres
contendo a data e a hora para o cliente. O Servidor em implementa um laço
infinito, recebendo e tratando solicitações de serviços.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 59
O servidor implementado acima trata uma solicitação de serviço por vez.
Isto pode ser problemático quando existem vários clientes solicitando serviços
ao mesmo tempo e o servidor leva um longo tempo tratando cada solicitação.
Nesta situação o cliente pode ficar um longo tempo a espera de atendimento. Isto
pode ser remediado por meio da implementação de servidores Multithreaded.
Apesar do serviço implementado pelo exemplo XI.2 não exigir um servidor
Multithreaded, uma vez que o cliente é atendido rapidamente, o servidor
Daytime foi alterado para exemplificar a implementação de um servidor
Multithreaded. O exemplo XI.3 mostra o código da versão Multithreaded do
servidor.

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

public class ServerData


{

public static void main(String args[])


{
ServerSocket ssocket=null;

try { ssocket = new ServerSocket(pt); }


catch(Exception e) {System.err.println(e);
System.exit(1);}

while(true)
{
try
{
Socket socket = ssocket.accept();
(new serversec(socket)).start();
}
catch(Exception e) {System.err.println(e);}
}
}
}

class serversec extends Thread


{
Socket socket;

public serversec(Socket aSocket) {socket = aSocket;}

public void run()


{

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 60
try
{
BufferedWriter out = new BufferedWriter (new
OuputStreamWriter (
socket.getOuputStream()));
out.write((new Date()).toString()+”\n”);
out.flush();
out.close();
socket.close();
}
catch(Exception e) {System.err.println(e);}
}
}

Exemplo XI.3 – Servidor de Daytime Multithreaded.

No exemplo XI.3, ao receber uma conexão o servidor cria um Thread


para atender o cliente e fica disponível para receber novas solicitações. Esse
exemplo pode ser usado como esqueleto para desenvolvimento de servidores
mais complexos, porém, neste caso é necessário limitar o número de Threads
que podem ser criados.

Comunicação Sem Conexão (UDP)


Como já dissemos na seção anterior, nem sempre é necessário um canal
de comunicação confiável entre duas aplicações. Para estes casos existe o
protocolo UDP, que provê uma forma de comunicação onde a aplicação envia
pacotes de dados, chamados de datagramas, para outra aplicação, sem garantias
se e quando a mensagem vai chegar, nem se o conteúdo está preservado.

As portas do protocolo UDP obedecem a mesma distribuição das TCP


porém são distintas uma da outra, de modo que o programador pode associar
uma porta TCP de um determinado número à uma aplicação em um host e o
mesmo número de porta UDP a outra aplicação no mesmo host.

As classes DatagramPacket e DatagramSocket contém os


métodos necessários para realizar este tipo de comunicação. Para ilustrar o uso
destas classes modificaremos os exemplos XI.1 e XI.2 para implementarmos
uma aplicação Cliente/Servidor Daytime que usa o protocolo UDP. O Exemplo
XI.4 mostra o código fonte do cliente e o Exemplo XI.5 mostra o código do
servidor.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 61

Analisando o código da aplicação cliente podemos notar que é necessário


criar um objeto da classe DatagramPacket para representar os Datagrama onde
os dados são armazenados. No nosso exemplo colocamos também os dados do
Host e a porta, porém estes dados poderiam ser omitidos na construção do
Datagrama e serem passados somente no envio/recepção do pacote ou na
construção do DatagramSocket. O Datagrama deverá armazenar os dados
enviados pelo servidor e foi dimensionado para conter 64 bytes. O método
receive() do objeto DatagramSocket aguarda o recebimento do pacote,
tendo como argumento o objeto da classe DatagramPacket. Após o recebimento
do pacote os dados são convertidos para String e exibidos na saída padrão.

O servidor, diferentemente do servidor TCP, precisa saber a quem deve


enviar os pacotes, uma vez que não é estabelecida uma conexão. Podíamos
simplesmente passar o nome do host pela linha de comando, mas resolvemos
adotar uma estratégia de Broadcasting. Nesta abordagem os datagramas são
enviados a vários computadores e não apenas um. Isto é feito passando-se como
argumento para o método InetAddress.getByName() o endereço de
Broadcast da rede.

import java.io.*;
import java.net.*;

public class DataClienteUDP


{
public static void main(String args[]) throws Exception
{
if (args.length != 1)
{
System.err.println("Uso: java DataClienteUDP host");
System.exit(1);
}
byte [] buff = new byte[64];
DatagramSocket ds = new DatagramSocket();
DatagramPacket dp = new DatagramPacket(buff, buff.length,
InetAddress.getByName(args[0]),5013);
ds.receive(dp);
String s = new String(dp.getData());
System.out.println("Data e hora recebida
de”+dp.getAddress()+
" : "+s);
}
}

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 62

Exemplo XI.4 – Cliente Daytime UDP.

import java.io.*;
import java.net.*;

public class DataServerUDP


{
public static void main(String args[]) throws Exception
{
DatagramSocket ds;
DatagramPacket dp;
InetAddress addr = InetAddress.getByName(“255.255.255.0”);
ds = new DatagramSocket();

byte [] buff;
for (;;)
{
Thread.sleep(1000);
String s = (new Date()).toString();
buff = s.getBytes();
dp = new DatagramPacket(buff, buff.length, addr, 5013);
ds.send(dp);
}
}
}

Exemplo XI.5 – Servidor Daytime UDP.

O tipo de endereço de Broadcast depende da classe de endereçamento IP


da rede. O endereço usado no exemplo IX.5 funciona para a classe de
endereçamento C. Para descobrir que tipo de classe pertence a rede onde está seu
computador olhe o primeiro byte do endereço IP de sua máquina e verifique
junto a tabela XI.3.

Primeiro byte do endereço IP Classe Endereço de Broadcast


0 a 126 A 255.0.0.0
128 a 191 B 255.255.0.0
192 a 223 C 255.255.255.0

Tabela XI.3 – Classes para comunicação básica.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 63
O envio dos dados é feito pelo método send() do objeto
DatagramSocket, tendo como argumento um objeto da classe DatagramPacket.
Note que não é necessário um “Socket servidor” uma vez que o servidor envia
os pacotes independentemente de existir clientes solicitando-os.

Comunicação por meio de URL


URLs

Um URL (Uniform Resource Locator) é uma referência (um endereço) a


um recurso na Internet. O URL é dividido em partes, sendo que apenas a
primeira parte é obrigatória. A maioria das URLs é dividida em três partes:

Informação sobre o Host


678
123
Protocolo 123
http://dpi.ufv.br/professores.html

endereço do recurso no Host

A parte do protocolo define o protocolo que deve ser usado para acessar
o recurso. Os protocolos mais comuns são FTP, HTTP e file, este último
indicando que o recurso se encontra no sistema de arquivos local. O protocolo é
seguido do caractere “:”.

A parte com informação sobre o Host fornece a informação necessária


para acessar o Host onde está localizado o recurso. Esta parte é omitida caso o
recurso esteja no sistema de arquivos local. A informação sobre o Host é
precedida por duas barras (“//”), no caso de aplicação na Internet e apenas por
uma barra (“/”), caso contrário. A informação sobre o Host também pode ser
dividida em três partes: a) o nome do domínio do Host; b) o nome e senha do
usuário para login; e c) o número da porta caso necessário, após o nome do host,
precedida pelo caractere “:”. Exemplos:

http://java.sun.com:80/doc/tutorial.html
http://infax.com.br."claudio"."1234"/base/vendas

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 64
A última parte de uma URL representa o caminho até o recurso no
sistema de arquivos do Host. Esta seção é separada da seção anterior por uma
barra simples (“/”).

Manipulando URLs em Java

A linguagem Java fornece as seguintes classes para manipulação de


URLs:

Classe Descrição
URL Representa um URL
URLConnection Classe abstrata que representa uma conexão entre uma
aplicação e um URL. Instâncias desta classe podem ser
usadas para ler e escrever no recurso referenciado pela
URL.
URLEncoder Usada para lidar com o formato MIME.

Tabela XI.3 – Classes para manipulação de URLs.

Um objeto URL é criado passando como parâmetro para o construtor o


URL na forma de String:

URL dpi = new URL("http://www.dpi.ufv.br");

Um objeto URL pode ser construído passando como parâmetro outro


URL para servir de endereço base. Por exemplo

URL profs = new URL (dpi,”professores.html”);

Isto tem o mesmo efeito que gerar uma instância da classe URL com
primeiro construtor, passando como parâmetro o endereço:

http://www.dpi.ufv.br/professores.html

Os construtores geram a exceção MalformedURLException, se o


URL é inválido. Portanto, o programador deve providenciar o código para a
captura e tratamento desta exceção.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 65
De posse de um objeto URL é possível obter um objeto InputStream para
ler os dados endereçados pelo URL, utilizando o método openStream() do
objeto URL. O Exemplo XI.4 mostra o código de um programa que pode ser
usado para listar na saída padrão o conteúdo de um URL passado pela linha de
comando.

import java.net.*;
import java.io.*;

public class LeURL


{
public static void main(String[] args) throws Exception
{
if (args.length < 1)
{
System.err.println("uso: java LeURL <URL>...");
System.exit(1);
}
URL url = new URL(args[0]);
BufferedReader in = new BufferedReader(
new InputStreamReader(url.openStream()));

String linha;

while ((linha = in.readLine()) != null)


System.out.println(linha);

in.close();
}
}

Exemplo XI.4 – Leitor de URL.

Comunicando por meio de URLConnection

O programador pode utilizar o método openConnection() do objeto


URL para obter uma conexão entre a aplicação é o recurso referenciado pelo o
URL. O método openConnection() retorna um objeto URLConnection,
que permite que a aplicação escreva e leia através da conexão. Alguns URLs,
como os conectados à scripts CGI2 (Common-Gateway Interface), permitem que

2
Common-Gateway Interface (CGI) é um mecanismo para gerar páginas Web dinamicamente.
Os dados são obtidos de fórmulários HTML e submetidos a um programa binário no servidor

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 66
aplicação cliente escreva informação no URL. A saída do programa CGI pode
ser interceptada pelo programa Java de modo que possa ser exibida para o
usuário. Esta forma de comunicação com scripts CGI é melhor do que por meio
de formulários HTML, uma vez que o usuário não precisa navegar entre páginas
para visualizar os formulários e a resposta retornada. O Applet Java se encarrega
de enviar os dados e exibir o resultado na mesma página. Para ilustrar esta forma
de comunicação mostraremos um programa que submete uma cadeia de
caracteres a um URL para que seja invertido e enviado de volta. O script CGI
utilizado, escrito em Perl é exibido no exemplo XI.5 e foi escrito por Hassan
Schroeder, um membro da equipe de desenvolvimento da linguagem Java. Este
CGI pode ser acessado no URL http://java.sun.com/cgi-
bin/backwards.

#!/opt/internet/bin/perl
read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
@pairs = split(/&/, $buffer);
foreach $pair (@pairs)
{
($name, $value) = split(/=/, $pair);
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",
hex($1))/eg;
# Stop people from using subshells to execute commands
$value =~ s/~!/ ~!/g;
$FORM{$name} = $value;
}

print "Content-type: text/plain\n\n";


print "$FORM{'string'} reversed is: ";
$foo=reverse($FORM{'string'});
print "$foo\n";
exit 0;

Exemplo XI.5 – Script backwards para inverter uma cadeia de caracteres.

O exemplo XI.6 contém o programa que envia uma cadeia de caracteres


ao URL e recebe de volta outra cadeia de caracteres que é a inversão da
primeira. A ação necessária é codificar a cadeia de caracteres por meio do
método estático encode() da classe URLEncoder:

que gera a resposta na forma de uma página Web. O programa pode ser escrito em uma
variadade delinguagens, como Perl e C.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 67
String string = URLEncoder.encode(args[0]);

Isto é necessário porque a string enviada a um URL necessita de uma


codificação particular, como por exemplo, os espaços em branco são
substituídos pelo caractere “+”, os campos são separados pelo caractere “&”e
valor do campo é separado do nome do campo pelo caracteres “=”.

Em seguida o programa cria um objeto URL relacionado com o endereço


onde se encontra o script, abre uma conexão e define que será usada para entrada
e saída.

URL url = new URL("http://java.sun.com/cgi-


bin/backwards");
URLConnection c = url.openConnection();
c.setDoOutput(true);

Neste momento o programa está preparado para trabalhar com o URL


como se fosse um Stream. O Stream para escrever no URL é obtido do objeto
URLConnection por meio do método getOutputStream() e o Stream
para ler do URL é obtido do objeto URLConnection por meio do método
getInputStream(). Primeiro o programa submete a cadeia de caracteres a
ser invertida precedida pelo nome do campo e pelo caractere “=”:

out.println("string=" + string);

import java.io.*;
import java.net.*;

public class Inverte


{
public static void main(String[] args) throws Exception
{

if (args.length != 1)
{
System.err.println("Uso: java Inverte string");
System.exit(1);
}

String string = URLEncoder.encode(args[0]);

URL url = new URL("http://java.sun.com/cgi-

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 68
bin/backwards");
URLConnection c = url.openConnection();
c.setDoOutput(true);

PrintWriter out = new PrintWriter(c.getOutputStream());


out.println("string=" + string);
out.close();

BufferedReader in = new BufferedReader(new


InputStreamReader(
c.getInputStream()));
String retorno;

while ((retorno = in.readLine()) != null)


System.out.println(retorno);

in.close();
}
}

Exemplo XI.6 – Programa que escreve em um URL.

Após isso o Stream de saída é fechado e o Stream de entrada é aberto. A


cadeia de caracteres invertida é lida e exibida no dispositivo de saída padrão.

No exemplo apresentado o script CGI usa o POST METHOD para ler


dados enviados pelo cliente. Alguns scripts CGI usam o GET METHOD para ler
dados do cliente, no entanto, este último está ficando rapidamente obsoleto
devido a maior versatilidade do primeiro.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 69

Capítulo IV – Computaç ã o
Distribuída (RMI)
A RMI (Invocação de métodos remotos) é uma tecnologia que coloca a
programação com rede em um nível mais alto. RMI torna possível que objetos
distribuídos em uma rede se comuniquem de forma transparente para o
programador utilizando chamadas de procedimentos remotos.
O principal objetivo da tecnologia RMI é permitir que programadores
desenvolvam programas distribuídos utilizando a mesma sintaxe e semântica de
programas Java convencionais. Antes da introdução da RMI no mundo Java, no
JDK 1.1, para fazer com que dois objetos em máquinas diferentes se
comunicassem o programador deveria definir um protocolo de comunicação e
escrever código utilizando socket para implementar este protocolo. Com RMI a
maior parte do trabalho quem realiza é a máquina virtual Java.
Existem outras tecnologias, como CORBA (Common Object Request
Brocker Architecture), que também tem como objetivo fazer com que objetos
distribuídos em uma rede se comuniquem. Java também tem suporte a CORBA,
mas para projetos em um ambiente Java puro, a RMI é consideravelmente mais
simples que a CORBA.

Criando nossa agenda distribuída


De modo exemplificar o uso de RMI modificaremos a agenda distribuída
fazendo uso desta tecnologia. Os passos a serem seguidos são:

1. Escrever e compilar a interface que descreve a como serão as chamadas


do cliente ao servidor;
2. Escrever e compilar a classe que implementa a interface do passo 1
(objeto servidor);
3. Gerar Stubs e Skeleton do objeto distribuído;
4. Desenvolver o código que disponibiliza o objeto;
5. Escrever e compilar o código para o cliente RMI; e
6. Testar.

Implementar interface do objeto remoto

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 70
O primeiro passo quando se deseja criar um objeto Remoto com RMI é
implementar uma interface para este Objeto. Essa interface deve herdar a
interface Remote. É através dessa interface Remote, que não tem métodos, que a
máquina virtual Java sabe qual objeto pode ser disponibilizado para acesso
remoto. Abaixo temos o exemplo da interface do nosso objeto:

1 public interface Agenda extends java.rmi.Remote{


2 public void inserir(Pessoa p)
3 throws java.rmi.RemoteException;
4 public Pessoa getPessoa(String nome)
5 throws java.rmi.RemoteException;
6 public java.util.Enumeration getPessoas()
7 throws java.rmi.RemoteException;
8 }

Exemplo XX.XX – Interface do objeto remoto.

A interface Remote deve ser herdada pela nossa interface Agenda.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 71

Capítulo V - Acesso a B a nco de


Dados
No dias de hoje, uma linguagem sem recursos para acesso a sistemas de
Banco de Dados está fadada ao fracasso. Pensando nisso a Sun incluiu como
parte do núcleo de bibliotecas de classes da linguagem Java uma API com o
objetivo de preencher esta função, chamada de JDBC (segundo a Sun JDBC é
apenas um acronismo, no entanto, muitas pessoas acreditam que é uma sigla
para Java Database Connectivity). JDBC é uma API baseada no X/Open SQL
Call Level Interface, tendo sido desenvolvida originalmente como um pacote
separado, porém a partir do JDK1.1 passou a fazer parte do núcleo básico de
pacotes. Utilizando a API JDBC é possível conectar um programa Java com
servidores de Banco de Dados e executar comandos SQL (Structure Query
Language). Sendo uma API independente do Sistema Gerenciador de Banco de
Dados, não é necessário escrever uma aplicação para acessar um Banco de
Dados Oracle, outra para uma Base de Dados Sybase, outra para o DB2, e assim
por diante.

A idéia de se usar uma camada intermediária entre o Banco de Dados e a


aplicação, com o objetivo de isolá-la das particularidades do SGBD, não é nova.
O exemplo mais popular deste enfoque é a API ODBC (Open DataBase
Connectivity), proposta pela Microsoft. O leitor pode estar se perguntando
porque a Sun resolveu propor mais uma API em vez de adotar a ODBC. Existem
vários motivos, porém o principal é que a API ODBC simplesmente não é
adequada para a linguagem Java. Isto ocorre porque ODBC foi desenvolvida
para ser usada na linguagem C e é baseada fortemente no uso de ponteiros,
estrutura que não existe em Java.

Modelos de Acesso a Servidores


O modelo mais simples de aplicação Cliente/Servidor é o chamado de
modelo de duas camadas, onde a aplicação acessa diretamente o Banco de
Dados. A figura XIII.1 mostra o esquema para uma aplicação que acessa um
Banco de Dados usando o modelo de duas camadas.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 72

Aplicação
Java

DBMS

Figura XIII.1 – Modelo de acesso a Banco de Dados em duas camadas.

Aplicação Java
ou Applet

HTTP, RMI, CORBA

Aplicação Servidora
Java
JDBC

DBMS

Figura XIII.2 – Modelo de acesso a Banco de Dados em três camadas.

Tipos de Drivers JDBC


Os drivers JDBC devem suportar o nível de entrada do padrão ANSI
SQL-2. No momento, os drivers JDBC existentes se encaixam em um dos quatro
tipos abaixo:

1. Ponte JDBC-ODBC com driver ODBC – o driver JDBC acessa o banco de


dados via drivers ODBC. Como ODBC é um código binário e, em alguns
casos, compõe o código do cliente, é necessário instalar em cada máquina
cliente que usa o driver. Essa é uma solução adequada somente para uma

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 73
interna corporativa, ou em aplicações que adotam o modelo de três camadas
sendo a camada intermediária um servidor Java.

SGBD1

Cliente Ponte SGBD2


Java JDBC-ODBC
SGBD3

2. Driver Java parcial e Api Nativa – neste caso as chamadas JDBC são
convertidas para as chamadas às APIs nativas do SGBD. Como o driver
possui uma parte em código binário é necessário instalar algum código na
máquina cliente, como é feito nos drivers do tipo 1.

Protocolo do SGBD
Cliente JDBC Java & SGBD
Java Código Binário

3. Driver puro Java e protocolo de rede – neste caso as chamadas JDBC são
convertidas para um protocolo de rede independente do SGBD que é depois
traduzido para as chamadas às APIs nativas do SGBD por um servidor. Esta
é uma arquitetura em três camadas, onde o servidor middleware é capaz de
conectar seus clientes Java puros com vários SGBDs. Esta solução permite o
desenvolvimento de clientes 100% Java, tendo como consequência a não
necessidade de instalação de qualquer código na máquina cliente.

JDBC DRIVER (100% Java) SGBD1


Cliente Servidor de SGBD2
Java acesso SGBD3

4. Driver Java Puro e protocolo nativo - neste caso as chamadas JDBC são
convertidas para as chamadas às APIs nativas do SGBD pelo driver, que foi
escrito totalmente em Java.

Protocolo do SGBD
Cliente JDBC Java SGBD
Java (100% Java)

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 74
Atualmente existe uma maior disponibilidade dos drivers tipo 1 e 2, mas a
tendência é que estes desapareçam, sendo substituídos pelos drivers do tipo 3 e
4.

Obtendo os Drivers JDBC


Informações sobre como obter drivers JDBC podem ser obtidas no site
http://www.javasoft.com/products/jdbc. Outra alternativa é
acessar as páginas dos fabricantes de SGBD, para verificar se existe driver
disponível.

Preparando um Banco de Dados


Os exemplos deste livro usam a ponte JDBC-ODBC para conectar com o
Banco de Dados. Isto facilita para os usuários que possuem um gerenciador
Banco de Dados pessoal como o Access, Paradox, e outros semelhantes. Além
disso, como driver JDBC-ODBC está incorporado ao SDK, o usuário não
necessita procurar um driver para testar os exemplos. O lado negativo desta
abordagem está na necessidade de configurar o ODBC e no fato de que as
aplicações remotas deverão ser desenvolvidas em três camadas. No entanto,
nada impede que o leitor use outro driver para rodar os exemplos, bastando para
isso alterar a chamada da carga do driver.

Primeiramente é necessário criar uma base de dados em algum SGBD.


Nos exemplos deste livro será usada uma base contendo dados sobre livros,
alunos e empréstimos de livros aos alunos. Não trataremos neste livro dos
conceitos relacionados com banco de dados relacionais nem sobre a linguagem
de consulta SQL. Existem vários textos sobre o assunto onde o leitor pode
buscar informação.

As figuras XIII.3 a XII.5 mostram as tabelas que formam a base de dados


usada nos exemplos. O banco de dados é formado por três tabelas. Uma para
armazenar os dados dos alunos, outra para receber os dados dos livros e uma
terceira para conter os dados dos empréstimos.

alunos
matricula nome
1 Railer Costa Freire
2 Alexandre Altoé

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 75
3 André M. A. Landro
4 Ana Maria Freitas
5 Claudia Maria
6 Alexandra Moreira

Figura XIII.3 – Tabela de alunos

livros
codlivro titulo volume
1 Curso Pratico de Java 1
2 Curso Pratico de Java 2
3 Introdução a Compiladores 1
4 Fundamentos de Banco de Dados 1
5 Redes de Computadores 1
6 Redes de Computadores Fácil 2
7 Lógica matemática 1
8 Engenharia de Software para Leigos 1
9 Aprenda Computação Gráfica em duas 1
10 Aprenda Inteligência Artificial em 5 1

Figura XIII.4 – Tabela de livros.

emprestimos
codlivr matricu data_empresti data_devoluc
1 1 01/01/99 10/01/99
7 3 03/01/99 13/01/99
9 6 12/01/99 22/01/99
1 3 20/01/99 30/01/99
4 2 03/02/99 13/02/99
10 2 12/02/99 22/02/99

Figura XIII.5 – Tabela de empréstimos.

Em um Banco de Dados Relacional cada tabela representa um conjunto


de entidades ou relacionamentos entre entidades, e cada linha da tabela
representa uma entidade particular ou um relacionamento entre entidades.
Assim, cada linha das tabelas alunos e livros representa um aluno e um livro
respectivamente. Já na tabela empréstimos cada linha representa o
relacionamento por empréstimo de um livro a um aluno. Para estabelecer este

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 76
tipo de relacionamento em um Banco de Dados Relacional é preciso colocar na
tabela que representa o relacionamento os atributos chaves de cada entidade que
participa da relação. Atributos chaves são os atributos que identificam cada
entidade. No caso dos alunos é seu número de matrícula, uma vez que pode
existir dois alunos com o mesmo nome. Já no caso de um livro o seu atributo
chave é o código do livro. Um Banco de Dados Relacional pode ser representado
pelo diagrama de classes da UML, como mostrado pela figura XIII.6, onde cada
tabela é vista como uma classe.

Alunos livros

matricula codlivro
nome titulo
volume
emprestimos

data_emprestimo
data_devolução

Figura XIII.6 – Diagrama de Classes do Banco de Dados.

Para criação das tabelas em banco de dados relacional deve-se usar


comandos DDL (Data Definition Language). O exemplo XX.XX mostra os
comandos em DDL para a criação das tabelas do exemplo:

CREATE TABLE ALUNOS (MATRICULA INT PRIMARY KEY,


NOME VARCHAR(50) NOT NULL);

CREATE TABLE LIVROS (CODLIVRO INT PRIMARY KEY,


TITULO VARCHAR(50) NOT NULL,
VOLUME INT NOT NULL);

CREATE TABLE EMPRESTIMOS (


CODLIVRO INT NOT NULL,
MATRICULA INT NOT NULL,
DATAEMP DATE NOT NULL,
DATADEV DATE NOT NULL,
CONSTRAINT PK_EMP PRIMARY KEY
(CODLIVRO, MATRICULA, DATAEMP),
CONSTRAINT FK_EMP1 FOREIGN KEY (CODLIVRO )
REFERENCES LIVROS (CODLIVRO ),

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 77
CONSTRAINT FK_EMP2 FOREIGN KEY (MATRICULA)
REFERENCES ALUNOS (MATRICULA ));

Exemplo XX.XX – Comandos em DDL para criação das tabelas em um SGBD


relacional.

Configurando o ODBC
Como utilizamos nos exemplos a ponte JDBC-ODBC é necessário
configurar o ODBC para acessar a base de dados acima. Na plataforma
Windows 98 isso é feito da seguinte forma:

1. Execute o programa de configuração do ODBC por meio do ícone


“ODBC de 32bits” do painel de controle.

2. Clique na pasta “NFD do sistema” e em seguida no botão de


“adicionar...”. O NFD do sistema é escolhido no lugar da opção
“NFD do usuário” porque permite o compartilhado à base de dados.

3. Selecione o driver do banco de dados e pressione o botão de concluir.


Por exemplo, se você estiver usando o Access selecione a opção
“Driver para o Microsoft Access (*.mdb)”.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 78

Figura XIII.7 – Seleção do driver ODBC no Windows.

4. Ao surgir a tela para a entrada seleção do Banco de Dados, utilize a


tecla “Selecionar...” localizar o arquivo onde está a base de dados e
preencha a janela de texto “Nome da fonte de dados” com o nome
que será usado para referenciar a base.

5. Feche o programa de configuração.

A configuração de uma base de dados para ser acessada via ODBC


possui vários outros detalhes que consideramos não ser relevantes para os
propósitos deste livro. A configuração como apresentada acima é a suficiente
para se executar os exemplos deste livro.

Exemplo Inicial
O pacote java.sql fornece as classes e interfaces necessárias para a
conexão com uma base de dados e a posterior manipulação dos dados. As etapas
para se criar uma aplicação cliente de um SGBD em Java são as seguintes:

1. Carregar o driver JDBC.


2. Estabelecer a conexão.
3. Criar um objeto Statement.
4. Executar o comando SQL por meio de um método do objeto
Statement.
5. Receber o resultado, se for o caso.

Para comentar cada uma destas etapas utilizaremos o exemplo XII.1 que
mostra o código de uma aplicação que lista no dispositivo de saída padrão o
nome de todos os alunos.

import java.sql.*;
import java.net.URL;

class jdbc
{
public static void main(String a[])

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 79
{
try
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con =
DriverManager.getConnection("jdbc:odbc:biblioteca");
Statement stmt = con.createStatement();
ResultSet rs =
stmt.executeQuery("SELECT NOME FROM alunos");
System.out.println("Nome");
while(rs.next())
System.out.println(rs.getString("nome"));
stmt.close();
con.close();
} catch(Exception e)
{System.out.println(e.getMessage());
e.printStackTrace();}
}
}

Exemplo XX.XX – Código para listar o nome dos alunos.

Carregando o Driver
A primeira etapa é carregar o driver JDBC. Para isso é usado o método
estático forName() da classe Class. Em caso de erro este método lança a
exceção ClassNotFoundException. O método cria uma instância do
driver e o registra junto ao DriverManager. No exemplo XIII.1 é carregado o
driver JDBC-ODBC que vem junto com o SDK.

Estabelecendo a conexão
A segunda etapa é realizada por meio do método estático
getConnection() da classe DriverManager. Este método, na sua forma mais
simples, recebe como parâmetro um URL que faz referência a base de dados e
retorna um objeto da classe Connection, que representa a conexão com a base de
dados. Já discutimos sobre URLs no capítulo XI. No entanto, existem algumas
particularidades no que refere a URLs que fazem referência à Banco de Dados.
O formato padrão deste tipo de URL é o seguinte:

jdbc:<subprotocolo>:<identificador>

onde:

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 80

1. jdbc representa o protocolo;

2. <subprotocolo> se refere ao driver ou ao mecanismo de conexão com


o Banco de Dados, que pode ser suportado por um ou mais drivers.
No exemplo XII.1 o nome é utilizada a ponte JDBC-ODBC,
representada pela palavra odbc no subprotocolo.

3. <identificador> é a parte onde se identifica o Banco de Dados. A


forma de identificação varia de acordo com o subprotocolo. No nosso
exemplo é colocado o mesmo nome usado para identificar a fonte de
dados na configuração do ODBC. A sintaxe para o subprotocolo odbc
é a seguinte:

jdbc:odbc:<fonte de dados>[;<atributo>=<valor>]*

onde <atributo> e <valor> representam parâmetros a serem passados


para o gerente de conexão do Banco de Dados.

Um banco de dados acessado remotamente requer maiores informações,


como o nome do host e a porta. Tanto o uso local como remoto pode requerer a
identificação do usuário, assim como uma senha. Estes dados podem ser
passados como parâmetro no método getConnection():

getConnection("jdbc:odbc:contas",”ana”,”sght”);

ou como parte do URL. Alguns exemplos de URLs estão descritos na tabela


XIII.1

URL Descrição
jdbc:odbc:biblioteca Referencia fonte de dados biblioteca via
ponte JDBC-ODBC.
jdbc:odbc:bd1;CacheSize=20 Referencia fonte de dados bd1 via ponte
JDBC-ODBC. É definido o tamanho do
cache.
jdbc:odbc:contas;UID=ana;PWD=sght Referencia fonte de dados contas via
ponte JDBC-ODBC. É passado também o
nome do usuário e a senha.
jdbc:oracle:thin:@sap.dpi.ufv.br:1521:agenda Referencia fonte de dados agenda no host
remoto sap.dpi.ufv.br via subprotocolo
oracle. É passado também o número da
porta usada no acesso.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 81

Tabela.1 – Exemplos de URLs JDBC.

Criando e Executando Comandos


É necessário cria um ou mais objetos da classe Statement, que possui os
métodos necessários para manipular a base de dados. Este objeto é criado por
meio do método createStatement() do objeto da classe Connection.

Statement stmt = con.createStatement();

Podemos então usar o objeto Statement para executar comandos de


manipulação do Banco de Dados. No exemplo XIII.1 o objetivo é recuperar s
nomes dos alunos. Este objetivo é atingido por meio da execução do comando
SQL

SELECT nome FROM alunos

passado como parâmetro para o método executeQuery() do objeto


Statement. Este método retorna um objeto que implementa a interface
ResultSet, e que fornece os meios de acesso ao resultado da consulta. Muitas
vezes, como no exemplo, o resultado de uma consulta é uma tabela com várias
linhas. O programador pode utilizar o objeto ResultSet para acessar cada linha
da tabela resultante em sequência. Para isso o objeto mantém um apontador para
a linha corrente, chamado de cursor. Inicialmente o cursor é posicionado antes
da primeira linha, movimentado para próxima linha por meio de chamadas ao
método next() do objeto ResultSet.
O método executeQuery() é usado apenas para consultas. Além
desse método, a classe Statement possui o método execute() que retorna
múltiplos ResultSets e o método executeUpdate(), para atualização
(comandos INSERT, DELETE e UPDATE da linguagem SQL), criação de
tabelas (comandos CREATE TABLE) e remoção (DROP TABLE). O valor de
retorno do método executeUpdate() é um valor inteiro indicando o
número de linhas afetadas ou zero no caso do DROP TABLE. Um exemplo de
um comando para inserir um uma novo aluno na tabela seria:

stmt.executeUpdate("INSERT INTO alunos VALUES(7, 'Ana


mia')");

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 82

Recuperando Valores
O objeto ResultSet possui os métodos necessários para recuperar os
valores de cada coluna da tabela, bastando passar o nome da coluna como
parâmetro. No exemplo XIII.1 é utilizado método getString() para
recuperar o valor da coluna “nome”. Os métodos para recuperação possuem o
formato geral getXXX(), onde XXX é o nome de um tipo. A tabela XIII.2
mostra qual o método mais indicado para cada tipo SQL.

T S I B R F D D N B C V L B V L D T T
I M N I E L O E U I H A O I A O A I I
N A T G A O U C M T A R N N R N T M M
Y L E I L A B I E R C G A B G E E E
I L G N T L M R H V R I V S
N I E T E A I A A Y N A T
T N R L C R R A R A
T C R B M
H Y I P
A N
R A
R
Y
getByte X x x x x x x x x x x x x
getShort x X x x x x x x x x x x x
getInt x x X x x x x x x x x x x
getLong x x x X x x x x x x x x x
getFloat x x x x X x x x x x x x x
getDouble x x x x x X X x x x x x x
getBigDecimal x x x x x x x X X x x x x
getBoolean x x x x x x x x x X x x x
getString x x x x x x x x x x X X x x x x x x x
getBytes X X x
getDate x x x X x
getTime x x x X x
getTimestamp x x x x X
getAsciiStream x x X x x x
getUnicodeStre x x X x x x
am

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 83
getBinaryStrea x x X
m
getObject x x x x x x x x x x x x x x x x x x x
“x” indica que o método pode ser usado para recuperar o valor no tipo SQL especificado.
“X” indica que o método é recomendado para ser usado na recuperação do valor no tipo SQL especificado

Tabela XIII.2 – Tabelas com os métodos indicados para recuperação de


valores.

É possível recuperar o valor da coluna passando como parâmetro o


número da coluna no lugar de seu nome. Neste caso a recuperação no nome do
aluno no exemplo XIII.1 ficaria na seguinte forma:

rs.getString(1);

Transações e Nível de Isolamento


Transação
Uma Transação é um conjunto de operações realizadas sobre um banco
de dados tratadas atomicamente, em outras palavras, ou todas operações são
realizadas e o seu resultado registrado permanentemente na base de dados ou
nenhuma operação é realizada. Por default, o banco de dados trata cada operação
como uma transação, realizando implicitamente uma operação de commit ao fim
de cada uma delas. A operação de commit registra permanentemente o resultado
da transação na tabela.

No entanto, existem situações onde é necessário tratar como uma


transação um conjunto de operações, e não apenas uma transação. Por exemplo,
suponha que em um Banco de Dados de uma agência bancária exista uma tabela
com informações sobre a conta de corrente e outra com informações sobre
contas de poupança. Suponha também que um cliente deseje transferir o dinheiro
da conta corrente para uma conta de poupança. Essa transação é constituída
pelas seguintes operações:

1. Caso exista saldo suficiente, subtração do montante da transferência


do saldo da conta corrente.
2. Adição do montante da transferência ao saldo da conta de poupança.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 84
As operações acima precisam ocorrer totalmente ou o efeito de nenhuma
delas deve ser registrado na base de dados. Caso contrário podemos ter uma
situação onde o dinheiro sai da conta corrente mas não entra na conta da
poupança. Este estado, onde as informações do banco de dados não reflete a
realidade, é chamado de estado inconsistente.

De modo a obter esse controle sobre as transações é necessário


desabilitar o modo de auto-commit. Isto é feito por meio método
setAutoCommit() do objeto Connection.

con.setAutoCommit(false);

A partir do momento em que é executado o comando acima, o


programador é responsável pela indicação do final da transação, por meio da
execução do método commit() do objeto Connection.

con.commit();

Se alguma exceção for levantada durante a execução de qualquer


operação da transação, o programador pode usar o método rollback() para
desfazer as operações já realizadas após o último commit().

con.setAutoCommit(false);
try
{
Statement stmt = con.createStatement();
stmt.executeUpdate(“UPDATE ...” );
stmt.executeUpdate(“UPDATE ...” );
con.commit();
stmt.close();
}
catch(Exception e){con.rollback();}
finally
{
try{ con.setAutoCommit(true);}
catch(SQLException sqle)
{System.out.prinln(sql.getMessage());}
}

Exemplo XX.X – Uso dos métodos commit() e rollback().

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 85
Níveis de isolamento
Além da atomicidade outra propriedade desejável em uma transação é o
isolamento. A propriedade de isolamento implica que uma transação não é
afetada pelas operações realizadas por outras transações que estão sendo
realizadas concorrentemente.

O isolamento completo entre transações prejudica muito a execução


concorrente de transações e pode ser desnecessário em determinados tipos de
aplicações. Por isso os SGBDs permitem que o programador defina o nível de
isolamento entre as transações. De acordo com o relaxamento do isolamento
certos problemas devido a interferência entre as transações podem ocorrer e o
programador deve estar ciente disso.

O número de níveis de isolamento, sua nomenclatura e características


depende do SGBD utilizado. Descreveremos os níveis de isolamento definidos
no pacote java.sql. Para exemplificar os problemas que podem ocorrer
devido a interferência entre transações utilizaremos um banco de dados exemplo
com a seguinte tabela:

NumCC Saldo
10189-9 200,00
20645-7 300,00

• Read uncommitted - É o nível menos restritivo. Pode ocorrer leituras de


registros não committed (Dirty reads). Usados em onde não existe
concorrência ou não existem alterações em registros ou quando essas
alterações não são relevantes. Exemplo de problema: Uma transação deve
transferir R$50,00 da conta 10189-9 para a conta 20645-7 e uma segunda
transação deve somar R$70,00 à conta 10189-9. A figura abaixo mostra o
estado inicial e o estado final desejado da tabela:

Estado antes das Estado desejado após as


transações transações

NumCC Saldo NumCC Saldo


10189-9 200,00 10189-9 220,00
20645-7 300,00 20645-7 350,00

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 86
Cada transação é divida em operações de leitura e escrita. Suponha que o
intercalamento das operações seja feito como mostrado abaixo:

Transação 1 Transação 2
leitura do saldo 10189
Escrita do Saldo-50,00
leitura do saldo 10189
leitura do saldo 20645
(falha na transação,
realizado rollback) Escrita do Saldo+70,00

Como a transação 1 falhou o valor lido pela transação 2 é um valor que não
foi tornado permanente na tabela. Isto faz com que a transação 2 opere sobre
um resultado desfeito. A tabela resultante, mostrada abaixo estará em um
estado inconsistente.

NumCC Saldo
10189-9 220,00
20645-7 300,00

• Read committed - Somente registros committed podem ser lidos. Evita o


problema de Dirty reads, no entanto duas leituras de um mesmo item em
uma mesma transação podem possuir valores diferentes, uma vez que o valor
pode ser mudado por outra transação entre duas leituras.

• Repeatable Read - Somente registros committed podem ser lidos, além


disso impede a alteração de um item lido pela transação. Evita o problema de
Dirty reads e o problema do non-repeatable Read .

• Serializable - É o nível mais restritivo. Impede Dirty reads e non-repeatable


reads. Além disso impede o problema de phantom reads onde um conjunto
de registros satisfazendo a condição WHERE é lido enquanto outra transação
insere novos registros que satisfazem a condição.

Para se definir o nível de isolamento na linguagem Java usa-se um objeto


DatabaseMetaData que é obtido por meio do objeto getMetaData() do
Connection. Primeiro é preciso saber se o SGBD suporta o nível de

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 87
isolamento desejado para depois definir o nível. O exemplo XX.X mostra uma
sequência típica comandos.

DatabaseMetaData meta=con.getMetaData();

if(meta.supportsTransactionIsolationLevel(
con.TRANSACTION_READ_COMMITTED)) {
con.setTransactionIsolation(
con.TRANSACTION_READ_COMMITTED);
}
else return;

Exemplo XX.X – Exemplo do estabelecimento do nível de isolamento.

A tabela abaixo mostra as constantes relacionadas com os níveis de


isolamento da linguagem Java:

Constante
TRANSACTION_NONE
TRANSACTION_READ_UNCOMMITTED
TRANSACTION_READ_COMMITTED
TRANSACTION_REPEATABLE_READ
TRANSACTION_SERIALIZABLE

Tabela XX.X – Tabela com as constantes dos níveis de isolamento.

Prepared Statements
Cada vez que se executa um comando SQL passado por meio de uma
String. Este String deve ser analisado pelo processador de SQL do SGBD que
irá, no caso da String estar sintaticamente correta, gerar um código binário que
será executado para atender à solicitação. Todo esse processo é caro e sua
execução repetidas vezes terá um impacto significativo sobre o desempenho da
aplicação e do SGBD como um todo.
Existem duas abordagens para tentar solucionar esse problema:
Comandos preparados (prepared statements) e procedimentos armazenados
(stored procedures). Discutiremos primeiramente os prepared statements.
Prepared Statement é indicado nos casos onde um comando será
executado várias vezes em uma aplicação. Neste caso é melhor compilar o
comando uma única vez e toda vez que for necessário executá-lo basta enviar o

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 88
comando compilado. Além disso, o comando pré-compilado pode ser
parametrizado, tornando-o mais genérico e, portanto, apto a expressar um maior
número de consultas.
Para criar um Prepared Statement é necessário obter um objeto
PreparedStatement por meio do método prepareStatement() do
objeto Connection, passando como argumento um comando SQL.

PreparedStatement pstmt = con.prepareStatement(


“INSERT INTO alunos(matricula,nome) VALUES(?, ? )”);

O comando anterior insere uma nova linha na tabela alunos com os


valores das colunas matricula e nome passados por parâmetro. O caractere ‘?’
representa o parâmetro. Este tipo de comando só possui valor tendo parâmetros,
caso contrário teria pouca chance de ser reutilizado. Para executar o comando
devemos especificar o valor dos parâmetros e executar o comando, como
mostrado no exemplo abaixo:

pstmt.clearParameters();
pstmt.setInt(1,8);
pstmt.setString(2,”Clara Maria”);
pstmt.executeUpdate();

Antes de especificar os parâmetros é necessário limpar qualquer outro


parâmetro previamente especificado. Para especificar os parâmetros são
utilizados um conjunto de métodos com o nome no formato setXXX(), onde
XXX é o tipo sendo passado. O primeiro parâmetro do método setXXX() é o
índice da ocorrência do caractere ‘?’ que será substituído pelo valor. O segundo
parâmetro é o valor que será transmitido.

Procedimentos Armazenados (Stored Procedures)

A maioria dos SGBDs possuem algum tipo de linguagem de


programação interna, como por exemplo a PL/SQL do Oracle ou mesmo Java e
C/C++. Estas linguagens permitem que os desenvolvedores insiram parte do
código da aplicação diretamente no banco de dados e invoquem este código a
partir da aplicação. Esta abordagem possui as seguintes vantagens:

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 89

•Reuso de código – o código precisa ser escrito apenas uma vez e usado
em várias aplicações, comunicando com várias linguagens.
•Independencia entre a aplicação e o esquema do BD – se o esquema
mudar, provavelmente apenas os procedimentos armazenados.
•Desempenho – os procedimentos são previamente compilados,
eliminando esta etapa.
•Segurança – as aplicações possuem privilégio apenas para execução de
procedimentos armazenados, evitando assim acessos não autorizados.

A sintaxe dos procedimentos armazenados depende do SGBD em


questão. Utilizaremos um exemplo em PL/SQL. No exemplo abaixo o
procedimento retorna o nome do aluno a partir de sua matricula.

CREATE OR REPLACE PROCEDURE sp_obtem_nome


(id IN INTEGER, Nome_aluno out VARCHAR2)IS
BEGIN
SELECT nome INTO Nome_aluno
FROM alunos
WHERE matricula = id;
END;
/

Para invocar o procedimento anterior de dentro de uma aplicação Java é


necessário obter um objeto CallableStatement por meio do método
prepareCall() do objeto Connection, passando como argumento um
comando SQL.

CallableStatement cstmt =
con.prepareCall("{ CALL sp_obtem_nome(?,?)}");
cstmt.registerOutParameter(2, Types.VARCHAR);
cstmt.setInt(1, 3);
cstmt.execute();
System.out.prinln(“O nome do aluno numero 3 :”
+cstmt.getString(2);

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 90

Agenda Eletrônica versão JDBC


Pessoa

import java.io.*;

public class pessoa implements Serializable


{
String Nome;
String Tel;

// Construtor
public pessoa(String n, String t) {Nome = n; Tel = t;}

public String getNome(){return Nome;}


public String getTel(){return Tel;}
}

agenda
import java.util.*;
import java.io.*;
import java.sql.*;
import java.net.URL;

public class agenda


{
Connection con=null;

// Construtor
public agenda()throws Exception
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
con=DriverManager.getConnection("jdbc:odbc:agenda");
}

/**
CloseAgenda
*/
public void CloseAgenda()
{
if (con != null) try{con.close();}catch(Exception e){};
}

/**
inserir

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 91
*/
public void inserir(pessoa p)
{
if (con == null) return;

try
{
Statement stmt = con.createStatement();
stmt.executeUpdate("INSERT INTO pessoas(nome,telefone)
"+
"values('"+p.getNome()+"','"+p.getTel()+"')");

stmt.close();
}catch(Exception e) {System.err.println(e);}
}

/**
Consultar
*/

/**
listar
*/
public Enumeration getLista()
{
if (con == null) return null;

Vector pessoas = new Vector();

try {
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery
("SELECT Nome, Telefone FROM pessoas");
while(rs.next())
pessoas.addElement(new
pessoa(rs.getString("Nome"),
rs.getString("Telefone")));
stmt.close();
} catch(Exception e) {System.out.println(e.getMessage());
e.printStackTrace();}

return pessoas.elements();
}
}

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

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 92

/**
CLASS server
*/
class server
{
public static void main(String args[])
{
String oHost="localhost";
ServerSocket ssocket=null;
serversec oServersec;
int pt = 4444;
agenda ag;

try {ag = new agenda();}


catch(Exception e){System.err.println(e); return;};
if (args.length > 0) pt = Integer.parseInt(args[0]);

try
{
ssocket = new ServerSocket(pt);
pt = ssocket.getLocalPort();
oHost =
ssocket.getInetAddress().getHostName().trim();
}
catch(Exception e) {System.err.println(e);
System.exit(1);}

System.out.println("Porta:"+pt+" Host: "+oHost);

while(true)
{
try
{
Socket clisocket = ssocket.accept();
oServersec = new serversec(ag,clisocket);
oServersec.start();
} catch(Exception e) {System.err.println(e);}
}
}
}

/**
CLASS serversec
*/
class serversec extends Thread
{
Socket oSocket;
BufferedWriter soutput;

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 93
BufferedReader cinput;
agenda ag;

public serversec(agenda ag, Socket aoSocket)


{
this.ag = ag;
oSocket = aoSocket;
}

public void run()


{
try
{
soutput = new BufferedWriter(new

OutputStreamWriter(oSocket.getOutputStream()));
cinput = new BufferedReader(new

InputStreamReader(oSocket.getInputStream()));

String losLinha = cinput.readLine();


switch(losLinha.charAt(0))
{
case 'i': String Nome = cinput.readLine();
String Tel = cinput.readLine();
ag.inserir(new pessoa(Nome,Tel));
soutput.write("OK\n#\n");
break;
case 'l': pessoa p;
for (Enumeration e = ag.getLista();
e.hasMoreElements() ;)
{
p = (pessoa) e.nextElement();

soutput.write(p.getNome()+"\n"+p.getTel()+"\n");
}
soutput.write("#\n");
break;
}

soutput.flush();
Thread.yield();
soutput.close();
oSocket.close();
}
catch(Exception e) {System.err.println(e);}
}
}

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 94
Applet
import java.awt.*;
import java.applet.*;
import java.util.*;
import java.net.*;
import java.io.*;

public class agendaapplet extends Applet


{
int port = 4444;

TextField txtNome = new TextField();


TextField txtTel = new TextField();
Label label1 = new Label();
Label label2 = new Label();
Button btIns = new Button();
Button btList = new Button();
Button btCons = new Button();
Button btSair = new Button();
TextArea Saida = new TextArea();
Button btLimpar = new Button();

public void init()


{
setLayout(null);
setSize(376,224);
add(txtNome);
txtNome.setBounds(108,48,232,24);
add(txtTel);
txtTel.setBounds(108,84,232,24);
label1.setText("Nome");
add(label1);
label1.setBounds(24,48,60,26);
label2.setText("Telefone");
add(label2);
label2.setBounds(24,84,60,26);
btIns.setActionCommand("button");
btIns.setLabel("Inserir");
add(btIns);
btIns.setBackground(java.awt.Color.lightGray);
btIns.setBounds(12,12,49,23);
btList.setActionCommand("button");
btList.setLabel("Listar");
add(btList);
btList.setBackground(java.awt.Color.lightGray);
btList.setBounds(149,12,60,23);
btCons.setActionCommand("button");
btCons.setLabel("Consultar");
add(btCons);

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 95
btCons.setBackground(java.awt.Color.lightGray);
btCons.setBounds(75,12,60,23);
btSair.setActionCommand("button");
btSair.setLabel("Sair");
add(btSair);
btSair.setBackground(java.awt.Color.lightGray);
btSair.setBounds(297,12,60,23);
add(Saida);
Saida.setBounds(24,120,338,90);
btLimpar.setActionCommand("button");
btLimpar.setLabel("Limpar");
add(btLimpar);
btLimpar.setBackground(java.awt.Color.lightGray);
btLimpar.setBounds(223,12,60,23);

SymMouse aSymMouse = new SymMouse();


btSair.addMouseListener(aSymMouse);
btIns.addMouseListener(aSymMouse);
btList.addMouseListener(aSymMouse);
btLimpar.addMouseListener(aSymMouse);
}

public void transmit(int port,String mensagem)


{
BufferedWriter soutput;
BufferedReader cinput;
Socket clisoc=null;
try
{
if (clisoc != null) clisoc.close();
clisoc = new

Socket(InetAddress.getByName(getCodeBase().getHost()),port);
soutput = new BufferedWriter (new
OutputStreamWriter(clisoc.getOutputStream()));
cinput = new BufferedReader(new
InputStreamReader(clisoc.getInputStream()));

soutput.write(mensagem+"\n");
soutput.flush();

String losRet = cinput.readLine();


while (losRet.charAt(0)!='#')
{
Saida.setText(Saida.getText()+losRet+"\n");
losRet = cinput.readLine();
}
Thread.sleep(500);

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 96
soutput.close();
clisoc.close();
}
catch(Exception e) {System.err.println(e);}
}

class SymMouse extends java.awt.event.MouseAdapter


{
public void mouseClicked(java.awt.event.MouseEvent event)
{
Object object = event.getSource();
if (object == btSair) btSair_MouseClick(event);
else if (object == btIns) btIns_MouseClick(event);
else if (object == btList) btList_MouseClick(event);
else if (object == btLimpar)
btLimpar_MouseClick(event);
}
}

void btSair_MouseClick(java.awt.event.MouseEvent event)


{
System.exit(0);
}

void btIns_MouseClick(java.awt.event.MouseEvent event)


{
String nome = txtNome.getText();
String tel = txtTel.getText();

if (nome.length()>0 && tel.length()>0)


transmit(port,"i\n"+nome+"\n"+tel+"\n");
}

void btList_MouseClick(java.awt.event.MouseEvent event)


{
transmit(port,"l\n");
}

void btLimpar_MouseClick(java.awt.event.MouseEvent event)


{
Saida.setText("");
}
}

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 97

Capítulo VI Servlets e JSP


Servlets e JSP são duas tecnologias desenvolvidas pela Sun para
desenvolvimento de aplicações na Web a partir de componentes Java que
executem no lado servidor. Essas duas tecnologias fazem parte da plataforma
J2EE (Java 2 Platform Enterprise Edition) que fornece um conjunto de
tecnologias para o desenvolvimento de soluções escaláveis e robustas para a
Web. Neste livro abordaremos apenas as tecnologias Servlets e JSP, sendo o
suficiente para o desenvolvimento de sites dinâmicos de razoável complexidade.
Se a aplicação exigir uma grande robustez e escalabilidade o leitor deve
considerar o uso em conjunto de outras tecnologias da plataforma J2EE.

Servlets
Servlets são classes Java que são instanciadas e executadas em
associação com servidores Web, atendendo requisições realizadas por meio do
protocolo HTTP. Ao serem acionados, os objetos Servlets podem enviar a
resposta na forma de uma página HTML ou qualquer outro conteúdo MIME. Na
verdade os Servlets podem trabalhar com vários tipos de servidores e não só
servidores Web, uma vez que a API dos Servlets não assume nada a respeito do
ambiente do servidor, sendo independentes de protocolos e plataformas. Em
outras palavras Servlets é uma API para construção de componentes do lado
servidor com o objetivo de fornecer um padrão para comunicação entre clientes
e servidores. Os Servlets são tipicamente usados no desenvolvimento de sites
dinâmicos. Sites dinâmicos são sites onde algumas de suas páginas são
construídas no momento do atendimento de uma requisição HTTP. Assim é
possível criar páginas com conteúdo variável, de acordo com o usuário, tempo,
ou informações armazenadas em um banco de dados.

Servlets não possuem interface gráfica e suas instâncias são executadas


dentro de um ambiente Java denominado de Container. O container gerencia as
instâncias dos Servlets e provê os serviços de rede necessários para as
requisições e respostas. O container atua em associação com servidores Web
recebendo as requisições reencaminhada por eles. Tipicamente existe apenas
uma instância de cada Servlet, no entanto, o container pode criar vários threads
de modo a permitir que uma única instância Servlet atenda mais de uma

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 98
requisição simultaneamente A figura XX fornece uma visão do relacionamento
destes componentes.

Máquina Virtual Java

Requisições
Container
Servidor
Web Instâncias de Servlets
Respostas

Figura VI-1. Relacionamento entre Servlets, container e servidor Web

Servlets provêem uma solução interessante para o relacionamento


cliente/servidor na Internet, tornando-se uma alternativa para a implantação de
sistemas para a Web. Antes de entrarmos em detalhes na construção de Servlets,
compararemos esta solução com outras duas soluções possíveis para implantação
de aplicações na Internet.

Applets X Servlets
Apesar de ser uma solução robusta existem problemas no uso de Applets para
validação de dados e envio para o servidor. O programador precisa contar com o
fato do usuário possuir um navegador com suporte a Java e na versão
apropriada. Você não pode contar com isso na Internet, principalmente se você
deseja estender a um grande número de usuário o acesso às suas páginas. Em se
tratando de Servlets, no lado do cliente pode existir apenas páginas HTML,
evitando restrições de acesso às páginas. Em resumo, o uso de Applets não é
recomendado para ambientes com múltiplos navegadores ou quando a semântica
da aplicação possa ser expressa por componentes HTML.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 99
CGI X Servlets
Como visto no Erro! A origem da referência não foi encontrada.,
scripts CGI (Common Gateway Interface), acionam programas no servidor. O
uso de CGI sobrecarrega o servidor uma vez cada requisição de serviço acarreta
a execução de um programa executável (que pode ser escrito em com qualquer
linguagem que suporte o padrão CGI) no servidor, além disso, todo o
processamento é realizado pelo CGI no servidor. Se houver algum erro na
entrada de dados o CGI tem que produzir uma página HTML explicando o
problema. Já os Servlets são carregados apenas uma vez e como são executados
de forma multi-thread podem atender mais de uma mesma solicitação por
simultaneamente. Versões posteriores de CGI contornam este tipo de problema,
mas permanecem outros como a falta de portabilidade e a insegurança na
execução de código escrito em uma linguagem como C/C++.

A API Servlet
A API Servlet é composta por um conjunto de interfaces e Classes. O
componente mais básico da API é interface Servlet. Ela define o
comportamento básico de um Servlet. A figura XX.XX mostra a interface
Servlet.

public interface Servlet {


public void init(ServletConfig config)
throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req,
ServletResponse res)
throws ServletException, IOException;
public String getServletInfo();
public void destroy();
}

Figura XV.XX. Interface Servlet.

O método service() é responsável pelo tratamento de todas das


requisições dos clientes. Já os métodos init() e destroy() são chamados
quando o Servlet é carregado e descarregado do container, respectivamente. O
método getServletConfig() retorna um objeto ServletConfig que

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 100
contém os parâmetros de inicialização do Servlet. O método
getServletInfo() retorna um String contendo informações sobre o
Servlet, como versão e autor.

Tendo como base a interface Servlet o restante da API Servlet se


organiza hierarquicamente como mostra a figura XV.XX.

Servlet

GenericServlet

HttpServlet

Figura XV.XX. Hierarquia de classes da API Servlet.

A classe GenericServlet implementa um servidor genérico e


geralmente não é usada. A classe HttpServlet é a mais utilizada e foi
especialmente projetada para lidar com o protocolo HTTP. A figura XX.XX
mostra a definição da classe interface HttpServlet.

HttpServlet

public abstract class HttpServlet


extends GenericServlet
implements java.io.Serializable

Figura XV.XX. Definição da classe HttpServlet.

Note que a classe HttpServlet é uma classe abstrata. Para criar um


Servlet que atenda requisições HTTP o programador deve criar uma classe
derivada da HttpServlet e sobrescrever pelo menos um dos métodos
abaixo:

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 101

doGet Trata as requisições HTTP GET.


doPost Trata as requisições HTTP POST.
doPut Trata as requisições HTTP PUT.
doDelete Trata as requisições HTTP DELETE.

Tabela XV.XX. Métodos da classe HttpServlet que devem ser


sobrescritos para tratar requisições HTTP.

Todos esses métodos são invocados pelo servidor por meio do método
service(). O método doGet() trata as requisições GET. Este tipo de
requisição pode ser enviada várias vezes, permitindo que seja colocada em um
bookmark. O método doPost() trata as requisições POST que permitem que o
cliente envie dados de tamanho ilimitado para o servidor Web uma única vez,
sendo útil para enviar informações tais como o número do cartão de crédito. O
método doPut() trata as requisições PUT. Este tipo de requisição permite que
o cliente envie um arquivo para o servidor à semelhança de como é feito via
FTP. O método doPut() trata as requisições DELETE, permitindo que o
cliente remova um documento ou uma página do servidor. O método
service(), que recebe todas as requisições, em geral não é sobrescrito, sendo
sua tarefa direcionar a requisição para o método adequado.

Exemplo de Servlet
Para entendermos o que é um Servlet nada melhor que um exemplo
simples. O exemplo XV.XX gera uma página HTML em resposta a uma
requisição GET. A página HTML gerada contém simplesmente a frase Ola
mundo!!!. Este é um Servlet bem simples que ilustra as funcionalidades básicas
da classe.

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

public class Ola extends HttpServlet


{
public String getServletInfo() { return "Ola versão 0.1";}

public void doGet(HttpServletRequest req, HttpServletResponse res)


throws IOException, ServletException

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 102
{
res.setContentType("text/html");
java.io.PrintWriter out = res.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet</title>");
out.println("</head>");
out.println("<body>Ola mundo!!!");
out.println("</body>");
out.println("</html>");
out.close();
}
}

Exemplo XV.XX. Servlet Ola.

O método doGet() recebe dois objetos: um da classe


HttpServletRequest e outro da classe HttpServletResponse. O
HttpServletRequest é responsável pela comunicação do cliente para o
servidor e o HttpServletResponse é responsável pela comunicação do
servidor para o cliente. Sendo o exemplo XV.XX apenas um exemplo simples
ele ignora o que foi enviado pelo cliente, tratando apenas de enviar uma página
HTML como resposta. Para isso é utilizado o objeto da classe
HttpServletResponse. Primeiramente é usado o método
setContentType() para definir o tipo do conteúdo a ser enviado ao cliente.
Esse método deve ser usado apenas uma vez e antes de se obter um objeto do
tipo PrintWriter ou ServletOutputStream para a resposta. Após isso
é usado o método getWriter() para se obter um objeto do tipo
PrintWriter que é usado para escrever a resposta. Neste caso os dados da
resposta são baseados em caracteres. Se o programador desejar enviar a resposta
em bytes deve usar o método getOutputStream() para obter um objeto
OutputStream. A partir de então o programa passa usar o objeto
PrintWriter para enviar a página HTML.

Compilando o Servlet
A API Servlet ainda não foi incorporado ao SDK, portanto, para compilar
um Servlet é preciso adicionar a API Servlet ao pacote SDK. Existem várias
formas de se fazer isso. A Sun fornece a especificação da API e diversos

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 103
produtores de software executam a implementação. Atualmente, a especificação
da API Servlet está na versão 2.XX. Uma das implementações da API que pode
ser baixada gratuitamente pela Internet é a fornecida pelo projeto Jakarta
(http://jakarta.apache.org) denominada de Tomcat. A
implementação da API Servlet feita pelo projeto Jakarta é a implementação de
referência indicada pela Sun. Ou seja, é a implementação que os outros
fabricantes devem seguir para garantir a conformidade com a especificação da
API. No entanto, uma vez que o Tomcat é a implementação mais atualizada da
API, é também a menos testada e, por consequência, pode não ser a mais estável
e com melhor desempenho.

Instalando o Tomcat
Assim como para se executar um Applet era preciso de um navegador
Web com Java habilitado no caso de Servlets é preciso de servidor Web que
execute Java ou que passe as requisições feitas a Servlets para programas que
executem os Servlets. O Tomcat é tanto a implementação da API Servlet como a
implementação de um container, que pode trabalhar em associação com um
servidor Web como o Apache ou o IIS, ou pode também trabalhar isoladamente,
desempenhando também o papel de um servidor Web. Nos exemplos aqui
mostrados usaremos o Tomcat isoladamente. Em um ambiente de produção esta
configuração não é a mais adequada, uma vez que os servidores Web possuem
um melhor desempenho no despacho de páginas estáticas. As instruções para
configurar o Tomcat para trabalhar em conjunto com um servidor Web podem
ser encontradas junto às instruções gerais do programa. As figuras XV.XX
ilustram essas duas situações.

Servidor Web
Internet
Servlet habilitado

Figura XV.XX. Servidor Web habilitado para Servlet.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 104

Internet Servidor Web

Servlet Container

Figura XV.XX. Servidor Web reencaminhando as requisições para o Servlet


container.

A versão estável do Tomcat é a 3.2.3.XX é após baixá-la do site do


projeto Jakarta o usuário deve descomprimir o arquivo. Por exemplo, no
ambiente Windows o usuário pode descomprimir o arquivo na raiz do disco C:,
o que gerará a seguinte árvore de diretórios:

C:\ jakarta-tomcat-3.2.3
|
|______bin
|______conf
|______doc
|______lib
|______logs
|______src
|______webapps

No diretório bin encontram-se os programas execução e interrupção do


container Tomcat. No diretório conf encontram-se os arquivos de configuração.
No diretório doc encontram-se os arquivos de documentação. No diretório lib
encontram-se os bytecodes do container e da implementação da API. No
diretório logs são registradas as mensagens da geradas durante a execução do
sistema. No diretório src encontram-se os arquivos fontes do container e da
implementação da API de configuração. Finalmente, No diretório webapps
encontram-se as páginas e códigos das aplicações dos usuários.

No ambiente MS-Windows aconselhamos usar um nome dentro do


formato 8.3 (oito caracteres para o nome e três para o tipo). Assim o diretório
jakarta-tomcat-3.2.3 poderia ser mudado para simplesmente tomcat.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 105
Antes de executar o Tomcat é necessário definir duas variáveis de
ambiente. Por exemplo, supondo que no MS-Windows o Tomcat foi instalado no
diretório c:\tomcat e que o SDK está instalado no diretório c:\jdk1.3
então as seguintes variáveis de ambiente devem ser definidas:

set JAVA_HOME=C:\jdk1.3
set TOMCAT_HOME=C:\tomcat

Agora é possível executar o Tomcat por meio do seguinte comando:

C:\tomcat\bin\startup.bat

Para interromper a execução servidor basta executar o arquivo

c:\tomcat\bin\shutdown.bat

Falta de espaço para variáveis de ambiente


Caso ao iniciar o servidor apareça a mensagem “sem espaço de ambiente”
clique com o botão direito do mouse no arquivo .bat e edite as propriedades definindo o
ambiente inicial com 4096. Feche o arquivo é execute novamente.

Ao entrar em execução o servidor lê as configurações constantes no


arquivo server.xml e, por default, se anexa à porta 8080. Para verificar se o
programa está funcionando corretamente execute um navegador como o
Netscape ou o Internet Explorer e digite a seguinte URL:

http://127.0.0.1:8080/index.html

A figura XX.XX mostra a tela principal do Tomcat.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 106

Figura XV.XX. Tela inicial do Tomcat.

A número porta default para recebimento das requisições HTTP pode ser
alterada por meio da edição do arquivo server.xml do diretório conf como
mostrado abaixo:

<Connector className="org.apache.tomcat.service.PoolTcpConnector">
<Parameter name="handler"

value="org.apache.tomcat.service.http.HttpConnectionHandler"/>
<Parameter name="port" value="Número da porta"/>
</Connector>

No entanto, caso o Tomcat esteja operando em conjunto com um


servidor, o ideal é que o Tomcat não responda requisições diretamente.

Preparando para executar o Servlet


Compilando o Servlet

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 107
Antes de executar o Servlet e preciso compilá-lo. Para compilá-lo é
preciso que as classes que implementam a API Servlet estejam no classpath.
Para isso é preciso definir a variável de ambiente. No ambiente MS-Windows
seria
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\servlet.jar

e no ambiente Unix seria

CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/servlet.jar

Alternativamente, é possível indicar o classpath na própria linha de


execução do compilador Java. Por exemplo, No ambiente MS-Windows ficaria
na seguinte forma:

javac -classpath "%CLASSPATH%;c:\tomcat\lib\servlet.jar Ola.java

Criando uma aplicação no Tomcat


Agora é preciso definir onde deve ser colocado o arquivo compilado.
Para isso é preciso criar uma aplicação no Tomcat ou usar uma das aplicações já
existentes. Vamos aprender como criar uma aplicação no Tomcat. Para isso é
preciso cria a seguinte estrutura de diretórios abaixo do diretório webapps do
Tomcat:

webapps
|_____ Nome aplicação
|_____ Web-inf
|_____classes

Diretório de Aplicações
Na verdade é possível definir outro diretório para colocar as aplicações do Tomcat. Para
indicar outro diretório é preciso editar o arquivo server.xml e indicar o diretório por meio da
diretiva home do tag ContextManager.

O diretório de uma aplicação é denominado de contexto da aplicação. É


preciso também editar o arquivo server.xml do diretório conf, incluindo as
linhas:

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 108
<Context path="/nome aplicação"
docBase="webapps/ nome aplicação" debug="0”
reloadable="true" >
</Context>

Finalmente, é preciso criar (ou copiar de outra aplicação) um arquivo


web.xml no diretório Web-inf com o seguinte conteúdo:

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

<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application
2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
<web-app>
</web-app>

Copie então o arquivo compilado Ola.class para o subdiretório


/webapps/nome aplicação/Web-inf/classes do Tomcat.

Executando o Servlet
Invocando diretamente pelo Navegador
Podemos executar um Servlet diretamente digitando a URL do Servlet no
navegador. A URL em geral possui o seguinte formato:

http://máquina:porta/nome aplicação/servlet/nome servlet

A palavra servlet que aparece na URL não indica um subdiretório no


servidor. Ela indica que esta é uma requisição para um Servlet. Por exemplo,
suponha que o nome da aplicação criada no Tomcat seja teste. Então a URL
para a invocação do Servlet do exemplo XX.XX teria a seguinte forma:

http://localhost:8080/teste/servlet/Ola

A URL para a chamada do Servlet pode ser alterada de modo a ocultar


qualquer referência à diretórios ou a tecnologias de implementação. No caso do
Tomcat essa configuração é no arquivo web.xml do diretório Web-inf da
aplicação. Por exemplo, para eliminar a palavra servlet da URL poderíamos

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 109
inserir as seguintes linhas no arquivo web.xml entre os tags <web-app> e
</web-app>:

<servlet>
<servlet-name>
Ola
</servlet-name>
<servlet-class>
Ola
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>
Ola
</servlet-name>
<url-pattern>
/Ola
</url-pattern>
</servlet-mapping>

Invocando em uma página HTML


No caso de uma página HTML basta colocar a URL na forma de link.
Por exemplo,

<a href="http://localhost:8080/teste/servlet/Ola>Servlet Ola</a>

Neste caso o Servlet Ola será solicitado quando o link associado ao texto
“Servlet Ola” for acionado.

Diferenças entre as requisições GET e POST


Os dois métodos mais comuns, definidos pelo protocolo HTTP, de se
enviar uma requisições a um servidor Web são os métodos POST e GET. Apesar
de aparentemente cumprirem a mesma função, existem diferenças importantes
entre estes dois métodos. O método GET tem por objetivo enviar uma requisição
por um recurso. As informações necessárias para a obtenção do recurso (como
informações digitadas em formulários HTML) são adicionadas à URL e, por
consequência, não são permitidos caracteres inválidos na formação de URLs,

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 110
como por espaços em branco e caracteres especiais. Já na requisição POST os
dados são enviados no corpo da mensagem.

O método GET possui a vantagem de ser idempotente, ou seja, os


servidores Web podem assumir que a requisição pode ser repetida, sendo
possível adicionar à URL ao bookmark. Isto é muito útil quando o usuário deseja
manter a URL resultante de uma pesquisa. Como desvantagem as informações
passadas via GET não podem ser muito longas, um vez o número de caracteres
permitidos é por volta de 2K.

Já as requisições POST a princípio podem ter tamanho ilimitado. No


entanto, elas não são idempotente, o que as tornam ideais para formulários onde
os usuários precisam digitar informações confidenciais, como número de cartão
de crédito. Desta forma o usuário é obrigado a digitar a informação toda vez que
for enviar a requisição, não sendo possível registrar a requisição em um
bookmark.

Concorrência
Uma vez carregado o Servlet não é mais descarregado, a não ser que o
servidor Web tenha sua execução interrompida. De modo geral, cada requisição
que deve ser direcionada a determinada instância de Servlet é tratada por um
thread sobre a instância de Servlet. Isto significa que se existirem duas
requisições simultâneas que devem ser direcionadas para um mesmo objeto o
container criará dois threads sobre o mesmo objeto Servlet para tratar as
requisições. A figura XX.XX ilustra esta situação.

usuário
thread1
1 Servlet1

usuário thread2
2
thread1 Servlet2
usuário
3

Figura XV.XX. Relacionamento entre as instâncias dos Servlets e os threads.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 111
Em conseqüência disto temos o benefícios de uma sobrecarga para
servidor, uma vez que a criação de threads é menos onerosa do que a criação de
processos, e uma aparente melhora no tempo de resposta.

Por outro lado, o fato dos Servlets operarem em modo multi-thread


aumenta a complexidade das aplicações e cuidados especiais, como visto no
capítulo sobre concorrência, devem tomados para evitar comportamentos
erráticos. Por exemplo, suponha um Servlet que receba um conjunto de números
inteiros e retorne uma página contendo a soma dos números. A exemplo XX.XX
mostra o código do Servlet. O leitor pode imaginar um código muito mais
eficiente para computar a soma de números, mas o objetivo do código do
exemplo é ilustrar o problema da concorrência em Servlets. O exemplo contém
também um trecho de código para recebimento de valores de formulários, o que
será discutido mais adiante.

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

public class Soma extends HttpServlet {

Vector v = new Vector(5);


protected void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, java.io.IOException {
v.clear();
Enumeration e = req.getParameterNames();

while (e.hasMoreElements()) {
String name = (String)e.nextElement();
String value = req.getParameter(name);
if (value != null) v.add(value);
}

res.setContentType("text/html");
java.io.PrintWriter out = res.getWriter();
out.println("<html>");
out.println("<head><title>Servlet</title></head>");
out.println("<body>");
out.println("<h1> A soma e'");
int soma =0;
for(int i =0; i< v.size() ; i++) {
soma += Integer.parseInt((String)v.get(i));
}
out.println(soma);
out.println("<h1>");
out.println("</body>");

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 112
out.println("</html>");
out.close();
}
}

Exemplo XX.XX- Servlet com problemas de concorrência.

Note que o Servlet utiliza uma variável de instância para referenciar o


Vector que armazena os valores. Se não forem usadas primitivas de
sincronização (como no código do exemplo) e duas requisições simultâneas
chegarem ao Servlet o resultado pode ser inconsistente, uma vez que o Vector
poderá conter parte dos valores de uma requisição e parte dos valores de outra
requisição. Neste caso, para corrigir esse problema basta declarar a variável
como local ao método doPost() ou usar primitivas de sincronização.

Obtendo Informações sobre a Requisição


O objeto HttpServletRequest passado para o Servlet contém
várias informações importantes relacionadas com a requisição, como por
exemplo o método empregado (POST ou GET), o protocolo utilizado, o
endereço remoto, informações contidas no cabeçalho e muitas outras. O Servlet
do exemplo XX.XX retorna uma página contendo informações sobre a
requisição e sobre o cabeçalho da requisição.

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

public class RequestInfo extends HttpServlet


{
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException
{
res.setContentType("text/html");
PrintWriter out = res.getWriter();
out.println("<html><head>");
out.println("<title>Exemplo sobre Requisicao de Info </title>");
out.println("</head><body>");
out.println("<h3> Exemplo sobre Requisicao de Info </h3>");
out.println("Metodo: " + req.getMethod()+”<br>”);
out.println("Request URI: " + req.getRequestURI()+”<br>”);
out.println("Protocolo: " + req.getProtocol()+”<br>”);

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 113
out.println("PathInfo: " + req.getPathInfo()+”<br>”);
out.println("Endereco remoto: " + req.getRemoteAddr()+”<br><br>”);
Enumeration e = req.getHeaderNames();
while (e.hasMoreElements())
{
String name = (String)e.nextElement();
String value = req.getHeader(name);
out.println(name + " = " + value+"<br>");
}
out.println("</body></html>");
}

public void doPost(HttpServletRequest req, HttpServletResponse res)


throws IOException, ServletException
{
doGet(req, res);
}
}

Exemplo XX.XX- Servlet que retorna as informações sobre a requisição.

Note que o método doPost() chama o método doGet(), de modo


que o Servlet pode receber os dois tipos de requisição. A figura XX.XX mostra o
resultado de uma execução do Servlet do exemplo XX.XX.

Exemplo sobre Requisicao de Info

Metodo: GET
Request URI: /servlet/RequestInfo
Protocolo: HTTP/1.0
PathInfo: null
Endereco remoto: 127.0.0.1

Connection = Keep-Alive
User-Agent = Mozilla/4.7 [en] (Win95; I)
Pragma = no-cache
Host = localhost:8080
Accept = image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*
Accept-Encoding = gzip
Accept-Language = en
Accept-Charset = iso-8859-1,*,utf-8

Figura XX.XX- Saída da execução do Servlet que exibe as informações sobre a


requisição.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 114

Lidando com Formulários


Ser capaz de lidar com as informações contidas em formulários HTML é
fundamental para qualquer tecnologia de desenvolvimento de aplicações para
Web. É por meio de formulários que os usuários fornecem dados, preenchem
pedidos de compra e (ainda mais importante) digitam o número do cartão de
crédito. As informações digitadas no formulário chegam até o Servlet por meio
do objeto HttpServletRequest e são recuperadas por meio do método
getParameter() deste objeto. Todo item de formulário HTML possui um
nome e esse nome é passado como argumento para o método
getParameter() que retorna na forma de String o valor do item de
formulário.

O Servlet do exemplo XX.XX exibe o valor de dois itens de formulários


do tipo text. Um denominado nome e o outro denominado de sobrenome.
Em seguida o Servlet cria um formulário contendo os mesmos itens de
formulário. Note que um formulário é criado por meio do tag <form>. Como
parâmetros opcionais deste tag temos método da requisição (method), é a URL
para onde será submetida a requisição (action). No caso do exemplo, o
método adotado é o POST e a requisição será submetida ao próprio Servlet
Form.

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

public class Form extends HttpServlet


{
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException
{
res.setContentType("text/html");

PrintWriter out = res.getWriter();


out.println("<html>");
out.println("<head><title>Trata formulario</title></head>");
out.println("<body bgcolor=\"white\">");

out.println("<h3>Trata formulario</h3>");
String nome = req.getParameter("nome");

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 115
String sobreNome = req.getParameter("sobrenome");
if (nome != null || sobreNome != null)
{
out.println("Nome = " + nome + "<br>");
out.println("Sobrenome = " + sobreNome);
}
out.println("<P>");
out.print("<form action=\"Form\" method=POST>");
out.println("Nome : <input type=text size=20 name=nome><br>");
out.println("Sobrenome: <input type=text size=20 name=sobrenome><br>");
out.println("<input type=submit>");
out.println("</form>");
out.println("</body></html>");
}

public void doPost(HttpServletRequest req, HttpServletResponse res)


throws IOException, ServletException
{
doGet(req, res);
}
}

Exemplo XX.XX- Servlet para lidar com um formulário simples.

Lidando com Cookies


Um cookie nada mais é que um bloco de informação que é enviado do
servidor para o navegador no cabeçalho página. A partir de então, dependendo
do tempo de validade do cookie, o navegador reenvia essa informação para o
servidor a cada nova requisição. Dependo do caso o cookie é também
armazenado no disco da máquina cliente e quando o site é novamente visitado o
cookie enviado novamente para o servidor, fornecendo a informação desejada.

Os cookies foram a solução adotada pelos desenvolvedores do Netscape


para implementar a identificação de clientes sobre um protocolo HTTP que não
é orientado à conexão. Esta solução, apesar das controvérsias sobre a
possibilidade de quebra de privacidade, passou ser amplamente adotada e hoje
os cookies são parte integrante do padrão Internet, normalizados pela norma
RFC 2109.

A necessidade da identificação do cliente de onde partiu a requisição e o


monitoramento de sua interação com o site (denominada de sessão) é importante
para o desenvolvimento de sistemas para a Web pelas seguintes razões:

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 116

• É necessário associar os itens selecionados para compra com o


usuário que deseja adquiri-los. Na maioria da vezes a seleção dos
itens e compra é feita por meio da navegação de várias páginas do
site e a todo instante é necessário distinguir os usuários que estão
realizando as requisições.
• É necessário acompanhar as interação do usuário com o site para
observar seu comportamento e, a partir dessas informações, realizar
adaptações no site para atrair um maior número de usuários ou
realizar campanhas de marketing.
• É necessário saber que usuário está acessando o site para, de acordo
com o seu perfil, fornecer uma visualização e um conjunto de
funcionalidades adequadas às suas preferências.

Todas essas necessidades não podem ser atendidas com o uso básico do
protocolo HTTP, uma vez que ele não é orientado à sessão ou conexão. Com os
cookies é possível contornar essa deficiência, uma vez que as informações que
são neles armazenadas podem ser usadas para identificar os clientes. Existem
outras formas de contornar a deficiência do protocolo de HTTP, como a
codificação de URL e o uso de campos escondidos nas páginas HTML, mas o
uso de cookies é a técnica mais utiliza, por ser mais simples e padronizada. No
entanto, o usuário pode impedir que o navegador aceite cookies, o que torna o
ato de navegar pela Web muito desagradável. Neste caso, é necessário utilizar as
outras técnicas para controle de sessão.

A API Servlet permite a manipulação explicita de cookies. Para controle


de sessão o programador pode manipular diretamente os cookies, ou usar uma
abstração de nível mais alto, implementada por meio do objeto HttpSession.
Se o cliente não permitir o uso de cookies a API Servlet fornece métodos para a
codificação de URL. O exemplo XX.XX mostra o uso de cookies para
armazenar as informações digitadas em um formulário.

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

public class CookieTeste extends HttpServlet


{
public void doGet(HttpServletRequest req, HttpServletResponse
res)
throws IOException, ServletException
{

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 117
res.setContentType("text/html");

PrintWriter out = res.getWriter();


out.println("<html>");
out.println("<body bgcolor=\"white\">");
out.println("<head><title>Teste de Cookies</title></head>");
out.println("<body>");

out.println("<h3>Teste de Cookies</h3>");

Cookie[] cookies = req.getCookies();


if (cookies.length > 0)
{
for (int i = 0; i < cookies.length; i++)
{
Cookie cookie = cookies[i];
out.print("Cookie Nome: " + cookie.getName() +
"<br>");
out.println(" Cookie Valor: " + cookie.getValue() +"<br><br>");
}
}

String cName = req.getParameter("cookienome");


String cValor = req.getParameter("cookievalor");
if (cName != null && cValor != null)
{
Cookie cookie = new Cookie(cName ,cValor);
res.addCookie(cookie);
out.println("<P>");
out.println("<br>");
out.print("Nome : "+cName +"<br>");
out.print("Valor : "+cValor);
}

out.println("<P>");
out.print("<form action=\"CookieTeste\" method=POST>");
out.println("Nome : <input type=text length=20 name=cookienome><br>");
out.println("Valor : <input type=text length=20 name=cookievalor><br>");
out.println("<input type=submit></form>");
out.println("</body>");
out.println("</html>");
}

public void doPost(HttpServletRequest req, HttpServletResponse res)


throws IOException, ServletException
{
doGet(req, res);
}
}

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 118

Exemplo XX.XX- Servlet para lidar com Cookies.

Para se criar um cookie é necessário criar um objeto Cookie, passando


para o construtor um nome e um valor, sendo ambos instâncias de String. O
cookie é enviado para o navegador por meio do método addCookie() do
objeto HttpServletResponse. Um vez que os cookies são enviados no
cabeçalho da página, o método addCookie() deve ser chamado antes do
envio de qualquer conteúdo para o navegador. Para recuperar os cookies
enviados pelo navegador usa-se o método getCookies() do objeto
HttpServletRequest que retorna um array de Cookie. Os métodos
getName() e getvalue() do objeto Cookie são utilizados para recuperar
o nome o valor da informação associada ao cookie.

Os objetos da classe Cookie possuem vários métodos para controle do


uso de cookies. É possível definir tempo de vida máximo do cookie, os domínios
que devem receber o cookie (por default o domínio que deve receber o cookie é
o que o criou), o diretório da página que deve receber o cookie, se o cookie deve
ser enviado somente sob um protocolo seguro e etc. Por exemplo, para definir a
idade máxima de um cookie devemos utilizar o método setMaxAge(),
passando um inteiro como parâmetro. Se o inteiro for positivo indicará em
segundos o tempo máximo de vida do cookie. Um valor negativo indica que o
cookie deve apagado quando o navegador terminar. O valor zero indica que o
cookie deve ser apagado imediatamente. O trecho de código exemplo XX.XX
mostra algumas alterações no comportamento default de um cookie.

...
Cookie cookie = new Cookie(cName ,cValor);
cookie.setDomain(“*.uvf.br”); // todos os domínios como dpi.ufv.br mas não *.dpi.ufv.br
cookie.setMaxAge (3600); // uma hora de tempo de vida
...

Exemplo XX.XX- Mudanças no comportamento default do cookie.

Lidando com Sessões


A manipulação direta de cookies para controle de sessão é um tanto baixo
nível, uma vez que o usuário deve se preocupar com a identificação, tempo de
vida e outros detalhes. Por isso a API Servlet fornece um objeto com controles

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 119
de nível mais alto para monitorar a sessão, o HttpSession. O objeto
HttpSession monitora a sessão utilizando cookies de forma transparente. No
entanto, se o cliente não aceitar o uso de cookies é possível utilizar como
alternativa a codificação de URL para adicionar o identificador da sessão. Essa
opção, apesar de ser mais genérica, não á primeira opção devido a possibilidade
de criação de gargalos pela necessidade da análise prévia de todas requisições
que chegam ao servidor. O exemplo XX.XX mostra o uso de um objeto
HttpSession para armazenar as informações digitadas em um formulário.

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

public class SessionTeste extends HttpServlet


{
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException
{
resp.setContentType("text/html");

PrintWriter out = resp.getWriter();


out.println("<html><head>");
out.println("<title>Teste de Sessao</title>");
out.println("</head>");
out.println("<body>");
out.println("<h3>Teste de Sessao</h3>");
HttpSession session = req.getSession(true);
out.println("Identificador: " + session.getId());
out.println("<br>");
out.println("Data: ");
out.println(new Date(session.getCreationTime()) + "<br>");
out.println("Ultimo acesso: ");
out.println(new Date(session.getLastAccessedTime()));

String nomedado = req.getParameter("nomedado");


String valordado = req.getParameter("valordado");
if (nomedado != null && valordado != null)
{
session.setAttribute(nomedado, valordado);
}

out.println("<P>");
out.println("Dados da Sessao:" + "<br>");
Enumeration valueNames = session.getAttributeNames();

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 120

while (valueNames.hasMoreElements())
{
String name = (String)valueNames.nextElement();
String value = (String) session.getAttribute(name);
out.println(name + " = " + value+"<br>");
}

out.println("<P>");
out.print("<form action=\"SessionTeste\" method=POST>");
out.println("Nome: <input type=text size=20 name=nomedado><br>");
out.println("Valor: <input type=text size=20 name=valordado><br>");
out.println("<input type=submit>");
out.println("</form>");
out.println("</body></html>");
}

public void doPost(HttpServletRequest req, HttpServletResponse resp)


throws IOException, ServletException
{
doGet(req, resp);
}
}

Exemplo XX.XX- Servlet para lidar com Sessões.

Para controlar a sessão é necessário obter um objeto HttpSession por


meio do método getSession() do objeto HttpServletRequest. Opcionalmente, o
método getSession() recebe como argumento um valor booleano que indica se é
para criar o objeto HttpSession se ele não existir (argumento true) ou se é para
retorna null caso ele não exista (argumento false). Para se associar um objeto ou
informação à sessão usa-se o método setAttribute() do objeto HttpSession,
passando para o método um String e um objeto que será identificado pelo String.
Note que o método aceita qualquer objeto e, portanto, qualquer objeto pode ser
associado à sessão. Os objetos associados a uma sessão são recuperados com o
uso método getAttribute() do objeto HttpSession, que recebe como argumento o
nome associado ao objeto. Para se obter uma enumeração do nomes associados à
sessão usa-se o método getAttributeNames() do objeto HttpSession.

A figura XX.XX mostra o resultado da execução do exemplo XX.XX.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 121
Teste de Sessao

Identificador: session3
Data: Sun May 28 15:19:15 GMT-03:00 2000
Ultimo acesso: Sun May 28 15:19:43 GMT-03:00 2000

Dados da Sessao:
Alcione = 4
Alexandra = 6
Nome
Valor

Enviar Consulta

Figura VI-1 Saída resultante da execução do Servlet que lida com Sessões.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 122

JSP
Servlets é uma boa idéia, mas você se imaginou montando uma página
complexa usando println()? Muitas vezes o desenvolvimento de um site é
uma tarefa complexa que envolve vários profissionais. A tarefa de projeto do
layout da página fica a cargo do Web Designer, incluindo a diagramação dos
textos e imagens, aplicação de cores, tratamento das imagens, definição da
estrutura da informação apresentada no site e dos links para navegação pela
mesma. Já o Desenvolvedor Web é responsável pela criação das aplicações que
vão executar em um site. O trabalho destes dois profissionais é somado na
criação de um único produto, mas durante o desenvolvimento a interferência
mutua deve ser a mínima possível. Ou seja, um profissional não deve precisar
alterar o que é foi feito pelo outro profissional para cumprir sua tarefa. A
tecnologia Servlet não nos permite atingir esse ideal. Por exemplo, suponha que
um Web Designer terminou o desenvolvimento de uma página e a entregou para
o Desenvolvedor Web codificar em um Servlet. Se após a codificação o Web
Designer desejar realizar uma alteração na página será necessário que ele altere o
código do Servlet (do qual ele nada entende) ou entregar uma nova página para o
Desenvolvedor Web para que ele a codifique totalmente mais uma vez. Qualquer
uma dessas alternativas são indesejáveis e foi devido a esse problema a Sun
desenvolveu uma tecnologia baseada em Servlets chamada de JSP.
Java Server Pages (JSP) são páginas HTML que incluem código Java e
outros tags especiais. Desta forma as partes estáticas da página não precisam ser
geradas por println(). Elas são fixadas na própria página. A parte dinâmica
é gerada pelo código JSP. Assim a parte estática da página pode ser projetada
por um Web Designer que nada sabe de Java.
A primeira vez que uma página JSP é carregada pelo container JSP o
código Java é compilado gerando um Servlet que é executado, gerando uma
página HTML que é enviada para o navegador. As chamadas subsequentes são
enviadas diretamente ao Servlet gerado na primeira requisição, não ocorrendo
mais as etapas de geração e compilação do Servlet.
A figura XX.XX mostra um esquema das etapas de execução de uma
página JSP na primeira vez que é requisitada. Na etapa (1) a requisição é enviada
para um servidor Web que reencaminha a requisição (etapa 2) para o container
Servlet/JSP. Na etapa (3) o container verifica que não existe nenhuma instância
de Servlet correspondente à página JSP. Neste caso, a página JSP é traduzida
para código fonte de uma classe Servlet que será usada na resposta à requisição.
Na etapa (4) o código fonte do Servlet é compilado, e na etapa (5) é criada uma

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 123
instância da classe. Finalmente, na etapa (6) é invocado o método service()
da instância Servlet para gerar a resposta à requisição.

Navegador (1)
Requisição de
página JSP Servidor
Http
(2)
Encaminha a requisição
(6)
Resposta à (3)
requisição Container Traduz Página
Servlet/JSP
jsp
(5) (4)
Instancia e Compila
executa

Bytecode Fonte
Servlet Servlet

Figura VI-1 Etapas da primeira execução de uma página JSP.

A idéia de se usar scripts de linguagens de programação em páginas


HTML que são processados no lado servidor para gerar conteúdo dinâmico não
é restrita à linguagem Java. Existem várias soluções desse tipo fornecida por
outros fabricantes. Abaixo segue uma comparação de duas das tecnologias mais
populares com JSP.

PHP X JSP
PHP (Personal Home Pages) é uma linguagem script para ser executada
no lado servidor criada em 1994 como um projeto pessoal de Rasmus Lerdorf.
Atualmente encontra-se na versão 4. A sintaxe é fortemente baseada em C mas
possui elementos de C++, Java e Perl. Possui suporte à programação OO por
meio de classes e objetos. Possui também suporte extensivo à Banco de dados
ODBC, MySql, Sybase, Oracle e outros. PHP é uma linguagem mais fácil no
desenvolvimento de pequenas aplicações para Web em relação à JSP, uma vez

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 124
que é uma linguagem mais simples e menos rígida do que JSP. No entanto, a
medida que passamos para aplicações de maior porte, o uso de PHP não é
indicado, uma vez que necessário o uso de linguagens com checagem mais
rígidas e com maior suporte à escalabilidade, como é o caso de Java.

ASP X JSP
ASP (Active Server Pages) é a solução desenvolvida pela Microsoft®
para atender as requisições feitas à servidores Web. Incorporada inicialmente
apenas ao Internet Information Server (IIS), no entanto, atualmente já é
suportada por outros servidores populares, como o Apache. O desenvolvimento
de páginas que usam ASP envolve a produção de um script contendo HTML
misturado com blocos de código de controle ASP. Este código de controle pode
conter scripts em JavaScript ou VBScript. A primeira vantagem de JSP sobre
ASP é que a parte dinâmica é escrita em Java e não Visual Basic ou outra
linguagem proprietária da Microsoft, portanto JSP é mais poderoso e fácil de
usar. Em segundo lugar JSP é mais portável para outros sistemas operacionais e
servidores WEB que não sejam Microsoft.

Primeiro exemplo em JSP


Para que o leitor possa ter uma idéia geral da tecnologia JSP
apresentaremos agora a versão JSP do Olá mundo. O exemplo XX.XX mostra o
código da página.

<html>
<head>
<title>Exemplo JSP</title>
</head>
<body>
<%
String x = "Ol&aacute; Mundo!";
%>
<%=x%>
</body>
</html>

Exemplo XX.XX- Versão JSP do Olá mundo.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 125
Quem está habituado aos tags HTML notará que se trata basicamente de
uma página HTML contendo código Java delimitado pelos símbolos “<%” e
“%>”. Para facilitar a visualização destacamos os scripts Java com negrito. No
primeiro trecho de script é declarada uma variável x com o valor “Olá mundo”
(a seqüência &acute; é denota ‘á’ em HTML). No segundo trecho de script o
conteúdo da variável x é extraído e colocado na página resultante da execução
do Servlet correspondente. Em seguida mostraremos como executar o exemplo
XX.XX.

Executando o arquivo JSP

Para executar o exemplo XX.XX salve-o com a extensão .jsp. Por


exemplo ola.jsp. Se você estiver usando o servidor Tomcat, coloque-o
arquivo no subdiretório /webapps/examples/jsp do Tomcat. Por exemplo
examples/jsp/teste. Para invocar o arquivo JSP basta embutir a URL em
uma página ou digitar diretamente a seguinte URL no navegador.

http://localhost:8080/examples/jsp/ola.jsp

Usamos o diretório /webapps/examples/jsp para testar rapidamente


o exemplo. Para desenvolver uma aplicação é aconselhável criar um diretório
apropriado como mostrado na seção que tratou de Servlets.
O Servlet criado a partir da página JSP é colocado em um diretório de
trabalho. No caso do Tomcat o Servlet é colocado em subdiretório associado à
aplicação subordinado ao diretório /work do Tomcat. O exemplo XX.XX
mostra os principais trechos do Servlet criado a partir da tradução do arquivo
ola.jsp pelo tradutor do Tomcat. Note que o Servlet é subclasse de uma
classe HttpJspBase e não da HttpServlet. Além disso, o método que
executado em resposta à requisição é o método _jspService() e não o
método service(). Note também que todas as partes estáticas da página JSP
são colocadas como argumentos do método write() do objeto referenciado
out.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 126

public class _0002fjsp_0002fola_00032_0002ejspola_jsp_0 extends HttpJspBase {


....
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
....
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
...
try {
...
out.write("<html>\r\n <head>\r\n <title>Exemplo JSP</title>\r\n
</head>\r\n <body>\r\n");
String x = "Ol&aacute; Mundo!";

out.write("\r\n");
out.print(x);
out.write("\r\n </body>\r\n</html>\r\n");
...
} catch (Exception ex) {
...
}
}
}

Exemplo XX.XX- Servlet correspondente à página JSP do Olá mundo.

Objetos implícitos
No exemplo XX.XX pode-se ver a declaração de variáveis que
referenciam a alguns objetos importantes. Estas variáveis estão disponíveis para
o projetista da página JSP. As variáveis mais importantes são:

Classe Variável
HttpServletRequest request
HttpServletResponse response
PageContext pageContext
ServletContext application
HttpSession session
JspWriter out

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 127
Os objetos referenciados pelas variáveis request e response já
tiveram seu uso esclarecido na seção sobre Servlets. O objeto do tipo
JspWriter tem a mesma função do PrinterWriter do Servlet. Os outros
objetos terão sua função esclarecida mais adiante.

Tags JSP
Os tags JSP possuem a seguinte forma geral:

<% Código JSP %>

O primeiro caractere % pode ser seguido de outros caracteres que


determinam o significado preciso do código dentro do tag. Os tags JSP possuem
correspondência com os tags XML. Existem cinco categorias de tags JSP:

Expressões
Scriptlets
Declarações
Diretivas
Comentários

Em seguida comentaremos cada uma dessas categorias.

Expressões

<%= expressões %>

Expressões são avaliadas, convertidas para String e colocadas na


página enviada. A avaliação é realizada em tempo de execução, quando a página
é requisitada.

Exemplos:
<%= new java.util.Date() %>
<%= request.getMethod() %>

No primeiro exemplo será colocado na página a data corrente em


milésimo de segundos e no segundo será colocado o método usado na

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 128
requisição. Note que cada expressão contém apenas um comando Java. Note
também que o comando Java não é terminado pelo caractere ‘;’.

Scriptlets

<% código Java %>

Quando é necessário mais de um comando Java ou o resultado da


computação não é para ser colocado na página de resposta é preciso usar outra
categoria de tags JSP: os Scriptlets . Os Scriptlets permitem inserir trechos de
código em Java na página JSP. O exemplo XX.XX mostra uma página JSP
contendo um Scriptlet que transforma a temperatura digitada em celcius para o
equivalente em Fahrenheit.

<html>
<head><title>Conversao Celcius Fahrenheit </title></head>
<body>

<%
String valor = request.getParameter("celcius");
if (valor != null )
{
double f = Double.parseDouble(valor)*9/5 +32;
out.println("<P>");
out.println("<h2>Valor em Fahrenheit:" +f +"<h2><br>");
}
%>
<form action=conversao.jsp method=POST>
Celcius: <input type=text size=20 name=celcius><br>
<input type=submit>
</form>

</body>
</html>

Exemplo XX.XX- Página JSP que converte graus Celcius para Fahrenheit.

Note o uso das variáveis request e out sem a necessidade de


declaração. Todo o código digitado é inserido no método _jspService(). A
figura XX.XX mostra o resultado da requisição após a digitação do valor 30 na
caixa de texto do formulário.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 129

Valor em Fahrenheit:86.0

Celcius:

Enviar Consulta

Figura VI-XX Resultado da conversão de 30 graus celcius.

O código dentro do scriptlet é inserido da mesma forma que é escrito e


todo o texto HTML estático antes e após ou um scriptlet é convertido para
comandos print(). Desta forma o scriptlets não precisa conter comandos para
código estático e blocos de controle abertos afetam o código HTML envolvidos
por scriptlets. O exemplo XX.XX mostra dois formas de se produzir o mesmo
efeito. No código da esquerda os Scriplets se intercalam com código HTML. O
código HTML, quando da tradução da página JSP para Servlet é inserido como
argumentos de métodos println() gerando o código da direita. Ambas as
formas podem ser usadas em páginas JSP e produzem o mesmo efeito.

Previs&atilde;o do Tempo out.println("Previs&atilde;o do Tempo");


<% if (Math.random() < 0.5) { %> if (Math.random() < 0.5) {
Hoje vai <B>fazer sol</B>! out.println(" Hoje vai <B>fazer sol</B>!");
<% } else { %> } else {
Hoje vai <B>chover</B>! out.println(" Hoje vai <B>chover</B>!");
<% } %> }

Exemplo XX.XX- Dois códigos equivalentes.

Declarações

<%! Código Java %>

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 130
Uma declaração JSP permite definir variáveis ou métodos que são
inseridos no corpo do Servlet. Como as declarações não geram saída, elas são
normalmente usadas em combinação com expressões e scriptlets. O Exemplo
XX.XX mostra a declaração de uma variável que é usada para contar o número
de vezes que a página corrente foi requisitada desde que foi carregada.

<%! Private int numAcesso = 0; %>


Acessos desde carregada:
<%= ++ numAcesso %>

Exemplo XX.XX- Declaração de uma variável usando o tag de declaração.

As variáveis declaradas desta forma serão variáveis de instância. Já as


variáveis declaradas em Scriptlets são variáveis locais ao método
_jspService(). Por isso é possível contar o número de requisições com o
exemplo XX.XX. Se variável fosse declarada em um Scriptlet a variável seria
local ao método _jspService() e, portanto, teria seu valor reinicializado a
cada chamada.
Como já foi dito, os tags de declarações permitem a declaração de
métodos. O Exemplo XX.XX mostra a declaração de um método que converte
celcius para Fahrenheit.

<%!
private double converte(double c)
{
return c*9/5 +32;
}
%>

Exemplo XX.XX- Declaração de um método para a conversão de celcius para


Fahrenheit.

Comentários

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 131
Existem dois tipos de comentários utilizados em páginas JSP. O primeiro
exclui todo o bloco comentado da saída gerada pelo processamento da página. A
forma geral deste tipo de comentário é a seguinte:

<%--comentário --%>

O segundo tipo de comentário é o utilizado em páginas HTML. Neste


caso o comentário é enviado dentro da página de resposta. A forma geral deste
tipo de comentário é a seguinte:

<!—comentário -->

Diretivas
Diretivas são mensagens para JSP container. Elas não enviam nada para a
página mas são importantes para definir atributos JSP e dependências com o JSP
container. A forma geral da diretivas é a seguinte:

<%@ Diretiva atributo="valor" %>

ou
<%@ Diretiva atributo1 ="valor1"
atributo2 ="valor2"
...
atributoN =" valorN " %>

Em seguida comentaremos as principais diretivas.

Diretiva page

<%@ page atributo1 ="valor1" ... atributoN =" valorN " %>

A diretiva page permite a definição dos seguintes atributos:

import
contentType
isThreadSafe

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 132
session
buffer
autoflush
info
errorPage
isErrorPage
language

Segue a descrição de cada um desses atributos.

Atributo e Forma Geral Descrição


import="package.class" Permite especificar os pacotes que devem ser
importados para serem usados na página JSP.
ou
Exemplo:
import="package.class1,..
.,package.classN" <%@ page import="java.util.*" %>

contentType="MIME-Type" Especifica o tipo MIME da saída. O default é text/html.

Exemplo:

<%@ page contentType="text/plain" %>

possui o mesmo efeito do scriptlet

<%
response.setContentType("text/plain")
;
%>

isThreadSafe="true|false" Um valor true (default) indica um processamento


normal do Servlet, onde múltiplas requisições são
processadas simultaneamente. Um valor false indica que
o processamento deve ser feito por instancias separadas
do Servlet ou serialmente.
session="true|false” Um valor true (default) indica que a variável
predefinida session (HttpSession) deve ser
associada à sessão, se existir, caso contrário uma nova
sessão deve ser criada e associada a ela. Um valor false
indica que nenhuma sessão será usada.
buffer="sizekb|none" Especifica o tamanho do buffer para escrita usado pelo
objeto JspWriter. O tamanho default não é menor que
8k..
autoflush="true|false” Um valor true (default) indica que o buffer deve ser

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 133
esvaziado quando estiver cheio.
info="mensagem" Define uma cadeia de caracteres que pode ser
recuperada via getServletInfo().
errorPage="url” Especifica a página JSP que deve ser processada em
caso de exceções não capturadas.
isErrorPage="true|false” Indica se a página corrente pode atuar como página de
erro para outra página JSP. O default é false.
Language="java” Possibilita definir a linguagem que está sendo usada. No
momento a única possibilidade é Java.

Tabela VI.XX –Atributos da diretiva page.

Diretiva include

<%@ include file="relative url" %>

Permite incluir arquivos no momento em que a página JSP é traduzida


em um Servlet.

Exemplo:

<%@ include file="/meuarq.html" %>

Extraindo Valores de Formulários


Uma página JSP, da mesma forma que um Servlet, pode usar o objeto
referenciado pela variável request para obter os valores dos parâmetros de um
formulário. O exemplo XX.XX usado para converter graus Celcius em
Fahrenheit fez uso deste recurso. O exemplo XX.XX mostra outra página JSP
com formulário. Note que o scriptlet é usado para obter o nome e os valores de
todos os parâmetros contidos no formulário. Como o método
getParameterNames() retorna uma referência a um objeto
Enumeration é preciso importar o pacote java.util, por meio da diretiva
page.

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


<html><body>
<H1>Formulário</H1>
<%

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 134
Enumeration campos = request.getParameterNames();
While(campos.hasMoreElements()) {
String campo = (String)campos.nextElement();
String valor = request.getParameter(campo); %>
<li><%= campo %> = <%= valor %></li>
<% } %>

<form method="POST" action="form.jsp">


Nome: <input type="text" size="20" name="nome" ><br>
Telefone: <input type="text" size="20" name="telefone"><br>
<INPUT TYPE=submit name=submit value="envie">
</form>
</body></html>

Exemplo VI.XX – Página JSP com formulário.

A figura XX.XX mostra o resultado da requisição após a digitação dos


valores Alcione e 333-3333 nas caixas de texto do formulário.

Formulário
• telefone = 333-3333
• nome = Alcione
• submit = envie

Nome:
Telefone:

envie

Figura VI.XX- Saída do exemplo XX.XX.

Criando e Modificando Cookies

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 135
Da mesma for que em Servlets os cookies em JSP são tratados por meio
da classe Cookie. Para recuperar os cookies enviados pelo navegador usa-se o
método getCookies() do objeto HttpServletRequest que retorna um
arranjo de Cookie. Os métodos getName() e getvalue() do objeto
Cookie são utilizados para recuperar o nome o valor da informação associada
ao cookie. O cookie é enviado para o navegador por meio do método
addCookie() do objeto HttpServletResponse. O exemplo XX.XX
mostra uma página JSP que exibe todos os cookies recebidos em uma requisição
e adiciona mais um na resposta.

<html><body>
<H1>Session id: <%= session.getId() %></H1>
<%
Cookie[] cookies = request.getCookies();
For(int i = 0; i < cookies.length; i++) { %>
Cookie name: <%= cookies[i].getName() %> <br>
Value: <%= cookies[i].getValue() %><br>
antiga idade máxima em segundos:
<%= cookies[i].getMaxAge() %><br>
<% cookies[i].setMaxAge(5); %>
nova idade máxima em segundos:
<%= cookies[i].getMaxAge() %><br>
<% } %>
<%! Int count = 0; int dcount = 0; %>
<% response.addCookie(new Cookie(
”Cookie " + count++, ”Valor " + dcount++)); %>
</body></html>

Exemplo VI.XX – Página JSP que exibe os cookies recebidos.

A figura XX.XX mostra o resultado após três acessos seguidos à página


JSP. Note que existe um cookie a mais com o nome JSESSIONID e valor igual
à sessão. Este cookie é o usado pelo container para controlar a sessão.

Session id: 9ppfv0lsl1


Cookie name: Cookie 0
value: Valor 0
antiga idade máxima em segundos: -1
nova idade máxima em segundos: 5
Cookie name: Cookie 1

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 136
value: Valor 1
antiga idade máxima em segundos: -1
nova idade máxima em segundos: 5
Cookie name: JSESSIONID
value: 9ppfv0lsl1
antiga idade máxima em segundos: -1
nova idade máxima em segundos: 5

Figura VI.XX- Saída do exemplo XX.XX após três acessos.

Lidando com sessões

O atributos de uma sessão são mantidos em um objeto HttpSession


referenciado pela variável session. Pode-se armazenar valores em uma sessão
por meio do método setAttribute() e recuperá-los por meio do método
getAttribute(). O tempo de duração default de uma sessão inativa (sem o
recebimento de requisições do usuário) é 30 minutos mas esse valor pode ser
alterado por meio do método setMaxInactiveInterval(). O exemplo
XX.XX mostra duas páginas JSP. A primeira apresenta um formulário onde
podem ser digitados dois valores recebe dois valores de digitados em um
formulário e define o intervalo máximo de inatividade de uma sessão em 10
segundos. A segunda página recebe a submissão do formulário, insere os valores
na sessão e apresenta os valores relacionados com a sessão assim como a
identificação da sessão.

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


<html><body>
<H1>Formulário</H1>
<H1>Id da sess&atilde;o: <%= session.getId() %></H1>
<H3><li>Essa sess&atilde;o foi criada em
<%= session.getCreationTime() %></li></H3>

<H3><li>Antigo intervalo de inatividade =


<%= session.getMaxInactiveInterval() %></li>
<% session.setMaxInactiveInterval(10); %>
<li>Novo intervalo de inatividade=
<%= session.getMaxInactiveInterval() %></li>
</H3>

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 137

<%
Enumeration atribs = session.getAttributeNames();
while(atribs.hasMoreElements()) {
String atrib = (String)atribs.nextElement();
String valor = (String)session.getAttribute(atrib); %>
<li><%= atrib %> = <%= valor %></li>
<% } %>

<form method="POST" action="sessao2.jsp">


Nome: <input type="text" size="20" name="nome" ><br>
Telefone: <input type="text" size="20" name="telefone" >
<br>
<INPUT TYPE=submit name=submit value="envie">
</form>
</body></html>

<html><body>
<H1>Id da sess&atilde;o: <%= session.getId() %></H1>
<%
String nome = request.getParameter("nome");
String telefone = request.getParameter("telefone");

if (nome !=null && nome.length()>0)


session.setAttribute("nome",nome);
if (telefone !=null &&telefone.length()>0)
session.setAttribute("telefone",telefone);
%>

<FORM TYPE=POST ACTION=sessao1.jsp>


<INPUT TYPE=submit name=submit Value="Retorna">
</FORM>
</body></html>

Exemplo VI.XX – Exemplo do uso de sessão.

O exemplo XX.XX mostra que a sessão é mantida mesmo quando


o usuário muda de página. As figura XX.XX e XX.XX mostram o resultado da
requisição após a digitação dos valores Alcione e 333-3333 nas caixas de
texto do formulário, à submissão para página sessao2.jsp e o retorno à
página sessao1.jsp.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 138

Formulário
Id da sessão: soo8utc4m1
Essa sessão foi criada em 1002202317590
Antigo intervalo de inatividade = 1800
Novo intervalo de inatividade= 10
telefone = 333-3333
nome = Alcione

Nome:
Telefone:
envie

Figura VI.XX- Tela da página sessao1.jsp.

Id da sessão: soo8utc4m1
Retorna

Figura VI.XX- Tela da página sessao2.jsp.

O Uso de JavaBeans

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 139
A medida que o código Java dentro do HTML torna-se cada vez mais
complexo o desenvolvedor pode-se perguntar: Java em HTML não é o problema
invertido do HTML em Servlet? O resultado não será tão complexo quanto
produzir uma página usando println()? Em outras palavras, estou
novamente misturando conteúdo com forma?

Para solucionar esse problema a especificação de JSP permite o uso de


JavaBeans para manipular a parte dinâmica em Java. JavaBeans já foram
descritos detalhadamente em um capítulo anterior, mas podemos encarar um
JavaBean como sendo apenas uma classe Java que obedece a uma certa
padronização de nomeação de métodos, formando o que é denominado de
propriedade. As propriedades de um bean são acessadas por meio de métodos
que obedecem a convenção getXxxx e setXxxx. , onde Xxxx é o nome da
propriedade. Por exemplo, getItem() é o método usado para retornar o valor
da propriedade item. A sintaxe para o uso de um bean em uma página JSP é:

<jsp:useBean id="nome" class="package.class" />

Onde nome é o identificador da variável que conterá uma referência para


uma instância do JavaBean. Você também pode modificar o atributo scope
para estabelecer o escopo do bean além da página corrente.

<jsp:useBean id="nome" scope="session" class="package.class" />

Para modificar as propriedades de um JavaBean você pode usar o


jsp:setProperty ou chamar um método explicitamente em um
scriptlet. Para recuperar o valor de uma propriedade de um JavaBean você
pode usar o jsp:getProperty ou chamar um método explicitamente em
um scriptlet. Quando é dito que um bean tem uma propriedade prop do
tipo T significa que o bean deve prover um método getProp() e um método
do tipo setProp(T). O exemplo XX.XX mostra uma página JSP e um
JavaBean. A página instancia o JavaBean, altera a propriedade mensagem e
recupera o valor da propriedade, colocando-o na página.

Página bean.jsp

<HTML> <HEAD>
<TITLE>Uso de beans</TITLE>
</HEAD> <BODY> <CENTER>

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 140
<TABLE BORDER=5> <TR><TH CLASS="TITLE"> Uso de JavaBeans </TABLE>
</CENTER> <P>
<jsp:useBean id="teste" class=”curso.BeanSimples" />
<jsp:setProperty name="teste" property="mensagem" value=”Ola mundo!" />
<H1> Mensagem: <I>
<jsp:getProperty name="teste" property="mensagem" /> </I></H1>
</BODY> </HTML>

Arquivo Curso/BeanSimples.java

package curso;

public class BeanSimples {


private String men = "Nenhuma mensagem";

public String getMensagem() {


return(men);
}

public void setMensagem(String men) {


this.men = men;
}
}

Exemplo VI.XX – Exemplo do uso de JavaBean.

A figura XX.XX mostra o resultado da requisição dirigida à página


bean.jsp.

Uso de JavaBeans

Mensagem: Ola mundo!


Figura VI.XX- Resultado da requisição à página bean.jsp.

Se no tag setProperty usarmos o valor “*” para o atributo


property então todos os valores de elementos de formulários que possuírem
nomes iguais à propriedades serão transferidos para as respectivas propriedades

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 141
no momento do processamento da requisição. Por exemplo, seja uma página jsp
contendo um formulário com uma caixa de texto com nome mensagem, como
mostrado no exemplo XX.XX. Note que, neste caso, a propriedade mensagem
do JavaBean tem seu valor atualizado para o valor digitado na caixa de texto,
sem a necessidade de uma chamada explícita no tag setProperty. Os
valores são automaticamente convertidos para o tipo correto no bean.

<HTML> <HEAD><TITLE>Uso de beans</TITLE> </HEAD>


<BODY> <CENTER>
<TABLE BORDER=5> <TR><TH CLASS="TITLE"> Uso de JavaBeans </TABLE>
</CENTER> <P>
<jsp:useBean id="teste" class="curso.BeanSimples" />
<jsp:setProperty name="teste" property="*" />
<H1> Mensagem: <I>
<jsp:getProperty name="teste" property="mensagem" />
</I></H1>

<form method="POST" action="bean2.jsp">


Texto: <input type="text" size="20" name="mensagem" ><br>
<INPUT TYPE=submit name=submit value="envie">
</form>

</BODY> </HTML>

Exemplo VI.XX – Exemplo de atualização automática da propriedade.

A figura XX.XX mostra o resultado da requisição dirigida à página


bean2.jsp após a digitação do texto Olá!

Uso de JavaBeans

Mensagem: Ola!
Texto:
envie

Figura VI.XX- Resultado da requisição à página bean2.jsp.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 142
Escopo

Existem quatro valores possíveis para o escopo de um objeto: page,


request, session e application. O default é page. A tabela
XX.XX descreve cada tipo de escopo.

Escopo Descrição
page Objetos declarados com nesse escopo são válidos até a
resposta ser enviada ou a requisição ser encaminhada
para outro programa no mesmo ambiente, ou seja, só
podem ser referenciados nas páginas onde forem
declarados. Objetos declarados com escopo page são
referenciados pelo objeto pagecontext.
request Objetos declarados com nesse escopo são válidos durante
a requisição e são acessíveis mesmo quando a requisição
é encaminhada para outro programa no mesmo ambiente.
Objetos declarados com escopo request são
referenciados pelo objeto request.
session Objetos declarados com nesse escopo são válidos durante
a sessão desde que a página seja definida para funcionar
em uma sessão. Objetos declarados com escopo
session são referenciados pelo objeto session.
application Objetos declarados com nesse escopo são acessíveis por
páginas no mesmo servidor de aplicação. Objetos
declarados com escopo application são
referenciados pelo objeto application.

Tabela VI.XX –Escopo dos objetos nas páginas JSP.

Implementação de um Carrinho de compras

O exemplo abaixo ilustra o uso de JSP para implementar um carrinho de


compras virtual. O carrinho de compras virtual simula um carrinho de compras
de supermercado, onde o cliente vai colocando os produtos selecionados para
compra até se dirigir para o caixa para fazer o pagamento. No carrinho de
compras virtual os itens selecionados pelo usuário são armazenados em uma
estrutura de dados até que o usuário efetue o pagamento. Esse tipo de exemplo

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 143
exige que a página JSP funcione com o escopo session para manter o
carrinho de compras durante a sessão. O exemplo XX.XX mostra um exemplo
simples de implementação de carrinho de compras. O exemplo é composto por
dois arquivos: um para a página JSP e um para o JavaBean que armazena os
itens selecionados.

Página compras.jsp

<html>
<jsp:useBean id="carrinho" scope="session" class="compra.Carrinho" />
<jsp:setProperty name="carrinho" property="*" />
<body bgcolor="#FFFFFF">

<%
carrinho.processRequest(request);
String[] items = carrinho.getItems();
if (items.length>0) {
%>
<font size=+2 color="#3333FF">Voc&ecirc; comprou os seguintes itens:</font>
<ol>
<%
for (int i=0; i<items.length; i++) {
out.println("<li>"+items[i]);
}
}
%>
</ol>
<hr>
<form type=POST action= compras.jsp>
<br><font color="#3333FF" size=+2>Entre um item para adicionar ou remover:
</font><br>
<select NAME="item">
<option>Televis&atilde;o
<option>R&aacute;dio
<option>Computador
<option>V&iacute;deo Cassete
</select>
<p><input TYPE=submit name="submit" value="adicione">
<input TYPE=submit name="submit" value="remova"></form>
</body>
</html>

JavaBean compra/Carrinho.java

package compra;

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 144

import javax.servlet.http.*;
import java.util.Vector;
import java.util.Enumeration;

public class Carrinho {


Vector v = new Vector();
String submit = null;
String item = null;

private void addItem(String name) {v.addElement(name); }

private void removeItem(String name) {v.removeElement(name); }

public void setItem(String name) {item = name; }

public void setSubmit(String s) { submit = s; }

public String[] getItems() {


String[] s = new String[v.size()];
v.copyInto(s);
return s;
}

private void reset() {


submit = null;
item = null;
}

public void processRequest(HttpServletRequest request)


{
if (submit == null) return;

if (submit.equals("adicione")) addItem(item);
else if (submit.equals("remova")) removeItem(item);
reset();
}
}

Exemplo VI.XX – Implementação de um carrinho de compras Virtual.

O exemplo XX.XX implementa apenas o carrinho de compras, deixando


de fora o pagamento dos itens, uma vez que esta etapa depende de cada sistema.
Geralmente o que é feito é direcionar o usuário para outra página onde ele
digitará o número do cartão de crédito que será transmitido por meio de uma
conexão segura para o servidor. Existem outras formas de pagamento, como

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 145
boleto bancário e dinheiro virtual. O próprio carrinho de compras geralmente é
mais complexo, uma vez que os para compra devem ser obtidos dinamicamente
de um banco de dados. A figura XX.XX mostra a tela resultante de algumas
interações com o carrinho de compras.

Figura VI.XX- Carrinho de compras virtual.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 146

Reencaminhando ou Redirecionando requisições


Existem algumas situações onde pode ser desejável transferir uma
requisição para outra URL. Isto é feito com frequência em sistemas que
combinam o uso de Servlets juntamente com JSP. No entanto, a transferência
pode ser para qualquer recurso. Assim, podemos transferir uma requisição de um
Servlet para uma página JSP, HTML ou um Servlet. Da mesma forma uma
página JSP pode transferir uma requisição para uma página JSP, HTML ou um
Servlet.
Existem dois tipos de transferência de requisição: o redirecionamento e o
reencaminhamento. O redirecionamento é obtido usando o método
sendRedirect() de uma instância HttpServletResponse, passando
como argumento a URL de destino. O exemplo XX.XX mostra o código de um
Servlet redirecionando para uma página HTML.

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

public class Redireciona extends HttpServlet


{
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
{
res.sendRedirect("/test/index.html");
}
}

Exemplo VI.XX – Redirecionamento de requisição.

Note pelo exemplo que é preciso passar o contexto do recurso


(/teste). No caso de redirecionamento o a requisição corrente é perdida e uma
nova requisição é feita para a URL de destino. Por isso não se deve associar
nenhum objeto à requisição, uma vez que o objeto HttpServletRequest
corrente será perdido. O que ocorre na prática é que o servidor envia uma
mensagem HTTP 302 de volta para o cliente informando que o recurso foi
transferido para outra URL e o cliente envia uma nova requisição para a URL
informada.
Já no caso de reencaminhamento a requisição é encaminhada diretamente
para a nova URL mantendo todos os objetos associados e evitando uma nova ida

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 147
ao cliente. Portanto, o uso de reencaminhamento é mais eficiente do que o uso
de redirecionamento. O reencaminhamento é obtido usando o método
forward() de uma instância RequestDispatcher, passando como
argumento os objetos HttpServletRequest e HttpServletResponse
para a URL de destino. Uma instância RequestDispatcher é obtida por
meio do método getRequestDispatcher()de uma instância
ServletContext , que é obtido, por sua vez, por meio do método
getServletContext() do Servlet. O exemplo XX.XX mostra o código de
um Servlet reencaminhando a requisição para uma página JSP.

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

public class Reencaminha extends HttpServlet


{
public void doGet(HttpServletRequest request, HttpServletResponse response)
{
try
{
getServletContext().getRequestDispatcher("/index.html").
forward(request,response);
}catch (Exception e) {
System.out.println("Servlet falhou: ");
e.printStackTrace();
}
}
}

Exemplo VI.XX – Reencaminhamento de requisição.

Note que não é necessário passar o contexto na URL, como é feito no


redirecionamento, uma vez que a requisição é encaminhada no contexto
corrente.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 148

Uma Arquitetura para comércio eletrônico

O projeto de uma solução para comércio eletrônico é uma tarefa


complexa e deve atender diversos requisitos. Nesta seção mostraremos uma
modelo de arquitetura básico para comércio eletrônico que pode ser adaptado
para soluções mais específicas. Este modelo implementa o padrão de projeto
MVC, procurando, desta forma, isolar esses aspectos de um sistema de
computação.

Tipos de aplicações na WEB


Podemos enquadra as aplicações na Web em um dos seguintes tipos:

• Business-to-consumer (B2C) – entre empresa e consumidor. Exemplo: uma


pessoa compra um livro na Internet.

• Business-to-business (B2B) – Troca de informações e serviços entre


empresas. Exemplo: o sistema de estoque de uma empresa de automóveis
detecta que um item de estoque precisa ser resposta e faz o pedido
diretamente ao sistema de produção do fornecedor de autopeças. Neste tipo
de aplicação a linguagem XML possui um papel muito importante, uma vez
que existe a necessidade de uma padronização dos tags para comunicação de
conteúdo.

• User-to-data – acesso à bases de informação. Exemplo: uma usuário


consulta uma base de informação.

• User-to-user – chat, e troca de informações entre usuários (Morpheus).

O exemplo que mostraremos é tipicamente um caso de User-to-data,


(agenda eletrônica na Web) mas possui a mesma estrutura de um B2C.

Arquitetura MVC para a Web


A figura XX.XX contém um diagrama de blocos que mostra a
participação de Servlets, JSP e JavaBeans na arquitetura proposta. A idéia é
isolar cada aspecto do modelo MVC com a tecnologia mais adequada. A página

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 149
JSP é ótima para fazer o papel da visão, uma vez que possui facilidades para a
inserção de componentes visuais e para a apresentação de informação. No
entanto, é um pouco estranho usar uma página JSP para receber e tratar uma
requisição. Esta tarefa, que se enquadra no aspecto de controle do modelo MVC
é mais adequada a um Servlet, uma vez que neste momento componentes de
apresentação são indesejáveis. Finalmente, é desejável que a modelagem do
negócio fique isolada dos aspectos de interação. A proposta é que a modelagem
do negócio fique contida em classes de JavaBeans. Em aplicações mais
sofisticadas a modelagem do negócio deve ser implementada por classes de
Enterprise JavaBeans (EJB), no entanto esta forma de implementação foge ao
escopos deste livro. Cada componente participa da seguinte forma:

• Servlets – Atuam como controladores, recebendo as requisições dos


usuários. Após a realização das análises necessária sobre a requisição,
instancia o JavaBean e o armazena no escopo adequado (ou não caso o bean
já tenha sido criado no escopo) e encaminha a requisição para a página JSP.

• JavaBeans – Atuam como o modelo da solução, independente da requisição


e da forma de apresentação. Comunicam-se com a camada intermediária que
encapsula a lógica do problema.

• JSP – Atuam na camada de apresentação utilizando os JavaBeans para


obtenção dos dados a serem exibidos, isolando-se assim de como os dados
são obtidos. O objetivo é minimizar a quantidade de código colocado na
página.

• Camada Intermediária (Middleware) – Incorporam a lógica de acesso aos


dados. Permitem isolar os outros módulos de problemas como estratégias de
acesso aos dados e desempenho. O uso de EJB (Enterprise JavaBeans) é
recomendado para a implementação do Middleware, uma vez que os EJBs
possuem capacidades para gerência de transações e persistência. Isto implica
na adoção de um servidor de aplicação habilitado para EJB.

A figura XX.XX mostra a interação entre os componentes.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 150

Servidor de Aplicação
1

Navegador Web Servlet Cria uma instância


Requisição (controlador) 2

3 JavaBean
(modelo)
4
5
JSP
Resposta (Apresentação)

MiddleWare

JDBC

SGBD

Figura XV.XX. Arquitetura de uma aplicação para Comércio Eletrônico.

Essa arquitetura possui as seguintes vantagens:

1. Facilidade de manutenção: a distribuição lógica das funções entre os


módulos do sistema isola o impacto das modificações.

2. Escalabilidade: Modificações necessária para acompanhar o aumento da


demanda de serviços (database pooling, clustering, etc) ficam concentradas
na camada intermediária.

A figura abaixo mostra a arquitetura física de uma aplicação de comércio


eletrônico.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 151

Figura XV.XX. Arquitetura física de uma aplicação para Comércio Eletrônico.

Demilitarized Zone (DMZ) é onde os servidores HTTP são instalados. A


DMZ é protegida da rede púbica por um firewall, também chamado de firewall
de protocolo. O firewall de protocolo deve ser configurado para permitir tráfego
apenas através da porta 80. Um segundo firewall, também chamado de firewall
de domínio separa a DMZ da rede interna. O firewall de domínio deve ser
configurado para permitir comunicação apenas por meio das portas do servidor
de aplicação

Agenda Web: Um Exemplo de uma aplicação Web usando a


arquitetura MVC
O exemplo a seguir mostra o desenvolvimento da agenda eletrônica para
o funcionamento na Web. A arquitetura adotada é uma implementação do
modelo MVC. Apenas, para simplificar a solução, a camada intermediária foi
simplificada e é implementada por um JavaBean que tem a função de gerenciar a
conexão com o banco de dados. O banco de dados será composto por duas
tabelas, uma para armazenar os usuários autorizados a usar a tabela e outra para
armazenar os itens da agenda. A figura XX.XX mostra o esquema conceitual do
banco de dados e a figura XX.XX mostra o comando para a criação das tabelas.
Note que existe um relacionamento entre a tabela USUARIO e a tabela PESSOA,
mostrando que os dados pessoais sobre o usuário ficam armazenados na agenda.

USUARIO PESSOA
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
Java na Prática – Volume II 152

1:1 1:1

Figura XV.XX. Esquema conceitual do banco de dados para a agenda.

As tabelas do BD devem ser criadas de acordo com o seguinte script:

CREATE TABLE PESSOA (ID INT PRIMARY KEY,


NOME VARCHAR(50) NOT NULL,
TELEFONE VARCHAR(50),
ENDERECO VARCHAR(80),
EMAIL VARCHAR(50),
HP VARCHAR(50),
CELULAR VARCHAR(20),
DESCRICAO VARCHAR(80));

CREATE TABLE USUARIO (ID INT PRIMARY KEY,


LOGIN VARCHAR(20) NOT NULL,
SENHA VARCHAR(20) NOT NULL,
CONSTRAINT FK_USU FOREIGN KEY (ID)
REFERENCES PESSOA(ID));

Figura XV.XX. Script para criação das tabelas.

Para se usar a agenda é necessário que exista pelo menos um usuário


cadastrado. Como no exemplo não vamos apresentar uma tela para cadastro de
usuários será preciso cadastrá-los por meio comandos SQL. Os comandos da
figura XX.XX mostram como cadastrar um usuário.

INSERT INTO PESSOA(ID,NOME,TELEFONE,ENDERECO,EMAIL)


VALUES(0,'Alcione de Paiva Oliveira','3899-1769',
'PH Rolfs','alcionepaiva@globo.com');

INSERT INTO USUARIO(ID,LOGIN,SENHA) VALUES(0,'Alcione','senha');

Figura XV.XX. Script para cadastra um usuário.

O sistema e-agenda é composta pelos seguintes arquivos:

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 153

Arquivo Descrição
agenda.html Página inicial do site, contendo o formulário para a
entrada do login e senha para entrar no restante do
site.
principal.jsp Página JSP contendo o formulário para entrada de
dados para inserção, remoção ou consulta de itens da
agenda.
LoginBean.java JavaBean responsável por verificar se o usuário está
autorizado a acessar a agenda.
AgendaServlet.java Servlet responsável pelo tratamento de requisições
sobre alguma função da agenda (consulta, inserção e
remoção)
AcaoBean.java JavaBean responsável pela execução da ação
solicitada pelo usuário.
ConnectionBean.java JavaBean responsável pelo acesso ao DB e controle
das conexões.

Tabela XV.XX. Arquivos do sistema e-agenda.

O diagrama de colaboração abaixo mostra as interação entre os


componentes do sistema.

1
agenda.html AgendaServlet
4 6
2
5 3
principal.jsp LoginBean ConnectionBean

8
AcaoBean 7

1 e 4 – Requisições
2 e 6 – instanciações
4 – reencaminhamento de
requisições
3,5,7 e 8 – Chamadas de métodos

Figura XV.XX. Interação entre os componentes do sistema.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 154

Descreveremos agora cada componente da aplicação. O exemplo XX.XX


mostra código HTML da página agenda.html. Esta é a página inicial da
aplicação. Ela contém o formulário para a entrada do login e senha para entrar
no restante do site.

1 <HTML>
2 <HEAD>
3 <TITLE>Agenda</TITLE>
4 </HEAD>
5
6 <BODY BGCOLOR="#FFFFFF">
7 <P align="center"><IMG src="tit.gif" width="350" height="100" border="0"></P>
8 <BR>
9
10 <CENTER>
11 <FORM method="POST" name="TesteSub" onsubmit="return TestaVal()"
12 action="/agenda/agenda"><BR>
13 Login:<INPUT size="20" type="text" name="login"><BR><BR>
14 Senha:<INPUT size="20" type="password" name="senha"><BR><BR><BR>
15 <INPUT type="submit" name="envia" value="Enviar">
16 <INPUT size="3" type="Hidden" name="corrente" value="0"><BR>
17 </FORM>
18 </CENTER>
19 <SCRIPT language="JavaScript">
20 <!--
21 function TestaVal()
22 {
23 if (document.TesteSub.login.value == "")
24 {
25 alert ("Campo Login nao Preenchido...Form nao Submetido")
26 return false
27 }
28 else if (document.TesteSub.senha.value == "")
29 {
20 alert ("Campo Senha nao Preenchido...Form nao Submetido")
31 return false
32 }
33 else
34 {
35 return true
36 }
37 }
38 //--></SCRIPT>
39 </BODY></HTML>

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 155
Exemplo VI.XX – agenda.html.
O formulário está definido nas linha 11 a 17. Na linha 12 o parâmetro
action indica a URL que dever receber a requisição. A URL é virtual e sua
associação com o Servlet AgendaServlet será definida no arquivo
web.xml. Na linha 16 é definido um campo oculto (Hidden) como o nome de
corrente e valor 0. Ele será usado pelo AgendaServlet reconhecer a
página de onde saiu a requisição. As linha 19 a 31 definem uma função em
JavaScript que será usada para verificar se o usuário digitou o nome e a senha
antes de enviar a requisição ao usuário. O uso de JavaScript no lado cliente para
criticar a entrada do usuário é muito comum pois diminui a sobrecarga do
servidor.
O exemplo XX.XX mostra código da página principal.jsp. Esta
página contém o formulário para entrada de dados para inserção, remoção ou
consulta de itens da agenda. Na linha 4 a diretiva page define que o servidor
deve acompanhar a sessão do usuário e importa o pacote agenda. Na linha 7
um objeto da classe agenda.LoginBean é recuperado da sessão por meio do
método getAttribute(). Para recuperar o objeto é preciso passar para o
método o nome que está associado ao objeto na sessão. De forma semelhante, na
linha 8 um objeto da classe agenda.AcaoBean é recuperado da requisição
por meio do método getAttribute(). Este objeto é recuperado da
requisição porque cada requisição possui uma ação diferente associada. Na linha
9 é verificado se objeto agenda.LoginBean foi recuperado e se o retorno do
método getStatus() é true. Se o objeto agenda.LoginBean não foi
recuperado significa que existe uma tentativa de acesso direto à página
principal.jsp sem passar primeiro pela página agenda.html ou que a
sessão se esgotou. Se o método getStatus() retornar false significa que o
usuário não está autorizado a acessar essa página. Nestes casos é processado o
código associado ao comando else da linha 51 que apaga a sessão por meio do
método invalidate() do objeto HttpSession (linha 53) e mostra a
mensagem “Usuário não autorizado” (linha 55). Caso o objeto indique que o
usuário está autorizado os comandos internos ao if são executados. Na linha 11
é mostrada uma mensagem com o nome do usuário obtido por meio do método
getNome() do objeto agenda.LoginBean . Na linha 13 é mostrado o
resultado da ação anterior por meio do método toString() do objeto
agenda.AcaoBean. A ação pode ter sido de consulta, inserção de um novo
item na agenda e remoção de um item na agenda. No primeiro caso é mostrado
uma lista dos itens que satisfizeram a consulta. No segundo e terceiro casos é
exibida uma mensagem indicado se a operação foi bem sucedida.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 156

1 <HTML><HEAD>
2 <TITLE>Tela da Agenda </TITLE>
3 </HEAD><BODY bgcolor="#FFFFFF">
4 <%@ page session="true" import="agenda.*" %>
5
6 <%
7 agenda.LoginBean lb = (agenda.LoginBean) session.getAttribute("loginbean");
8 agenda.AcaoBean ab = (agenda.AcaoBean) request.getAttribute("acaobean");
9 if (lb != null && lb.getStatus())
10 { %>
11 <H2>Sess&atilde;o do <%= lb.getNome() %></H2>
12 <%
13 if (ab!=null) out.println(ab.toString());
14 %>
15
16 <P><BR></P>
17 <FORM method="POST" name="formprin" onsubmit="return TestaVal()"
18 action="/agenda/agenda">
19 Nome: <INPUT size="50" type="text" name="nome"><BR>
20 Telefone: <INPUT size="20" type="text" name="telefone"><BR>
21 Endere&ccedil;o: <INPUT size="50" type="text" name="endereco"><BR>
22 Email: <INPUT size="50" type="text" name="email"><BR><BR>
23 P&aacute;gina: <INPUT size="50" type="text" name="pagina"><BR>
24 Celular: <INPUT size="20" type="text" name="celular"><BR>
25 Descri&ccedil;&atilde;o: <INPUT size="20" type="text" name="descricao">
26 <BR><CENTER>
27 <INPUT type="submit" name="acao" value="Consulta">
28 <INPUT type="submit" name="acao" value="Insere">
29 <INPUT type="submit" name="acao" value="Apaga"></CENTER>
30 <INPUT size="3" type="Hidden" name="corrente" value="1">
31 </FORM>
32
33 <SCRIPT language="JavaScript"><!--
34 function TestaVal()
35 {
36 if (document.formprin.nome.value == "" &&
37 document.formprin.descricao.value== "")
38 {
39 alert ("Campo Nome ou Descricao devem ser Preenchidos!")
40 return false
41 }
42 else
43 {
44 return true
45 }
46 }
47 //--></SCRIPT>
48
49 <%
50 }
51 else
52 {
53 session.invalidate();

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 157
54 %>
55 <H1>Usu&aacute;rio n&atilde;o autorizado</H1>
56 <%
57 }
58 %>
59 </BODY></HTML>

Exemplo VI.XX – principal.jsp.

As linhas 17 a 31 definem o código do formulário de entrada. Nas linhas


17 e 18 são definidos os atributos do formulário. O atributo method indica a
requisição será enviada por meio do método POST. O atributo name define o
nome do formulário como sendo formprin. O atributo onsubmit define que
a função javaSript TestaVal() deve ser executada quando o formulário for
submetido. Finalmente, o atributo action define a URL para onde a requisição
deve ser enviada. Neste caso a URL é agenda/agenda que está mapeada para
o Servlet AgendaServlet. O mapeamento é feito no arquivo web.xml do
diretório web-inf do contexto agenda, como mostrado na figura XX.XX. As
linhas 19 a 25 definem os campos de texto para entrada dos valores. As linhas 27
a 29 definem os botões de submit. Todos possuem o mesmo nome, de forma que
o Servlet precisa apenas examinar o valor do parâmetro acao para determinar
qual ação foi solicitada Na linha 30 é definido um campo oculto (Hidden)
como o nome de corrente e valor 0. Ele será usado pelo AgendaServlet
reconhecer a página de onde saiu a requisição. As linha 33 a 47 definem uma
função em JavaScript que será usada para verificar se o usuário entrou com
valores nos campos de texto nome ou decricao. No mínimo um desses
campos deve ser preenchido para que uma consulta possa ser realizada.
O exemplo XX.XX mostra código do JavaBean usado para intermediar a
conexão com o banco de dados. O JavaBean ConnectionBean tem a
responsabilidade de abrir uma conexão com o banco de dados, retornar uma
referência desta conexão quando solicitado e registrar se a conexão esta livre ou
ocupada. Neste exemplo o se encarrega apenas de obter a conexão e fechá-la, no
entanto, em aplicação com maior número de acessos ao banco de dados pode ser
necessário um maior controle sobre o uso das conexões, mantendo-as em uma
estrutura de dados denominada de pool de conexões. Na linha 12 podemos
observar que o construtor da classe foi declarado com o modificador de acesso
private. Isto significa que não é possível invocar o construtor por meio de um
objeto de outra classe. Isto é feito para que se possa ter um controle sobre a
criação de instâncias da classe. No nosso caso permitiremos apenas que uma
instância da classe seja criada, de modo que todas as referências apontem para

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 158
esse objeto. Esta técnica de programação, onde se permite uma única instância
de uma classe é denominada de padrão de projeto Singleton. O objetivo de
utilizarmos este padrão é porque desejamos que apenas um objeto controle a
conexão com o banco de dados. Ma se o construtor não pode ser chamado
internamente como uma instância da classe é criada e sua referência é passada
para outros objetos? Esta é a tarefa do método estático getInstance()
(linhas 14 a 19). Este método verifica se já existe a instância e retorna a
referência. Caso a instância não exista ela é criada antes de se retornar a
referência. O método init() (linhas 21 a 27) é chamado pelo construtor para
estabelecer a conexão com o SGBD. Ele carrega o driver JDBC do tipo 4 para
HSQLDB e obtém uma conexão com o SGBD. O método
devolveConnection() (linhas 29 a 34) é chamado quando se deseja
devolver a conexão. Finalmente, o método getConnection() (linhas 36 a
46) é chamado quando se deseja obter a conexão.

1 package agenda;
2
3 import java.sql.*;
4 import java.lang.*;
5 import java.util.*;
6
7 public class ConnectionBean {
8 private Connection con=null;
9 private static int clients=0;
10 static private ConnectionBean instance=null;
11
12 private ConnectionBean() { init(); }
13
14 static synchronized public ConnectionBean getInstance() {
15 if (instance == null) {
16 instance = new ConnectionBean();
17 }
18 return instance;
19 }
20
21 private void init() {
22 try {
23 Class.forName("org.hsqldb.jdbcDriver");
24 con=
25 DriverManager.getConnection("jdbc:hsqldb:hsql://localhost","sa","");
26 } catch(Exception e){System.out.println(e.getMessage());};
27 }
28
29 public synchronized void devolveConnection(Connection con) {

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 159
30 if (this.con==con) {
31 clients--;
32 notify();
33 }
34 }
35
36 public synchronized Connection getConnection() {
37 if(clients>0) {
38 try {
39 wait(5000);
40 }
41 catch (InterruptedException e) {};
42 if(clients>0) return null;
43 }
44 clients ++;
45 return con;
46 }
47 }

Exemplo VI.XX – ConnectionBean.java.

O exemplo XX.XX mostra código do JavaBean usado para verificar se o


usuário está autorizado a usar a agenda. O JavaBean LoginBean recebe o
nome e a senha do usuário, obtém a conexão com o SGBD e verifica se o
usuário está autorizado, registrando o resultado da consulta na variável status
(linha 10). Tudo isso é feito no construtor da classe (linhas 12 a 35). Note que na
construção do comando SQL (linhas 17 a 20) é inserido uma junção entre as
tabelas PESSOA e USUARIO de modo a ser possível recuperar os dados
relacionados armazenados em ambas as tabelas. Os métodos getLogin(),
getNome() e getStatus() (linhas 36 a 38) são responsáveis pelo retorno
do login, nome e status da consulta respectivamente.

1 package agenda;
2
3 import java.sql.*;
4 import java.lang.*;
5 import java.util.*;
6
7 public class LoginBean {
8 protected String nome = null;
9 protected String login= null;
10 protected boolean status= false;
11

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 160
12 public LoginBean(String login, String senha)
13 {
14 this.login = login;
15 Connection con=null;
16 Statement stmt =null;
17 String consulta = "SELECT NOME FROM PESSOA, USUARIO "+
18 "WHERE USUARIO.ID = PESSOA.ID AND "+
19 "USUARIO.SENHA ='"+senha+"' AND "+
20 "USUARIO.LOGIN ='"+login+"'";
21 try {
22 con=ConnectionBean.getInstance().getConnection();
23 stmt = con.createStatement();
24 ResultSet rs =stmt.executeQuery(consulta);
25 if(rs.next()) {
26 status = true;
27 nome = rs.getString("NOME");
28 }
29 } catch(Exception e){System.out.println(e.getMessage());}
30 finally {
31 ConnectionBean.getInstance().devolveConnection(con);
32 try{stmt.close();}catch(Exception ee){};
33 }
34
35 }
36 public String getLogin(){return login;}
37 public String getNome(){return nome;}
38 public boolean getStatus(){return status;}
39 }

Exemplo VI.XX – LoginBean.java.

O exemplo XX.XX mostra código do Servlet que implementa a camada


de controle do modelo MVC. O Servlet AgendaServlet recebe as
requisições e, de acordo com os parâmetros, instância os JavaBeans apropriados
e reencaminha as requisições para as páginas corretas. Tanto o método
doGet() (linhas 9 a 12) quanto o método doPost()(linhas 13 a 17) invocam
o método performTask()(linhas 19 a 61) que realiza o tratamento da
requisição. Na linhas 24 a 26 do método performTask() é obtido o valor do
parâmetro corrente que determina a página que originou a requisição. Se o
valor for nulo é assumido o valor default zero. Na linha 30 é executado um
comando switch sobre esse valor, de modo a desviar para bloco de comandos
adequado. O bloco que vai da linha 32 até a linha 43 trata a requisição originada
em uma página com a identificação 0 (página agenda.html). Nas linhas 32 e
33 são recuperados o valor de login e senha digitados pelo usuário. Se algum

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 161
desses valores for nulo então a requisição deve ser reencaminhada para a página
de login (agenda.html) novamente (linha 35). Caso contrário é instanciado
um objeto LoginBean, inserido na sessão corrente e definida a página
principal.jsp como a página para o reencaminhamento da requisição
(linhas 38 a 41). Já o bloco que vai da linha 44 até a linha 54 trata a requisição
originada em uma página com a identificação 1 (página principal.jsp). Na
linha 44 é recuperado o objeto HttpSession corrente. O argumento false é
utilizado para impedir a criação de um novo objeto HttpSession caso não
exista um corrente. Se o valor do objeto for null, então a requisição deve ser
reencaminhada para a página de login (linha 47). Caso contrário é instanciado
um objeto AcaoBean, inserido na requisição corrente e definida a página
principal.jsp como a página para o reencaminhamento da requisição
(linhas 50 a 52). Na linha 56 a requisição é reencaminhada para a página
definida (página agenda.html ou principal.jsp).

1 package agenda;
2
3 import javax.servlet.*;
4 import javax.servlet.http.*;
5 import agenda.*;
6
7 public class AgendaServlet extends HttpServlet
8 {
9 public void doGet(HttpServletRequest request, HttpServletResponse response)
10 {
11 performTask(request,response);
12 }
13 public void doPost(HttpServletRequest request,
14 HttpServletResponse response)
15 {
16 performTask(request,response);
17 }
18
19 public void performTask(HttpServletRequest request,
20 HttpServletResponse response)
21 {
22 String url;
23 HttpSession sessao;
24 String corrente = request.getParameter("corrente");
25 int icorr=0;
26 if (corrente != null) icorr = Integer.parseInt(corrente);
27
28 try

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 162
29 {
30 switch(icorr)
31 {
32 case 0: String login = request.getParameter("login");
33 String senha = request.getParameter("senha");
34 if (login == null||senha == null)
35 url= "/agenda.html";
36 else
37 {
38 sessao = request.getSession(true);
39 sessao.setAttribute("loginbean",
40 new agenda.LoginBean(login,senha));
41 url= "/principal.jsp";
42 };
43 break;
44 case 1:
45 sessao = request.getSession(false);
46 if (sessao == null)
47 url= "/agenda.html";
48 else
49 {
50 request.setAttribute("acaobean",
51 new agenda.AcaoBean(request));
52 url= "/principal.jsp";
53 };
54 break;
55 }
56 getServletContext().getRequestDispatcher(url).forward(request,response);
57 }catch (Exception e) {
58 System.out.println("AgendaServlet falhou: ");
59 e.printStackTrace();
60 }
61 }
62 }

Exemplo VI.XX – AgendaServlet.java.

O exemplo XX.XX mostra código do JavaBean usado para realizar a


manutenção da agenda. O JavaBean AcaoBean é responsável pela consulta,
remoção e inserção de novos itens na agenda. Um objeto StringBuffer
referenciado pela variável retorno é utilizado pelo JavaBean para montar o
resultado da execução. O construtor (linhas 16 a 27) verifica o tipo de requisição
e invoca o método apropriado.
O método consulta() (linhas 29 a 77) é responsável pela realização
de consultas. As consultas podem ser realizadas sobre o campo nome ou

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 163
descrição e os casamentos podem ser parciais, uma vez que é usado o operador
LIKE. A consulta SQL é montada nas linhas 40 a 47. Na linha 50 é obtida uma
conexão com SGBD por meio do objeto ConnectionBean. Na linha 57 o
comando SQL é executado e as linhas 59 a 72 montam o resultado da consulta.
O método insere() (linhas 79 a 148) é responsável por inserir um
item na agenda. Na linha 95 é obtida uma conexão com SGBD por meio do
objeto ConnectionBean. Para inserir um novo item é preciso obter o número
do último identificador usado, incrementar o identificador e inserir na base o
item com o identificador incrementado. Esta operação requer que não seja
acrescentado nenhum identificador entre a operação de leitura do último
identificador e a inserção de um novo item. Ou seja, é necessário que essas
operações sejam tratadas como uma única transação e o isolamento entre as
transações sejam do nível Repeatable Read. A definição do inicio da transação é
feita no comando da linha 102. A mudança do nível de isolamento é feita pelos
comandos codificados nas linha 103 a 109. Na linha 112 é invocado o método
obtemUltimo() (linhas 150 a 171) para obter o último identificador
utilizado. As linhas 114 a 128 montam o comando SQL para a execução. O
comando SQL é executado na linha 131. O fim da transação é definido pelo
comando da linha 132. Ao fim da transação, de forma a não prejudicar a
concorrência, o nível de isolamento deve retornar para um valor mais baixo. Isto
é feito pelos comandos das linhas 133 a 137.
O método apaga() (linhas 173 a 201) é responsável por remover um
item da agenda. As linhas 175 a 180 contém o código para verificar se o usuário
digitou o nome associado ao item que deve ser removido. A linha 181 montam o
comando SQL para a execução. Na linha 184 é obtida uma conexão com SGBD
por meio do objeto ConnectionBean. O comando SQL é executado na linha
191.

1 package agenda;
2
3 import java.lang.*;
4 import java.util.*;
5 import java.sql.*;
6
7 public class AcaoBean
8 {
9 private Connection con=null;
10 private StringBuffer retorno = null;
11 private Statement stmt=null;
12 private String [] legenda= {"C&oacute;digo","Nome","Telefone",
13 "Endere&ccedil;o", "email","hp",

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 164
14 "celular","Descri&ccedil;&atilde;o"};
15
16 public AcaoBean(javax.servlet.http.HttpServletRequest request)
17 {
18 String acao = request.getParameter("acao");
19 if (acao.equals("Consulta"))
20 {
21 String nome = request.getParameter("nome");
22 String descri = request.getParameter("descricao");
23 consulta(nome,descri);
24 }
25 else if (acao.equals("Insere")) insere(request);
26 else if (acao.equals("Apaga")) apaga(request);
27 }
28
29 private void consulta(String nome,String descri)
30 {
31 String consulta = null;
32
33 if ((nome == null||nome.length()<1) &&
34 (descri == null|| descri.length()<1))
35 {
36 retorno = new StringBuffer("Digite o nome ou descricao!");
37 return;
38 }
39
40 if (descri == null|| descri.length()<1)
41 consulta = "SELECT * FROM PESSOA WHERE NOME LIKE '%"+
42 nome+"%'"+" ORDER BY NOME";
43 else if (nome == null|| nome.length()<1)
44 consulta = "SELECT * FROM PESSOA WHERE DESCRICAO LIKE '%"+
45 descri+"%'"+" ORDER BY NOME";
46 else consulta="SELECT * FROM PESSOA WHERE DESCRICAO LIKE '%"+
47 descri+"%' AND NOME LIKE '%"+nome+"%' ORDER BY NOME";
48 try
49 {
50 con=ConnectionBean.getInstance().getConnection();
51 if (con == null)
52 {
53 retorno = new StringBuffer("Servidor ocupado. Tente mais tarde.!");
54 return;
55 }
56 stmt = con.createStatement();
57 ResultSet rs = stmt.executeQuery(consulta);
58
59 retorno = new StringBuffer();
60 retorno.append("<br><h3>Resultado</h3><br>");
61 while(rs.next())
62 {

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 165
63
64 retorno.append("ID:").append(rs.getString("id"));
65 retorno.append("<br>Nome:").append(rs.getString("Nome"));
66 retorno.append("<br>Telefone:").append(rs.getString("Telefone"));
67 retorno.append("<br>Endereco:").append(rs.getString("Endereco"));
68 retorno.append("<br>email:").append(rs.getString("email"));
69 retorno.append("<br>hp:").append(rs.getString("hp"));
70 retorno.append("<br>celular:").append(rs.getString("celular"));
71 retorno.append("<br>descricao:").append(rs.getString("descricao"));
72 retorno.append("<br><br>");
73 }
74 } catch(Exception e){System.out.println(e.getMessage());}
75 finally {ConnectionBean.getInstance().devolveConnection(con);
76 try{stmt.close();}catch(Exception ee){};
77 }
78 }
79
80 private void insere(javax.servlet.http.HttpServletRequest request)
81 {
82 String[] par = {"telefone","endereco","email","hp","celular","descricao"};
83
84 StringBuffer comando = new StringBuffer("INSERT INTO PESSOA(");
85 StringBuffer values = new StringBuffer(" VALUES(");
86
87 String aux = request.getParameter("nome");
88 if (aux == null || aux.length()<1)
89 {
90 retorno = new StringBuffer("<br><h3>Digite o nome!</h3><br>");
91 return;
92 }
93
94 try
95 {
96 con=ConnectionBean.getInstance().getConnection();
97 if (con == null)
98 {
99 retorno = new StringBuffer("Servidor ocupado. Tente mais tarde!");
100 return;
101 }
102
103 con.setAutoCommit(false);
104 DatabaseMetaData meta=con.getMetaData();
105
106 if(meta.supportsTransactionIsolationLevel(
107 con.TRANSACTION_REPEATABLE_READ)) {
108 con.setTransactionIsolation(
109 con.TRANSACTION_REPEATABLE_READ);
110 }
111

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 166
112
113 int ultimo = obtemUltimo(con);
114 if (ultimo==-1) return;
115 ultimo++;
116 comando.append("id,nome");
117 values.append(ultimo+",'").append(aux).append("'");
118
119 for(int i=0;i<par.length;i++)
120 {
121 aux = request.getParameter(par[i]);
122 if (aux != null && aux.length()>0)
123 {
124 comando.append(",").append(par[i]);
125 values.append(",'").append(aux).append("'");
126 }
127 }
128 comando.append(")");
129 values.append(")");
130 aux = comando.toString()+values.toString();
131 stmt = con.createStatement();
132 stmt.executeUpdate(aux);
133 con.setAutoCommit(true);
134 if(meta.supportsTransactionIsolationLevel(
135 con.TRANSACTION_READ_COMMITTED)) {
136 con.setTransactionIsolation(
137 con.TRANSACTION_READ_COMMITTED);
138 }
139 retorno = new StringBuffer("<br><h3>Inserido!</h3><br>");
140 return;
141 } catch(Exception e)
142 {retorno =
143 new StringBuffer("<br><h3>Erro:"+e.getMessage()+"!</h3><br>"); }
144 finally
145 {
146 ConnectionBean.getInstance().devolveConnection(con);
147 try{stmt.close();}catch(Exception ee){};
148 }
149 }
150
151 private int obtemUltimo(Connection con)
152 {
153 String consulta = "SELECT MAX(ID) AS maior FROM PESSOA";
154 try
155 {
156 if (con == null)
157 {
158 retorno = new StringBuffer("Servidor ocupado. Tente mais tarde.!");
159 return -1;
160 }

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 167
161 stmt = con.createStatement();
162 ResultSet rs = stmt.executeQuery(consulta);
163 if(rs.next())
164 return Integer.parseInt(rs.getString("maior"));
165 else return 0;
166 } catch(Exception e) {
167 retorno =
168 new StringBuffer("<br><h3>Erro:"+e.getMessage()+"!</h3><br>");
169 return -1;
170 }
171 finally {try{stmt.close();}catch(Exception ee){};}
172 }
173
174 private void apaga(javax.servlet.http.HttpServletRequest request)
175 {
176 String aux = request.getParameter("nome");
177 if (aux == null || aux.length()<1)
178 {
179 retorno = new StringBuffer("<br><h3>Digite o nome!</h3><br>");
180 return;
181 }
182 String consulta = "DELETE FROM PESSOA WHERE NOME ='"+aux+"'";
183 try
184 {
185 con=ConnectionBean.getInstance().getConnection();
186 if (con == null)
187 {
188 retorno = new StringBuffer("Servidor ocupado. Tente mais tarde.!");
189 return;
190 }
191 stmt = con.createStatement();
192 stmt.executeUpdate(consulta);
193
194 retorno = new StringBuffer("<br><h3>Removido!</h3><br>");
195 return;
196 } catch(Exception e){
197 retorno = new StringBuffer("<br><h3>Erro:"+e.getMessage()+"!</h3><br>");
198 }
199 finally {
200 ConnectionBean.getInstance().devolveConnection(con);
201 try{stmt.close();}catch(Exception ee){};}
202 }
203
204 public String[] getLeg(){return legenda;}
205 public String toString(){return retorno.toString();}
}

Exemplo VI.XX – AcaoBean.java.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 168

Instalação do SGBD

O HSQLDB (www.hsqldb.org) é um SGBD de código aberto


desenvolvido em Java. Ele obedece os padrões SQL e JDBC. Possui as seguintes
características:

• Tamanho pequeno (≅ 100KB).


• Funciona como servidor, standalone e in-memory.
• Suporta transação.
• Integridade referencial.
• Procedimentos Armazenados em Java.
• Direitos de acessos

Para instalá-lo em no MS-Windows execute as seguintes etapas:

1) Descompacte o arquivo hsqldb_v.1.61.zip em um diretório qualquer. Por


exemplo : c:\sgbd

2) Coloque o seguinte comando em seu autoexec.bat

SET CLASSPATH=%CLASSPATH%;c:\sgbd\hsqldb_v.1.61\lib\hsqldb.jar

Execução em modo servidor

c:\sgbd\hsqldb_v.1.61\demo\runServer.bat

Execução do gerenciador gráfico

c:\sgbd\hsqldb_v.1.61\demo\runManager.bat

Instalação da Aplicação

Para instalar crie a seguinte estrutura de diretório abaixo do diretório


webapps do Tomcat:

páginas
HTML e JSP Servlets e
JavaBeans
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
arquivo
web.xml
Java na Prática – Volume II 169
webapps
|_____ agenda
|_____ Web-inf
|_____classes
|_______agenda

Figura XV.XX. Estrutura de diretórios para a aplicação agenda.

O arquivo web.xml deve ser alterado para conter mapeamento entre a


URL agenda e o Servlet AgendaServlet.

...
<web-app>
<servlet>
<servlet-name>
agenda
</servlet-name>
<servlet-class>
agenda.AgendaServlet
</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>
agenda
</servlet-name>
<url-pattern>
/agenda
</url-pattern>
</servlet-mapping>
...
</web-app>

Figura XV.XX. Arquivo web.xml para a agenda.

Considerações sobre a solução

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 170
A aplicação acima implementa uma agenda que pode ser acessada por meio
da Internet, no entanto, devido à falta de espaço e à necessidade de destacarmos
os pontos principais, alguns detalhes foram deixados de lado, como por exemplo
uma melhor interface com o usuário. Abaixo seguem alguns comentários sobre
algumas particularidades da aplicação:

1. O JavaBean da classe LoginBean é armazenado na sessão para


permitir a verificação se o acesso ao site é autorizado. Isto impede que os
usuários tentem acessar diretamente a página principal.jsp da
agenda. Caso tentem fazer isso, a sessão não conterá um objeto
LoginBean associado e, portanto, o acesso será recusado.

2. O JavaBean da classe AcaoBean é armazenado no objeto request


uma vez que sua informações são alteradas a cada requisição. Uma forma
mais eficiente seria manter o objeto AcaoBean na sessão e cada novo
requisição invocar um método do AcaoBean para gerar os resultados.
No entanto, o objetivo da nossa implementação não é fazer a aplicação
mais eficiente possível, e sim mostrar para o leitor uma aplicação com
variadas técnicas.

3. Apesar de termos adotado o padrão MVC de desenvolvimento a


aplicação não exibe uma separação total da camada de apresentação
(Visão) em relação à camada do modelo. Parte do código HTML da
visão é inserido pelo AcaoBean no momento da construção da String
contendo o resultado da ação. Isto foi feito para minimizar a quantidade
de código Java na página JSP. Pode-se argumentar que neste caso a
promessa da separação entre as camadas não é cumprida totalmente.
Uma solução para o problema seria gerar o conteúdo em XML e utilizar
um analisador de XML para gerar a página de apresentação. No entanto,
o uso da tecnologia XML foge ao escopo deste livro.

4. A solução apresenta código redundante para criticar as entradas do


usuário. Existe código JavaScript nas páginas, e código Java no Servlet e
JavaBeans. O uso de código JavaScript nas páginas para críticas de
entrada é indispensável para aliviarmos a carga sobre o servidor. Já o
código para crítica no servidor não causa impacto perceptível e útil para
evitar tentativas de violação.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 171

Capítulo VII Perguntas Frequentes


Como executar um programa a partir de uma aplicação em Java?

Resposta: isso pode ser feito com o método de instância exec da classe
Runtime.

Como criar um TextField que não exiba o que está sendo digitado para
se usado como campo de entrada de senhas?

Resposta: use o método setEchoChar() do TextField para definir qual


caractere que deve ser ecoado.

Como aumentar a área de ambiente do DOS para caber o CLASSPATH?

Resposta: coloque a seguinte linha no autoexec.bat

set shell=c:\command.com /e:4096

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 172

Bibliografia
Eckel B. Thinking in Java. 2nd Ed. New Jersey : Prentice Hall, 2000.

Gosling J., Joy W., Steele G. The Java Language Specification. Massachusetts :
Addison-Wesley, 1996.

Oaks S. Java Security. California : O’Reilly & Associates, Inc, 1998.

Oaks S., Wong H. Java Threads. 2ª Ed. California : O’Reilly & Associates, Inc,
1999.

Watt D. A. Programming Language Concepts and Paradigms. Great Britain :


Prentice Hall, 1990.

Ethan H., Lycklama E. How do you Plug Java Memory Leaks? Dr. Dobb´s
Journal, San Francisco, CA, No. 309, February 2000.

Wahli U. e outros. Servlet and JSP Programming with IBM WebSphere Studio
and VisualAge for Java, IBM RedBooks, California, May 2000.

Sadtler C. e outros. Patterns for e-business: User-to-Business Patterns for Topology


1 and 2 using WebSphere Advanced Edition, IBM RedBooks, California, April
2000.

Bagwel D. e outros. An Approach to Designing e-business Solutions, IBM


RedBooks, California, December 1998.

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 173

Links
Revistas
http://www.javaworld.com/
Revista online sobre Java.

Livros
http://www.eckelobjects.com/
Página do autor do livro Thinking in Java, atualmente em segunda edição. O
livro pode ser baixado gratuitamente no site.

http://www.redbooks.ibm.com/booklist.html
Livros da IBM

Servidores
http://jakarta.apache.org
Página do projeto Jakarta que desenvolveu o Tomcat.

http://www.metronet.com/~wjm/tomcat
Lista Tomcat

http://www.jboss.org
Servidor de aplicação gratuito habilitado para EJB

Dicas Java e recursos


http://java.sun.com/
Página da Sun com informações, tutoriais e produtos Java.

http://gamelan.earthweb.com/
Página da com informações, Applets, Lista de discussão, tutoriais.

http://www.inquiry.com/techtips/java_pro
Ask the Java Pro

http://www.jguru.com/
jGuru.com(Home): Your view of the Java universe

http://www.soujava.org.br

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 174
Bem Vindo ao SouJava!

Servlets e JSP
http://www.servlet.com/srvdev.jhtml
Servlet Inc : Developers Forum

http://www.servlets.com
Servlets.com

http://www.jspin.com/home
Jspin.com - The JSP Resource Index

http://www.burridge.net/jsp/jspinfo.html
Web Development with JSP: JSP, Java Servlet, and Java Bean
Information

http://www.apl.jhu.edu/~hall/java/Servlet-
Tutorial
A Tutorial on Java Servlets and Java Server Pages (JSP)

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 175

Índice
MVC.............................................. 148, 151
A
N
ASP ........................................................124
Níveis de isolamento ............................... 85
C
Nível de Isolamento................................. 83
Calendar ................................................1
P
CGI ..........................................................99
contexto da aplicação .............................107 PHP........................................................ 123
Cookies ..................................................115 pool de conexões.................................... 157
CORBA....................................................69 Prepared Statements ................................ 87
Procedimentos Armazenados................... 88
D
R
Demilitarized Zone ................................151
DMZ...............Consulte Demilitarized Zone RMI ......................................................... 69
E S
EJB.........................................................149 Servlets .................................................... 97
Enterprise JavaBeans ............. Consulte EJB Singleton................................................ 158
sites dinâmicos......................................... 97
J Stored Procedures.................................... 88
JSP ...................................................97, 122 T
M Transação................................................. 83
Mudança de Contexto..............................5

Alcione de P. Oliveira, Vinícius V. Maciel - UFV


Java na Prática – Volume II 1

Alcione de P. Oliveira, Vinícius V. Maciel - UFV

Potrebbero piacerti anche