Apostila Blabla4

Fazer download em pdf ou txt
Fazer download em pdf ou txt
Você está na página 1de 46

Sumário

1 Fundamentos da programação orientada a objetos 3


1.1 Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Herança . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4 Polimorfismo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

2 Princípios da programação na linguagem Java 8


2.1 Tipos primitivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2 Identificadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3 Expressões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3.1 Expressões retornando valores numéricos . . . . . . . . . . . . . . . . . . . 11
2.3.2 Expressões retornando valores booleanos . . . . . . . . . . . . . . . . . . . 12
2.3.3 Outros tipos de expressões . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.3.4 Controle do fluxo de execução . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.3.5 Comentários . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.4 Operações sobre objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.4.1 Arranjos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.4.2 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.5 Classes em Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.5.1 Pacotes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.5.2 Definição de classes em Java . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.5.3 O método main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.5.4 Visibilidade da classe e seus membros . . . . . . . . . . . . . . . . . . . . . 25
2.5.5 Classes derivadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.5.6 Classes abstratas e finais . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.5.7 Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.6 Exceções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.6.1 Tratamento de exceções . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.6.2 Erros e exceções de runtime . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.6.3 Propagando exceções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.6.4 Definindo e gerando exceções . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.7 O ambiente de Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.7.1 Ferramentas do Java SDK . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.7.2 Geração de código portátil . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

1
Programação orientada a objetos com Java Sumário

2.7.3 Desenvolvimento de aplicações . . . . . . . . . . . . . . . . . . . . . . . . 35

3 Uso das classes da API padrão de Java 37


3.1 Funcionalidades básicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.2 Entrada e saída . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.2.1 Transferência de texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.2.2 Transferência de bytes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.2.3 Manipulação de arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.2.4 Serialização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
3.3 Framework de coleções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.4 Extensões padronizadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

4 Desenvolvimento de aplicações gráficas 46


4.1 Apresentação gráfica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
4.2 Interfaces gráficas com usuários . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.2.1 Eventos da interface gráfica . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.2.2 Componentes gráficos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.2.3 Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
4.2.4 Janelas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.2.5 Gerenciadores de layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.3 Desenvolvimento de applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.3.1 Criação de applet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.3.2 Execução de applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
4.3.3 Passagem de parâmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.3.4 Contexto de execução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69

5 Desenvolvimento de aplicações distribuídas 72


5.1 Programação cliente-servidor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5.1.1 Conceitos preliminares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.1.2 Aplicações TCP/IP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
5.1.3 Aplicações UDP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
5.1.4 Aplicações HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.2 Acesso a bancos de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
5.2.1 Bancos de dados relacionais . . . . . . . . . . . . . . . . . . . . . . . . . . 84
5.2.2 SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
5.2.3 JDBC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
5.3 Servlets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
5.3.1 Ciclo de vida de um servlet . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.3.2 Fundamentos da API de servlets . . . . . . . . . . . . . . . . . . . . . . . . 92
5.4 Programação com objetos distribuídos . . . . . . . . . . . . . . . . . . . . . . . . . 94
5.4.1 Arquiteturas de objetos distribuídos . . . . . . . . . . . . . . . . . . . . . . 94
5.4.2 Java RMI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
5.4.3 Java IDL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113

A Palavras chaves de Java 117

c 2001 FEEC/UNICAMP 2
Programação orientada a objetos com Java 4.3. Desenvolvimento de applets

28 int finalPos = largura - altura;


29 while (x < finalPos) {
30 g.setColor(new Color((float)Math.random(),
31 (float)Math.random(),
32 (float)Math.random()));
33 g.drawRect(x, y, altura, altura);
34 x += incremento;
35 }
36 }
37 }

Se na página HTML o elemento especificado for

<p>
<APPLET code="AppletParameter.class" width="640" height="20">
<PARAM name="background" value="White">
</APPLET>

o applet será apresentado com fundo branco; com

<p>
<APPLET code="AppletParameter.class" width="640" height="20">
<PARAM name="background" value="yellow">
</APPLET>

será apresentado com fundo amarelo. Se for invocado sem argumento, tendo na página HTML o
elemento

<p>
<APPLET code="AppletParameter.class" width="640" height="20">
</APPLET>

os retângulos serão desenhados sobre um fundo cinza.

4.3.4 Contexto de execução


Em algumas situações pode ser de interesse fazer com que o applet interaja com o contexto no
qual ele está executando (usualmente, o navegador Web). A interface AppletContext especifica
algumas funcionalidades que permitem essa interação.
Para obter o contexto no qual o applet está executando, o método getAppletContext() da
classe Applet é utilizado. Esse método retorna um objeto AppletContext, a partir do qual é
possível obter referências a outros applets no mesmo contexto através de uma enumeração, usando o
método getApplets(). Alternativamente, se ao applet foi atribuído um nome usando o atributo
name na tag APPLET, uma referência a esse applet pode ser obtida através do método getAp-
plet().
Como exemplo, considere a execução de dois applets em uma página, onde um applet tem um
campo de texto para obter uma entrada do usuário e o outro tem uma área de texto para exibir as

c 2001 FEEC/UNICAMP 69
Programação orientada a objetos com Java 4.3. Desenvolvimento de applets

entradas que o usuário digitou na outra janela. O primeiro applet é definido em uma classe
Entrada,
enquanto que o segundo é definido em uma classe TextAreaApplet. A inclusão em uma página
HTML dá-se através de dois elementos APPLET:

<p>
<applet code="Entrada.class"
width="200" height="100"
name="entra">
</applet>
<applet code="TextAreaApplet.class"
width="200" height="100"
name="mostra">
</applet>
</p>

O applet TextAreaApplet tem simplesmente uma área para exibição de texto:

1 import java.awt.*;
2 import java.applet.*;
3 public class TextAreaApplet extends Applet {
4 TextArea ta = new TextArea(5, 30);
5 public void init() {
6 add(ta);
7 }
8 public void append(String s) {
9 ta.append("\n" + s);
10 }
11 }

O applet Entrada define um campo para entrada de texto e estabelece a conexão entre os
contextos em seu método de inicialização:

1 import java.applet.*;
2 import java.awt.*;
3 import java.awt.event.*;
4 public class Entrada extends Applet {
5 TextField tf = new TextField(30);
6 TextAreaApplet ta;
7 public void init() {
8 add(tf);
9 ta = (TextAreaApplet) getAppletContext().getApplet("mostra");
10 tf.addActionListener(new ActionListener() {
11 public void actionPerformed(ActionEvent ae) {
12 ta.append(tf.getText());
13 }
14 });

c 2001 FEEC/UNICAMP 70
Programação orientada a objetos com Java 4.3. Desenvolvimento de applets

15 }
16 }

É possível também determinar que o navegador deve carregar um novo documento a partir de
um applet, usando o método showDocument() de AppletContext. O argumento para esse
método é um objeto localizador uniforme de recursos, do tipo java.net.URL.

c 2001 FEEC/UNICAMP 71
Capítulo 5

Desenvolvimento de aplicações
distribuídas

Entre os atrativos de Java está a facilidade que essa linguagem oferece para desenvolver aplica-
ções para execução em sistemas distribuídos. Já em sua primeira versão, Java oferecia facilidades
para o desenvolvimento de aplicações cliente-servidor usando os mecanismos da Internet, tais como
os protocolos TCP/IP e UDP.
Se o cliente na aplicação distribuída precisa acessar um servidor de banco de dados relacional,
Java oferece uma API específica para tal fim, JDBC. Através das classes e interfaces desse pacote é
possível realizar consultas expressas em SQL a um servidor de banco de dados e manipular as tabelas
obtidas como resultado dessas consultas.
Em termos de desenvolvimento voltado para a World-Wide Web, Java oferece o já clássico me-
canismo de applets, código Java que executa em uma máquina virtual no lado do cliente Web, como
descrito na Seção 4.3. O mecanismo de servlets permite associar o potencial de processamento da
plataforma Java a servidores Web, permitindo construir assim uma camada middleware baseada no
protocolo HTTP e em serviços implementados em Java.
Aplicações distribuídas mais elaboradas podem ser desenvolvidas usando uma arquitetura de ob-
jetos distribuídos, onde aplicações orientadas a objetos lidam diretamente com referências a objetos
em processos remotos. Java oferece duas alternativas nessa direção, RMI (Remote Method Invocati-
on), uma solução 100% Java, e Java IDL, uma solução integrada à arquitetura padrão CORBA. Um
passo adiante na evolução desse tipo de sistema é a utilização do conceito de agentes móveis, onde
não apenas referências a objetos são manipuladas remotamente mas os próprios objetos — código e
estado — movem-se pela rede.

5.1 Programação cliente-servidor


O paradigma de programação distribuída através da separação das aplicações entre servidores
(aplicações que disponibilizam algum serviço) e clientes (aplicações que usam esses serviços) foi
a arquitetura de distribuição predominante nos anos 1990. Um dos seus atrativos é o aumento da
confiabilidade (a falha de uma máquina não necessariamente inviabiliza a operação do sistema como
um todo) e redução de custo (máquinas mais simples podem executar os serviços isoladamente, ao
invés de ter uma grande máquina fazendo todos os serviços).

72
Programação orientada a objetos com Java 5.1. Programação cliente-servidor

As aplicações clientes e servidoras são programas executando em máquinas distintas, trocando


informação através de uma rede de computadores. Para que os serviços possam ser solicitados, a
aplicação cliente deve conhecer quem fornece o serviço (o endereço da aplicação servidora) e qual o
protocolo pré-estabelecido para realizar a solicitação.
Entre as vantagens citadas para o modelo de programação cliente-servidor destacam-se:

Relaciona a execução de processos distintos.

Oferece uma estruturação do processamento distribuído baseado no conceito de serviços, tendo


um servidor, o provedor de serviços, e um cliente, o consumidor de serviços oferecidos.

Permite compartilhamento de recursos, com o servidor atendendo a vários clientes.

Oferece transparência de localização, com tratamento uniforme independentemente de proces-


sos estarem na mesma máquina ou em máquinas distintas.

Permite a comunicação através da troca de mensagens, oferecendo uma arquitetura fracamente


acoplada através das mensagens para solicitações (cliente para servidor) e respostas (servidor
para cliente).

Encapsula serviços, pois o cliente não precisa saber como servidor implementa o serviço, mas
apenas a interface para solicitação e resposta.

Estaremos estudando aqui como Java oferece e simplifica o suporte a esse tipo de programação
através das funcionalidades do pacote java.net. A partir da apresentação de alguns conceitos pre-
liminares, apresenta-se os fundamentos Java para criar aplicações distribuídas usando os mecanismos
de TCP/IP, UDP e HTTP.

5.1.1 Conceitos preliminares


A programação em redes de computadores é atualmente a regra, não a exceção. A principal
vantagem nesse modelo de programação é a possibilidade de distribuir tarefas computacionalmente
pesadas e conjuntos extensos de dados e informações entre diversas máquinas.
No entanto, há um custo associado a essa distribuição. Há necessidade de trocar mensagens entre
as máquinas envolvidas no processamento, com um custo (tempo) adicional necessário para efetivar
essa troca.
É importante também que os dispositivos envolvidos na troca de mensagens comuniquem-se
usando uma mesma linguagem, ou protocolo. Protocolos são organizados em camadas (ou pilhas)
de diferentes níveis de abstração. O conjunto de protocolos mais comuns na programação em rede
é aquele estabelecido pela arquitetura TCP/IP, que opera com um software de suporte oferecido pelo
sistema operacional de uma máquina ligada em rede.
A arquitetura TCP/IP organiza uma rede de computadores em quatro camadas:

Interface de rede: define padrões de conexão à rede física, seja local (Ethernet-CSMA/CD, Token
Ring, FDDI, ATM) ou de longa distância (HDLC, X.25, ATM). Opera com endereços físicos,
realizando a conversão entre endereços físicos e lógicos através dos protocolos ARP (Address
Resolution Protocol) e RARP (Reverse ARP).

c 2001 FEEC/UNICAMP 73
Programação orientada a objetos com Java 5.1. Programação cliente-servidor

Inter-redes: protocolos para transporte não-confiável de mensagens (IP — Internet Protocol), para
controle da comunicação e informe de erros (ICMP — Internet Control Message Protocol) e
para roteamento de mensagens (EGP — Exterior Gateway Protocol, RIP — Routing Informa-
tion Protocol). Endereços para comunicação host a host são lógicos (endereços IP).

Transporte: protocolos para transporte confiável de dados por conexão (TCP/IP — Transfer Control
Protocol/IP) e para transporte de datagramas, sem conexão (UDP — User Datagram Protocol).
Introduz o conceito de porta (endereço que identifica a aplicação na máquina). Endereços nesse
nível são descritos por um par (host, port).

