Apostila de Java

Propaganda
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
101001101
010110010
010101100
100011101
Área de código
Memória Principal
101001101
010110010
010101100
100011101
Linha de execução
(thread)
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
11001
01010
1010
0111
C
10100
01001
11001
01010
1010
0111
B
10100
01001
11001
01010
1010
0111
Código
Dados
1010001001110
0101010101010
1010000101101
1010100010101
0101011011001
0101010100101
0101010010101
0101001000000
1010101010101
Thread 1
Thread 2
Área de pilha
do thread2
10100111001
01010101010
10101010101
01010101000
11110101010
Á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
Thread(ThreadGroup g, String nome)
Thread(Runnable ob, String nome)
Thread(ThreadGroup g, Runnable ob,
String nome)
Thread(String nome)
Thread()
Thread(Runnable ob)
Descrição
Cria um novo thread com o nome
especificado dentro do grupo g.
Cria um novo thread para executar
sobre o objeto ob, com o nome
especificado.
Cria um novo thread para executar
sobre o objeto ob, dentro do grupo
g, com o nome especificado.
Cria um novo thread com o nome
especificado.
Cria um novo thread com o nome
default.
Cria um novo thread para executar
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
Java na Prática – Volume II
Thread(ThreadGroup g, Runnable ob)
10
sobre o objeto 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
currentThread()
Descrição
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
notify()
notifyAll()
wait()
wait(long timeout, int nanos)
wait(long timeout)
Descrição
Notifica um thread que está esperando sobre
um objeto.
Notifica todos os threads que está esperando
sobre um objeto.
Espera para ser notificado por outro thread.
Espera para ser notificado por outro thread.
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
static final int MAX_PRIORITY
static final int MIN_PRIORITY
static final int NORM_PRIORITY
Descrição
A prioridade máxima que um thread pode ter.
A prioridade mínima que um thread pode ter.
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
novo
thread
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
0
1(Thread.MIN_PRIORITY)
2
3
4
5(Thread.NORM_PRIORITY)
6
7
8
9
10(Thread.MAX_PRIORITY)
Prioridades MSWindows
THREAD_PRIORITY_IDLE
THREAD_PRIORITY_LOWEST
THREAD_PRIORITY_LOWEST
THREAD_PRIORITY_BELOW_NORMAL
THREAD_PRIORITY_BELOW_NORMAL
THREAD_PRIORITY_NORMAL
THREAD_PRIORITY_ABOVE_NORMAL
THREAD_PRIORITY_ABOVE_NORMAL
THREAD_PRIORITY_HIGHEST
THREAD_PRIORITY_HIGHEST
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 RoundRobin 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
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
24
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
Monitor de x
Monitor de x
Objeto x
Objeto x
!
passe
27
!
thread t1
thread t2
Instante 1: o thread t1 solicita
o passe ao monitor
do objeto x.
Instante 2: o thread t2 solicita
o passe ao monitor
do objeto x e é
bloqueado.
Monitor de x
Monitor de x
Objeto x
Objeto x
!
!
Instante 3: o thread t1 libera
o passe.
Instante 4: o thread t2 recebe
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
a) Versão com métodos
synchronized
b) Versão com blocos
synchronized
class FilaCirc
{
private final int TAM = 10;
private int vetInt[];
private int inicio, total;
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
{
if (total == TAM) throw new
Exception("Fila cheia!");
vetInt[(inicio+total)%TAM] = v;
total++;
}
public synchronized int getElement()
throws Exception
{
if (total == 0 ) throw new
Exception("Fila vazia!");
int temp = vetInt[inicio];
inicio = (++inicio)%TAM;
total--;
return temp;
}
28
public FilaCirc()
{
vetInt=new int[TAM];
inicio=0;
total =0;
}
public void addElement(int v)
throws Exception
{
synchronized(this) {
if (total == TAM) throw new
Exception("Fila cheia!");
vetInt[(inicio+total)%TAM] = v;
total++;
}
}
public int getElement()
throws Exception
{
synchronized(this) {
if (total == 0 ) throw new
Exception("Fila vazia!");
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)
class X
b)
class Y
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
Java na Prática – Volume II
33
{
{
X ob;
...
public int mb()
{
...
synchronized (ob)
{
// Notifica algum thread
ob.notify();
...
}
...
...
public synchronized int ma()
{
...
// Espera uma condição
while(!cond) wait();
// Prossegue com a
// condição satisfeita
...
}
...
}
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Receptor extends Thread
{
private GerenteMen ger;
public Receptor(ThreadGroup tg, String nome, GerenteMen aGer)
{
super(tg,nome);
ger = aGer;
}
public void run() {
String nome = Thread.currentThread().getName();
for (;;)
try {
String men = ger.getMen(nome);
if (men.equals("fim")) return;
System.out.println(nome+">Mensagem recebida:"+men);
} catch(Exception e) {System.out.println(e.getMessage());}
}
}
class Gerador extends Thread
{
private GerenteMen ger;
public Gerador(ThreadGroup tg, String nome, GerenteMen aGer)
{
super(tg,nome);
ger = aGer;
}
public void run() {
String nome = Thread.currentThread().getName();
ThreadGroup tg = Thread.currentThread().getThreadGroup();
Thread[] tl=null;
for (int i=0;i<100;i++)
{
if (tl==null || tl.length!=tg.activeCount())
tl= new Thread[tg.activeCount()];
tg.enumerate(tl);
int n = (int)(Math.random() * 1000)%tl.length;
if (tl[n]!= Thread.currentThread())
{
System.out.println(nome+">Mensagem enviada para "+
tl[n].getName()+":mensagem "+i);
ger.addMen(tl[n].getName(),"mensagem "+i);
}
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
Java na Prática – Volume II
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
38
}
tl= new Thread[tg.activeCount()];
tg.enumerate(tl);
for (int i=0;i<tl.length;i++)
if (tl[i]!= Thread.currentThread())
ger.addMen(tl[i].getName(),"fim");
}
}
public class TestaGerenteMen {
public static void main(String args[])throws Exception {
GerenteMen ger = new GerenteMen();
ThreadGroup tg = new ThreadGroup("tg");
Receptor r1 = new Receptor(tg,"r_um",ger);
Receptor r2 = new Receptor(tg,"r_dois",ger);
Gerador g = new Gerador(tg,"g",ger);
r1.start();
r2.start();
g.start();
}
}
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
inicializar
P
V
Descrição
Um valor inteiro maior ou igual a zero é atribuído ao semáforo.
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.
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
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
44
Java na Prática – Volume II
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
45
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
{
Image imgs[];
int
ind=0;
Thread t1;
Runnable
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();}
{t1.suspend();}
else
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,
{
g.drawOval (nX + 10, nY + 20, 20, 40);
g.drawLine (nX + 20, nY + 60, nX + 20, nY
g.drawLine (nX + 10, nY + 70, nX + 30, nY
g.drawLine (nX + 10, nY + 150, nX + 20, nY
g.drawLine (nX + 20, nY + 100, nX + 30, nY
}
public void paint (Graphics g, Applet Parent)
{
if (bFirstTime) {
bFirstTime = false;
drawStickFigure (g, nX, nY);
}
else { g.copyArea (nX, nY, 35, 155, 5, 0);}
}
int nY)
+ 100);
+ 70);
+ 100);
+ 150);
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
Camada de Transporte
SMTP
TCP
Camada de rede
SNMP
UDP
IP
Camada física
FTP
HTTP
IP
MIME
TELNET
Ethernet, X.25, Token Ring
- File Transfer Protocol
- Hypertext Transfer Protocol
- Internet Protocol
- Multi-purpose Internet Mail Extensions
UDP
SMTP
- Simple Mail Transfer Protocol
SNMP
- Simple Network Management Protocol
TCP
- Transmission Control Protocol
- 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
Dados
52
Aplicação
Transporte
TCP
Header
Dados
IP
Header
TCP
Header
Dados
Ethernet
Header
IP
Header
TCP
Header
Rede
Dados
Física
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-aponto 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
IP: 200.18.46.12
Host outrocomp.edu
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
HTTP
echo
FTP
SMTP
Finger
Daytime
pop3
Porta
80
7
20,21
25
79
13
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
Socket
Descrição
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
getInputStream()
possui
os
métodos
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
0 a 126
128 a 191
192 a 223
Classe
A
B
C
Endereço de Broadcast
255.0.0.0
255.255.0.0
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
http://dpi.ufv.br/professores.html
123
Protocolo
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
URL
URLConnection
URLEncoder
Descrição
Representa um URL
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.
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/cgibin/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/cgibin/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{
public void inserir(Pessoa p)
2
throws java.rmi.RemoteException;
3
public Pessoa getPessoa(String nome)
4
throws java.rmi.RemoteException;
5
public java.util.Enumeration getPessoas()
6
throws java.rmi.RemoteException;
7
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
Java
Ponte
JDBC-ODBC
SGBD2
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
Java
JDBC Java &
Código Binário
SGBD
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)
Cliente
Java
Servidor de
acesso
SGBD1
SGBD2
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
Java
JDBC Java
(100% Java)
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
SGBD
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
3
4
5
6
75
André M. A. Landro
Ana Maria Freitas
Claudia Maria
Alexandra Moreira
Figura XIII.3 – Tabela de alunos
livros
codlivro
1
2
3
4
5
6
7
8
9
10
titulo
volume
Curso Pratico de Java
Curso Pratico de Java
Introdução a Compiladores
Fundamentos de Banco de Dados
Redes de Computadores
Redes de Computadores Fácil
Lógica matemática
Engenharia de Software para Leigos
Aprenda Computação Gráfica em duas
Aprenda Inteligência Artificial em 5
1
2
1
1
1
2
1
1
1
1
Figura XIII.4 – Tabela de livros.
codlivr
1
7
9
1
4
10
emprestimos
matricu data_empresti data_devoluc
1
01/01/99
10/01/99
3
03/01/99
13/01/99
6
12/01/99
22/01/99
3
20/01/99
30/01/99
2
03/02/99
13/02/99
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
nome
codlivro
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.
2.
3.
4.
5.
Carregar o driver JDBC.
Estabelecer a conexão.
Criar um objeto Statement.
Executar o comando SQL por meio de um método do objeto
Statement.
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.
Referencia fonte de dados bd1 via ponte
jdbc:odbc:bd1;CacheSize=20
JDBC-ODBC. É definido o tamanho do
cache.
Referencia fonte de dados contas via
jdbc:odbc:contas;UID=ana;PWD=sght
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.
getByte
getShort
getInt
getLong
getFloat
getDouble
getBigDecimal
getBoolean
getString
getBytes
getDate
getTime
getTimestamp
getAsciiStream
getUnicodeStre
am
T
I
N
Y
I
N
T
S
M
A
L
L
I
N
T
I
N
T
E
G
E
R
B
I
G
I
N
T
R
E
A
L
F
L
O
A
T
D
O
U
B
L
E
D
E
C
I
M
A
L
X
x
x
x
x
x
x
x
x
x
X
x
x
x
x
x
x
x
x
x
X
x
x
x
x
x
x
x
x
x
X
x
x
x
x
x
x
x
x
x
X
x
x
x
x
x
x
x
x
x
X
x
x
x
x
x
x
x
x
X
x
x
x
x
x
x
x
x
x
X
x
x
N B C V L B V L
U I H A O I A O
M T A R N N R N
E
R C G A B G
R
H V R I V
I
A A Y N A
C
R R
A R
C
R B
H
Y I
A
N
R
A
R
Y
x x x x x
x x x x x
x x x x x
x x x x x
x x x x x
x x x x x
X x x x x
x X x x x
x x X X x x x x
X X x
x x x
x x x
x x x
x x X x x x
x x X x x x
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
D
A
T
E
T
I
M
E
T
I
M
E
S
T
A
M
P
x x x
x
X x
x
X
X
Java na Prática – Volume II
getBinaryStrea
m
getObject
83
x x X
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
10189-9
20645-7
•
Saldo
200,00
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
transações
NumCC
10189-9
20645-7
Saldo
200,00
300,00
Estado desejado após as
transações
NumCC Saldo
10189-9 220,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
10189-9
20645-7
Saldo
220,00
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
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
import
import
import
java.util.*;
java.io.*;
java.sql.*;
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
90
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
/**
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
92
Java na Prática – Volume II
BufferedReader
agenda
cinput;
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
93
Java na Prática – Volume II
Applet
import
import
import
import
import
java.awt.*;
java.applet.*;
java.util.*;
java.net.*;
java.io.*;
public class agendaapplet extends Applet
{
port = 4444;
int
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
94
Java na Prática – Volume II
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
95
Java na Prática – Volume II
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();
btSair_MouseClick(event);
if (object == btSair)
btIns_MouseClick(event);
else if (object == btIns)
btList_MouseClick(event);
else if (object == btList)
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
96
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
Servidor
Web
Respostas
Container
Instâncias de Servlets
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
doGet
doPost
doPut
doDelete
101
Trata as requisições HTTP GET.
Trata as requisições HTTP POST.
Trata as requisições HTTP PUT.
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
Internet
104
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
1
usuário
2
usuário
3
thread1
Servlet1
thread2
thread1
Servlet2
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
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
117
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 à
requisição
Container
Servlet/JSP
(5)
Instancia e
executa
Bytecode
Servlet
(3)
Traduz
Página
jsp
(4)
Compila
Fonte
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á 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 ´ é 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á 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
HttpServletResponse
PageContext
ServletContext
HttpSession
JspWriter
request
response
pageContext
application
session
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ão do Tempo
<% if (Math.random() < 0.5) { %>
Hoje vai <B>fazer sol</B>!
<% } else { %>
Hoje vai <B>chover</B>!
<% } %>
out.println("Previsão do Tempo");
if (Math.random() < 0.5) {
out.println(" Hoje vai <B>fazer sol</B>!");
} else {
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
info="mensagem"
errorPage="url”
isErrorPage="true|false”
Language="java”
133
esvaziado quando estiver cheio.
Define uma cadeia de caracteres que pode ser
recuperada via getServletInfo().
Especifica a página JSP que deve ser processada em
caso de exceções não capturadas.
Indica se a página corrente pode atuar como página de
erro para outra página JSP. O default é false.
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ão: <%= session.getId() %></H1>
<H3><li>Essa sessã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ã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
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
138
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
page
request
session
application
Descrição
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.
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.
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.
Objetos declarados com nesse escopo são acessíveis por
páginas no mesmo servidor de aplicação. Objetos
application
declarados
com
escopo
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ê 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ão
<option>Rádio
<option>Computador
<option>Ví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
Requisição
Cria uma instância
Servlet
(controlador)
2
3
JavaBean
(modelo)
4
5
Resposta
JSP
(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
1:1
152
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','[email protected]');
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
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.
agenda.html
Tabela XV.XX. Arquivos do sistema e-agenda.
O diagrama de colaboração abaixo mostra as interação entre os
componentes do sistema.
agenda.html
1
AgendaServlet
4
2
5
principal.jsp
8
LoginBean
AcaoBean
6
3
ConnectionBean
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
20
31
32
33
34
35
36
37
38
39
<HTML>
<HEAD>
<TITLE>Agenda</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF">
<P align="center"><IMG src="tit.gif" width="350" height="100" border="0"></P>
<BR>
<CENTER>
<FORM method="POST" name="TesteSub" onsubmit="return TestaVal()"
action="/agenda/agenda"><BR>
Login:<INPUT size="20" type="text" name="login"><BR><BR>
Senha:<INPUT size="20" type="password" name="senha"><BR><BR><BR>
<INPUT type="submit" name="envia" value="Enviar">
<INPUT size="3" type="Hidden" name="corrente" value="0"><BR>
</FORM>
</CENTER>
<SCRIPT language="JavaScript">
<!-function TestaVal()
{
if (document.TesteSub.login.value == "")
{
alert ("Campo Login nao Preenchido...Form nao Submetido")
return false
}
else if (document.TesteSub.senha.value == "")
{
alert ("Campo Senha nao Preenchido...Form nao Submetido")
return false
}
else
{
return true
}
}
//--></SCRIPT>
</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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<HTML><HEAD>
<TITLE>Tela da Agenda </TITLE>
</HEAD><BODY bgcolor="#FFFFFF">
<%@ page session="true" import="agenda.*" %>
<%
agenda.LoginBean lb = (agenda.LoginBean) session.getAttribute("loginbean");
agenda.AcaoBean ab = (agenda.AcaoBean) request.getAttribute("acaobean");
if (lb != null && lb.getStatus())
{ %>
<H2>Sessão do <%= lb.getNome() %></H2>
<%
if (ab!=null) out.println(ab.toString());
%>
<P><BR></P>
<FORM method="POST" name="formprin" onsubmit="return TestaVal()"
action="/agenda/agenda">
Nome: <INPUT size="50" type="text" name="nome"><BR>
Telefone: <INPUT size="20" type="text" name="telefone"><BR>
Endereço: <INPUT size="50" type="text" name="endereco"><BR>
Email: <INPUT size="50" type="text" name="email"><BR><BR>
Página: <INPUT size="50" type="text" name="pagina"><BR>
Celular: <INPUT size="20" type="text" name="celular"><BR>
Descrição: <INPUT size="20" type="text" name="descricao">
<BR><CENTER>
<INPUT type="submit" name="acao" value="Consulta">
<INPUT type="submit" name="acao" value="Insere">
<INPUT type="submit" name="acao" value="Apaga"></CENTER>
<INPUT size="3" type="Hidden" name="corrente" value="1">
</FORM>
<SCRIPT language="JavaScript"><!-function TestaVal()
{
if (document.formprin.nome.value == "" &&
document.formprin.descricao.value== "")
{
alert ("Campo Nome ou Descricao devem ser Preenchidos!")
return false
}
else
{
return true
}
}
//--></SCRIPT>
<%
}
else
{
session.invalidate();
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
156
Java na Prática – Volume II
54
55
56
57
58
59
157
%>
<H1>Usuário não autorizado</H1>
<%
}
%>
</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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package agenda;
import java.sql.*;
import java.lang.*;
import java.util.*;
public class ConnectionBean {
private Connection con=null;
private static int clients=0;
static private ConnectionBean instance=null;
private ConnectionBean() { init(); }
static synchronized public ConnectionBean getInstance() {
if (instance == null) {
instance = new ConnectionBean();
}
return instance;
}
private void init() {
try {
Class.forName("org.hsqldb.jdbcDriver");
con=
DriverManager.getConnection("jdbc:hsqldb:hsql://localhost","sa","");
} catch(Exception e){System.out.println(e.getMessage());};
}
public synchronized void devolveConnection(Connection con) {
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
Java na Prática – Volume II
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
159
if (this.con==con) {
clients--;
notify();
}
}
public synchronized Connection getConnection() {
if(clients>0) {
try {
wait(5000);
}
catch (InterruptedException e) {};
if(clients>0) return null;
}
clients ++;
return con;
}
}
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
2
3
4
5
6
7
8
9
10
11
package agenda;
import java.sql.*;
import java.lang.*;
import java.util.*;
public class LoginBean {
protected String nome = null;
protected String login= null;
protected boolean status= false;
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
Java na Prática – Volume II
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
160
public LoginBean(String login, String senha)
{
this.login = login;
Connection con=null;
Statement stmt =null;
String consulta = "SELECT NOME FROM PESSOA, USUARIO "+
"WHERE USUARIO.ID = PESSOA.ID AND "+
"USUARIO.SENHA ='"+senha+"' AND "+
"USUARIO.LOGIN ='"+login+"'";
try {
con=ConnectionBean.getInstance().getConnection();
stmt = con.createStatement();
ResultSet rs =stmt.executeQuery(consulta);
if(rs.next()) {
status = true;
nome = rs.getString("NOME");
}
} catch(Exception e){System.out.println(e.getMessage());}
finally {
ConnectionBean.getInstance().devolveConnection(con);
try{stmt.close();}catch(Exception ee){};
}
}
public String getLogin(){return login;}
public String getNome(){return nome;}
public boolean getStatus(){return status;}
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package agenda;
import javax.servlet.*;
import javax.servlet.http.*;
import agenda.*;
public class AgendaServlet extends HttpServlet
{
public void doGet(HttpServletRequest request, HttpServletResponse response)
{
performTask(request,response);
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
{
performTask(request,response);
}
public void performTask(HttpServletRequest request,
HttpServletResponse response)
{
String url;
HttpSession sessao;
String corrente = request.getParameter("corrente");
int icorr=0;
if (corrente != null) icorr = Integer.parseInt(corrente);
try
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
Java na Prática – Volume II
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
162
{
switch(icorr)
{
case 0: String login = request.getParameter("login");
String senha = request.getParameter("senha");
if (login == null||senha == null)
url= "/agenda.html";
else
{
sessao = request.getSession(true);
sessao.setAttribute("loginbean",
new agenda.LoginBean(login,senha));
url= "/principal.jsp";
};
break;
case 1:
sessao = request.getSession(false);
if (sessao == null)
url= "/agenda.html";
else
{
request.setAttribute("acaobean",
new agenda.AcaoBean(request));
url= "/principal.jsp";
};
break;
}
getServletContext().getRequestDispatcher(url).forward(request,response);
}catch (Exception e) {
System.out.println("AgendaServlet falhou: ");
e.printStackTrace();
}
}
}
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
2
3
4
5
6
7
8
9
10
11
12
13
package agenda;
import java.lang.*;
import java.util.*;
import java.sql.*;
public class AcaoBean
{
private Connection con=null;
private StringBuffer retorno = null;
private Statement stmt=null;
private String [] legenda= {"Código","Nome","Telefone",
"Endereço", "email","hp",
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
Java na Prática – Volume II
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
164
"celular","Descrição"};
public AcaoBean(javax.servlet.http.HttpServletRequest request)
{
String acao = request.getParameter("acao");
if (acao.equals("Consulta"))
{
String nome = request.getParameter("nome");
String descri = request.getParameter("descricao");
consulta(nome,descri);
}
else if (acao.equals("Insere")) insere(request);
else if (acao.equals("Apaga")) apaga(request);
}
private void consulta(String nome,String descri)
{
String consulta = null;
if ((nome == null||nome.length()<1) &&
(descri == null|| descri.length()<1))
{
retorno = new StringBuffer("Digite o nome ou descricao!");
return;
}
if (descri == null|| descri.length()<1)
consulta = "SELECT * FROM PESSOA WHERE NOME LIKE '%"+
nome+"%'"+" ORDER BY NOME";
else if (nome == null|| nome.length()<1)
consulta = "SELECT * FROM PESSOA WHERE DESCRICAO LIKE '%"+
descri+"%'"+" ORDER BY NOME";
else consulta="SELECT * FROM PESSOA WHERE DESCRICAO LIKE '%"+
descri+"%' AND NOME LIKE '%"+nome+"%' ORDER BY NOME";
try
{
con=ConnectionBean.getInstance().getConnection();
if (con == null)
{
retorno = new StringBuffer("Servidor ocupado. Tente mais tarde.!");
return;
}
stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(consulta);
retorno = new StringBuffer();
retorno.append("<br><h3>Resultado</h3><br>");
while(rs.next())
{
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"));
retorno.append("<br>Nome:").append(rs.getString("Nome"));
65
retorno.append("<br>Telefone:").append(rs.getString("Telefone"));
66
retorno.append("<br>Endereco:").append(rs.getString("Endereco"));
67
retorno.append("<br>email:").append(rs.getString("email"));
68
retorno.append("<br>hp:").append(rs.getString("hp"));
69
retorno.append("<br>celular:").append(rs.getString("celular"));
70
retorno.append("<br>descricao:").append(rs.getString("descricao"));
71
retorno.append("<br><br>");
72
}
73
} catch(Exception e){System.out.println(e.getMessage());}
74
finally {ConnectionBean.getInstance().devolveConnection(con);
75
try{stmt.close();}catch(Exception ee){};
76
}
77
}
78
79
private void insere(javax.servlet.http.HttpServletRequest request)
80
{
81
String[] par = {"telefone","endereco","email","hp","celular","descricao"};
82
83
StringBuffer comando = new StringBuffer("INSERT INTO PESSOA(");
84
StringBuffer values = new StringBuffer(" VALUES(");
85
86
String aux = request.getParameter("nome");
87
if (aux == null || aux.length()<1)
88
{
89
retorno = new StringBuffer("<br><h3>Digite o nome!</h3><br>");
90
return;
91
}
92
93
try
94
{
95
con=ConnectionBean.getInstance().getConnection();
96
if (con == null)
97
{
98
retorno = new StringBuffer("Servidor ocupado. Tente mais tarde!");
99
return;
100
}
101
102
con.setAutoCommit(false);
103
DatabaseMetaData meta=con.getMetaData();
104
105
if(meta.supportsTransactionIsolationLevel(
106
con.TRANSACTION_REPEATABLE_READ)) {
107
con.setTransactionIsolation(
108
con.TRANSACTION_REPEATABLE_READ);
109
}
110
111
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
Java na Prática – Volume II
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
166
int ultimo = obtemUltimo(con);
if (ultimo==-1) return;
ultimo++;
comando.append("id,nome");
values.append(ultimo+",'").append(aux).append("'");
for(int i=0;i<par.length;i++)
{
aux = request.getParameter(par[i]);
if (aux != null && aux.length()>0)
{
comando.append(",").append(par[i]);
values.append(",'").append(aux).append("'");
}
}
comando.append(")");
values.append(")");
aux = comando.toString()+values.toString();
stmt = con.createStatement();
stmt.executeUpdate(aux);
con.setAutoCommit(true);
if(meta.supportsTransactionIsolationLevel(
con.TRANSACTION_READ_COMMITTED)) {
con.setTransactionIsolation(
con.TRANSACTION_READ_COMMITTED);
}
retorno = new StringBuffer("<br><h3>Inserido!</h3><br>");
return;
} catch(Exception e)
{retorno =
new StringBuffer("<br><h3>Erro:"+e.getMessage()+"!</h3><br>"); }
finally
{
ConnectionBean.getInstance().devolveConnection(con);
try{stmt.close();}catch(Exception ee){};
}
}
private int obtemUltimo(Connection con)
{
String consulta = "SELECT MAX(ID) AS maior FROM PESSOA";
try
{
if (con == null)
{
retorno = new StringBuffer("Servidor ocupado. Tente mais tarde.!");
return -1;
}
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
Java na Prática – Volume II
167
stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(consulta);
if(rs.next())
return Integer.parseInt(rs.getString("maior"));
else return 0;
} catch(Exception e) {
retorno =
new StringBuffer("<br><h3>Erro:"+e.getMessage()+"!</h3><br>");
return -1;
}
finally {try{stmt.close();}catch(Exception ee){};}
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
}
private void apaga(javax.servlet.http.HttpServletRequest request)
{
String aux = request.getParameter("nome");
if (aux == null || aux.length()<1)
{
retorno = new StringBuffer("<br><h3>Digite o nome!</h3><br>");
return;
}
String consulta = "DELETE FROM PESSOA WHERE NOME ='"+aux+"'";
try
{
con=ConnectionBean.getInstance().getConnection();
if (con == null)
{
retorno = new StringBuffer("Servidor ocupado. Tente mais tarde.!");
return;
}
stmt = con.createStatement();
stmt.executeUpdate(consulta);
retorno = new StringBuffer("<br><h3>Removido!</h3><br>");
return;
} catch(Exception e){
retorno = new StringBuffer("<br><h3>Erro:"+e.getMessage()+"!</h3><br>");
}
finally {
ConnectionBean.getInstance().devolveConnection(con);
try{stmt.close();}catch(Exception ee){};}
}
public String[] getLeg(){return legenda;}
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
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
arquivo
web.xml
Servlets e
JavaBeans
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
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/ServletTutorial
A Tutorial on Java Servlets and Java Server Pages (JSP)
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
174
Java na Prática – Volume II
175
Índice
MVC.............................................. 148, 151
A
ASP ........................................................124
N
Níveis de isolamento ............................... 85
Nível de Isolamento................................. 83
C
Calendar ................................................1
CGI ..........................................................99
contexto da aplicação .............................107
Cookies ..................................................115
CORBA....................................................69
D
Demilitarized Zone ................................151
DMZ...............Consulte Demilitarized Zone
P
PHP........................................................ 123
pool de conexões.................................... 157
Prepared Statements ................................ 87
Procedimentos Armazenados................... 88
R
RMI ......................................................... 69
E
S
EJB.........................................................149
Enterprise JavaBeans ............. Consulte EJB
J
Servlets .................................................... 97
Singleton................................................ 158
sites dinâmicos......................................... 97
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
Alcione de P. Oliveira, Vinícius V. Maciel - UFV
1
Download