Aplicação: define conjunto de serviços manipulados por usuários. Serviços utilizam filosofia cliente-
servidor, com os servidores estabelecendo portas para disponibilização do serviço. Algumas
portas de serviços TCP/IP já são pré-definidas, sendo denominadas de portas notáveis. Exem-
plos de portas notáveis incluem: 7, echo (reenvia o que recebe); 21, ftp (transferência de
arquivos); 23, telnet (terminal virtual); 25, smtp (correio eletrônico); e 37, time (envia hora e
data local da máquina).

Na versão corrente do protocolo, endereços IP ocupam 32 bits e são divididos em cinco classes.
As classes A, B e C têm seus endereços estruturados em um prefixo de identificação de classe (biná-
rios 0, 10 e 110, respectivamente), identificador de subrede (7, 14 e 21 bits) e identificador de host.
A classe D (prefixo 1110) é utilizada para multicast, enquanto que endereços da classe E (11110) são
reservados para uso futuro. Usualmente, endereços IP são representados por uma quádrupla de valo-
res decimais correspondente aos quatro grupos de 8 bits do endereço. Nessa forma de representação,
endereços iniciados por valores entre 0 e 127 são da classe A; entre 128 e 191, classe B; entre 192 e
223, classe C; entre 224 e 239, classe D; e entre 240 e 247, classe E.
Existe também uma forma de representação simbólica de endereços IP baseada em nomes de
domínios. Domínios são partições da rede Internet organizados hierarquicamente em estruturas de
domínios e sub-domínios. Existe um mapeamento entre endereços IP representados simbolicamente
e numericamente, o qual é realizado por servidores de nome distribuídos pela Internet através do
Sistemas de Nomes de Domínio (DNS).
O desenvolvimento de software na arquitetura TCP/IP segue a filosofia de particionamento em
múltiplos processos concorrentes. Isso permite a simplificação de projeto, implementação e mani-
pulação de software para ambientes distribuídos, assim como a gerência independente de protocolos
em diversos níveis.
O software é organizado na forma de processos independentes. No nível mais próximo da má-
quina, isso permite o isolamento dos dispositivos físicos através da utilização de device drivers.
No nível da camada de transporte, dois tipos de processos são suportados. A informação em pro-
cessos TCP (entrada ou saída) é manipulada através do conceito de streams (arquivos seqüenciais).
Já a informação em processos UDP é manipulada através do conteúdo de pacotes datagramas, envia-
dos sem verificação de conteúdo ou garantia de recebimento. O processo IP atua como chaveador de
datagramas.
As portas estabelecem a ligação entre o processo da aplicação e os processos IP, estando associ-
adas a buffers finitos com acesso controlado. O acesso a portas pode sofrer bloqueio devido a uma
tentativa de ler de uma porta com buffer vazio ou de escrever para porta com buffer cheio.

c 2001 FEEC/UNICAMP 74
Programação orientada a objetos com Java 5.1. Programação cliente-servidor

5.1.2 Aplicações TCP/IP


Para estabelecer a conexão TCP, é preciso identificar as extremidades dessa conexão tanto no
processo cliente como no processo servidor. Essas extremidades são soquetes, identificados por um
endereço de rede e um número de porta. A conexão pode ser interpretada como uma ligação direta
entre os dois processos, através da qual os bytes podem fluir nos dois sentidos.
Um soquete TCP estabelece uma conexão stream bidirecional entre os endereços (hostC,portC)
e (hostS,portS), ou seja, entre uma aplicação cliente em execução na máquina hostC controlando a
porta portC e outra aplicação servidora em execução na máquina hostS monitorando a porta portS de
hostS. A aplicação cliente utiliza a porta portC da máquina hostC para enviar solicitações de serviços
e para receber retornos a suas solicitações. A aplicação servidora monitora constantemente a porta
portS da máquina hostS aguardando a chegada de solicitações de serviço. Quando alguma solicitação
é recebida, a aplicação servidora executa o serviço e utiliza a conexão para enviar o retorno com os
resultados do serviço.
Java suporta a troca de bytes entre um cliente e um servidor TCP através do estabelecimento
de uma conexão entre eles. Todas as funcionalidades de Java referentes ao estabelecimento de uma
conexão TCP estão agregadas no pacote java.net.

Clientes TCP em Java


Em Java, a classe que permite o estabelecimento de uma conexão pelo lado do cliente éSocket.
Para criar um soquete, o construtor da classe tipicamente utilizado é:

public Socket(InetAddress address, int port) throws IOException

Uma vez que a conexão entre cliente e servidor tenha sido estabelecida pela criação dos correspon-
dentes soquetes, os dados da aplicação podem fluir através dos streams a ela associados.
Os argumentos do construtor estabelecem o endereço IP da máquina remota na conexão. A classe
InetAddress de java.net permite representar endereços IP como objetos Java. Uma vez que
um objeto Java esteja representando um endereço IP, ele pode ser utilizado como parâmetro para
métodos de outras classes que manipulam transferências de dados através dos protocolos TCP/IP.
Os métodos estáticos dessa classe permitem definir tais objetos associados à representação sim-
bólica ou numérica de endereços IP — InetAddress.getByName(String host)— ou as-
sociados à máquina local — InetAddress.getLocalHost().
Este exemplo ilustra a manipulação de endereços IP através dos métodos dessa classe:

1 import java.net.*;
2 public class WhoIs {
3 public static void main(String[] args) {
4 try {
5 InetAddress myself = InetAddress.getLocalHost();
6 System.out.println("Local host is " +
7 myself.getHostName() +
8 " at IP address " +
9 myself.getHostAddress());
10 }

c 2001 FEEC/UNICAMP 75
Programação orientada a objetos com Java 5.1. Programação cliente-servidor

11 catch (UnknownHostException uhe) {


12 System.err.println(uhe);
13 }
14 // Processamento dos argumentos na linha de comando
15 int count = 0;
16 InetAddress otherHost;
17 while (count > args.length) {
18 try{
19 otherHost = InetAddress.getByName(args[count]);
20 System.out.println("Host " + otherHost.getHostName() +
21 " is at IP address " +
22 otherHost.getHostAddress());
23 }
24 catch (UnknownHostException uhe) {
25 System.err.println(uhe);
26 }
27 ++count;
28 }
29 }
30 }

A classe Socket oferece também métodos para obter informações sobre os endereços (máquina
e porta) envolvidos na conexão e para estabelecer timeouts associados à conexão.
Quando um soquete é criado, automaticamente são estabelecidos streams de entrada e de saída
para a transferência de dados pela conexão. Os métodos getInputStream() e getOutputS-
tream() da classe Socket permitem identificar os objetos associados a esses streams.
Streams implementam o conceito de cadeias unidirecionais de dados (FIFO, First-In, First-Out)
apenas de escrita ou apenas de leitura. Assim, uma aplicação pode obter dados do início de um stream
de entrada e pode enviar dados para o final de um stream de saída de dados, sempre seqüencialmente.
Streams em Java são suportados por classes do pacotejava.io (Seção 3.2). Para leitura seqüen-
cial de bytes utiliza-se um objeto da classeInputStream; para enviar bytes para um stream utiliza-
se um objetoOutputStream e seus métodoswrite(), para agregar bytes ao stream, eflush(),
para assegurar que os bytes inseridos sejam efetivamente encaminhados a seu destino.

Servidores TCP em Java


O processo servidor na arquitetura TCP deve estar preparado para responder a solicitações de
conexões por parte dos clientes, permanecendo em estado de espera entre solicitações. Assim, um
servidor TCP/IP deve realizar duas tarefas básicas: permanecer em execução aguardando (listening)
a chegada de requisições em alguma porta pré-especificada; e responder à solicitação através de uma
conexão estabelecida com o cliente em função da requisição recebida.
Em Java, a classe que permite a criação de servidores com essa funcionalidade é ServerSoc-
ket, também do pacotejava.net. O principal método desta classe éaccept(), que implementa
a espera bloqueada por uma solicitação no endereço de porta especificado na construção do objeto.

c 2001 FEEC/UNICAMP 76
Programação orientada a objetos com Java 5.1. Programação cliente-servidor

O retorno desse método é um objeto da classe Socket que estabelece a conexão com a aplicação
cliente.
Esse exemplo mostra o código para um servidor que responde a qualquer solicitação com uma
mensagem fixa, cujo conteúdo é a seqüência de bytes que compõe um endereço URL:

1 import java.io.*;
2 import java.net.*;
3 import java.util.*;
4 public class TCPServer1 {
5 public static void main(String[] args) {
6 ServerSocket ss = null;
7 Socket cliente = null;
8 OutputStream os = null;
9 try {
10 ss = new ServerSocket(0);
11 System.out.println("Server: Aguardando na porta " +
12 ss.getLocalPort());
13 while (true) {
14 cliente = ss.accept();
15 os = cliente.getOutputStream();
16 System.out.println("Server: " +
17 "Processando solicitacao de " +
18 cliente.getInetAddress().getHostName());
19 String data =
20 "http://www.dca.fee.unicamp.br/cursos/PooJava/";
21 byte[] buffer = data.getBytes();
22 System.out.println("Server: Enviando \"" +
23 new String(buffer) + "\"");
24 os.write(buffer);
25 os.flush();
26 }
27 }
28 catch (Exception e) {
29 System.err.println(e);
30 }
31 finally {
32 try {
33 os.close();
34 cliente.close();
35 ss.close();
36 }
37 catch (Exception e) {
38 e.printStackTrace();
39 }
40 }

c 2001 FEEC/UNICAMP 77
Programação orientada a objetos com Java 5.1. Programação cliente-servidor

41 }
42 }

Uso de múltiplas threads


Tipicamente, por razões de eficiência, um servidor TCP é implementado como um processo
multithreaded. Um dos possíveis problemas na execução de aplicações segundo o modelo cliente-
servidor está associado com o tempo de atendimento a uma solicitação pelo servidor. Se o servidor
for um processo monolítico, ele estará indisponível para receber novas requisições enquanto a soli-
citação não for completamente atendida. A solução para este problema depende da possibilidade de
se estabelecer um processamento independente para o atendimento de cada solicitação ao servidor,
liberando tão cedo quanto possível o servidor para receber novas solicitações.
O conceito de processamento independente é parte integrante da linguagem Java, através de mul-
tithreading. Todo processamento em Java está associado a alguma thread, sendo que novas threads
de execução podem ser criadas a partir de qualquer thread. A criação de novas threads é em geral
associada a classes que implementam a interface Runnable do pacote java.lang. Essa inter-
face especifica o método run(), os quais são utlizados para criar objetos da classe Thread, com
métodos start() e stop(), entre outros.
Com as facilidades suportadas pela linguagem, torna-se atrativo implementar servidores multith-
readed, cujo corpo principal de processamento resume-se a um laço eterno para aceitar solicitações
na porta especificada e criar um objeto thread para atender à solicitação recebida. A funcionalidade
do serviço que será executado pela thread é definida no corpo do método run() que implementa a
interface Runnable associada à thread criada. Cada thread criada existe exclusivamente durante o
tempo necessário para atender à solicitação.
O seguinte exemplo revisita o servidor TCP anteriormente apresentado usando o mecanismo de
múltiplas threads de execução:

1 import java.io.*;
2 import java.net.*;
3 import java.util.*;
4 class DataProvider implements Runnable {
5 Socket client;
6 OutputStream os = null;
7 public DataProvider(Socket s) throws IOException {
8 client = s;
9 os = client.getOutputStream();
10 }
11 public void run() {
12 String data =
13 "http://www.dca.fee.unicamp.br/courses/PooJava/";
14 byte[] buffer = data.getBytes();
15 try {
16 os.write(buffer);
17 os.flush();
18 os.close();

c 2001 FEEC/UNICAMP 78
Programação orientada a objetos com Java 5.1. Programação cliente-servidor

19 client.close();
20 }
21 catch (Exception e) {
22 System.err.println(e);
23 }
24 }
25 }
26 public class TCPServer2 {
27 public static void main(String[] args) {
28 ServerSocket ss = null;
29 Socket cliente = null;
30 try {
31 ss = new ServerSocket(0);
32 System.out.println("Server: Aguardando na porta " +
33 ss.getLocalPort());
34 while (true) {
35 cliente = ss.accept();
36 System.out.println("Server: " +
37 "Processando solicitacao de " +
38 cliente.getInetAddress().getHostName());
39 DataProvider dp = new DataProvider(cliente);
40 new Thread(dp).start();
41 }
42 }
43 catch (Exception e) {
44 System.err.println(e);
45 }
46 finally {
47 try {
48 ss.close();
49 }
50 catch (Exception e) {
51 System.err.println(e);
52 }
53 }
54 }
55 }

5.1.3 Aplicações UDP


Aqui serão apresentadas as funcionalidades que Java oferece para a programação cliente-servidor
usando o protocolo de transporte UDP.
A principal diferença em relação à programação cliente-servidor em TCP é que o protocolo UDP
não suporta o conceito da transferência por streams de dados. UDP trabalha diretamente com o con-
ceito de pacotes (datagramas). Assim, UDP não oferece a garantia de envio ou recepção e nem de

c 2001 FEEC/UNICAMP 79
Programação orientada a objetos com Java 5.1. Programação cliente-servidor

ordenação correta dos pacotes. Por outro lado, a ausência desses mecanismos permite uma transfe-
rência mais rápida.
O endereçamento em UDP dá-se como para a programação TCP, usando a classe JavaInetAd-
dress.

Soquetes UDP
Assim como para o protocolo TCP, UDP estabelece uma conexão entre o processo da aplicação
e a rede através de um soquete.
Em Java, soquetes UDP são manipulados através de objetos da classe DatagramSocket. O
construtor padrão para essa classe cria um soquete local na primeira porta disponível. Alternativa-
mente, outro construtor permite especificar o número da porta desejado.
Ao contrário da classe Socket, um DatagramSocket não estabelece uma conexão com uma
máquina remota, mas simplesmente um acesso local para a rede que pode ser utilizada para enviar e
receber pacotes, através dos métodos send() e receive(), respectivamente.
Há um método connect() associado a objetos da classeDatagramSocket; no entanto, esse
método atua como um filtro, só permitindo enviar ou receber pacotes para ou de um endereço IP ao
qual o soquete foi conectado. O método disconnect() permite desconectar um soquete de um
destino pré-especificado.

Datagramas
Os pacotes enviados e recebidos através dos soquetes UDP são objetos Java da classe Data-
gramPacket. Objetos dessa classe podem ser construídos de duas maneiras, dependendo se serão
enviados ou recebidos através do soquete UDP:

Pacotes a enviar. Nesse caso, deve ser utilizado o construtor que incorpora em seus argumentos o
arranjo de bytes a enviar, seu tamanho, e o endereço de destino (máquina, especificada pelo
seu endereço IP, e porta).

Pacotes a receber. Nesse caso, os argumentos especificam apenas o arranjo de bytes para onde o
conteúdo do pacote será transferido e o limite no tamanho do pacote que será recebido nesse
arranjo.

Uma vez que um pacote tenha sido recebido, a informação sobre sua origem pode ser obtida
através dos métodos getAddress() e getPort(). Os dados efetivamente recebidos podem ser
extraídos do pacote usando o método getData(); o método getLength() permite determinar a
dimensão dos dados.

Multicast
Um MulticastSocket é uma especialização de um DatagramSocket que permite que
uma aplicação receba pacotes datagramas associados a um endereço multicast (classe D, endereços
entre 224.0.0.1 e 239.255.255.255). Não é preciso nenhuma funcionalidade especial para apenas
enviar datagramas para um endereço multicast.

c 2001 FEEC/UNICAMP 80
Programação orientada a objetos com Java 5.1. Programação cliente-servidor

Todos os soquetes multicast que estejam inscritos em um endereço multicast recebem o datagra-
ma que foi enviado para esse endereço e porta. Para gerenciar a inscrição de um soquete em um
endereço multicast, dois métodos são oferecidos na classe MulticastSocket.
O primeiro, joinGroup(InetAddress m), permite à aplicação juntar-se a um grupo mul-
ticast. É o método que inscreve o soquete no grupo associado ao endereço multicast especificado
como argumento.
O outro método, para desligar-se de um grupo multicast, éleaveGroup(InetAddress m),
que desconecta o soquete do grupo multicast especificado.

5.1.4 Aplicações HTTP


A World Wide Web (WWW ou simplesmente Web) é a primeira concretização de uma rede mun-
dial de informação através de computadores. Proposta em 1989 no CERN (Suíça) por Tim-Berners
Lee, ela interconecta principalmente documentos hipertexto (expressos em HTML — HyperText
Markup Language) usando a infra-estrutura da Internet para a transferência de informação. Sua
difusão expandiu-se principalmente após a popularização de interfaces gráficas com o usuário para
navegação em hipertexto, iniciada a partir do lançamento do aplicativo Mosaic no NCSA (EUA).
A Web pode ser vista como um serviço de aplicação da arquitetura TCP/IP. Como tal, a arquitetu-
ra da Web define um esquema de endereçamento (URL) no nível da aplicação; estabelece protocolos
de sessão (soquetes TCP/IP) e apresentação (HTML e auxiliares); define um protocolo (HTTP) no
nível da aplicação; e segue o modelo cliente-servidor.
As aplicações clientes na Web são usualmente navegadores (browsers) responsáveis pela apresen-
tação de documentos HTML e pela solicitação de um recurso a servidores Web. O recurso pode ser
um documento HTML ou um arquivo contendo informação texto ou binária (imagem, áudio, vídeo,
applet Java, etc), conforme estabelecido pelo elemento que originou a solicitação.
Não necessariamente o software navegador precisa saber manipular todos os tipos de recursos,
podendo ele ativar rotinas auxiliares de exibição para tipos não reconhecidos. As rotinas auxiliares
podem ser ativadas sob a forma de programas executáveis externos ao navegador ou através de plug-
ins. O conceito de plug-in foi desenvolvido pela Nestcape, sendo constituído por uma interface de
programação (API) padronizada para ativar funcionalidades carregadas dinamicamente.
Servidores Web são responsáveis por atender a solicitações de clientes, por default operando
na porta notável 80. As funcionalidades básicas de servidores Web incluem: organizar e gerenciar
os recursos HTTP; prover acesso seguro a esses recursos; e processar scripts (extensões CGI, por
exemplo — ver Seção 5.3).
Java também oferece suporte ao desenvolvimento de aplicações sobre a Web usando recursos do
pacote java.net. Dentre os conceitos oferecidos pela linguagem Java para a programação distri-
buída há uma grande ênfase na programação direcionada para a Web, com suporte para a manipulação
de recursos Web através de URL, a manipulação de conexões HTTP, a conversão de strings para o
formato de codificação www-urlencoded e suporte à manipulação de conteúdos de diferentes tipos
MIME.

Endereçamento por URL


Um endereço localizador uniforme de recursos (URL) é definido por uma seqüência de caracteres
que identifica de forma global e precisa um recurso na Web. A forma genérica de um URL é

c 2001 FEEC/UNICAMP 81
Programação orientada a objetos com Java 5.1. Programação cliente-servidor

esquema:parte_específica

onde o esquema identifica o protocolo utilizado para manipular o recurso, tais como http, ftp,
mailto, telnet e nntp. A parte específica descreve o endereço do recurso de acordo com a infra-
estrutura e o tipo de recurso.
Na Internet, a parte específica de um URL toma a forma genérica

//user:password@host:port/url-path

onde nem todos os campos devem necessariamente estar presentes para todos os recursos endereça-
dos. Quando port é omitido, a porta notável para o esquema especificado é utilizada (21 para ftp,
23 para telnet, 25 para mailto, 80 para http). O campo url-path é dependente do esquema.
A classe java.net.URL oferece a funcionalidade de nível mais alto (menor detalhamento)
para a especificação de recursos Web. Cada recurso está associado a um objeto dessa classe, sendo
que o localizador (URL) do recurso é especificado na construção do objeto.
Uma vez que o objeto URL esteja instanciado, há três maneiras de realizar a transferência do
conteúdo do recurso para a aplicação local.
Na primeira forma, através do método openStream(), obtém-se um fluxo de leitura de bytes
que permite transferir o conteúdo do recurso. Outra possibilidade é usar o métodoopenConnecti-
on(), que retorna um objeto da classe (abstrata) URLConnection. Esta classe permite manipular
um maior número de detalhes referentes à conexão URL, tais como obter dimensão, tipo e codifica-
ção do conteúdo, manipulação do conteúdo associado a um stream de entrada, obtenção do cabeçalho
e outras. Finalmente, é possível usar o método getContent(), que obtém o conteúdo do recurso
diretamente. Nesse caso, um objeto ContentHandler específico para o tipo de recurso recebido
será ativado.

Conexões HTTP
O protocolo no nível da aplicação para a transferência de hipertexto (HTTP, HyperText Transfer
Protocol) opera sobre o protocolo TCP/IP para estabelecer um mecanismo de serviço com estrutura
requisição-resposta. Uma das características peculiares de HTTP é a composição flexível do cabeça-
lho, composto por diversas linhas, o que permite sua utilização como integrador de diversos formatos
e não apenas de documentos HTML.
Essa flexibilidade reflete-se também na maior complexidade desse protocolo. No entanto, é pos-
sível estabelecer servidores HTTP operando com configurações simplificadas, onde nem todos os
serviços previstos no protocolo são implementados.
Os principais serviços de HTTP incluem:

GET: solicita ao servidor o envio de um recurso; é o serviço essencial para o protocolo.

HEAD: variante de GET que solicita ao servidor o envio apenas de informações sobre o recurso.

PUT: permite que o cliente autorizado armazene ou altere o conteúdo de um recurso mantido pelo
servidor.

POST: permite que o cliente envie mensagens e conteúdo de formulários para servidores que irão
manipular a informação de maneira adequada.

c 2001 FEEC/UNICAMP 82
Programação orientada a objetos com Java 5.2. Acesso a bancos de dados

DELETE: permite que o cliente autorizado remova um recurso mantido pelo servidor.
Um cabeçalho HTTP é composto por uma linha contendo a especificação do serviço e recurso
associado, seguida por linhas contendo parâmetros. Um exemplo de requisição gerada por um cliente
HTTP é:
GET http://www.dca.fee.unicamp.br/
Accept: text/html, image/gif, image/jpeg
User-Agent: Mozilla/3.0
para a qual o cabeçalho da resposta poderia ser:
HTTP/1.1 200 OK
Date: Wed, 24 Mar 1999 23:23:45 GMT
Server: Apache/1.2b6
Connection: close
Content-Type: text/html
Content-length: 648
A indicação do tipo de conteúdo do recurso (usada nos parâmetrosAccept e Content-Type)
seguem a especificação no padrão MIME (Multipurpose Internet Mail Extensions).
A classe HttpURLConnection é uma especialização da classe URLConnection. Quando
um objeto da classe URL invoca openConnection() é esse o tipo de conexão retornada quando
o protocolo é HTTP.
Além das funcionalidades de conexões URL, essa classe define várias constantes associadas espe-
cificamente ao protocolo HTTP (tais como os códigos de erros) e alguns poucos métodos específicos
de conexão HTTP.

Codificação e decodificação de dados


A tradução de strings para o formato esperado por um servidor Web a partir de um formulário,
x-www-form-urlencoded, é suportada através da classe URLEncoder. Essa classe oferece o método
estático encode(String s), retornando uma string com o conteúdo codificado do argumento.
O formato www-urlencoded agrega em uma única string uma série de pares na forma atribu-
to=valor separados pelo símbolo ’&’. Nesse formato, espaços são convertidos para o símbolo ’+’
e caracteres com conotação especial — tais como +, = e & — são representados por seqüência de
escape ’%xx’ para a representação hexadecimal do valor ASCII do caráter. Assim, o caráter ’=’ que
faça parte do nome de um atributo ou parte do conteúdo de um valor será codificado na string na
forma ’%3d’.
O processo de tradução a partir de uma string nesse formato, que seria o necessário para imple-
mentar um serviço em Java que recebesse dados de um formulário, é oferecido pela classe URLDe-
coder, através do método estático decode(String).

5.2 Acesso a bancos de dados


A linguagem Java vem se destacando como uma alternativa viável para a integração de apli-
cações novas e legadas na Internet. Essa infra-estrutura de integração não estaria completa se não
contemplasse o acesso a sistemas de bancos de dados.

c 2001 FEEC/UNICAMP 83
Programação orientada a objetos com Java 5.2. Acesso a bancos de dados

Um sistema de banco de dados é constituído por uma coleção organizada de dados (a base de
dados) e pelo software que coordena o acesso a esses dados (o sistema gerenciador de banco de dados,
ou SGBD). Utilizar um sistema de banco de dados ao invés de simples arquivos para armazenar de
forma persistente os dados de uma aplicação apresenta diversas vantagens, das quais se destacam:
o desenvolvedor da aplicação não precisa se preocupar com os detalhes do armazenamento,
trabalhando com especificações mais abstratas para a definição e manipulação dos dados;
tipicamente, um SGBD incorpora mecanismos para controle de acesso concorrente por múlti-
plos usuários e controle de transações;
é possível compartilhar dados comuns entre mais de uma aplicação mesmo que a visão dos
dados de cada aplicação seja distinta.
Existem soluções para integrar aplicações Java a bancos de dados orientados a objetos e a bancos
de dados relacionais. Neste caso, a solução é padronizada através da especificação JDBC.

5.2.1 Bancos de dados relacionais


Um banco de dados relacional organiza seus dados em relações. Cada relação pode ser vista
como uma tabela, onde cada coluna corresponde a atributos da relação e as linhas correspondem às
tuplas ou elementos da relação. Em uma nomenclatura mais próximas àquela de sistemas de arquivos,
muitas vezes as tuplas são denominadas registros e os atributos, campos.
Um conceito importante em um banco de dados relacional é o conceito de atributo chave, que
permite identificar e diferenciar uma tupla de outra. Através do uso de chaves é possível acelerar o
acesso a elementos (usando índices) e estabelecer relacionamentos entre as múltiplas tabelas de um
sistema de banco de dados relacional.
Essa visão de dados organizados em tabelas oferece um conceito simples e familiar para a estru-
turação dos dados, sendo um dos motivos do sucesso de sistemas relacionais de dados. Certamente,
outros motivos para esse sucesso incluem o forte embasamento matemático por trás dos conceitos uti-
lizados em bancos de dados relacionais e a uniformização na linguagem de manipulação de sistemas
de bancos de dados relacionais através da linguagem SQL.
Sob o ponto de vista matemático, uma relação é o subconjunto do produto cartesiano dos domí-
nios da relação. Sendo um conjunto, é possível realizar operações de conjuntos — tais como união,
interseção e diferença — envolvendo duas relações de mesma estrutura.
No entanto, um dos pontos mais fortes do modelo relacional está nos mecanismos de manipula-
ção estabelecidos pela Álgebra Relacional. Os três principais operadores da álgebra relacional são
seleção, projeção e junção.
A operação de seleção tem como argumento uma relação e uma condição (um predicado) envol-
vendo atributos da relação e/ou valores. O resultado é uma outra relação contemplando apenas as
tuplas para as quais a condição foi verdadeira.
A operação de projeção tem como argumento uma relação e uma lista com um subconjunto dos
atributos da relação. O resultado é outra relação contendo todas as tuplas da relação mas apenas com
os atributos especificados.
Observe que se a lista de atributos não englobar a chave da relação, o resultado dessa operação
poderia gerar tuplas iguais. Sob o ponto de vista estritamente matemático, os elementos duplicados
devem ser eliminados, pois não fazem sentido para um conjunto.

c 2001 FEEC/UNICAMP 84
Programação orientada a objetos com Java 5.2. Acesso a bancos de dados

A operação de junção recebe como argumentos duas relações e uma condição (um predicado)
envolvendo atributos das duas relações. O resultado é uma relação com os atributos das duas relações
contendo as tuplas que satisfizeram o predicado especificado. A operação de junção não é uma
operação primitiva, pois pode ser expressa em termos da operação de produto cartesiano e da seleção,
mas é uma das operações mais poderosas da álgebra relacional.
A forma mais usual de junção é aquela na qual a condição de junção é a igualdade entre valores
de dois atributos das relações argumentos. Essa forma é tão usual que recebe o nome de junção
natural. Nesse caso, o atributo comum aparece apenas uma vez na relação resultado, já que ele teria
para todas as tuplas o mesmo valor nas duas colunas.

5.2.2 SQL
SQL é uma linguagem padronizada para a definição e manipulação de bancos de dados relaci-
onais. Tipicamente, um SGBD oferece um interpretador SQL que permite isolar a aplicação dos
detalhes de armazenamento dos dados. Se o projetista da aplicação tiver o cuidado de usar apenas
as construções padronizadas de SQL, ele poderá desenvolver a aplicação sem se preocupar com o
produto SGBD que estará sendo utilizado depois.
As três componentes de SQL são:
1. uma linguagem de definição de dados (DDL) para definir e revisar a estrutura de bancos de
dados relacionais;

2. uma linguagem de controle de dados (DCL) para especificar mecanismos de segurança e inte-
gridade dos dados; e

3. uma linguagem de manipulação de dados (DML) para ler e escrever os dados.


A DDL supre as facilidades para a criação e manipulação de esquemas relacionais. Uma das
necessidades de uma aplicação que irá armazenar seus dados em um sistema de banco de dados
relacional é como criar uma identidade para o conjunto de tabelas de sua aplicação. Esse mecanismo
não é padronizado em SQL, podendo variar de fornecedor a fornecedor de banco de dados. Algumas
possibilidades incluem
CREATE SCHEMA name [AUTHORIZATION {user | group}]
ou
CREATE DATABASE name [On {default | device}]
[Log On device [= size]]
Para criar uma tabela, o comando CREATE TABLE é utilizado:
CREATE TABLE name ( ATTRIBUTE DOMAIN [UNIQUE] [{NULL | NOT NULL}])
Alguns fabricantes adotam, ao invés da forma UNIQUE para indicação de chave da relação, um
comando Primary Key após a criação da tabela para indicar qual atributo formará ou quais atri-
butos comporão a chave primária da relação.
O campo ATTRIBUTE especifica o nome para a aplicação da coluna da tabela, enquantoDOMAIN
especifica o tipo de dado para a coluna. Alguns tipos de dados usuais em SQL são INTEGER,

c 2001 FEEC/UNICAMP 85
Programação orientada a objetos com Java 5.2. Acesso a bancos de dados

DECIMAL(p,q) — dos dígitos são casas decimais, FLOAT(p) — é a precisão, CHARACTER(n)


7 8 8

— string de caracteres, BIT(n) — arranjo de valores booleanos, DATE, TIME e TIMESTAMP.


9 9

Além desses comandos, ALTER TABLE permite modificar a estrutura de uma tabela existente e
DROP TABLE permite remover uma tabela.
O principal comando da DML de SQL é SELECT. Por exemplo, para obter todos os dados de
uma relação utiliza-se a forma básica:

SELECT * FROM table

Através do mesmo comando, é possível especificar projeções e seleções de uma tabela:

SELECT columns FROM table WHERE condition

A condição pode envolver comparações entre atributos e/ou valores constantes, podendo para
tanto utilizar comparadores tais como igual (=), maior que (>), menor que (<), maior ou igual que
(>=), menor ou igual que (<=), diferente (!= ou <>) e comparadores de strings ( LIKE ‘string’, onde
‘string’ pode usar os caracteres ‘_’ e ’%’ para indicar um caráter qualquer os uma seqüência qual-
quer de caracteres, respectivamente). As comparações podem ser combinadas através dos conectivos
lógicos And, Or e Not.
É possível expressar as quatro operações aritméticas (+, -, *, /), tanto para a condição como para
a especificação de recuperação dos dados.
É possível também especificar a ordem desejada de apresentação dos dados, usando para tal a for-
ma SELECT...ORDER BY.... Uma alternativa a esse comando é SELECT...GROUP BY...,
que ordena os dados e não apresenta elementos que tenham o mesmo valor para a cláusula de agru-
pamento.
Uma importante categoria de funções de SQL incluem as funções de agregação, que permitem
computar a média ( Avg), a quantidade ( Count), o maior ou menor valor ( Max ou Min) e o total
(Sum) das expressões especificadas.
Através do mesmo comando Select, é possível especificar consultas envolvendo múltiplas
tabelas, como em

SELECT nome, data FROM Pedidos, Clientes


WHERE Pedidos.NumCliente = Clientes.NumCliente

Nesse caso, a junção das duas tabelas Pedidos e Clientes é realizada tendo como Num-
Cliente como atributo de junção.
É possível especificar consultas internas a uma consulta, como em

SELECT NumProduto From Produtos


WHERE PrecoUnit > (SELECT Avg(PrecoUnit) FROM Produtos)

Para qualificar os resultados de uma subconsulta, podem ser utilizadas as funções All (a con-
dição foi verdadeira para todos os elementos resultantes da subconsulta), Any (verdade para pelo
menos um elemento), Exists (verdade se resultado da consulta tem pelo menos um elemento), In
(verdade se o valor especificado faz parte do resultado), Not Exists (verdade se subconsulta teve
resultado vazio) e Not In (verdade se valor especificado não faz parte do resultado da subconsulta).
SQL oferece ainda mecanismos para inserir elementos em uma tabela,

c 2001 FEEC/UNICAMP 86
Programação orientada a objetos com Java 5.2. Acesso a bancos de dados

INSERT INTO table (columns) VALUES (values)

para modificar valores de colunas de um elemento,

UPDATE table SET column = value [, column = value]*


WHERE condition

e para remover elementos de uma tabela,

DELETE FROM table


WHERE condition

SQL pode ser utilizado diretamente pelo usuário, quando o SGBD oferece um interpretador SQL
interativo, ou através de comandos embutidos em uma aplicação desenvolvida em uma linguagem
de programação. No caso dessa linguagem ser Java, a forma de interagir com o banco de dados é
especificado por JDBC.

5.2.3 JDBC
Java permite o acesso a bancos de dados relacionais através das funcionalidades definidas no
pacote java.sql, que define o “produto JDBC”.
JDBC é uma API para execução e manipulação de resultados a consultas SQL através de Java.
Para desenvolver uma aplicação com Java e bancos de dados relacionais, é preciso ter disponível:

O pacote JDBC (padrão na distribuição da plataforma de desenvolvimento Java desde sua


versão 1.1);

Acesso ao servidor, o sistema gerenciador de banco de dados que entende SQL; e

O driver JDBC adequado ao tipo de SGBD acessado.

Uma vez que esses recursos estejam disponíveis, a aplicação Java tem acesso ao banco de dados
relacional através da execução dos seguintes passos:

1. Habilitar o driver;

2. Estabelecer uma conexão com o banco de dados;

3. Executar consulta SQL; e

4. Apresentar resultados da consulta.

Driver JDBC
Do ponto de vista da aplicação Java, um driver nada mais é do que uma classe cuja funcionalidade
precisa ser disponibilizada para a aplicação. A funcionalidade básica que um driver deve oferecer é
especificada através da interface Driver.
A classe DriverManager estabelece um conjunto básico de serviços para a manipulação de
drivers JDBC. Como parte de sua inicialização, essa classe tentará obter o valor da propriedade

c 2001 FEEC/UNICAMP 87
Programação orientada a objetos com Java 5.2. Acesso a bancos de dados

jdbc.drivers de um arquivo de definição de propriedades e carregar os drivers especificados


pelos nomes das classes.
Alternativamente, um driver pode ser carregado explicitamente para a JVM; a forma usual para
executar essa tarefa é através do método forName() da classe Class, como em
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

Conexão com banco de dados


Uma vez que o driver esteja carregado, a aplicação Java pode estabelecer uma conexão com o ge-
renciador de banco de dados. Para especificar com qual banco de dados deseja-se estabelecer a cone-
xão, é utilizada uma string na forma de um URL na qual o protocolo éjdbc: e o restante da string é
dependente do driver. Por exemplo, o driver jdbc:odbc especifica o formato jdbc:odbc:dsn,
onde dsn é o data source name que identifica o banco de dados.
Identificado o banco de dados, a sessão a ser estabelecida para o acesso ao banco de dados será
controlada por um objeto de uma classe que implementa a interface Connection. O DriverMa-
nager oferece o método getConnection() para executar essa tarefa.
O encerramento de uma sessão é sinalizado pelo método close() da conexão:
import java.sql.*;
...
String DB = "jdbc:...";
...
Connection c = DriverManager.getConnection(DB);
...
c.close(); // encerra a sessão

Execução da consulta
Estabelecida a conexão ao banco de dados, é possível criar uma consulta e executá-la a partir
da aplicação Java. Para representar uma consulta, o JDBC utiliza um objeto de uma classe que
implementa a interface Statement. Um objeto dessa classe pode ser obtido através do método
createStatement() da classe Connection.
Uma vez que um objeto Statement esteja disponível, é possível aplicar a ele o método exe-
cuteQuery(), que recebe como argumento uma string representando uma consulta SQL.
O resultado da execução da consulta é disponibilizado através de um objeto ResultSet.
import java.sql.*;
...
Connection c;
c = ...;
Statement s = c.createStatement();
String query;
query = ...;
ResultSet r = s.executeQuery(query);
...
s.close();

c 2001 FEEC/UNICAMP 88
Programação orientada a objetos com Java 5.2. Acesso a bancos de dados

Os métodos da interfaceResultSet permitem a manipulação dos resultados individuais de uma


tabela de resultados. Métodos como getDouble(), getInt(), getString() e getTime(),
que recebem como argumento a especificação de uma coluna da tabela, permitem acessar o valor da
coluna especificada na tupla corrente para os diversos tipos de dados suportados.
Para varrer a tabela, um cursor é mantido. Inicialmente, ele está posicionado antes do início da
tabela, mas pode ser manipulado pelos métodos first(), next(), previous(), last() e
absolute(int row).
Por exemplo,

ResultSet r = s.executeQuery("Select * from Clientes");


System.out.println("ID NOME");
while (r.next())
System.out.println(r.getString("ClienteID")+
" " + r.getString("Nome"));
r.close();

Para lidar com atributos que podem assumir valores nulos, o método wasNull() é oferecido.
Ele retorna verdadeiro quando o valor obtido pelo método getXXX() for nulo, onde XXX é um dos
tipos SQL.
A interface ResultSetMetadata permite obter informação sobre a tabela com o resultado da
consulta. Um objeto desse tipo pode ser obtido através da aplicação do método getMetaData()
ao ResultSet:

ResultSetMetaData m = r.getMetaData();

Uma vez obtido esse objeto, a informação desejada pode ser obtida através de métodos tais como
getColumnCount(), getColumnLabel(), getColumnTypeName() e getColumnTy-
pe(). O último método retorna tipos que podem ser identificados a partir de constantes definidas
para a classe java.sql.Types.
Além da forma Statement, JDBC oferece duas formas alternativas que permitem respecti-
vamente ter acesso a comandos SQL pré-compilados ( PreparedStatement) e a procedimentos
armazenados no banco de dados (CallableStatement).

Exemplo completo
Este exemplo ilustra o mecanismo básico para uma aplicação Java acessar um banco de dados. O
objetivo é apresentar o conteúdo da seguinte relação, a tabela “notas” do banco de dados “poojava”:

> java PoojavaDB


fname lname activ grd
Joao Silva 1 6
Joao Silva 2 7
Pedro Souza 1 8
Pedro Souza 2 5
Maria Santos 1 7
Maria Santos 2 8

c 2001 FEEC/UNICAMP 89
Programação orientada a objetos com Java 5.3. Servlets

O resultado acima foi obtido a partir da execução da seguinte aplicação, acessando um gerencia-
dor de banco de dados PostgreSQL:

1 import java.sql.*;
2 public class PoojavaDB {
3 public static void main(String[] args) {
4 try {
5 // carrega driver
6 Class.forName("postgresql.Driver");
7 // estabelece conexao com banco de dados
8 Connection c =
9 DriverManager.getConnection("jdbc:postgresql:poojava",
10 "ricarte","");
11 // monta e executa consulta
12 Statement s = c.createStatement();
13 ResultSet r = s.executeQuery("Select * from notas");
14 // apresenta estrutura da tabela
15 ResultSetMetaData m = r.getMetaData();
16 int colCount = m.getColumnCount();
17 for (int i=1; i<=colCount; ++i)
18 System.out.print(m.getColumnName(i) + "\t\t");
19 System.out.println();
20 // apresenta resultados da consulta
21 while (r.next())
22 System.out.println(r.getString(1) + " " +
23 r.getString(2) + " " +
24 r.getInt(3) + "\t\t" +
25 r.getInt(4));
26 r.close();
27 // fecha conexao com banco de dados
28 c.close();
29 }
30 catch (Exception e) {
31 System.err.println(e);
32 }
33 }
34 }

5.3 Servlets
Servlets oferecem uma maneira alternativa a CGI para estender as funcionalidades de um servidor
Web. Na verdade, a API de servlet de Java oferece mecanismos adequados à adaptação qualquer
servidor baseado em requisições e respostas, mas é em aplicações Web que servlets têm sido mais
utilizados.

c 2001 FEEC/UNICAMP 90
Programação orientada a objetos com Java 5.3. Servlets

CGI (Common Gateway Interface) é a especificação de uma interface que permite que servidores
Web tenham acesso a funcionalidades oferecidas por programas executando no ambiente da máquina
servidora. Através de programas conectados a essa interface é possível por exemplo conectar uma
base de dados à Web ou gerar dinamicamente o conteúdo de uma página HTML.
O servidor Web reconhece uma requisição CGI quando o URL especificado na solicitação iden-
tifica um arquivo executável (programa ou script) localizado em um diretório específico dentro do
espaço Web de recursos disponibilizados aos clientes. Parâmetros podem ser repassados ao programa
CGI especificando-os no URL, separados do nome do recurso pelo caráter ’?’.
Tipicamente um programa CGI pode ser desenvolvido em qualquer linguagem de programação
que tenha acesso à leitura de variáveis de ambiente e à manipulação dos streams padrões de entrada
e saída de dados do sistema operacional (stdin, System.in; stdout, System.out).
Com o uso de servlets, a arquitetura da Web torna-se um base atrativa para o desenvolvimento de
aplicações distribuídas em Java. A utilização de browsers HTML simplifica o desenvolvimento das
aplicações cliente. Servidores Web suportam os mecanismos básicos de conexão ao cliente. Assim,
o desenvolvimento irá se concentrar na extensão dos serviços através dos servlets.

5.3.1 Ciclo de vida de um servlet


A execução de um servlet não difere muito de uma aplicação CGI em sua forma de interação com
o servidor. As quatro principais etapas nessa interação são:

1. cliente envia solicitação ao servidor;

2. servidor invoca servlet para a execução do serviço solicitado;

3. servlet gera o conteúdo em resposta à solicitação do cliente; e

4. servidor envia resultado do servlet ao cliente.

Quando um servlet é carregado pela primeira vez para a máquina virtual Java do servidor, o
seu método init() é invocado. Esse método tipicamente prepara recursos para a execução do
serviço (por exemplo, abrir arquivos ou ler o valor anterior de um contador de número de acessos)
ou estabelece conexão com outros serviços (por exemplo, com um servidor de banco de dados). O
método destroy() permite liberar esses recursos (fechar arquivos, escrever o valor final nessa
sessão do contador de acessos), sendo invocado quando o servidor estiver concluindo sua atividade.
Uma diferença fundamental entre um servlet e uma aplicação CGI é que a classe que implementa
o servlet permanece carregada na máquina virtual Java após concluir sua execução. Um programa
CGI, ao contrário, inicia um novo processo a cada invocação — por este motivo, CGI deve utilizar
mecanismos adicionais para manter o estado entre execuções, sendo a forma mais comum a utilização
de arquivos em disco. Com um servlet, tais mecanismos são necessários apenas na primeira vez que
é carregado e ao fim da execução do servidor, ou eventualmente como um mecanismo de checkpoint.
Servlets também oferecem como vantagem o fato de serem programas Java. Assim, eles permitem
a utilização de toda a API Java para a implementação de seus serviços e oferecem adicionalmente
portabilidade de plataforma.

c 2001 FEEC/UNICAMP 91
Programação orientada a objetos com Java 5.3. Servlets

5.3.2 Fundamentos da API de servlets


O suporte a servlets é uma extensão padronizada ao pacote Java, não sendo parte da distribuição
básica do Java SDK. Assim, quem quiser desenvolver servlets deve obter o JSDK, o Java Servlet
Development Kit. Adicionalmente, o servidor Web deve suportar o acesso a servlets, o que pode
ocorrer de forma nativa (como no caso do Java Web Server) ou através de módulos add-on (como no
caso de apache).
O JSDK inclui as classes que implementam a API de servlets organizadas em pacotes, dos quais
os principais são javax.servlet e javax.servlet.http. Adicionalmente, uma aplicação
servletrunner permite desenvolver e testar servlets antes de integrá-los a servidores.
Os três mecanismos alternativos básicos para criar um servlet são:

1. Estender a classe javax.servlet.GenericServlet, quando o servlet não for imple-


mentar nenhum protocolo específico de comunicação;

2. Estender a classe javax.servlet.HttpServlet, para servlets que manipulam dados


específicos do protocolo HTTP; ou

3. Implementar a interface javax.servlet.Servlet.

Na primeira forma básica de implementar um servlet, estendendo a classe GenericServlet,


o método service() deve ser definido. Esse método descreve o serviço que o servlet estará ofere-
cendo.
Esse exemplo ilustra o código de um servlet que responde à solicitação de serviço com uma
mensagem fixa:

1 import javax.servlet.*;
2 import java.io.*;
3 public class OiServlet extends GenericServlet {
4 public void service(ServletRequest solicitacao,
5 ServletResponse resposta)
6 throws ServletException, IOException {
7 resposta.setContentType("text/plain");
8 PrintWriter saida = resposta.getWriter();
9 saida.println("Oi!");
10 }
11 }

Nesse exemplo, o método getWriter() é utilizado para estabelecer o canal de envio de dados
desde o servlet — no caso, um objeto PrintWriter, permitindo o envio de textos. Alternati-
vamente, dados binários poderiam ser enviados através de um OutputStream, obtido através do
método getOutputStream().
A classe HttpServlet é uma extensão deGenericServlet especificamente projetada para
a conexão de servlets a servidores HTTP. Assim, métodos dedicados a lidar com solicitações HTTP,
tais como doGet(), doPost() e doPut(), são definidos. A implementação padrão do método
service() reconhece qual o tipo de solicitação recebida e invoca o método correspondente.

c 2001 FEEC/UNICAMP 92
Programação orientada a objetos com Java 5.3. Servlets

Este exemplo ilustra a utilização de um servlet que envia uma mensagem fixa no corpo de uma
página HTML em resposta a uma requisição GET ao servidor Web, usando para tal o método do-
Get():
1 import javax.servlet.*;
2 import javax.servlet.http.*;
3 import java.io.*;
4 public class OiHttpServlet extends HttpServlet {
5 public void doGet(HttpServletRequest solicitacao,
6 HttpServletResponse resposta)
7 throws ServletException, IOException {
8 resposta.setContentType("text/html");
9 PrintWriter saida = resposta.getWriter();
10 saida.println("<HTML>");
11 saida.print("<HEAD><TITLE>");
12 saida.print("Resposta do servlet");
13 saida.println("</TITLE></HEAD>");
14 saida.println("<BODY><P>Oi!</P></BODY>");
15 saida.println("</HTML>");
16 }
17 }
Outros métodos que suportam a interação do servlet através de solicitações HTTP incluem ge-
tLastModified(), que é invocado pelo servidor para obter a data da última modificação do “do-
cumento” (um número negativo se não houver informação ou long com o número de segundos desde
1 de janeiro de 1970 GMT); e getServletInfo(), que retorna uma string de documentação
sobre o servlet, tal como nome, autor e versão.
A passagem de dados de um formulário do cliente para o servlet pode se dar através do método
getParameter(), que permite obter uma string com o valor do campo especificado. Por exemplo,
se um formulário HTML especificasse um campo de texto para entrada do nome do usuário, como
em
<INPUT type="text" name="username" size=20>
esse valor poderia ser obtido no código do servlet do exemplo anterior através da invocação
String nome = solicitacao.getParameter("username");
Além de getParameter(), o método getParameterValues() retorna um arranjo de
strings com todos os valores de um determinado parâmetro. Outros métodos são oferecidos para ob-
ter informação das aplicações que estão invocando o servlet, tais comogetRemoteHost(), get-
ServerName(), getServerPort() e, especificamente para HTTP, getHeaderNames() e
getHeader().
A forma de integrar o servlet ao servidor Web é dependente da implementação; por exemplo,
alguns servidores especificam um diretório (tal como servlet) onde as classes servlets são con-
centradas e a invocação dá-se pela invocação direta da URL, como em
http://site/servlet/OiHttpServlet

c 2001 FEEC/UNICAMP 93
Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

5.4 Programação com objetos distribuídos


Na programação distribuída usando a arquitetura cliente-servidor, clientes e servidores podem
ser implementados usando qualquer paradigma de programação. Assim, é possível que um serviço
específico seja executado por um método de algum objeto. No entanto, mesmo que o cliente tam-
bém tenha sido desenvolvido orientação a objetos, na comunicação entre o cliente e o servidor esse
paradigma deve ser esquecido, devendo ser utilizado algum protocolo pré-estabelecido de troca de
mensagens para a solicitação e resposta ao serviço.
Um sistema de objetos distribuídos é aquele que permite a operação com objetos remotos. Dessa
forma é possível, a partir de uma aplicação cliente orientada a objetos, obter uma referência para um
objeto que oferece o serviço desejado e, através dessa referência, invocar métodos desse objeto —
mesmo que a instância desse objeto esteja em uma máquina diferente daquela do objeto cliente.
O conceito básico que suporta plataformas de objetos distribuídos é o conceito de arquiteturas
de objetos. Essencialmente, uma arquitetura orientada a objetos estabelece as regras, diretrizes e
convenções definindo como as aplicações podem se comunicar e inter-operar. Dessa forma, o foco
da arquitetura não é em como a implementação é realizada, mas sim na infra-estrutura e na interface
entre os componentes da arquitetura.
Na plataforma Java, dois mecanismos são oferecidos para o desenvolvimento de aplicações usan-
do o conceito de objetos distribuídos: Java RMI e Java IDL. RMI (invocação remota de métodos) é
um mecanismo para desenvolver aplicações com objetos distribuídos que opera exclusivamente com
objetos Java. Java IDL utiliza a arquitetura padrão CORBA para integração de aplicações Java a
aplicações desenvolvidas em outras linguagens.

5.4.1 Arquiteturas de objetos distribuídos


No paradigma de arquiteturas de objetos, há três elementos principais. A arquitetura OO forne-
ce uma descrição abstrata do software — que categorias de objetos serão utilizadas, como estarão
particionados e como interagirão. As interfaces distribuídas são as descrições detalhadas das fun-
cionalidades do software. Finalmente, a implementação é composta por módulos de software que
suportam as funcionalidades especificadas nas interfaces distribuídas.
O uso de interfaces distribuídas permite isolar a arquitetura de um sistema de sua implementa-
ção. Dessa forma, o sistema pode ser construído com um alto grau de independência em relação às
implementações específicas de suas funcionalidades, ou seja, é possível substituir implementações
específicas com pequeno impacto sobre o sistema como um todo.
A adoção do paradigma de arquitetura de objetos permite também atingir um alto grau de inter-
operabilidade através da adoção de uma infra-estrutura padronizada de comunicação entre objetos
através das interfaces. Assim, cada componente da arquitetura deve se preocupar apenas em como
se dará sua comunicação com a infra-estrutura de comunicação, estabelecida através de um objeto
wrapper. Sem essa padronização, seria necessário estabelecer os mecanismos de comunicação com
todos os demais componentes do sistema.
Em uma arquitetura de objetos, alguma forma deve ser estabelecida para que clientes possam
localizar serviços que estão sendo oferecidos. Isso é usualmente oferecido na forma de um serviço
básico da plataforma de objetos distribuídos. Do ponto de vista de quem oferece o serviço, é preciso
habilitar o objeto para que seus métodos possam ser invocados remotamente. Isto é realizado através
das seguintes atividades:

c 2001 FEEC/UNICAMP 94
Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

Descrever o serviço. Na arquitetura de objetos, a descrição ou especificação de serviços é determi-


nada através das interfaces. Em Java, isto é realizado através da especificação oferecida por
uma interface.

Implementar o serviço. Isto é realizado através do desenvolvimento de uma classe Java que imple-
mente a interface especificada.

Anunciar o serviço. Quando um objeto que implementa o serviço torna-se ativo, é preciso que ele
seja registrado em um “diretório de serviços” de forma que potenciais clientes possam localizá-
lo.

Sob o ponto de vista do objeto cliente, que vai usar o serviço do objeto remoto, é preciso localizar
o serviço, o que é feito acessando o registro de serviços. Portanto, é necessário que servidores e
clientes estejam de acordo com o local (a máquina) onde o registro é realizado. Como resultado
dessa tarefa, obtém-se uma referência ao objeto remoto que pode ser utilizada como se fosse uma
referência para o objeto local.
A utilização desse mecanismo de localizar o serviço através de um diretório ou registro permite
que as máquinas clientes ignorem totalmente em que máquinas os serviços solicitados estão operando
— uma facilidade conhecida como transparência de localização.
Um dos principais objetivos em uma plataforma de objetos distribuídos é atingir transparência de
localização, tornando uniforme a forma de utilização de objetos independentemente desses objetos
estarem na máquina local da aplicação ou em máquinas distintas.
A fim de que se atinja transparência de localização, as seguintes funcionalidades devem ser ofe-
recidas:

1. Localizar e carregar classes remotas;

2. Localizar e obter referências a objetos remotos; e

3. Habilitar a invocação de métodos de objetos remotos.

A primeira funcionalidade, não muito diferente do que ocorre em sistemas com objetos locais, é
necessária para que a aplicação conheça as facilidades oferecidas pelo objeto remoto.
Referências a objetos também são utilizadas em sistemas com objetos locais, porém com diferen-
ças significativas. Em um sistema local, as referências a objetos são tipicamente manipuladores com
especificação de endereços de memória. No caso de objetos remotos, esses endereços da memória
de outra máquina não têm validade na máquina local. Assim, é preciso oferecer mecanismos que
traduzam essas referências entre máquinas de forma transparente para o programador.
Para a invocação de métodos de um objeto remoto, além da necessidade de se localizar a refe-
rência ao método é preciso oferecer mecanismos para tornar transparente a passagem de argumentos
para e o retorno de valores desde o método.
Além dessas funcionalidades, a comunicação de falhas no oferecimento da transparência de loca-
lização ao programador é essencial. Assim, funcionalidades para comunicar exceções entre máquinas
também dever ser suportadas pela plataforma de objetos distribuídos.

c 2001 FEEC/UNICAMP 95
Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

5.4.2 Java RMI


RMI (Remote Method Invocation) é uma das abordagens da tecnologia Java para prover as funci-
onalidades de uma plataforma de objetos distribuídos. Esse sistema de objetos distribuídos faz parte
do núcleo básico de Java desde a versão JDK 1.1, com sua API sendo especificada através do pacote
java.rmi e seus subpacotes.
Através da utilização da arquitetura RMI, é possível que um objeto ativo em uma máquina virtual
Java possa interagir com objetos de outras máquinas virtuais Java, independentemente da localização
dessas máquinas virtuais.
A arquitetura RMI oferece a transparência de localização através da organização de três camadas
entre os objetos cliente e servidor:

1. A camada de stub/skeleton oferece as interfaces que os objetos da aplicação usam para interagir
entre si;

2. A camada de referência remota é o middleware entre a camada de stub/skeleton e o protocolo


de transporte. É nesta camada que são criadas e gerenciadas as referências remotas aos objetos;

3. A camada do protocolo de transporte oferece o protocolo de dados binários que envia as soli-
citações aos objetos remotos pela rede.

Desenvolvimento da aplicação RMI


No desenvolvimento de uma aplicação cliente-servidor usando Java RMI, como para qualquer
plataforma de objetos distribuídos, é essencial que seja definida a interface de serviços que serão
oferecidos pelo objeto servidor.
A especificação de uma interface remota é equivalente à definição de qualquer interface em Java,
a não ser pelos seguintes detalhes: a interface deverá, direta ou indiretamente, estender a interface
Remote; e todo método da interface deverá declarar que a exceção RemoteException (ou uma
de suas superclasses) pode ser gerada na execução do método.
Esse exemplo ilustra a definição de uma interface remota para um objeto que contém um contador
inteiro:

1 import java.rmi.*;
2 public interface Count extends Remote {
3 void set(int val) throws RemoteException;
4 void reset() throws RemoteException;
5 int get() throws RemoteException;
6 int increment() throws RemoteException;
7 }

Esse contador é manipulado por quatro métodos: set(), para definir um valor inicial para o conta-
dor; reset(), para reiniciar o contador com o valor 0; get(), para consultar o valor do contador
sem alterá-lo; e increment(), que lê o valor atual do contador e incrementa-o.
Os serviços especificados pela interface RMI deverão ser implementados através de uma clas-
se Java. Nessa implementação dos serviços é preciso indicar que objetos dessa classe poderão ser
acessados remotamente.

c 2001 FEEC/UNICAMP 96
Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

A implementação do serviço se dá através da definição de uma classe que implementa a inter-


face especificada. No entanto, além de implementar a interface especificada, é preciso incluir as
funcionalidades para que um objeto dessa classe possa ser acessado remotamente como um servidor.
A implementação da interface remota se dá da mesma forma que para qualquer classe imple-
mentando uma interface Java, ou seja, a classe fornece implementação para cada um dos métodos
especificados na interface.
As funcionalidades de um servidor remoto são especificadas na classe abstrataRemoteServer,
do pacote java.rmi.server. Um objeto servidor RMI deverá estender essa classe ou, mais
especificamente, uma de suas subclasses. Uma subclasse concreta de RemoteServer oferecida
no mesmo pacote é UnicastRemoteObject, que permite representar um objeto que tem uma
única implementação em um servidor (ou seja, não é replicado em vários servidores) e mantém uma
conexão ponto-a-ponto com cada cliente que o referencia.
Tipicamente, a declaração de uma classe que implementa um servidor remoto RMI terá a forma

public class ... extends UnicastRemoteObject implements ... {


...
}

Esse exemplo oferece uma possível implementação para a interface remota previamente especi-
ficada:

1 import java.rmi.*;
2 import java.rmi.server.UnicastRemoteObject;
3 public class CountImpl extends UnicastRemoteObject
4 implements Count {
5 private int sum;
6 public CountImpl() throws RemoteException {
7 }
8 public void set(int val) throws RemoteException {
9 sum = val;
10 }
11 public void reset() throws RemoteException {
12 sum = 0;
13 }
14 public int get() throws RemoteException {
15 return sum;
16 }
17 public int increment() throws RemoteException {
18 return sum++;
19 }
20 }

Clientes e servidores RMI


Uma vez que a interface remota esteja definida e a classe que implementa o serviço remoto tenha
sido criada, o próximo passo no desenvolvimento da aplicação distribuída é desenvolver o servidor

c 2001 FEEC/UNICAMP 97
Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

RMI, uma classe que crie o objeto que implementa o serviço e cadastre esse serviço na plataforma
de objetos distribuídos.
Um objeto servidor RMI simples deve realizar as seguintes tarefas: criar uma instância do objeto
que implementa o serviço; e disponibilizar o serviço através do mecanismo de registro.
O desenvolvimento de um cliente RMI requer essencialmente a obtenção de uma referência remo-
ta para o objeto que implementa o serviço, o que ocorre através do cadastro realizado pelo servidor.
Uma vez obtida essa referência, a operação com o objeto remoto é indistingüível da operação com
um objeto local.

Usando o serviço de nomes


O aplicativo rmiregistry faz parte da distribuição básica de Java. Tipicamente, esse aplica-
tivo é executado como um processo de fundo (em background) que fica aguardando solicitações em
uma porta, que pode ser especificada como argumento na linha de comando. Se nenhum argumento
for especificado, a porta 1099 é usada como padrão.
O aplicativo rmiregistry é uma implementação de um serviço de nomes para RMI. O serviço
de nomes é uma espécie de diretório, onde cada serviço disponibilizado na plataforma é registrado
através de um nome do serviço, uma string única para cada objeto que implementa serviços em RMI.
Para ter acesso ao serviço de nomes a partir de uma classe Java, são oferecidos dois mecanismos
básicos. O primeiro utiliza a classe Naming, do pacote java.rmi. O segundo mecanismo utiliza
as facilidades oferecidas através das classes no pacote java.rmi.registry.
A classe Naming permite a realização da busca de um serviço pelo nome (lookup) usando o
método estático lookup(String nome), que retorna uma referência para o objeto remoto. O
serviço de registro aonde a busca se realiza é especificado pela string usando uma sintaxe similar à
URL:
rmi://objreg.host:port/objname
O protocolo padrão é rmi, sendo no momento o único suportado através desse método. Se não
especificado, o host é a máquina local e a porta é 1099. O nome de registro do objeto é a única parte
obrigatória desse argumento.
Além de lookup() os métodos bind(), rebind(), unbind() e list(), descritos na
seqüência, são também suportados.
Outra alternativa para ter acesso ao serviço de nomes a partir da aplicação Java é utilizar as
funcionalidades do pacote java.rmi.registry, que oferece uma classe e uma interface para
que classes Java tenham acesso ao serviço de nomes RMI.
A interface Registry representa uma interface para o registro de objetos RMI operando em
uma máquina específica. Através de um objeto dessa classe, é possível invocar o método bind()
que associa um nome de serviço (um String) ao objeto que o implementa.
Para obter uma referência para um objeto Registry são utilizados os métodos da classe Lo-
cateRegistry, todos estáticos, tais como getRegistry(). Há quatro versões básicas desse
método:
1. getRegistry(): obtém referência para o registro local operando na porta default;
2. getRegistry(int port): obtém referência para o registro local operando na porta es-
pecificada;

c 2001 FEEC/UNICAMP 98
Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

3. getRegistry(String host): obtém referência para o registro remoto operando na por-


ta default;

4. getRegistry(String host, int port): obtém referência para o registro remoto


operando na porta especificada.

O método estático createRegistry(int port) pode ser utilizado para iniciar um servi-
ço de registro na máquina virtual Java corrente na porta especificada como argumento, retornando
também um objeto da classe Registry.
Inicialmente, é preciso obter uma referência para o serviço de registro, através da invocação do
método:

Registry r = LocateRegistry.getRegistry();

Observe que a referência para Registry é em si uma referência para um objeto remoto, uma
vez que a interface Registry é uma extensão da interface Remote.
Uma vez que a referência para o serviço de registro tenha sido obtida, é possível acessar as
funcionalidades desse serviço através dos métodos da interface Registry. Particularmente, para
registrar um novo serviço utiliza-se o método bind():

r.bind(serviceName, myCount);

O objeto que está sendo registrado deve implementar também a interfaceRemote, que identifica
todos os objetos que podem ser acesados remotamente.
Outros serviços disponíveis através dos métodos de Registry incluem atualização, remoção
e busca dos serviços lá registrados. Para atualizar um registro já existente, o método rebind()
pode ser utilizado. Para eliminar um registro, utiliza-se o método unbind(). Dado o nome de um
serviço, o objeto Remote que o implementa pode ser obtido pelo método lookup(). O método
list() retorna um arranjo de String com os nomes de todos os serviços registrados.

Implementação do servidor RMI


Como observado, um objeto servidor RMI simples deve realizar as seguintes tarefas:

1. Criar uma instância do objeto que implementa o serviço; e

2. Disponibilizar o serviço através do mecanismo de registro.

Esse exemplo de servidor RMI para o contador remoto cria uma instância da implementação do
serviço e coloca-a à disposição de potenciais clientes, registrando-o no registry RMI:

1 import java.rmi.registry.*;
2 public class CountServer {
3 public static void main(String[] args) {
4 try {
5 String serviceName = "Count001";
6 CountImpl myCount = new CountImpl();
7 Registry r = LocateRegistry.getRegistry();

c 2001 FEEC/UNICAMP 99
Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

8 r.bind(serviceName, myCount);
9 System.out.println("Count Server ready.");
10 }
11 catch (Exception e) {
12 System.out.println("Exception: " + e.getMessage());
13 e.printStackTrace();
14 }
15 }
16 }

Cliente RMI
A principal etapa no desenvolvimento de uma aplicação cliente RMI é a obtenção da referência
remota para o objeto (remoto) que implementa o serviço desejado. Para tanto, o cliente RMI usa o
serviço padrão oferecido pelo mecanismo de registro de nomes de serviços.
Uma vez que a referência remota seja obtida, ela pode ser convertida (downcast) para uma refe-
rência para a interface que especifica o serviço. A partir de então, os métodos oferecidos pelo serviço
remoto são invocados da mesma forma que ocorre para objetos locais.
Esses exemplos ilustram o desenvolvimento de código cliente em RMI. No primeiro exemplo
desenvolve-se um cliente RMI que simplesmente invoca o método reset() através de uma refe-
rência remota para o objeto servidor:

1 import java.rmi.registry.*;
2 public class CountReset {
3 public static void main(String args[]) {
4 try {
5 Registry r = LocateRegistry.getRegistry();
6 Count myCount = (Count) r.lookup("Count001");
7 myCount.reset();
8 }
9 catch(Exception e) {
10 e.printStackTrace();
11 }
12 System.exit(0);
13 }
14 }

Nesse outro exemplo, o cliente utiliza os métodos para modificar e obter o valor do contador
remoto. Ele também ilustra a interação de um código com o registro RMI através da classeNaming:

1 import java.rmi.*;
2 public class CountClient {
3 public static void main(String args[]) {
4 try {
5 Remote remRef = Naming.lookup("Count001");
6 Count myCount = (Count) remRef;

c 2001 FEEC/UNICAMP 100


Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

7 int initValue = myCount.get();


8 System.out.print("De " + initValue + " para ");
9 long startTime = System.currentTimeMillis();
10 for (int i = 0 ; i < 1000 ; i++ )
11 myCount.increment();
12 long stopTime = System.currentTimeMillis();
13 System.out.println(myCount.get());
14 System.out.println("Avg Ping = "
15 + ((stopTime - startTime)/1000f)
16 + " msecs");
17 }
18 catch(Exception e) {
19 e.printStackTrace();
20 }
21 System.exit(0);
22 }
23 }

Esse terceiro exemplo ilustra a utilização de RMI a partir de um cliente desenvolvido como um
applet. Nesse applet, um campo de texto mostra o valor do contador no objeto servidor. Dois botões
são fornecidos, um para incrementar o valor mil vezes Start) e outro para obter o valor atual do
contador Get):

1 import java.rmi.*;
2 import java.awt.*;
3 import java.awt.event.*;
4 import java.applet.*;
5 public class AppletClient extends Applet
6 implements ActionListener {
7 Count remCount;
8 TextField tfCnt;
9 Button bStart, bGet;
10 String bslabel = "Start";
11 String bglabel = "Get";
12 public void init() {
13 try {
14 setLayout(new GridLayout(2,2));
15 add(new Label("Count:"));
16 tfCnt = new TextField(7);
17 tfCnt.setEditable(false);
18 add(tfCnt);
19 bStart = new Button(bslabel);
20 bStart.addActionListener(this);
21 bGet = new Button(bglabel);
22 bGet.addActionListener(this);

c 2001 FEEC/UNICAMP 101


Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

23 add(bStart);
24 add(bGet);
25 showStatus("Binding remote object");
26 remCount = (Count) Naming.lookup("Count001");
27 tfCnt.setText(Integer.toString(remCount.get()));
28 }
29 catch (Exception e) {
30 e.printStackTrace();
31 }
32 }
33 public void paint() {
34 try {
35 tfCnt.setText(Integer.toString(remCount.get()));
36 }
37 catch (Exception e) {
38 e.printStackTrace();
39 }
40 }
41 public void actionPerformed (ActionEvent ev) {
42 try {
43 String botao = ev.getActionCommand();
44 if (botao.equals(bslabel)) {
45 showStatus("Incrementing...");
46 for (int i = 0 ; i < 1000 ; i++ )
47 remCount.increment();
48 showStatus("Done");
49 }
50 else {
51 showStatus("Current count");
52 paint();
53 }
54 }
55 catch (Exception e) {
56 e.printStackTrace();
57 }
58 }
59 }

Definindo stubs e skeletons


Para que um serviço oferecido por um objeto possa ser acessado remotamente através de RMI,
é preciso também as classes auxiliares internas de stubs e skeletons, responsáveis pela comunicação
entre o objeto cliente e o objeto que implementa o serviço, conforme descrito na apresentação da
arquitetura RMI.
Uma vez que a interface e a classe do serviço tenham sido criadas e compiladas para byteco-

c 2001 FEEC/UNICAMP 102


Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

des usando um compilador Java convencional, é possível criar os correspondentes stubs e skeletons.
Para tanto, utiliza-se o aplicativo compilador RMI, rmic, disponibilizado juntamente com o kit de
desenvolvimento Java.
Um exemplo ilustra o processo de compilação RMI para o serviço do contador remoto. Considere
a implementação do serviço que foi previamente definida. O primeiro passo para a criação do stub
e do skeleton para esse serviço é obter a classe compilada, que por sua vez precisa da classe da
interface:

> javac Count.java


> javac CountImpl.java

Com a classe CountImpl.class disponível, a execução do comando

> rmic CountImpl

gera as classes CountImpl_Stub.class e CountImpl_Skel.class, correspondendo res-


pectivamente ao stub e ao skeleton para o serviço. O stub deverá ser disponibilizado junto ao código
do cliente RMI, enquanto que o skeleton deverá estar disponível junto ao código do servidor.
Uma classe stub oferece implementações dos métodos do serviço remoto que são invocadas no
lado do cliente. Internamente, esses métodos empacotam marshall) os argumentos para o método e
os envia ao servidor. A implementação correspondente no lado servidor, no skeleton, desempacota
(unmarshall) os dados e invoca o método do serviço. Obtido o valor de retorno do serviço, o método
no skeleton empacota e envia esse valor para o método no stub, que ainda estava aguardando esse
retorno. Obtido o valor de retorno no stub, esse é desempacotado e retornado à aplicação cliente
como resultado da invocação remota.
Internamente, o processo de marshalling utiliza o mecanismo de serialização de Java. Assim,
argumentos e valores de retorno de métodos remotos invocados através de RMI estão restritos a tipos
primitivos de Java e a objetos de classes que implementam Serializable.

Usando fábricas de objetos remotos


Pode haver situações em que não seja interessante registrar cada implementação de um serviço
no registry — por exemplo, quando o servidor não sabe quantos objetos criar de antemão ou quando
a quantidade de pequenos serviços registrados e tão grande que pode tornar a busca por um serviço
ineficiente.
Nessas situações, pode ser interessante utilizar uma fábrica de objetos remotos. Nesse caso, o
servidor que está registrado em rmiregistry não é uma implementação individual do serviço,
mas sim um gerenciador de instâncias de implementação do serviço. Esse gerenciador deve imple-
mentar uma interface remota que permita que o cliente obtenha uma referência remota para o serviço
desejado em duas etapas:

1. obtendo a referência para o gerenciador através da invocação do método lookup(); e

2. obtendo a referência para o serviço propriamente dito através da invocação do método do


gerenciador que retorna a referência.

c 2001 FEEC/UNICAMP 103


Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

Esses exemplos usando contadores inteiros ilustram a utilização do conceito de fábrica de objetos
remotos. Além da implementação do serviço e da sua correspondente interface, é preciso inicialmente
definir uma interface para a fábrica. Nesse exemplo, essa interface especifica a funcionalidade de um
“gerenciador de contadores”, que recebe o nome do contador e retorna uma referência remota para
um objeto contador:
1 import java.rmi.*;
2 public interface CountManager extends Remote {
3 Count getCount(String nome) throws RemoteException;
4 }
No lado servidor, o que muda em relação ao exemplo anterior é que agora não é mais o objeto
que implementa o contador que deve ser cadastrado no registry, mas sim o objeto fábrica, uma im-
plementação da interface especificada para o “gerenciador de contadores”. Essa fábrica, por sua vez,
mantém um registro interno dos objetos criados para poder retornar as referências solicitadas pelos
clientes remotos. Essa classe combina as funcionalidades da implementação de uma interface remota
com aquelas de um servidor RMI:
1 import java.rmi.*;
2 import java.rmi.registry.*;
3 import java.rmi.server.*;
4 import java.util.*;
5 public class CManagerImpl extends UnicastRemoteObject
6 implements CountManager {
7 private Hashtable counters = new Hashtable();
8 public CManagerImpl() throws RemoteException {
9 }
10 public Count getCount(String nome) throws RemoteException {
11 Count rem = null;
12 if (counters.containsKey(nome))
13 rem = (Count) counters.get(nome);
14 else {
15 rem = new CountImpl();
16 counters.put(nome,rem);
17 System.out.println("New counter: " + nome);
18 }
19 return rem;
20 }
21 public static void main(String[] args) {
22 try {
23 String serviceName = "CountFactory";
24 CManagerImpl myCM = new CManagerImpl();
25 Registry r = LocateRegistry.getRegistry();
26 r.bind(serviceName, myCM);
27 System.out.println("CountFactory ready.");
28 }

c 2001 FEEC/UNICAMP 104


Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

29 catch (Exception e) {
30 e.printStackTrace();
31 }
32 }
33 }

No lado do cliente há referências agora a duas interfaces remotas, uma para o “gerenciador de
contadores” e outra para o contador. A primeira delas é resolvida através do serviço de registro do
RMI, enquanto que a referência para o objeto do segundo tipo de interface é obtido a partir dessa
referência para o gerenciador que foi obtida:

1 import java.rmi.*;
2 public class CountClient {
3 public static void main(String args[]) {
4 String nome = "Count001";
5 try {
6 CountManager cm =
7 (CountManager) Naming.lookup("CountFactory");
8 if (args.length > 0)
9 nome = args[0];
10 Count myCount = cm.getCount(nome);
11 int initValue = myCount.get();
12 System.out.print("De " + initValue + " para ");
13 long startTime = System.currentTimeMillis();
14 for (int i = 0 ; i < 1000 ; i++ )
15 myCount.increment();
16 long stopTime = System.currentTimeMillis();
17 System.out.println(myCount.get());
18 System.out.println("Avg Ping = "
19 + ((stopTime - startTime)/1000f)
20 + " msecs");
21 }
22 catch(Exception e) {
23 e.printStackTrace();
24 }
25 System.exit(0);
26 }
27 }

Execução com RMI


A execução da aplicação cliente-servidor em RMI requer, além da execução da aplicação cliente
e da execução da aplicação servidor, a execução do serviço de registro de RMI. Além do princípio
básico de execução de aplicações RMI, a arquitetura RMI oferece facilidades para operação com
código disponibilizado de forma distribuída e ativação dinâmica, além de outros serviços distribuídos.

c 2001 FEEC/UNICAMP 105


Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

O registro RMI ( rmiregistry) executa isoladamente em uma máquina virtual Java. O ser-
vidor da aplicação, assim como a implementação do serviço, estão executando em outra máquina
virtual Java; sua interação com o registro (ao invocar o método bind()) se dá através de uma refe-
rência remota. Da mesma forma, cada aplicação cliente pode ser executada em sua própria máquina
virtual Java; suas interações com o registro (método lookup()) e com a implementação do serviço
(usando os correspondentes stub e skeleton) dão-se também através de referências remotas.
Portanto, para executar uma aplicação RMI é preciso inicialmente disponibilizar o serviço de
registro RMI. Para tanto, o aplicativo rmiregistry deve ser executado.
Com o rmiregistry disponível, o servidor pode ser executado. Para tanto, essa máquina
virtual Java deverá ser capaz de localizar e carregar as classes do servidor, da implementação do
serviço e do skeleton.
Após a execução do comando que ativa a aplicação servidor, a mensagem “Count Server ready.”
deverá surgir na tela, indicando que o servidor obteve sucesso na criação e registro do serviço e
portanto está apto a responder às solicitações de clientes.
Finalmente, com o servidor já habilitado para responder às solicitações, o código cliente pode ser
executado. Essa máquina virtual deverá ser capaz de localizar e carregar as classes com a aplicação
cliente, a interface do serviço e o stub para a implementação do serviço. Seria possível também ter
várias ativações simultâneas de CountClient em diferentes máquinas virtuais Java.
No caso mais simples de execução, as diversas máquinas virtuais Java estarão executando em
uma mesma máquina, compartilhando um CLASSPATH comum. No entanto, há mecanismos para
permitir o carregamento de classes em uma aplicação RMI envolvendo classes remotas.

Operação com objetos em máquinas remotas


Na descrição da operação de aplicações distribuídas usando RMI, assumiu-se que as aplicações
clientes, servidor e de registro eram processos distintos; porém considerou-se que todas as classes
necessárias para a operação das aplicações estavam localizadas em algum diretório do CLASSPATH
local.
No caso de execução em máquinas separadas, há duas formas de fazer a distribuição das classes
de modo que clientes e servidores possam executar corretamente. Na primeira forma, a estratégia
é distribuir explicitamente as classes necessárias e incluí-las em diretórios onde elas possam ser
localizadas quando necessário. No lado cliente, essas classes complementares seriam a interface
do serviço e o stub para a implementação do serviço. No lado servidor, seriam essas as classes de
implementação do serviço e o correspondente skeleton.
A outra forma é utilizar os mecanismos de carregamento dinâmico de classes distribuídas, em
alternativa ao class loader padrão da máquina virtual Java. Por exemplo, se a execução do cliente
se dá através de um applet, o AppletClassLoader oferece as funcionalidades necessárias para
localizar uma classe que está localizada no mesmo diretório de onde foi carregada a classe original.
Em RMI, há uma alternativa adicional de se utilizar o RMIClassLoader, que permite o carre-
gamento de stubs e skeletons a partir de um URL (especificado através da propriedade java.rmi.
server.codebase). Essa propriedade deve ser estabelecida para a máquina virtual Java que irá
executar o servidor, como em

>java -Djava.rmi.server.codebase=http://mhost/mdir/ CountServer

c 2001 FEEC/UNICAMP 106


Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

Deste modo, quando o servidor realizar o cadastro do serviço no registry, esse codebase será
embutido na referência do objeto. Quando o cliente obtiver a referência ao objeto remoto do registry
e seu class loader falhar em localizar a classe stub no CLASSPATH local, sua máquina virtual Java
fará uma conexão HTTP commhost para obter a classe correspondente — assim como outras classes
eventualmente necessárias para execução do serviço no lado cliente.
De forma similar, caso o rmiregistry estivesse operando em outra máquina, distinta daquela
onde as aplicações clientes e servidor estivessem executando, seria necessário especificar no código
das aplicações a máquina que executa rmiregistry, seja através do método getRegistry()
da classe LocateRegistry ou através da especificação de URL no protocolo RMI nos métodos
da classe Naming.
Como para qualquer situação na qual a máquina virtual Java irá carregar classes localizadas de
forma distribuída, é preciso adicionalmente estabelecer qual a política de segurança para operar com
código proveniente das outras máquinas. Essa política será enforçada pelo gerenciador de seguran-
ça, que pode ser definido pela invocação do método correspondente antes de qualquer invocação a
métodos de RMI:
System.setSecurityManager(new RMISecurityManager());
O uso dessas facilidades pode ser apreciado nos exemplos modificados para o código do servidor
e cliente da aplicação do contador distribuído. No caso do servidor:
1 import java.rmi.*;
2 import java.rmi.server.*;
3 import java.rmi.registry.*;
4 import java.net.SocketPermission;
5 public class CountServer {
6 public static void main(String[] args) {
7 // Create and install the security manager
8 System.setSecurityManager(new RMISecurityManager());
9 catch (Exception e) {
10 e.printStackTrace();
11 }
12 try {
13 String serviceName = "Count001";
14 // Create CountImpl
15 CountImpl myCount = new CountImpl();
16 Registry r = LocateRegistry.getRegistry(args[0]);
17 r.rebind(serviceName, myCount);
18 System.out.println("Count Server ready.");
19 }
20 catch (Exception e) {
21 System.out.println("Exception: " + e.getMessage());
22 e.printStackTrace();
23 }
24 }
25 }

c 2001 FEEC/UNICAMP 107


Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

No caso da aplicação cliente, o código modificado é apresentado abaixo:

1 import java.rmi.*;
2 import java.rmi.registry.*;
3 public class CountClient {
4 public static void main(String args[]) {
5 // Create and install the security manager
6 System.setSecurityManager(new RMISecurityManager());
7 try {
8 Count myCount = (Count)Naming.lookup("rmi://" +
9 args[0] + "/Count001");
10 // Calculate Start time
11 long startTime = System.currentTimeMillis();
12 // Increment 1000 times
13 System.out.print("Incrementing... ");
14 for (int i = 0 ; i < 1000 ; i++ )
15 myCount.increment();
16 System.out.println(myCount.get());
17 // Calculate stop time; print out statistics
18 long stopTime = System.currentTimeMillis();
19 System.out.println("Avg Ping = "
20 + ((stopTime - startTime)/1000f)
21 + " msecs");
22 }
23 catch(Exception e) {
24 System.err.println("System Exception" + e);
25 }
26 System.exit(0);
27 }
28 }

A especificação da interface e a implementação do serviço permanecem inalteradas para esses


exemplos.

Ativação dinâmica
Na primeira especificação de RMI (JDK 1.1), era necessário que um serviço oferecido por um
objeto fosse explicitamente ativado em alguma máquina virtual Java e então fosse cadastrado em um
serviço de registro.
O problema com essa abordagem ocorre principalmente quando, por algum motivo não previsto,
o serviço torna-se indisponível — não há como sinalizar o cliente ou o registro sobre esse fato. O
serviço de ativação, oferecido a partir de JDK 1.2, permite contornar essa limitação. As principais
facilidades oferecidas pelo mecanismo de ativação remota incluem:

a possibilidade de criar automaticamente um objeto remoto devido a solicitações de obtenção


de referências ao objeto;

c 2001 FEEC/UNICAMP 108


Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

o suporte a grupos de ativação, permitindo a ativação de vários objetos remotos executando em


uma mesma máquina virtual;

a possibilidade de reiniciar a execução de objetos remotos que tenham encerrado sua execução
em decorrência de alguma falha do sistema.

Para tornar um objeto que implementa um serviço como remotamente ativável, é preciso satis-
fazer três requisitos. Primeiro, é preciso implementar o serviço como uma subclasse de Activa-
table, do pacote java.rmi.activation. Segundo, é preciso criar construtores de ativação
na implementação do serviço. Finalmente, é preciso registrar o objeto e seu método de ativação no
serviço de ativação.
A classe Activatable oferece construtores com argumentos específicos para o registro (no
serviço de ativação) e a ativação de objetos (incluindo o URL onde o bytecode para o objeto pode
ser localizado, um objeto da classe MarshalledObject representando os argumentos de inici-
alização do objeto e um flag booleano indicando se o objeto deve ser reiniciado com seu grupo) e
para a reativação de objetos (incluindo como argumento um objeto ActivationID, previamente
designado pelo serviço de ativação). Esses construtores deverão ser invocados nos construtores do
objeto que implementam o serviço. Particularmente, o serviço de ativação busca um construtor com
dois argumentos dos tipos ActivationID e MarshalledObject.
A classe ActivationDesc permite registrar a informação de ativação de um objeto sem criar
uma instância deste objeto, usando para tanto o método estático register() da classe Activa-
table.
Antes de criar um serviço ativável, é preciso criar ou especificar o grupo de ativação ao qual ele
pertence. Isto é realizado através dos métodos das classes ActivationGroup, Activation-
GroupID e ActivationGroupDesc, todas do pacote java.rmi.activation. Um grupo
define um conjunto de objetos ativáveis que devem compartilhar o mesmo espaço de endereçamento,
executando na mesma máquina virtual.
O serviço de ativação é implementado em uma máquina virtual com um objeto da classe Ac-
tivator do pacote java.rmi.activation. A aplicação rimd, distribuída juntamente com o
pacote básico do JDK 1.2, é um daemon que implementa esse serviço.

Coleta de lixo distribuída


O processo de remoção de objetos remotamente não-referenciados ocorre de maneira automática.
Cada servidor com objetos exportados mantém uma lista de referências remotas aos objetos que ele
oferece. Através de comunicação com o cliente, ele é notificado quando a referência é liberada na
aplicação remota.
Cada referência remota recebe também um período de validade; quando esse período expira,
a referência é eliminada e o cliente é notificado. Esse mecanismo oferece uma alternativa para a
liberação de objetos que tenham sido referenciados por clientes que eventualmente tenham falhado,
ficando impedidos de sinalizar que a referência havia sido liberada.
Embora não sejam utilizadas normalmente por programadores, as funcionalidades do distributed
garbage collector estão especificadas através das classes do pacote java.rmi.dgc.

c 2001 FEEC/UNICAMP 109


Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

Callback
Nas aplicações em uma arquitetura de objetos distribuídos, nem sempre a comunicação no estilo
cliente-servidor é suficiente para atender aos requisitos da aplicação. É usual que o servidor RMI aja
algumas vezes como cliente, invertendo os papéis com o cliente RMI original.
Considere o exemplo do applet cliente RMI. Nesse applet, não há como saber se outro cliente
do mesmo objeto remoto realizou alguma atualização no valor do contador a não ser pressionando o
botão Get e verificando se houve mudança. Essa é uma situação típica em muitas aplicações, sendo
clara a necessidade de realizar tais notificações de forma automática.
O mecanismo para atingir esse objetivo é utilizar a estratégia de callback. Esta técnica é tipica-
mente utilizada quando a aplicação cliente requer um retorno do servidor mas não quer permanecer
bloqueado aguardando a resposta. Através dessa técnica, o servidor obtém uma referência para o
cliente de forma que pode invocar remotamente um método do objeto cliente. Assim, quando a exe-
cução do serviço solicitado é concluída, o servidor pode notificar o cliente através da invocação do
método disponibilizado pelo cliente para uso remoto.
Basicamente, assim como para o objeto de serviço RMI, deve-se oferecer uma interface remota
para o cliente a fim de permitir que o servidor tenha acesso ao “serviço” de atualização do cliente.
Esse exemplo ilustra tal situação, com um método que será invocado pelo servidor quando seu valor
for alterado, quando passará um valor inteiro para o cliente com a valor atualizado:

1 import java.rmi.*;
2 public interface CountClientInterface extends Remote {
3 void update(int val) throws RemoteException;
4 }

Observe que neste exemplo a interface remota do serviço também foi atualizada de forma a
permitir o cadastro dos clientes interessados na atualização:

1 import java.rmi.*;
2 public interface Count extends Remote {
3 void set(int val) throws RemoteException;
4 void reset() throws RemoteException;
5 int get() throws RemoteException;
6 int increment() throws RemoteException;
7 void addClient(CountClientInterface c) throws RemoteException;
8 void remClient(CountClientInterface c) throws RemoteException;
9 }

Com callback, ambos cliente e servidor deverão implementar o serviço remoto especificado.
Considere o código para o servidor:

1 import java.util.*;
2 import java.rmi.*;
3 import java.rmi.server.UnicastRemoteObject;
4 public class CountImpl extends UnicastRemoteObject
5 implements Count {
6 private int sum;

c 2001 FEEC/UNICAMP 110


Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

7 // Lista de clientes registrados


8 private Vector clientes = new Vector();
9 public CountImpl() throws RemoteException {
10 }
11 public void set(int val) throws RemoteException {
12 sum = val;
13 }
14 public void reset() throws RemoteException {
15 sum = 0;
16 }
17 public int get() throws RemoteException {
18 return sum;
19 }
20 public int increment() throws RemoteException {
21 sum++;
22 if (sum%100 == 0)
23 update();
24 return sum;
25 }
26 public void addClient(CountClientInterface client)
27 throws RemoteException {
28 clientes.add(client);
29 }
30 public void remClient(CountClientInterface client)
31 throws RemoteException {
32 clientes.remove(client);
33 }
34 public void update() throws RemoteException {
35 CountClientInterface cci;
36 for (int i=0; i<clientes.size(); ++i) {
37 cci = (CountClientInterface) clientes.elementAt(i);
38 cci.update(sum);
39 }
40 }
41 }

Similarmente, para o código do cliente:

1 import java.rmi.*;
2 import java.awt.*;
3 import java.awt.event.*;
4 import java.applet.*;
5 import java.rmi.server.*;
6 public class AppletClient extends Applet
7 implements ActionListener, CountClientInterface {

c 2001 FEEC/UNICAMP 111


Programação orientada a objetos com Java 5.4. Programação com objetos distribuídos

8 Count remCount;
9 TextField tfCnt;
10 Button bStart;
11 String bslabel = "Start";
12 public void init() {
13 try {
14 setLayout(new GridLayout(3,1));
15 add(new Label("Count:"));
16 tfCnt = new TextField(7);
17 tfCnt.setEditable(false);
18 add(tfCnt);
19 bStart = new Button(bslabel);
20 bStart.addActionListener(this);
21 add(bStart);
22 UnicastRemoteObject.exportObject(this);
23 showStatus("Binding remote object");
24 remCount = (Count) Naming.lookup("Count001");
25 showStatus("Registering with remote object");
26 remCount.addClient(this);
27 tfCnt.setText(Integer.toString(remCount.get()));
28 }
29 catch (Exception e) {
30 e.printStackTrace();
31 }
32 }
33 public void paint() {
34 try {
35 tfCnt.setText(Integer.toString(remCount.get()));
36 }
37 catch (Exception e) {
38 e.printStackTrace();
39 }
40 }
41 public void actionPerformed (ActionEvent ev) {
42 try {
43 showStatus("Incrementing...");
44 for (int i = 0 ; i < 1000 ; i++ )
45 remCount.increment();
46 showStatus("Done");
47 }
48 catch (Exception e) {
49 e.printStackTrace();
50 }
51 }

c 2001 FEEC/UNICAMP 112

Você também pode gostar