INTRODUÇÃO AO JAVA WEB
Realização: Lecom
Informações de contato André Farina – Lecom Rua Manoel Bento da Cruz, 11-29, Bauru - SP Fixo: 14 4009.8900 Internet: www.lecom.com.br Email: andre.farina@lecom.com.br Rafael Fantini da Costa – Lecom / MNIX Rua Manoel Bento da Cruz, 11-29, Bauru - SP Fixo: 14 4009.8910 / Cel: 14 8104.9339 Internet: www.mnix.com.br Email: rafael.costa@mnix.com.br
Licença de uso Este material está licenciado sob a Licença Creative Commons Atribuição-NãoComercial-SemDerivações 4.0 Internacional. Para ver uma cópia desta licença, visite http://creativecommons.org/licenses/by-nc-nd/4.0/
CONTEÚDO 1. INTRODUÇÃO .......................................................................................................................... 1 1.1 O que é Java? ........................................................................................................................ 1 1.2 Características da Linguagem ................................................................................................ 1 1.3 Plataforma Java ..................................................................................................................... 1 1.4 Java Development Kit (JDK) ................................................................................................. 2 1.5 A Máquina Virtual Java ......................................................................................................... 2 1.6 Garbage Collection ................................................................................................................ 3 1.7 Convenções de Código .......................................................................................................... 3 2. A LINGUAGEM JAVA ............................................................................................................... 4 2.1 Princípios .............................................................................................................................. 4 2.2 Membros de uma Classe ........................................................................................................ 4 2.3 O Método “main” .................................................................................................................. 5 2.4 Exercícios.............................................................................................................................. 5 2.5 Variáveis e Operadores .......................................................................................................... 5 2.6 Palavras Reservadas .............................................................................................................. 6 2.7 Tipos de Dados ...................................................................................................................... 6 2.7.1 Inteiro ............................................................................................................................. 6 2.7.2 Ponto Flutuante............................................................................................................... 7 2.7.3 Caractere ........................................................................................................................ 7 2.7.4 Lógico ............................................................................................................................ 7 2.7.5 Tipo de Referência .......................................................................................................... 7 2.8 Escopo de Variáveis ............................................................................................................... 8 2.9 Operadores ............................................................................................................................ 8 2.9.1 Precedência de Operadores ............................................................................................. 9 2.10 Constantes ....................................................................................................................... 9 2.11 Controle de Fluxo ................................................................................................................ 9 2.11.1 if – else ......................................................................................................................... 9 2.11.2 switch ......................................................................................................................... 10 2.11.3 while ........................................................................................................................... 10 2.11.4 for ............................................................................................................................... 10 2.11.5 continue – break .......................................................................................................... 11 2.12 Exercícios .......................................................................................................................... 11 3. MÉTODOS ............................................................................................................................... 12 3.1 Modificadores de Acesso ..................................................................................................... 12 3.2 Parâmetros .......................................................................................................................... 12 3.3 Retorno ............................................................................................................................... 12 4. PROGRAMAÇÃO ORIENTADA A OBJETOS ........................................................................ 13
4.1 O Antecessor ....................................................................................................................... 13 4.2 Conceitos ............................................................................................................................ 13 4.2.1 Abstração ...................................................................................................................... 13 4.2.2 Encapsulamento ............................................................................................................ 14 4.2.3 Classe e Objeto ............................................................................................................. 14 4.2.4 Herança ........................................................................................................................ 16 4.2.5 Polimorfismo ................................................................................................................ 17 4.2.6 Modularidade ............................................................................................................... 18 4.3 Benefícios ........................................................................................................................... 18 4.4 Construtores ........................................................................................................................ 19 4.5 this & super ......................................................................................................................... 19 4.6 Exercícios............................................................................................................................ 20 4.7 Membros Estáticos .............................................................................................................. 20 4.7.1 Ocorrências de membros estáticos ................................................................................ 20 4.8 Exercícios............................................................................................................................ 20 5. CLASSES ABSTRATAS E INTERFACES ............................................................................... 22 5.1 Classes Abstratas ................................................................................................................. 22 5.2 Interfaces ............................................................................................................................. 23 5.3 Exercício ............................................................................................................................. 23 6. EXCEÇÕES .............................................................................................................................. 24 6.1 Definição de Exception ....................................................................................................... 24 6.2 Classes de Exceções ............................................................................................................ 24 6.3 Tratamento de Exceções ...................................................................................................... 24 6.3.1 Capturando Exceções.................................................................................................... 25 6.3.2 Deixando uma Exceção Passar ...................................................................................... 26 6.3.3 Lançando uma Exceção ................................................................................................ 26 6.4 Exercícios............................................................................................................................ 27 7. SERVLETS ............................................................................................................................... 27 7.1 O Protocolo HTTP............................................................................................................... 27 7.2 Common Gateway Interface ................................................................................................ 29 7.3 O que são Servlets? ............................................................................................................. 29 7.4 Funcionamento da Servlet ................................................................................................... 30 7.5 Respostas ............................................................................................................................ 31 7.5.1 Exercícios ......................................................................................................................... 32 7.5.2 Status HTTP ................................................................................................................. 32 7.5.3 Exercícios ..................................................................................................................... 33 7.6 Requisições ......................................................................................................................... 33 7.6.1 Parâmetros da requisição .............................................................................................. 33 7.6.2 Exercícios ..................................................................................................................... 33
7.6.3 Cabeçalhos de Requisição ............................................................................................. 33 8. COOKIES ................................................................................................................................. 35 8.1 Definição ............................................................................................................................. 35 8.2 Estrutura.............................................................................................................................. 35 8.3 Adicionando Cookies........................................................................................................... 35 8.4 Recuperando Cookies .......................................................................................................... 36 8.5 Exercícios............................................................................................................................ 36 9. SESSÕES.................................................................................................................................. 37 9.1 Funcionamento .................................................................................................................... 37 9.2 Exercícios............................................................................................................................ 38 9.3 Validade da Sessão .............................................................................................................. 38 9.4 Exercícios............................................................................................................................ 38 10. CICLO DE VIDA DA SERVLET ............................................................................................ 39 10.1 Inicialização ...................................................................................................................... 39 10.2 Atendendo as Requisições ................................................................................................. 39 10.3 Finalização ........................................................................................................................ 40 10.4 Servlet Context .................................................................................................................. 40 10.5 Exercícios .......................................................................................................................... 41 11. VISÃO GERAL SOBRE JSP .................................................................................................. 42 11.1 O que é uma página JSP? ................................................................................................... 42 11.2 Diretivas ............................................................................................................................ 42 11.2.1 Diretiva page .............................................................................................................. 43 11.2.2 Diretiva include .......................................................................................................... 43 11.3 Expressões ......................................................................................................................... 43 11.4 Exercícios .......................................................................................................................... 43 11.5 Scriptlets ........................................................................................................................... 44 11.6 Objetos Implícitos ............................................................................................................. 44 11.7 Declarações ....................................................................................................................... 44
1. INTRODUÇÃO 1.1 O que é Java? Java é uma linguagem de programação orientada a objetos mantida pela Oracle e originalmente desenvolvida pela Sun Microsystems. Baseada no C++, o Java foi projetado para ser uma linguagem portável para todas as plataformas. A portabilidade é alcançada através do uso de um interpretador que traduz os bytecodes (código resultante da compilação de um programa Java) em instruções de máquina. Isso dispensa a tarefa de portar o código para diversas plataformas, uma vez que basta instalar a máquina virtual na plataforma alvo e o programa Java funcionará como na plataforma original.
1.2 Características da Linguagem
Orientada a objetos: Paradigma de programação mais utilizado na atualidade. Entre os principais benefícios estão o reuso de código e maior facilidade na manutenção dos sistemas desenvolvidos;
Simples e robusta: Inspirada no C++, o Java apresenta várias melhorias em relação a linguagem da qual foi originada. Possui várias características que previnem o programador de cometer erros comuns no C++, além de estruturas que facilitam a programação, tornandoa mais produtiva;
Gerenciamento automático de memória: O Java abstrai o conceito de referências, livrando o programador da (árdua) tarefa de lidar com ponteiros. Além disso, tanto alocação quanto a liberação de memória são geridas pela máquina virtual Java, liberando o programador da preocupação de gerenciar a memória em baixo nível. A este mecanismo de liberação de memória damos o nome de Garbage Collection;
Independência de plataforma: Uma das características mais marcantes do Java é a sua capacidade de executar programas em plataformas diferentes daquela para a qual o programa foi inicialmente desenvolvido, como evidenciado pelo mote “write once, run everywhere”. Isso é possível graças à utilização da máquina virtual que interpreta bytecodes;
Multi-threading: A execução de tarefas paralelas é facilitada pelo uso da biblioteca de threading e pelos recursos de sincronização.
1.3 Plataforma Java A tecnologia está separada em três edições com propósitos distintos:
Java Standard Edition (Java SE): ferramentas e frameworks básicos para qualquer aplicação Java (inclusive para as outras duas plataformas). Suficientemente completo para desenvolvimento de aplicações com interface gráfica, por exemplo;
Java Enterprise Edition (Java EE): ferramentas e frameworks para desenvolvimento de aplicações distribuídas e sites voltados para o uso corporativo. Engloba tecnologias como EJB, JMS, RMI, CORBA, etc;
Java Micro Edition (Java ME): ferramentas e frameworks para desenvolvimento de aplicações executadas em dispositivos móveis e integrados: celulares, set-top boxes, impressoras, eletrodomésticos, etc.
1
1.4 Java Development Kit (JDK) O JDK é o kit de desenvolvimento disponibilizado pela Oracle que permite ao desenvolvedor criar aplicativos para a plataforma Java. A última versão, 1.7, do JDK, pode ser encontrada em http://www.oracle.com/technetwork/pt/java/javase/downloads O JDK compreende:
Java Runtime Environment (JRE) utilizado para executar as aplicações
Ferramenta de desenvolvimento: compilador, empacotador JAR, debugger, utilitários (como o Java2WSDL), etc
Biblioteca de código (framework) com vários recursos prontos para uso: criação de aplicativos com recursos visuais, manipulação de estruturas de dados, etc
1.5 A Máquina Virtual Java O JRE provê os requisitos mínimos para a execução Java: a Java Virtual Machine (JVM), implementação das bibliotecas e arquivos de suporte. Além de ser o coração da JVM, o JRE é a
pedra angular da filosofia “write once, run everywhere”. Explicando a imagem anterior:
Na compilação, ao invés do código-fonte ser convertido em código-objeto, as instruções são geradas em bytecode. O bytecode é genérico e independe de arquitetura ou sistema operacional; 2
Quando um programa Java é executado, o arquivo bytecode é interpretado pela JVM. Cada sistema operacional / arquitetura possui sua implementação própria de JVM e a mesma deverá ser instalada na máquina onde o programa será executado. Os browser, por exemplo, possui uma implementação própria da JVM, utilizada para execução de Applets (como a applet de segurança do banco).
1.6 Garbage Collection Comparativamente, tanto no C quanto no C++, o programa desenvolvido é responsável pela alocação e liberação de memória, sendo responsável por um grande número de erros: memory leak, crashes, etc. No Java, ao criar um objeto, a plataforma se encarrega de alocar a quantia certa de memória e manter o registro de utilização do mesmo. Assim, quando não estiver mais sendo usado (referenciado), o objeto é “coletado” e a memória ocupada por ele é devolvida ao sistema. Por ser uma operação custosa, o Garbage Collection é acionado automaticamente apenas quando a aplicação sendo executada excede um limiar de memória em uso. Essa liberação ocorre numa thread em background para evitar o travamento do programa. Embora seja possível invocar o Garbage Collector manualmente utilizando a chamada System.gc(), destruir objetos diretamente não é permitido. Para forçar a liberação, é preciso eliminar todas as referências a esse objeto e, em seguida, invocar o System.gc().
1.7 Convenções de Código Convenções de código são importantes para desenvolvedores por um número de razões:
80% da vida útil de um software é gasto em manutenção;
Dificilmente um software é mantido por toda a sua vida pelo autor original;
Convenções de código aumentam a legibilidade do código, permitindo que os desenvolvedores entendam o código mais rapidamente e corretamente.
Por ser extenso e fora do escopo deste curso, deixo aqui o link para a página que contém uma definição extensa e completa das convenções de código utilizadas pelos desenvolvedores Java ao redor do mundo: http://www.oracle.com/technetwork/java/javase/documentation/codeconvtoc136057.html
3
2. A LINGUAGEM JAVA Como o Java é uma linguagem orientada a objetos, o desenvolvimento de programas é feito por meio de classes. Uma classe java segue a seguinte sintaxe: <modificador de acesso> class <nome da classe> { <Declaração das Variáveis de Instância (Atributos)> <Declaração de Métodos> public static void main( String args[] ) { // corpo principal do programa } } Por exemplo: public class Exemplo1 { String mensagem = "Minha mensagem"; public void imprimeTexto(String texto) { // Mostra 'texto' no console System.out.println(texto); } public static void main( String args[] ) { imprimeTexto(mensagem); } }
2.1 Princípios Assim como toda linguagem, o Java possui suas particularidades:
Identificadores (nomes de variável, classe, método) devem começar sempre por letras ou underscore ( _ )
Comandos são finalizados com um ponto e vírgula ( ; )
Java é case-sensitive, ou seja, diferencia maiúsculas de minúsculas (diferente de Delphi)
Blocos de código são delimitados por um par de chaves { }
Comentários são precedidos por // ou contidos entre /* */ o // Isto é um comentário o /* Isto também é */
2.2 Membros de uma Classe As variáveis de um objeto (instância) são chamadas de campos e, juntamente com os métodos, compõe os elementos básicos para a construção de uma classe. Como tal, são denominados de membros da classe. Os campos são blocos de memória reservados para o armazenamento de informações durante a vida 4
útil de um objeto e, portanto, constituem o estado interno do mesmo. Campos são inicializados na construção do objeto e ficam disponíveis para utilização por todos os seus métodos. Os métodos, por sua vez, definem as operações (ações) que podem ser realizadas pela classe.
2.3 O Método “main” Para executar, toda aplicação Java precisa de um ponto de entrada e este ponto é o método main(). A declaração deve conter a seguinte assinatura para que a aplicação consiga ser iniciada com sucesso: public static void main( String[] args ) { … } Quando um programa é executado, o interpretador chamará primeiramente o método main da classe. É ele quem controla o fluxo de execução do programa e executa qualquer outro método necessário para a funcionalidade da aplicação. Nem toda classe terá um método main (normalmente apenas uma classe tem um “main”). Uma classe que não possui um método main() não pode ser “executada” pois não representa um programa em Java. Ela será sim, utilizada como classe utilitária para a construção de outras classes ou mesmo de um programa.
2.4 Exercícios Utilizando a IDE Eclipse: 1. Crie um Java Project que imprima “Hello World” 2. Crie um programa com uma classe que contenha as quatro operações algébricas básicas (soma, subtração,...) e outra classe que faça uso das mesmas
2.5 Variáveis e Operadores Variáveis são denominações dadas a porções de memória que armazenam algum dado. Toda variável válida tem as seguintes características:
É declarada antes de ser inicializada;
Possui um tipo, um identificador e um escopo;
Podem ser locais, quando declaradas em métodos ou são campos, quando declaradas no corpo da classe;
Podem ser inicializadas na declaração.
Como Java é uma linguagem fortemente tipada, todas as variáveis devem ser declaradas antes de serem usadas. O tipo de uma variável determina o tipo de informação que pode ser armazenada nela. Variáveis em Java podem ser declaradas como campos, no corpo da classe, ou podem ser declaradas localmente em qualquer parte da implementação de um método. Variáveis também possuem um escopo, o qual determina onde no programa ela estará visível e poderá ser acessada. Declaração de variáveis é feita como segue: tipo identificador [= valor] [, identificador [= valor]]; Exemplos: String texto = “Exemplos de declaração de variáveis”; int umNumero, UmNumero; char letra = ‘i’;
5
2.6 Palavras Reservadas Alguns identificadores reservados para uso da própria linguagem e recebem o nome de palavras reservadas. São elas: abstract assert boolean break byte case catch char class const
continue default do double else enum extends final finally float
for goto if implements import instanceof int interface long native
new package private protected public return short static strictfp super
switch synchronized this throw throws transient try void volatile while
2.7 Tipos de Dados No Java, uma variável pode pertencer a quatro tipos distintos: classes, interfaces, arrays (vetores) e tipos primitivos. Variáveis com classes, interfaces e arrays contém apenas a referência de memória (ponteiros) par ao objeto real, enquanto tipos primitivos são armazenados em sua totalidade e possuem tamanho fixo. O tamanho e as características de cada tipo primitivo são consistentes entre as plataformas, não havendo variações entre plataformas diferentes, como ocorre no C e C++. Os tipos primitivos são: Categoria
Tipo
Tamanho
Valor padrão
Inteiro
byte
8 bits
0
Inteiro
short
16 bits
0
Inteiro*
int
32 bits
0
Inteiro
long
64 bits
0L
Ponto flutuante
float
32 bits
0.0f
Ponto flutuante*
double
64 bits
0.0
Caractere
char
16 bits
'\u0000'
Lógico
boolean
8 bits
False
Os tipos marcados com um asterisco ( * ) são o padrão para literais definidos no código.
2.7.1 Inteiro Tipos inteiros armazenam valores numéricos sem casas decimais. Por padrão, literais inteiros são do tipo int e podem ser especificados como hexadecimais utilizando o prefixo 0x . Para literais do tipo long, deve-se utilizar o sufixo L . Ex: int hexa = long outro long combo int errado
0xfe; // = 1L; // = 0xfeL; = 1L; //
OK OK // OK, equivale a 254 Não compila
6
Tipo
Variação
byte
-128 .. +127
short
-32768 .. +32767
int
-2147483648 .. +2147483647
long
-9 quintilhões .. +9 quintilhões
2.7.2 Ponto Flutuante Variáveis de ponto flutuante armazenam números com casas decimais (conjunto dos números reais). Deve ser utilizado o ponto ( . ) como separador de casas decimais e, quando necessário, os números podem ser escritos em notação exponencial utilizando a caractere E . Para literais do tipo float, deve-se utilizar o sufixo f . Ex: double expo = 1.1e4; // OK, equivale a 1.1 * 10^4 = 11000.0 float outro = 1.2f; // OK float errado = 1.2; // Não compila
2.7.3 Caractere Uma variável char armazena apenas 1 caractere em seu interior – o armazenamento de palavras é reservado a classe String, que não é um tipo primitivo. Internamente, o Java utiliza o padrão UNICODE para codificação das variáveis do tipo char. O literal char é identificado pelo uso de aspas simples ( ' ) ao redor da letra. Alguns exemplos: char letra = 'a'; char linha = '\n'; // Nova linha char simples = '\''; // Aspas simples
char dupla = '\"'; // Aspas dupla char unicode = '\u007F'; // Valor UNICODE char tabula = '\t'; // Tabulação
2.7.4 Lógico Uma variável boolean pode armazenar apenas os valores true e false . Não é possível utilizar valores numéricos para testes lógicos, como ocorre no C / C++.
2.7.5 Tipo de Referência Tipos se separam em primitivos ou referência. Como vimos, os primitivos armazenam valores numéricos, caracteres e booleanos. Tipos de referência são classes, interfaces e arrays, e diferentemente dos tipos primitivos, podem ser definidos pelo desenvolvedor. Uma grande diferença entre os tipos primitivos e de referência é a alocação: tipos primitivos são alocados na Stack por possuir tamanho fixo e tipos de referência são alocados na Heap, por não possuírem tamanho definido. Em função disso, tipos primitivos são sempre passados por valor, enquanto tipos de referência são passados por referência. Um exemplo prático da diferença: ao alterar um parâmetro inteiro recebido por uma função, você estará alterando uma cópia da variável que foi passada como parâmetro. Caso fosse um array, a mudança seria refletida na variável original.
7
2.8 Escopo de Variáveis O escopo de uma variável é delimitado pelo bloco { } no qual ela está inserida e isso, por sua vez, determina o ciclo de vida da variável e quem terá acesso sobre ela. De forma geral, quando uma variável é declarada no corpo de uma classe, chamamos de campo e quando é declarada no corpo de um método, chamados de local. Uma variável local só pode ser acessada pelo método que a definiu, enquanto um campo tem o nível de acesso definido pelo seu modificador de acesso (public, private, protected).
2.9 Operadores Operadores são funções especiais e intrínsecos da linguagem. No Java, temos: Operador
Função
Exemplo
=
Atribuição
int var = 2, var1 = -1; var = var1 + 12; var1 = var2 = 50;
+
Adição
2+2
4
-
Subtração
1-2
-1
*
Multiplicação
1.1 * salario
/
Divisão
100 / 40 100 / 40.0
2 2.5
%
Resto da divisão
100 % 40
20
==
Igual
x == 10
!=
Diferente
1 != 1
false
<
Menor
1<2
true
>
Maior
1>2
false
>=
Maior ou igual
2 <= 2
true
<=
Menor ou igual
2 >= 3
false
Pertence à classe
// Mamifero m; m instanceof Animal
true
AND
(0 < 1) && (12 > 6)
true
||
OR
(0 > 1) || (12 < 6)
false
!
NOT
!(2 == 3)
true
+=
Soma e atribui
x += 20 // x = 10
x é 30
-=
Subtrai e atribui
x -= 20 // x = 10
x é -10
*=
Multiplica e atribui
x *= 2 // x = 10
x é 20
%=
Resto e atribui
x %= 3 // x = 10
xé1
/=
Divide e atribui
x /= 3 // x = 10
xé3
++
Incremento
x++ // x = 10
x é 11
--
Decremento
x-- // x = 10
xé9
instanceof &&
Resultado
8
2.9.1 Precedência de Operadores Entende-se por precedência a ordem na qual os operadores são executados em uma expressão. Operadores com mesma precedência são executados da esquerda para a direita, de acordo com a associatividade. Por exemplo: int n = 21 / 3 * 5; // n = 35 int n = 21 / (3 * 5); // n = 1
A tabela abaixo mostra as precedências entre diferentes operadores. Precedência
Operadores
1
++ -- !
2
*/%
3
+-
4
< > <= >= instanceof
5
== !=
6
&& ||
7
= += -= /= %=
2.10 Constantes Uma constante pode tanto ser definida como um atributo de classe como uma variável local. Uma constante uma vez que foi declarada e atribuído um valor para a mesma, não é permitido que outra atribuição seja efetuada, ocasionando um erro de compilação caso isso ocorra. A atribuição do valor inicial pode ser feita no momento da declaração, ou posteriormente em outra parte do programa. Para declarar uma variável do tipo constante, devemos utilizar o modificador final dessa forma: final <tipo> <identificador> [= valor];
2.11 Controle de Fluxo 2.11.1 if – else Sintaxe
Exemplo
if (expr_boolean) { // Corpo 1 } else { // Corpo 2 }
if (idade < 18) { System.out.println("Entrada bloqueada"); } else { System.out.println("Entrada permitida"); }
Se a expressão booleana for verdadeira, o Bloco 1 é executado, caso contrário, o Bloco 2 é executado.
9
2.11.2 switch O switch é normalmente utilizado para determinar o fluxo a partir de uma expressão com vários resultados possíveis. A limitação é que a expressão avaliada pode ser apenas dos tipos char, byte, short, int, String e enumeradores. Sintaxe
Exemplo
switch (expr) { case const1: // Corpo 1 break; case const2: // Corpo 2 break; ... [default: // Corpo n] }
switch (nome) { case "Fanta": System.out.println("Pessoa"); break; case "Lecom": System.out.println("Empresa"); break; default: System.out.println("Desconhecido"); }
É importante notar que: O case deve ser seguido por um literal ou uma constante. Todo bloco deve ser finalizado por um break , exceto o bloco default.
2.11.3 while Executa um bloco de comandos sucessivas vezes até que a condição seja falsa. A expressão de comparação é avaliada antes que o laço seja executado. Sintaxe Exemplo while (expr_boolean) { // Corpo }
while (i < n) { pot *= x; i++; }
2.11.4 for Em sua forma mais utilizada, o loop possui um trecho inicial com inicialização das variáveis, seguida por uma expressão de comparação e depois a parte final com o incremento ou decremento das variáveis do laço. Sintaxe
Exemplo
for (init; expr_boolean; pos) { // Corpo }
for (int i = 0; i < array.length; i++) { double elemento = array[i]; }
Logo ao iniciar, o for executa o trecho <init> e, em seguida, testa a condição <expr_boolean>. Caso seja verdadeira, executa os comandos no bloco e, ao final, executa o comando em <pos> seguido pelo teste de condição em <expr_boolean>, <bloco>, <pos>, <expr_boolean>,… até que <expr_boolean> seja falsa. O funcionamento se aproxima ao trecho abaixo: init; while (expr_boolean) { // Corpo pos; }
10
2.11.5 continue – break O break, quando executado, faz com que o laço mais interno seja interrompido e força a saída do mesmo, como se a condição de repetição estivesse falsa. Os usos mais comuns são no switch – usado para sinalizar o fim de um case – e para abortar um laço diante de algum gatilho interno (ex: encontrou o elemento desejado num array). O continue funciona de maneira análoga, porém oposta. Ele força que o laço seja interrompido, mas força uma nova repetição do laço e, consequentemente, uma nova avaliação da expressão lógica.
2.12 Exercícios Faça:
Um programa que calcula o fatorial de um número real
Um programa que ordena os valores de um vetor de entrada
Um programa que calcula o perímetro do círculo de raio igual ao parâmetro de entrada realizada
Um programa que imprime os números primos contidos entre os valores passados por parâmetro
11
3. MÉTODOS Num programa orientado a objetos, o estado interno de um programa é determinado pelos campos e o comportamento é definido pelos métodos. Em Java, um método equivale a uma função / subrotina / procedimento encontrado em outras linguagens de programação. É importante notar que não existem métodos globais – todo método deve, obrigatoriamente, estar contido em uma classe. Sintaxe: [modificadores] tipo_retorno identificador([argumentos]) { // Corpo } Exemplo: public static final int soma(int a, int b) { return a + b; }
3.1 Modificadores de Acesso O acesso a campos e métodos é controlado por meio do uso dos modificadores de acesso. A tabela abaixo relaciona os modificadores de acesso com a acessibilidade do membro em diferentes pontos do código. public
protected
<nenhum>
private
Mundo
Sim
Não
Não
Não
Subclasse
Sim
Sim
Não
Não
Mesmo pacote
Sim
Sim
Sim
Não
Sim Sim Sim Própria classe Nota: Os modificadores de acesso não podem ser aplicados a variáveis locais.
Sim
3.2 Parâmetros Métodos podem ter zero ou mais parâmetros sendo que, mesmo na ausência de parâmetros, é obrigatório informar os parênteses vazios tanto na definição quanto na chamada do método. Ao nome do método acrescido dos parâmetros dá-se o nome de assinatura do método. Na definição de um método, a declaração de parâmetros assemelha-se à declaração de variáveis locais, utilizando uma vírgula separadora entre os parâmetros, como no método soma acima.
3.3 Retorno Métodos podem (ou não) retornar valores. Para sinalizar que existe um retorno, precisamos definir de antemão o tipo do valor retornado e, no corpo da função, utilizar a palava reservada return seguida pela expressão a ser retornada (veja o exemplo do método soma acima). Caso não haja um retorno, é necessário definir o tipo de retorno como sendo void na assinatura do método. Quando chamado, o return faz com que o método encerre sua execução e volte para a posição anterior na pilha de instruções. Mesmo funções declaradas com retorno void podem lançar mão do return; como uma ferramenta de controle de fluxo. No exemplo abaixo, curto-circuitamos a execução do método quando o saldo for insuficiente. if (saldo < 0) { return; }
12
4. PROGRAMAÇÃO ORIENTADA A OBJETOS 4.1 O Antecessor Anteriormente à programação orientada a objetos, o paradigma dominante era a programação estruturada. Neste paradigma, a abordagem é decompor um programa em subprogramas, realizando o particionamento até que a unidade de subprograma alcançada tenha coesão e inteligibilidade. Neste paradigma, o programador é forçado a fixar atenção nos procedimentos (verbos do problema) ao invés dos dados (substantivos do problema). Essa disparidade tem como resultado um aumento na complexidade do software, uma vez a modelagem de um problema pensando em suas ações não é uma tarefa trivial – um paradigma que busca representar um problema baseando-se nos substantivos seria mais recomendado. A partir dessa ideia foi desenvolvido o paradigma de Programação orientada a objetos.
4.2 Conceitos Os conceitos básicos que permeiam a programação orientada a objetos são:
Abstração;
Encapsulamento;
Classe e Objeto;
Herança;
Polimorfismo;
Modularidade;
Mensagens e métodos.
4.2.1 Abstração Abstração consiste em ignorar aspectos não-relevantes de um domínio, concentrando-se apenas nos assuntos principais do problema. Assim, abstrair consiste basicamente no processo de retirar do domínio do problema os detalhes relevantes e representá-los não mais na linguagem de domínio, e sim na linguagem de solução, como Java, C++, etc.
13
4.2.2 Encapsulamento A propriedade de implementar dados e procedimentos correlacionados em uma mesma entidade recebe o nome de Encapsulamento. A ideia por trás do encapsulamento é a de que um sistema orientado a objetos não deve depender da implementação interna, e sim da interface de seus componentes. Vejamos o exemplo de um circuito integrado (CI). Pode-se notar o encapsulamento de vários componentes como transistores, resistores, capacitores e outros interligados por um circuito. O usuário que utiliza um CI não precisa se preocupar com os mecanismos internos de um CI – apenas com o comportamento da interface (os pinos).
4.2.3 Classe e Objeto
Um Objeto é um elemento que formaliza o modo pelo qual compreendemos algo no domínio do problema, refletindo a capacidade do sistema de guardar informações sobre tal elemento, interagir com ele, ou ambas as coisas. Uma Classe descreve (define) um conjunto de objetos semelhantes que compartilham a mesma semântica. Um relacionamento análogo ao de Classe-Objeto seria a relação entre uma Receita de Bolo e um Bolo criado com a receita: a receita pode ser encarada como uma fórmula para criação bolos, mas ela, em si, não é um bolo. Considere um programa para um banco, uma entidade extremamente importante para o nosso sistema é a conta – é uma boa prática generalizarmos as informações da conta relevantes ao domínio do problema, juntamente com as funcionalidades (ações) que toda conta deve possuir.
14
Entre os dados de uma conta que são importantes, podemos listar: número da conta, nome do cliente, saldo, limite, etc. Entre as ações que gostaríamos de atribuir a uma conta, temos: sacar uma quantidade x, depositar uma quantidade y, imprimir o nome do dono da conta, retornar o saldo atual, transferir uma quantidade x para uma outra conta y, etc. Começando pelos conteúdos de uma conta, podemos traduzir as informações acima para uma classe que ficaria como no trecho abaixo: class Conta { int numero; String nome; double saldo; double limite; }
Agora temos a definição da estrutura de uma conta. Não é possível, no entanto, obter o saldo da conta, pois o que temos é apenas a definição de uma conta – não é uma conta propriamente dita. Para essa tarefa, precisamos construir uma conta, sua definição, assim conseguiremos manipular a conta como gostaríamos. A esta definição damos o nome de classe e aos produtos da construção de uma classe damos o nome de objeto. Para podermos efetivamente usar um objeto criado a partir da classe Conta, além de construir o objeto, precisamos também de um ponto de início no programa a partir do qual o sistema operacional passa o controle a nossa aplicação. Vamos criar um arquivo Programa.java para isso: class Programa { public static void main(String[] args) { Conta minhaConta; minhaConta = new Conta(); } }
Para criar (construir, instanciar) uma Conta, basta usar a palavra reservada new seguida pelo nome da classe (neste caso, Conta) e parênteses. Através da variável minhaConta agora podemos acessar o objeto recém-criado para obter/alterar seu nome, saldo, etc class Programa { public static void main(String[] args) { Conta minhaConta; minhaConta = new Conta(); minhaConta.nome = "Fanta"; minhaConta.saldo = 1.99; } }
É importante fixar que o ponto em “minhaConta.saldo” foi utilizado para acessar o campo saldo no objeto minhaConta. Agora, minhaConta pertence ao Fanta, e tem saldo de R$ 1,99. Dentro da classe, também iremos declarar o que cada conta faz, e como é feito – os comportamentos que cada classe tem, isto é, o que ela faz. Por exemplo, de que maneira que uma Conta saca dinheiro? Especificaremos isso dentro da própria classe Conta, e não em um local desatrelado das informações da própria Conta. Essas “funções” declaradas no interior de uma classe são chamadas de método e recebem este nome, pois, determina a maneira de fazer uma operação com um objeto. 15
Como queremos a possibilidade de sacar dinheiro da conta, iremos criar um método que saca uma quantidade determinada e não retorna informações. void saca(double quantidade) { double novoSaldo = this.saldo - quantidade; this.saldo = novoSaldo; }
A palavra reservada void indica que o método não retorna nenhum dado àquele que o invocou. Métodos exigem a colocação de parênteses tanto nas declarações quanto nas chamadas para delimitar os parâmetros fornecidos aos mesmos. Neste exemplo, podemos ver que o método “saca” possui um parâmetro (ou argumento) – quantidade – que indica a quantia a ser sacada. Essa variável é uma variável comum, chamada também de temporária ou local, pois ao final da execução desse método, ela deixa de existir (escopo local). Dentro do método, também estamos declarando uma nova variável que assim como o argumento, vai morrer no fim do método, pois este é seu escopo. Para fins de escopo, o parâmetro é tratado como uma variável declarada no corpo do método. O próximo passo é mandar uma mensagem ao objeto “minhaConta” e pedir que ele execute o método “saca()”. Para denominar essa execução, utilizamos o termo “invocação de método”. public static void main(String[] args) { Conta minhaConta; minhaConta = new Conta(); minhaConta.nome = "Fanta"; minhaConta.saldo = 1.99; minhaConta.saca(1.00); }
Objetos interagem e se comunicam através de mensagens. As mensagens identificam os métodos a serem executados e, quando enviadas a um objeto, sinalizam ao objeto que uma invocação de método deve ser realizada. Para enviar uma mensagem, precisamos de:
Identificar o objeto que receberá a mensagem
Identificar o método que o objeto deverá executar
Passar os argumentos requeridos pelo método
4.2.4 Herança A herança possibilita a hierarquização de classes, onde uma classe mais especializada (classe filha ou subclasse) pode herdar as propriedades (métodos e atributos) e semântica de uma classe mais geral (classe pai ou superclasse). No exemplo a seguir dizemos que a classe Gerente herda todos os atributos e métodos da classe mãe Funcionario. Sendo mais preciso, ela também herda os atributos e métodos privados, porém não consegue acessá-los diretamente. class Funcionario { String nome; String cpf; double salario; }
16
class Gerente extends Funcionario { int senha; public boolean autentica(int senha) { if (this.senha == senha) { System.out.println("Acesso Permitido!"); return true; } else { System.out.println("Acesso Negado!"); return false; } } }
4.2.5 Polimorfismo O termos polimorfismo é originário do grego e quer dizer “possuidor de várias formas”. Na programação, está atrelado à capacidade de um método assumir várias implementações diferentes sob um mesmo nome e à capacidade do programa em discernir, dentre métodos homônimos, aquele que deve ser executado. Uma das maiores vantagens do uso de polimorfismo está na criação de programas mais claros e flexíveis, pois, elimina a necessidade de replicação de métodos ao mesmo tempo em que permite a extensão dos já existentes, como podemos observar no exemplo: class EmpregadoDaFaculdade { private String nome; private double salario; double getGastos() { return this.salario; } } class ProfessorDaFaculdade extends EmpregadoDaFaculdade { private int horasDeAula; double getGastos() { return super.getGastos() + this.horasDeAula * 10; } }
Polimorfismo também está relacionado à capacidade de uma variável em receber objetos de classes distintas. Veja: public class Aluno extends Pessoa { .... } Pessoa p; p = new Pessoa(); // OK p = new Aluno(); // OK p = new Object(); // ERRO
17
4.2.6 Modularidade Em programas grandes, ocasionalmente surge o problema: quero escrever uma classe com determinado nome, mas o mesmo já está em uso. Por exemplo: Você está integrando o código de um outro sistema ao seu e uma colisão de nomes ocorre em classes com funcionamentos diferentes. Para resolver este problema, recorremos a estrutura de diretórios para organizar as classes. Por sua vez, no Java, estes diretórios são mapeados para pacotes, estruturas que agrupam classes com objetivos comuns. Veja o pacote java.util – ele contém classes utilitárias. A utilização de pacotes não só permite uma organização maior do código como também facilita o compartilhamento do mesmo, já que existem modificadores de acesso para o nível de pacote, permitindo que determinado método / campo / classe seja visível apenas para classes pertencentes ao mesmo pacote.
4.3 Benefícios Observe a classe abaixo de uma simples aplicação de Zoológico: public class Zoo { private Animal[] lista = new Animal[8]; public Animal[] getLista(){ return this.lista; } public void adicionarAnimal( Animal p){ for(int i=0; i<lista.length; i++){ if (lista[i] == null){ lista[i] = p; break; } } }
Utilizamos a classe Animal para lançar mão do polimorfismo (Répteis, Mamíferos, etc). Não fosse por ela, teríamos um grande prejuízo ao criar estruturas distintas para receber cada um dos tipos de Animal: uma para Reptil, uma para Mamifero, etc. Como a estrutura dessas classes tem muito em comum, podemos generalizar a definição em uma superclasse a fim de evitar a replicação de código e facilitar a manutenção.
18
4.4 Construtores Construtores são métodos especiais, utilizados para personalizar a construção de objetos e possuem as seguintes características:
É o primeiro método executado pelo objeto
Deve possuir o mesmo nome da classe
Não tem um tipo de retorno (nem mesmo void)
É invocado através do uso do operador new (instanciação)
Tem modificador de acesso, normalmente public
Toda classe deve possuir ao menos um construtor. Se omitido, o compilador gerará um construtor público, com corpo vazio e sem parâmetros, como no exemplo abaixo: public Reptil() { }
Por serem os primeiros métodos a serem executados, construtores são utilizados para iniciar o estado interno de um objeto, configurando-o de acordo com os parâmetros fornecidos: public Reptil(String especie) { this.especie = especie; } public Reptil() { this("Desconhecido"); }
Nota: Caso alguma exception lançada no corpo do construtor não seja tratada, a criação do objeto falhará. O exemplo acima demonstra que, assim como ocorre com os métodos, podemos ter sobrecargas de um construtor. Inclusive, podemos fazê-los chamarem uns aos outros utilizando o this(). Aplicamos este recurso na definição de sobrecargas de um construtor que vão de um nível mais complexo (vários parâmetros) para um nível mais simples.
4.5 this & super Todos os métodos de uma instância recebem os argumentos implícitos this e base que nada mais são do que referências para o próprio objeto e para o objeto pai, respectivamente. Embora possa parecer pouco útil a primeira vista, essas referências são indispensáveis em muitas das situações encontradas pelo desenvolvedor Java:
Desambiguação: O construtor da classe Reptil recebe um parâmetro que possui o mesmo nome de um campo. Neste caso, utilizamos o this.especie para dizer ao compilador que queremos a variável membro e não a local. O mesmo vale para uma classe que define um campo com nome idêntico a um campo presente na superclasse e que deseja utilizar a variável da superclasse ao invés da própria;
Construtor: Caso seja necessário que uma sobrecarga do construtor chame outra, utilizamos o this() para sinalizar a intenção. Usando a base(), conseguimos chamar um construtor da superclasse – útil para situações onde a assinatura do construtor de sua classe difere da superclasse; 19
Polimorfismo: Muitas vezes, ao sobrescrever um método da superclasse, queremos apenas ampliar o comportamento original em vez de substituí-lo completamente. Em casos como esse, é comum invocar a versão da superclasse utilizando base.metodo().
4.6 Exercícios
Crie uma classe Poligono com os métodos calculaPerimetro() e calculaArea() e crie as classes Triangulo e Quadrado sobrescrevendo ambos os métodos.
4.7 Membros Estáticos Membros estáticos são aqueles que não pertencem a uma instância específica, mas sim à própria classe. Como tal, membros estáticos são compartilhados entre todas as instâncias da classe e não precisam de instanciação para serem usados. Vejamos o exemplo: class Usuario { public static int tentativas; ... public void login() { tentativas++; // Restante } }
Usuario.tentativas = 0; Usuario c1 = new Usuario(); Usuario c2 = new Usuario(); c1.login(); // tentativas = 1 c2.login(); // tentativas = 2 c1.tentativas = 0; c2.login(); // tentativas = 1
Para tornar um membro estático, basta acrescentar o modificador static à declaração do mesmo. Note que é possível utilizar o membro estático através da própria classe (Usuario.tentativas) ou através das instâncias (c1.tentativas). Independentemente da abordagem, a variável acessada é a mesma em ambos os casos. Embora úteis, membros estáticos exigem alguns cuidados especiais no manuseio. Primeiramente, não é possível utilizar as referências this e base, uma vez que o membro estático não existe em uma instância. Pela mesma razão, membros estáticos só podem referenciar outros membros estáticos.
4.7.1 Ocorrências de membros estáticos Nas bibliotecas do Java encontramos inúmeros casos de atributos e métodos estáticos. Nosso conhecido método main(), por exemplo, é um método estático, que é chamado pela JVM quando uma aplicação é executada. A classe Math prove métodos de classe que executam várias funções matemáticas, como funções trigonométricas e logaritmos. Um exemplo é o método Math.sqrt() que calcula a raiz quadrada de um número não negativo. A classe System prove variáveis de classe para representar o estado do sistema inteiro. System.out é uma variável de classe que faz referência ao objeto PrintStream, que representa o fluxo padrão de saída. O método println() é um método de instância da classe PrintStream.
4.8 Exercícios
Refaça os exercícios dos itens 2.12 e 4.6 utilizando seus novos conhecimentos
Crie um programa de perguntas e respostas com reprocessamento onde o usuário deverá responder uma série de perguntas múltipla escolha.
Ao término da jogada, será apresentado o número de acertos realizados na jogada 20
Após mostrar o número de acertos, imprimir também as 3 questões que mais tiveram respondidas erradas de todos os tempos (não apenas da jogada atual)
21
5. CLASSES ABSTRATAS E INTERFACES Em grandes sistemas, é comum que a hierarquia de classes torne-se complexa, onde as classes mais acima (raiz e proximidades) costumar ser bastante gerais, favorecendo o reuso. Passa a ser interessante a possibilidade de definir protótipos de classes que serão usadas para guiar a criação de outras classes. Elas implementam ou apenas definem comportamentos que são genéricos o suficiente para serem comuns a várias outras classes.
5.1 Classes Abstratas Voltando ao exemplo da classe Animal, podemos notar que a definição de animal é muito ampla e mesmo que animais possuam características compartilhadas, o modo como de operar de cada tipo de animal é muito diferente: alguns se locomovem pelo ar, outros por terra, etc. Sendo assim, temos que a classe Animal pode ser considerada um modelo que não deve ser criado diretamente. Uma das maneiras de criar tais modelos se dá através do uso de classes abstratas. Uma classe abstrata é uma classe incompleta que não pode ser construída, necessitando de subclasses que forneçam os detalhes necessários para que a classe esteja completa. Vejamos um exemplo da modelagem de animais: abstract class Animal { String especie; int idade; public public public public
abstract void locomover(); abstract void alimentar(); void nascer() { ... } morrer() { ... }
}
Foi utilizada a palavra reservada abstract para definir uma classe abstrata e também métodos abstratos. Se tentarmos instanciar uma classe abstrata receberemos um erro de compilação. Analogamente, métodos abstratos não possuem corpo, e devem ser obrigatoriamente implementados nas classes filhas. Uma classe abstrata pode conter:
Métodos e atributos como em classes convencionais. As classes derivadas de uma classe abstrata herdam da mesma forma os métodos e atributos. Estes métodos podem ser sobrescritos nas subclasses mas isto não é obrigatório;
Métodos abstratos, que obrigatoriamente devem ser sobrescritos pelas subclasses.
A existência de métodos abstratos ocorre apenas em classes abstratas. Como uma classe abstrata está num nível muito alto de abstração, muito frequentemente não possui detalhes suficientes para implementar determinados comportamentos que serão comuns a todas as suas subclasses. Ao invés disso, elas apenas definem quais serão estes comportamentos obrigatórios através da criação de métodos abstratos. Como pode ser visto no exemplo, para definir um método abstrato devemos prover apenas a assinatura do método, isto é, seu nome, argumentos e tipo de retorno. A implementação (corpo) deste método ficará a cargo de cada subclasse concreta, que poderá dar a sua versão para ele. O uso de classes e métodos abstratos é uma forma bastante comum e flexível para criarmos e utilizarmos bibliotecas orientadas a objetos: por meio de herança associamos nossas classes e implementamos os métodos abstratos. Por meio do polimorfismo, temos a garantia de que as nossas implementações serão compatíveis com os locais que fazem uso da classe abstrata.
22
5.2 Interfaces Suponhamos que exista a necessidade de fazer com que um animal bote ovos. Uma ideia seria acrescentar o método abstrato “botarOvo()” na classe Animal, mas nada garante que um Animal tenha a capacidade de botar ovos. Uma outra solução seria criar outra classe com este método, mas precisamos que nossa classe seja descendente de Animal, uma vez que o Java não suporta herança múltipla, como no C++. Devemos recorrer a interfaces para solucionar este problema: interface Oviparo { public static final int MAX_OVOS = 10; public void botarOvo(); } class Mamifero extends Animal implements Oviparo, Comparable { ... public void botarOvo() { ... } }
Existe muita semelhança entre interfaces e classes abstratas, mas interfaces possuem limitações em sua constituição: elas podem conter apenas constantes e métodos, sendo que nenhum destes métodos pode conter implementação (abstratos). Para uma classe implementar uma interface, devemos usar a palavra reservada implements (diferente de extends), como no exemplo anterior. É obrigatório que toda classe ao implementar uma interface implemente todos os métodos especificados pela mesma. Assim, interfaces fornecem a ideia de um “contrato” que qualquer subclasse deve obedecer. Outra característica importante é que uma classe pode derivar de apenas uma única outra classe (herança simples), mas pode implementar um número indeterminado de interfaces. Uma concepção errônea de quem está se iniciando no Java é que escrever interfaces é perda de tempo, uma vez que o código escrito “não serve pra nada”, uma vez que a classe implementadora é obrigada a fornecer uma implementação. Essa é uma maneira errada de pensar – O objetivo do uso de uma interface é deixar seu código mais flexível, e possibilitar a mudança de implementação sem maiores traumas (manutenibilidade). Não é apenas um código de prototipação, tampouco um cabeçalho – Trata-se de uma definição de um novo tipo e já podemos observar suas vantagens.
5.3 Exercício
Crie um programa de agenda com: o Uma classe abstrata Pessoa (codigo, nome) o Uma interface DoadorSangue com o método String getTipoSangue() o Uma classe Funcionário que estenda Pessoa e implemente DoadorSangue
Inclua o atributo int cracha com set e get
Adicione um atributo String tipoSangue cujo valor deverá se atribuído pelo construtor
o Uma classe Fornecedor, subclasse de Pessoa, com o campo String ramo
23
6. EXCEÇÕES Olhando os Javadocs (e recomendo que olhem com frequência), podemos encontrar alguns métodos que possuem a palavra reservada throws em sua assinatura, como o método File.createNewFile(): http://docs.oracle.com/javase/7/docs/api/java/io/File.html#createNewFile() A palavra reservada throws indica que este método realiza operações que podem retornar um ou mais tipos de Exceptions. Mas, afinal, o que são exceptions no Java?
6.1 Definição de Exception O termo exception é uma abreviação para “evento excepcional” – um evento ocorrido durante a execução do programa que rompe o fluxo normal de instruções. Quando um erro ocorre dentro de um método (como tentar acessar uma posição não existente em um array), o método cria um objeto e entrega-o ao Runtime – a este objeto damos o nome de exception. Esta exception carrega dentro de si informações relacionadas ao erro, incluindo o tipo do erro e algumas variáveis indicadoras do estado do programa no momento em que o erro ocorreu. O ato de criar um objeto da classe Exception é passá-lo ao Runtime é chamado “lançar uma exception”.
6.2 Classes de Exceções Tanto as exceções quanto os erros derivam da classe Throwlable:
Erros: São classes filhas da classe Error e indicam que ocorreu um erro irrecuperável, geralmente fatal para a execução do programa;
Exceções unchecked: Estas exceções são derivadas da classe RuntimeException e geralmente estão associadas com erros inerentes da execução do programa: acessar posições inexistentes de um array, chamar um método de um objeto inexistente (null), etc. É possível optar por não tratar essas exceções;
Exceções checked: Exceções que derivam da classe Exception. O desenvolvedor deve, obrigatoriamente, capturar exceções deste tipo ou um erro de compilação ocorrerá.
6.3 Tratamento de Exceções Quando um método lança uma exception, o Runtime encerra a execução do método no ponto onde a exceção foi lançada e inicia a busca por alguém que consiga tratá-la. A lista de candidatos é composta pelos métodos que foram chamados até o ponto onde o erro ocorreu. A esta lista de métodos damos o nome de call stack. A exception continua rompendo o fluxo dos métodos na call stack até que encontre um bloco de tratamento de exceptions. A ordem em que a busca ocorre é do método mais próximo da ocorrência do erro até o método main. Caso o Runtime não encontre nenhum bloco de tratamento adequado, o programa é terminado. As exceções existem em duas modalidades: checked e unchecked. A primeira obrigatoriamente precisa ser envolta em um bloco try-catch, do contrário, um erro de compilação ocorrerá. Como desenvolvedor Java, ao encontrar um método que lança exceptions, você tem alternativas:
Capturar a exceção e tratá-la
Deixá-la seguir para o método superior na call stack 24
Capturar a exceção e disparar uma exceção diferente
6.3.1 Capturando Exceções Se um bloco de código possui um ou mais métodos que podem disparar exceções, basta cercá-lo com um bloco try-catch para permitir que seu código esteja “escutando” possíveis exceções. É possível (e comum) que exista mais de um bloco de catch para um dado trecho de código, permitindo que cada catch trate uma exceção de um tipo específico. Opcionalmente, pode-se incluir um bloco finally após todos os blocos catch, o que sempre é executado ao final, mesmo que nenhuma exceção ocorra. Um exemplo de exceção aritmética: try { int erro = 1 / 0; // Divisão por zero } catch (ArithmeticException e) { // Tratamento do erro }
Nota: Se houvesse algum código após a divisão, ele nunca seria executado. Alterando o exemplo anterior, teremos os seguintes valores impressos no console: Código
Console
try {
passo 1 passo 2
int erro = 1 / 0; // Divisão por zero } catch (ArithmeticException e) { System.out.println("passo 1"); } finally { System.out.println("passo 2"); }
Podemos ir além e remover o bloco de catch, deixando apenas o finally. Com isso, o programa imprimirá “passo 2” no console antes da exception ser lançada para o Runtime e forçar o encerramento do programa: passo 2 Exception in thread "main" java.lang.ArithmeticException: / by zero at lecom.curso.Programa.main(Programa.java:7)
Voltando ao método File.createNewFile(), podemos observar que ele existem três possibilidades de exceção: IOException (checked), IllegalArgumentException (unchecked) e SecurityException (unchecked). Por ser uma exceção checked, devemos tratar a IOException, ficando a critério do desenvolvedor tratar as outras duas. Uma possível implementação ficaria como segue: File f = null; try { f = File.createTempFile("tmp", ".txt"); // … Bloco de operações com 'f' } catch (IOException e1) { // Tratamento de e1 } catch (SecurityException e2) { // Tratamento de e2 } finally { if (f != null) { f.deleteOnExit(); // Limpeza }
25
}
É importante notar que o polimorfismo do Java nos permite construir estruturas elaboradas de tratamento de exceptions. Complementando o exemplo do File.createTempFile(): try { f = File.createTempFile("tmp", ".txt"); // … Bloco de operações com 'f' } catch (IOException e1) { // Tratamento de e1 } catch (SecurityException e2) { // Tratamento de e2 } catch (Exception e3) { // Tratamento de exceções inesperadas } finally { if (f != null) { f.deleteOnExit(); // Limpeza } }
Veja que acrescentamos um novo bloco de catch com uma Exception e3. O efeito prático dessa estrutura é que todas as exceções não tratadas pelos blocos de catch anteriores serão direcionadas para a variável e3. Isso ocorre porque todas as exceções são subclasses de Exception.
6.3.2 Deixando uma Exceção Passar Existem casos onde não queremos tratar uma exceção do tipo checked. Felizmente, podemos livrar o método da obrigação de tratar a exceção acrescentando um “throws NomeDaException” à assinatura do mesmo. Desse modo, estamos delegando a responsabilidade de tratar a Exception para os métodos acima. public void meuMetodo() throws IOException { File f = File.createTempFile("tmp", ".txt"); // Restante do método }
6.3.3 Lançando uma Exceção Como desenvolvedor, seria interessante podermos criar nossas próprias exceções e lançá-las como resposta a um comportamento inesperado. Felizmente, isso é possível no Java. Para criar sua própria exceção do tipo checked, basta criar uma classe que seja filha de Exception. Caso deseje uma exceção unchecked, a classe deve ser filha de RuntimeException. Lançar uma exceção é igualmente simples: basta utilizarmos o comando throw (não confundir com o throws que acompanha a assinatura de um método) seguido pelo objeto da exceção: throw new MinhaException("Erro mortal");
Dica: Podemos encarar o throw como se fosse um return de exceções.
26
6.4 Exercícios
Refaça o exercício do item 5.3 fazendo com que os dados venham de um arquivo e tratando as Exceptions e criando uma Exception própria que deve ser lançada ao tentar inserir um contato com nome idêntico a um já existente na agenda.
7. SERVLETS Hoje é comum vincularmos a ideia de Web com dinamicidade, mudança, mas essa nem sempre foi a realidade: já houve um tempo onde sites eram apenas sítios de hipertexto estático ligados por meio de hiperlinks. Tecnologias da atualidade nos permitiram alterar esse panorama e transformar a Web no que vemos hoje: CGI, PHP, .NET, e, principalmente, Java. Java ganhou destaque como linguagem para a Web por ter várias características que favoreciam o desenvolvimento de páginas dinâmicas:
Portabilidade (JVM);
Produtividade (POO, Garbage Collection)
Comunidade ativa e numerosa (Apache Foundation e várias outras instituições open-source)
Escalabilidade (permite arquitetura distribuída)
Eficiência (Servlets persistentes entre requisições)
Recompilação automática (páginas JSP)
7.1 O Protocolo HTTP Antes de tratarmos de Servlets, é importante entendermos deste protocolo sobre o qual os Servlets estão fundamentados. O protocolo HTTP é um protocolo da camada de aplicação utilizado para distribuição de conteúdo hipermídia de maneira distribuída e colaborativa e é utilizado na navegação de páginas na internet. Os browsers utilizam o HTTP para obter conteúdo de servidores remotos e mostrá-los localmente. Os clientes obtêm conteúdo por meio de requisições enviadas ao servidor que, por sua vez, processa a requisição e devolve uma resposta ao cliente (possivelmente com algum conteúdo hipermídia). Por ser um protocolo stateless de comunicação cliente-servidor, nenhuma informação é mantida sobre os clientes ou requisições previamente recebidas. Requisições HTTP são compostas pelos seguintes elementos:
Um comando, também conhecido como método ou verbo
O endereço de um recurso no servidor, também chamado de caminho ou path
A versão do protocolo HTTP sendo utilizado
Juntando todos esses dados, teremos uma requisição que se assemelha ao exemplo abaixo: GET / HTTP/1.1 Host: www.google.com.br Existem diversos métodos HTTP disponíveis para uso nas requisições, mas os mais comuns são:
GET – utilizado para obter algum conteúdo do servidor, como uma página ou imagem
POST – utilizado para enviar dados de formulário ao servidor 27
HEAD – similar ao GET, mas envia apenas os cabeçalhos de resposta
Com a introdução da versão 1.1 do HTTP, novos métodos foram acrescentados:
PUT – armazena a entidade enviada no corpo da requisição
DELETE – remove o recurso especificados
OPTIONS – lista os métodos suportados pelo servidor no endereço fornecidos
Uma requisição também pode conter parâmetros adicionais, chamados cabeçalhos ou headers. Os headers mais comuns são:
User-Agent – contém informações sobre o cliente que iniciou a requisição (browser, versão, etc). o Chrome: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36
Accept – indica quais os tipos de recursos aceitos pelo cliente o Exemplos: */* (tudo), text/html, image/jpeg, etc.
Exemplo atualizado com os cabeçalhos: Estrutura
Exemplo
<Verbo> <Caminho> <Versão HTTP>
GET /ExemploServlet/ImprimeIp HTTP/1.1
Host: <Endereço base> <Nome do cabeçalho 1>: <Valor 1> ... <Nome do cabeçalho n>: <Valor n>
Host: localhost:8080 User-Agent: curl/7.30.0 Accept: */*
IMPORTANTE: A ordem precisa ser respeitada para que o servidor entenda a requisição corretamente. A padronização também está presente na resposta do servidor. Uma resposta bem formada contém:
Versão do protocolo, como na requisição
Código de status indicando sucesso (ou não) ao processar a requisição, acompanhado por mensagem auxiliar
Cabeçalhos, com informações adicionais com o tipo do conteúdo sendo retornado,
Corpo da resposta, contendo o conteúdo solicitado
Eis um exemplo de reposta completa: Estrutura
Exemplo
<Versão HTTP> <Código> <Mensagem> <Nome do cabeçalho 1>: <Valor 1> … <Nome do cabeçalho n>: <Valor n>
HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Cache-Control: no-cache Pragma: no-cache Expires: Thu, 01 Jan 1970 00:00:00 GMT Transfer-Encoding: chunked 28
Date: Sun, 09 Mar 2013 03:43:12 GMT <Conteúdo>
<html> <body> <h1>Hello World</h1> </body> </html>
No exemplo anterior, o código de status 200 indica que a requisição do cliente foi bem-sucedida. Os cabeçalhos trazem informações adicionais sobre o conteúdo retornado, como instruções para que o browser não faça cache da resposta. O conteúdo retornado é textual e corresponde a uma página HTML, porém dados binários também podem fazer parte de uma resposta. A tabela abaixo contempla os códigos mais comuns e seus significados: Código da resposta Mensagem Significado 200
OK
Requisição processada com sucesso
302
Moved Temporarily
Conteúdo solicitado está em outar URL
404
Page Not Found
Conteúdo solicitado não foi encontrado
500
Internal Server Error Erro inesperado no processamento da requisição
503
Service Unavailable
Serviço indisponível no momento
7.2 Common Gateway Interface O advento do Common Gateway Inteface, mais conhecido por CGI possibilitou o surgimento de conteúdo dinâmico na Web. Neste modelo, uma aplicação (processo) era executada a cada requisição HTTP, passando os parâmetros para a aplicação via Variáveis de Ambiente. A aplicação, por sua vez, retornava o código HTML que deveria ser mostrado para o browser que iniciou a requisição. A utilização de Servlets trouxe várias melhorias ao fluxo acima:
Maior desempenho resultante da persistência do Servlet na memória, que não precisa ser iniciado a cada nova solicitação. Além disso, conexões ociosas ao banco de dados são mantidas abertas para evitar o overhead de iniciar novas conexões (pool de recursos);
Implantação facilitada pelo uso de uma máquina virtual que abstrai os detalhes do hardware subjacente aumentando o nível de padronização e portabilidade;
Distribuição facilitada em ambientes heterogêneos em função do uso de uma máquina virtual.
7.3 O que são Servlets? Servlets são classes Java usadas para estender as capacidades dos servidores que hospedam aplicações acessadas por meio de um modelo de requisição-resposta (como o HTTP). Embora Servlets possam ser usadas para responder qualquer tipo de requisição, o uso mais comum é para estender a capacidade de aplicações hospedadas por servidores Web. Para aplicações deste tipo, a tecnologia de Servlets Java oferece classes específicas para uso com HTTP. Para que um servidor possa executar um Servlet, é preciso que ele implemente um Servlet Container, também conhecido como Servidor de Aplicações Java. Um Servlet Container é um 29
servidor que suporta as tecnologias JSP, Servlet, JSTL e JSF mas não o Java EE completo. Um dos mais conhecido é o Apache Tomcat. No contexto Web, o Servlet é reponsável por receber requisições HTTP feitas ao servidor e, a partir dos parâmetros recebidos, realizar o processamento relevante para finalmente retornar uma resposta ao cliente solicitante – seja uma página HTML, uma imagem JPEG, um arquivo JSON, etc. Os pacotes javax.servlet e javax.servlet.http fornecem as interfaces e classes necessárias para que possamos iniciar o desenvolvimento de Servlets para Web. Todos os Servlets precisam implementar a interface Servlet, a qual define os métodos para gestão do ciclo de vida. A classe HttpServlet oferece métodos como doGet() e doPost() para lidar com requisições HTTP.
7.4 Funcionamento da Servlet Para ilustrar o modo de operação do Servlet, começaremos com um exemplo simples. O servlet abaixo faz com que todas as requisições HTTP GET direcionadas para a URL <endereço do host>/<nome do servlet>/ImprimeIp recebam uma página HTML exibindo o IP do solicitante. package org.exemplo; import java.io.*; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; @WebServlet("/ImprimeIp") public class ImprimeIp extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); out.println("<html> <body>"); out.printf("<h1>Seu IP: %s</h1>", request.getRemoteAddr()); out.println("</body> </html>"); out.flush(); } }
Quando o HttpServlet recebe a requisição, ele verifica o verbo HTTP utilizado (GET, POST, PUT, ...) e busca o método doXXX() correspondente. Caso exista, o método é chamado com dois parâmetros:
Um objeto da classe HttpServletRequest, que encapsula a requisição recebida; e
Um objeto da classe HttpServletResponse, que encapsula a resposta do servlet.
Para devolver uma resposta ao cliente que iniciou a requisição, precisamos obter a instância do PrintWriter associado a resposta. O programa utiliza este PrintWriter para escrever no Stream de resposta vinculado ao cliente que iniciou a requisição. A instância do PrintWriter é obtida utilizando o método getWriter() presente no objeto response. A classe PrintWriter possui métodos que facilitam a escrita de texto num Stream, além de realizar a bufferização automática do output. No exemplo, utilizamos os métodos println() e printf() para adicionar Strings o Stream do servlet e, ao final, utilizamos o método flush() para forçar a escrita dos dados no Stream de saída. No exemplo, não estamos tratando a IOException lançada pelo getWriter(). No entanto, é considerada uma boa prática capturar exceções lançadas para a geração de logs no servidor. try { PrintWriter out = response.getWriter(); out.println("<html> <body>");
30
out.printf("<h1>Seu IP: %s</h1>", request.getRemoteAddr()); out.println("</body> </html>"); out.flush(); } catch (IOException e) { ServletContext context = getServletContext(); context.log("Erro ao obter writer", e); }
7.5 Respostas Servlets realizam trabalho por trás das cortinas durante o processamento de uma requisição. Embora não esteja visível no exemplo anterior, o texto que escrevemos no Stream de saída passou por alguns processamentos até finalmente chegar ao formato final. Antes de chegar ao cliente, a resposta precisa ser envelopada numa mensagem HTTP válida. O Servlet se encarrega de acrescentar os cabeçalhos HTTP à nossa resposta. Eis a resposta retornada pelo Servlet escrito no exemplo anterior: HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Transfer-Encoding: chunked Date: Sun, 09 Mar 2013 03:01:16 GMT
Cabeçalhos: Populados automaticamente
<html> <body> <h1>Seu IP: 0:0:0:0:0:0:0:1</h1></body> </html>
Corpo: Populado pelo doGet()
Quando aplicações Web precisam de controle sobre os cabeçalhos retornados, é possível utilizar o método setHeader() do objeto de response para definir ou modificar cabeçalhos HTTP. Um caso de uso comum é acrescentar cabeçalhos que instruam o browser do cliente a não fazer cache das respostas. Existem também as variações setDateHeader() e setIntHeader(), usadas para headers com valores de data e inteiros, respectivamente. Voltemos ao exemplo Java, agora com setHeader(): response.setHeader("Cache-Control","no-cache"); // HTTP 1.1 response.setHeader("Pragma","no-cache"); // HTTP 1.0 response.setDateHeader ("Expires", 0); // Proxy response.setDateHeader("Last-Modified", System.currentTimeMillis()); PrintWriter out = response.getWriter(); out.println("<html>"); ...
E os cabeçalhos da nova resposta: HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Cache-Control: no-cache Pragma: no-cache Expires: Thu, 01 Jan 1970 00:00:00 GMT Last-Modified: Sun, 09 Mar 2013 03:43:12 GMT Transfer-Encoding: chunked Date: Sun, 09 Mar 2013 03:43:12 GMT IMPORTANTE: A alteração dos cabeçalhos deve ser feita ANTES de que qualquer conteúdo de saída seja escrito. A ordem dos elementos na resposta é crítica para obter o resultado desejado. Primeiro o status (HTTP/1.1 200 OK), depois os cabeçalhos e por fim o corpo da mensagem. Violar 31
esta regra fará com que seja lançada uma IllegalStateException, consequentemente interrompendo a geração de uma resposta ao cliente. O header Content-Type é usado para sinalizar ao cliente o tipo de conteúdo sendo retornado e é útil para auxiliar os browsers a mostrar o conteúdo de maneira inteligente. Entre os possíveis valores para esse header, temos: text/html para páginas HTML, image/jpeg para imagens no formato JPEG, application/octet-stream para dados binários, etc. Devido à grande utilização, foi criado o método setContentType(), próprio para definir valores ao header Content-Type. Exemplo: FileInputStream in = null; try { response.setContentType("image/jpeg"); in = new FileInputStream("imagem.jpg"); // Obtem stream de saída binária do Servlet ServletOutputStream out = response.getOutputStream(); byte[] buffer = new byte[1024]; int bytes = 0; // Copia conteúdo binário para saída while ((bytes = in.read(buffer)) >= 0) { // write() pode lançar IOException out.write(buffer); } out.flush(); in.close(); } catch (IOException io) { // Tratamento de erro }
Nota: O trecho acima pode demorar alguns segundos para finalizar a execução, pois, os dados são enviados gradualmente para o cliente solicitante à medida que o Servlet escreve na ServletOutputStream. Caso a conexão seja encerrada (ex: browser foi fechado durante a execução), o método write() lançará uma IOException.
7.5.1 Exercícios
Crie uma Servlet que retorne uma página HTML com uma mensagem de boas-vindas caso o IP do cliente seja igual ao IP contido em um arquivo. o No Eclipse, crie um novo Dynamic Web Project
7.5.2 Status HTTP Como já vimos, toda resposta a uma requisição HTTP começa com o status da resposta. O status segue o formato <versão do http> <código da resposta> <mensagem auxiliar> e é responsável por indicar se a requisição foi bem-sucedida (ou não). Podemos controlar o status das respostas geradas por nossa aplicação assim como fazemos com os cabeçalhos – basta utilizarmos o método setStatus() do HttpServletResponse. A fim de melhorar a legibilidade do código, a classe HttpServletResponse define constantes para cada um dos códigos de status. Caso o setStatus() não seja utilizado, considera-se que houve sucesso no processamento e o código 200 é retornado. Código
Constante
200
SC_OK
302
SC_MOVED_TEMPORARILY 32
404
SC_NOT_FOUND
500
SC_INTERNAL_SERVER_ERROR
503
SC_SERVICE_UNAVAILABLE
7.5.3 Exercícios
Utilizando o exercício do item 7.5.1 como base, altere o status da resposta para 404 quando o arquivo não for encontrado
7.6 Requisições 7.6.1 Parâmetros da requisição Para um Web efetivamente dinâmica, o cliente precisa de meios de enviar dados para que o servidor consiga produzir conteúdos variados. Formulários HTML são uma das opções, uma vez que é possível utilizá-los para enviar dados ao servidor por meio do método HTTP Post. Os dados a serem enviados são incorporados como parâmetros ao corpo da requisição, podendo ser recuperados posteriormente no servidor. Outra forma de enviar parâmetros ao servidor é através da concatenação dos mesmos à URL de uma requisição GET. Para isso, precisamos acrescentar o caractere interrogação ( ? ) ao final da URL e, em seguida, acrescentar os parâmetros no formato <nome parâmetro>=<valor parâmetro> separados entre si por um caractere & . Exemplo: <URL base>/ExemploServlet/?nome=fanta&idade=26 A API de Servlets disponibiliza diversos métodos para capturar os parâmetros de uma requisição:
String HttpServletRequest.getParameter(String paramName) – Retorna o parâmetro especificado em paramName, ou null caso o parâmetro não exista
Enumeration HttpServletRequest.getParameterNames() – Retorna um enumerador com os nomes de todos os parâmetros
String HttpServletRequest.getQueryString() – Retorna a parte da URL de requisição que vem após o caractere interrogação ( ? ). Ex: nome=fanta&idade=26
7.6.2 Exercícios
Crie uma Servlet que receba os parâmetros altura e peso de uma requisição GET. Responda com uma página contendo o cálculo do Índice de Massa Corporal (IMC). Caso algum dos parâmetros não esteja presente, responda com o código de status 500 (ver item 7.5.2) o Cálculo do IMC: peso / (altura * altura)
Crie uma página HTML com um formulário de login e senha que envie uma requisição POST para uma Servlet. A Servlet retornará uma página de boas vindas em caso de sucesso. Caso contrário, responda com o código de status 401 (Forbidden).
7.6.3 Cabeçalhos de Requisição Vimos que uma mensagem de resposta HTTP é composta pelo status, cabeçalhos e corpo. Requisições possuem uma estrutura similar, como podemos ver no exemplo abaixo: Estrutura
Exemplo
<Verbo> <URL> <Versão HTTP>
GET /ExemploServlet/ImprimeIp HTTP/1.1 33
<Nome do cabeçalho 1>: <Valor 1> User-Agent: curl/7.30.0 <Nome do cabeçalho 2>: <Valor 2> Host: localhost:8080 <Nome do cabeçalho n>: <Valor n> Accept: */* A classe HttpServletRequest fornece métodos para obter mais informações sobre a requisição:
String HttpServletRequest.getHeader(String name) – Retorna o valor cabeçalho especificado em name, ou null caso o cabeçalho não exista
long HttpServletRequest.getDateHeader(String name) – Retorna o valor do cabeçalho como um long que representa uma data. O valor retornado equivale ao número de milissegundos desde 1o de Janeiro de 1970 ou -1, caso não o cabeçalho não exista. Se o valor do cabeçalho não for uma data, uma IllegalArgumentException é lançada
int HttpServletRequest.getIntHeader(String name) – Retorna o valor do cabeçalho como um int. Caso o cabeçalho não exista, retorna 01. Se o valor do cabeçalho não puder ser convertido para um int, uma NumberFormatException é lançada
Enumeration HttpServletRequest.getHeaderNames() – Retorna um enumerador com os nomes de todos os cabeçalhos presentes na requisição
Um cabeçalho bastante utilizado é o User-Agent, pois nele estão contidas informações sobre o cliente que iniciou a requisição (software e versão). Estas informações são úteis para que o Servlet possa gerar uma versão de conteúdo especialmente adaptada para o cliente, como um site que oferece uma versão para smartphones e outra para desktops.
34
8. COOKIES Por ser um protocolo stateless, o HTTP não mantém informações sobre requisições passadas e, portanto, é incapaz de agrupar requisições de um mesmo cliente. Isso representa um problema para aplicações que dependam de ações executadas anteriormente para poder operar, como é o caso de sistemas que possuem algum tipo de autenticação (e-commerce, webmail, etc). Uma das possibilidades é o uso de Cookies para realizar a persistência de informações (estado) associadas a um cliente.
8.1 Definição Cookies são pacotes de dados gerados pelo servidor e enviados ao cliente juntamente com a resposta da requisição. Esses dados são armazenados pelo browser do usuário, que se encarrega de enviá-los a cada requisição efetuada após o recebimento do cookie. Com isso, o servidor é capaz de fazer a distinção entre clientes e recuperar outras informações necessárias para a geração de uma resposta.
8.2 Estrutura Além do nome, valor e caminho, cookies possuem os seguintes campos:
Comment: Comentário descrevendo o propósito do cookie
MaxAge: Intervalo de tempo (em segundos) no qual o cookie é considerado válido
Domain: Define todos os servidores abaixo de um mesmo domínio
Path: Define os recursos abaixo do diretório virtual do recurso
8.3 Adicionando Cookies Para a manipulação de cookies, a API de Servlets disponibiliza a classe javax.servlet.http.Cookie que encapsula dados e operações de um cookie. Os campos de um cookie podem ser recuperados / alterados utilizando os vários getters e setters disponíveis na classe. O primeiro passo para utilizar um cookie é criá-lo passando ao construtor o nome e o valor do mesmo. O próximo passo é definir os campos (Comment, MaxAge, ...) relevantes ao objeto criado. Por fim, o cookie precisa ser adicionado ao objeto HttpServletResponse para que seja enviado ao browser do cliente, como mostra o exemplo: Cookie cookie = new Cookie("Nome", "Valor"); cookie.setMaxAge(120); cookie.setComment("Comentario sem acento"); response.addCookie(cookie);
Importante: NÃO utilize caracteres acentuados tanto em cookies quanto em cabeçalhos. O trecho acima cria um cookie com nome “Nome” e valor “Valor”. Ele será válido por 120 segundos a partir do instante em que for recebido e será excluído pelo browser após expirar. A resposta é acrescida do cabeçalho abaixo: Set-Cookie: Nome=Valor; Version=1; Comment="Comentario sem acento"; Max-Age=120; Expires=Sun, 09-Mar-2013 18:30:04 GMT
Caso o valor fornecido para MaxAge seja negativo, o cookie será mantido até que o browser seja encerrado. Se o valor for 0, o browser removerá o cookie imediatamente. 35
Assim como ocorre com todo cabeçalho, a adição de cookies na resposta do Servlet deve ocorrer antes que o conteúdo da resposta comece a ser escrito no Stream de saída.
8.4 Recuperando Cookies Cookies podem ser extraídos das requisições utilizando o método getCookies() da classe HttpServletRequest. O método retorna um array de objetos da classe Cookie, ou null caso não haja nenhum. Não existe um método que recupere um cookie específico a partir do nome, ficando para o desenvolvedor interar pelo array retornado em busca do cookie desejado.
8.5 Exercícios
Utilizando como base o Servlet de login do item 7.6.2, utilize um cookie para persistir as informações de login, evitando que o usuário precise digitar suas credenciais novamente
36
9. SESSÕES Além de cookies, podemos utilizar Sessões, ou Sessions, para persistir informações de estado entre requisições de um mesmo cliente. A API de Servlets possui um módulo de Gerenciamento de sessões de usuário onde podemos encontrar a classe HttpSession que possui métodos para manipulação de sessões.
9.1 Funcionamento É criado, no primeiro acesso, um identificador de sessão, ao qual é associado um objeto da classe javax.servlet.http.HttpSession na memória do container. O container, por sua vez, encarrega-se de carregar este identificador para todas as requisições oriundas daquele usuário. Para isso, utiliza-se 1) Cookies ou 2) URLs reescritas (informações adicionadas ao caminho) caso a opção (1) esteja disponível (usuário desabilitou os cookies, por exemplo). Com o identificador em mãos, a API disponibiliza o objeto HttpSession automaticamente para o Servlet. O Servlet usa o objeto HttpSession em memória para armazenar informações associadas a um usuário específico que acessa a aplicação. Essas informações estarão disponíveis em todas as requisições posteriores a todos os Servlets que compartilham o mesmo contexto. Para obter uma referência ao objeto da classe HttpSession, um Servlet deve utilizar o método getSession() da classe HttpServletRequest. Olhando a documentação, é possível verificar que existem duas sobrecargas:
HttpSession getSession(boolean create) – Retorna a HttpSession associada a requisição ou, se não houver uma sessão e create for true, retorna uma nova sessão, caso contrário, retorna null
HttpSession getSession() – Equivale a getSession(true)
O trecho a seguir obtém o objeto de sessão, se houver, ou cria um novo, caso ainda não exista: HttpSession session = request.getSession(); if (session.isNew()) { // Acabou de ser criada getServletContext().log("ID: " + session.getId()); // Inicializa session } else { // Recupera dados da session }
O método isNew() é utilizado para determinar se o identificador de sessão já foi enviado para o browser cliente. Caso seja uma sessão recém-criada, nós efetuamos a inicialização da mesma com dados que gostaríamos de recuperar em requisições futuras (usuário, senha, etc). Além deste método, a classe HttpSession oferece vários outros métodos. Entre eles estão:
void setAttribute(String attributeName, Object attributeValue)
Object getAttribute(String attributeName)
void removeAttribute(String attributeName)
Enumeration getAttributeNames()
Estes métodos permitem que o desenvolvedor controle os objetos vinculados à sessão do usuário de maneira similar ao funcionamento de um HashMap: sessao.setAttribute("Contador", new Integer(contador)); ... Integer contador = (Integer) sessao.getAttribute("Contador");
37
9.2 Exercícios
Substitua o uso de cookies por sessões no exercício do item 8.5
9.3 Validade da Sessão É possível definir um tempo máximo de inatividade que, se ultrapassado, faz com que a sessão expire – um recurso bastante utilizado pelos sites de Internet Banking para aumentar a segurança do usuário. Para que a sessão expire, basta que o cliente não execute nenhuma requisição durante o tempo limite. Quando isso ocorre, o usuário precisa de uma nova sessão válida, geralmente obtida por meio de um novo login. O desenvolvedor também pode revogar a validade de uma sessão manualmente. É este o caso em lugares onde o usuário efetua logout. Para controlar a validade da sessão, utilizamos os seguintes métodos da classe HttpSession:
int getMaxInactiveInterval() – obtêm o intervalo máximo de inatividade em segundos
void setMaxInactiveInterval(int interval) – atribui o intervalo máximo de inatividade
invalidate() – invalida sessão manualmente
9.4 Exercícios
Personalize a página de boas-vindas do exercício no item 9.2 mostrando o nome do usuário autenticado e acrescente um botão de logout
38
10. CICLO DE VIDA DA SERVLET O ciclo de vida de um Servlet é composto de 3 fases: inicialização, atendimento de requisições e finalização.
10.1 Inicialização Processo onde o Servlet é carregado pelo Servlet Container. O instante em que a inicialização ocorre depende do campo loadOnStartup na annotation @WebServlet: caso o valor seja um inteiro positivo, o Servlet é inicializado automaticamente pelo Container quando a aplicação é iniciada, começando pelos menores valores. Caso contrário, a inicialização ocorre quando é recebida a primeira requisição a ser mapeada para o Servlet. Exemplo de annotation: @WebServlet(urlPatterns = "/MeuServlet", loadOnStartup = 1)
Na inicialização, o Servlet Container executa o método init() do Servlet, dando chance ao Servlet de executar quaisquer passos necessários para sua inicialização, tais como:
Leitura de parâmetros de configuração
Inicialização de variáveis de classe (variáveis estáticas)
Inicialização de conexões ao banco de dados, etc
Ao sobrescrever o init() em seu Servlet, utilize o método getServletConfig() para obter uma instância da classe ServletConfig, a qual guarda os parâmetros de inicialização do Servlet. Em seguida, invoque o método getInitParameter() do ServletConfig para obter o parâmetro desejado. public void init() throws ServletException { super.init(); ServletConfig config = getServletConfig(); String smtp = config.getInitParameter("Email.SMTP"); if (smtp != null) { // Logica de inicialização } }
Também é possível obter um enumerador contendo o nome de todos os parâmetros do Servlet Config por meio de uma chamada ao método getInitParameterNames(). O Servlet somente poderá receber requisições após a conclusão de seu processo de inicialização. Para indicar que houve um problema durante a inicialização, o desenvolvedor deve lançar uma exception da classe ServletException ou UnavailableExcetion. Nestes casos, o Servlet Container deixará o Servlet em estado inativo, incapaz de receber requisições. Observando o construtor da UnavailableException, pode-se notar que uma das sobrecargas possui um parâmetro do tipo int. Este parâmetro recebe a estimativa, em segundos, de quanto tempo o Servlet deverá ficar inativo.
10.2 Atendendo as Requisições Após o término bem-sucedido do método init(), o Servlet é marcado como ativo pelo Servlet Container e está pronto para receber requisições. Na classe GenericServlet, utilizada para Servlets genéricos e superclasse da HttpServet, o método service() centraliza o processamento de requisições. A cada nova requisição recebida, o Servlet Container faz uma chamada ao método service() 39
passando como parâmetros um objeto que encapsula a requisição feita pelo cliente e outro objeto que encapsula a resposta que deverá ser encaminhada ao cliente. Felizmente, como vimos no item 7.4, a classe HttpServlet já cuida de encaminhar a requisição para o método relevante: doGet(), doPost(), etc.
10.3 Finalização O Servlet é finalizado quando o servidor é finalizado ou quando a aplicação se torna inativa pelo Servlet Container. A imagem abaixo traz uma ilustração do ciclo de vida nos vários estágios de uma aplicação.
A finalização de um Servlet deve ser tratada através da implementação do método destroy(). No instante em que o Servlet inicia a sua finalização, o método destroy() é chamado, permitindo a execução de rotinas de finalização como: Encerramento de conexões com bancos de dados, Finalização de threads que tenham sido lançados, etc. Exemplo: public void destroy() { super.destroy(); // Logica de finalização }
10.4 Servlet Context O objeto ServletContext contém os atributos e informações sobre o contexto em que o Servlet está sendo executado e contém métodos úteis para Servlets que desejam se comunicar com o Servlet Container para, por exemplo, escrever num arquivo de log. Existe um ServletContext por aplicação web por JVM e, portanto, todos os Servlets de uma mesma aplicação compartilham o mesmo ServletContext. O contexto é retornado a partir da chamada do método getServletContext() presente nas Servlets. 40
A classe define métodos para recuperar os parâmetros iniciais do contexto definidos no Deployment Descriptor: getInitParameterNames() e getInitParameter(). É importante notar que existem diferenças entre os parâmetros iniciais do contexto e os parâmetros iniciais do Servlet. O objeto ServletContext também possui métodos que podem ser usados para atribuir e recuperar atributos compartilhados por todos os Servlets do contexto:
Object getAttribute(String name)
Enumeration getAttributeNames()
void removeAttribute(String name)
void setAttribute(String name, Object value)
O funcionamento destes métodos é análogo aos métodos estudados no item 9.1. Um recurso bastante utilizado do ServletContext e presente em vários exemplos nesta apostila é o método log(). Ele permite que você adicione mensagens em um arquivo de log do Servidor de Aplicações. Você poderá utilizar esse método para depurar seus Servlets, gerar alertas de problemas na sua execução etc. No Tomcat, as mensagens geradas por meio do método log() são adicionadas a um arquivo de log chamado localhost.<aaaa-mm-ddd>.log , onde o bloco < > é substituído pela data da geração do log.
10.5 Exercícios
Implemente os métodos init() e destroy() da Servlet do exercício no item 9.4: o O init() deverá ler o arquivo que contém os dados do usuário e senha e inseri-los num array de usuários para evitar operações de leitura de arquivo em toda requisição. Além disso, também deverá abrir um arquivo para escrita e armazenar a instância num campo do Servlet. o O destroy() deverá fechar o arquivo que foi aberto para escrita durante o init()
41
11. VISÃO GERAL SOBRE JSP A tecnologia Java Server Pages, também conhecida com JSP, facilita a criação de conteúdo web que contenha partes estáticas e dinâmicas. O JSP disponibiliza todas as capacidades dinâmicas da tecnologia de Servlets, mas possui uma abordagem mais natural para a criação de conteúdo estático. A principal motivação para o uso da tecnologia JSP é evitar a formatação de saídas de texto em Servlets, concentrando no Servlet a lógica de controle e mantendo a responsabilidade sobre formatação de conteúdo em outro arquivo. Além de facilitar a manutenção, conseguimos paralelizar o desenvolvimento em duas frentes.
11.1 O que é uma página JSP? Uma página JSP é um documento texto que mescla dois tipos de texto: dados estáticos, que podem ser expressos em qualquer formato baseado em texto (como HTML, SVG, XML, etc) e elementos JSP, utilizados para a geração de conteúdo dinâmico. As páginas JSP podem ser reconhecidas pela sua extensão “.jsp” e podem ser encontradas na pasta “WebContent” do projeto Java Web. Os elementos dinâmicos que disponibilizados pelo JSP são: Elementos
Sintaxe
Comentários
<%--.. --%>
Declarações
<%! ..%>
Diretivas
Expressões
<%@ include .. %> <%@ page .. %> <%@ taglib .. %> <%= ..%>
Scriptlets
<% ..%>
11.2 Diretivas Diretivas são elementos que encaminham mensagens para o Container JSP e afetam a compilação da página JSP. As diretivas não aparecem no XML de saída. Formato
Exemplo
<%@ diretiva nomeAtributo1 = “valorAtributo1” nomeAtributo2 = ”valorAtributo2” ... %>
<%@ page language = "java" contentType = "text/html" pageEncoding = "ISO-8859-1" %>
Nota: Cada diretiva possui seu próprio conjunto de atributos específicos. Existem três diretivas: page, include e taglib (não contemplada nesta apostila)
page – Define propriedades relacionadas ao formato e linguagem da página atual. Este elemento precisa ser filho direto do nó raiz. Alguns dos atributos são: language, contentType, pageEncoding, import, errorPage e isErrorPage
include – Utilizando para inserir o conteúdo de outro arquivo (seja texto estático ou outra página JSP) no documento atual. Este elemento pode ser colocado em qualquer lugar do documento e seu possui apenas o atributo file, que deve conter o caminho para o arquivo a ser incluído. O funcionamento é similar ao da diretiva <include> do C / C++
42
11.2.1 Diretiva page O atributo info é usado para armazenar texto informativo sobre a página e podemos obter o conteúdo utilizando o método getServletInfo() do Servlet. Exemplo: <%@ page info="Autor: Fanta" %>
O atributo contentType informa o tipo de conteúdo gerado pelo documento JSP. O propósito é permitir que o browser consiga interpretar a página corretamente. Exemplo: <%@ page contentType="text/html" %>
O atributo pageEncoding informa a codificação do texto utilizado na página. Sem esse atributo, o conteúdo pode ser exibido incorretamente no browser. <%@ page pageEncoding="ISO-8859-1" %>
O atributo import funciona de maneira análoga ao import do Java. Todos os pacotes utilizados no documento JSP devem ser declarados utilizando este atributo. Exemplo: <%@ page import="java.util.*" %>
O atributo errorPage é usado para indicar a página JSP que deverá ser exibida caso ocorra algum erro durante o processamento da página que contém este elemento. Para habilitar esse comportamento, a página que fará o tratamento de erros deverá declarar a diretiva page com o atributo isErrorPage = true.
11.2.2 Diretiva include A diretiva include nos permite compartilhar o conteúdo comum a várias páginas JSP, seja ele dinâmico ou estático, facilitando a manutenção das mesmas. Pode-se, por exemplo, construir novas páginas JSP que incluem os arquivos cabecalho.jsp e rodape.html. Caso seja necessário mudar o cabeçalho ou rodapé, não precisaremos alterar todas as páginas, apenas estas duas. Exemplo: <%@ include file=“cabecalho.jsp” %> <!-- Conteúdo --> <%@ include file=“rodape.html” %>
11.3 Expressões Expressões utilizam a sintaxe <%= ..%> para imprimir o resultado de um comando Java durante a geração do conteúdo. Um trecho de código que imprime o IP do cliente que iniciou a requisição seria: <p>O seu endereço IP é "<%= request.getRemoteAddr()%>"<p>
Importante: O resultado da expressão deve ser do tipo String. Algumas alternativas são utilizar o método toString() do objeto ou o método estático String.format()
11.4 Exercícios
Utilize elementos dinâmicos para melhorar o exercício no item 10.5: 43
o Crie uma página JSP de cabeçalho que imprima o nome do usuário autenticado o Crie uma página JSP de rodapé que imprima a data atual (dd/mm/aaaa) o Transfira a geração de HTML para uma página JSP
Dica: Na Servlet de autenticação, utilize o método sendRedirect() para enviar o usuário para a página .JSP de boas-vindas
11.5 Scriptlets Scriptlets permitem a inserção de um bloco de código Java diretamente no corpo da página JSP e possuem o seguinte formato: Formato
Exemplo
<!-- HTML --> <% /* Bloco Java */ %> <!-- HTML -->
<p><% String arg = request.getParameter("r"); if (arg != null && arg.equals("42")) { out.println("Found!"); } else { out.println("Nothing here"); } out.flush(); %></p>
11.6 Objetos Implícitos Para facilitar o trabalho do desenvolvedor, alguns objetos são disponibilizados pelo JSP Container sem que seja necessária a sua declaração (como o objeto request do exemplo anterior). Os mais comuns são: Objeto
Classe
Função
request
HttpServletRequest
Encapsula a request enviada à página
response
HttpServletResponse
Encapsula a response que será devolvida ao cliente
out
JspWriter
Utilizada para escrever na Stream de saída
session
HttpSession
Encapsula a sessão associada à request
config
ServletConfig
ServletConfig associado à página
application
ServletContext
Contexto da Servlet
11.7 Declarações O elemento de declaração é usado para definir variáveis e métodos que deverão ser acrescentados à definição do Servlet JSP. Membros declarados num elemento de declaração ficam disponíveis para uso nas Scriptlets e Expressões, assim como os objetos implícitos. Formato
Exemplo
<%! /* Declaração */ %>
<%! public void erro(String msg) { getServletContext().log("ERRO: " + msg); } %> ... <%
44
if (request.getParameter("url") == null) { erro("Parâmetro 'url' não encontrado"); } %>
As declarações também podem ser usadas para a declaração dos métodos public void jspInit() e public void jspDestroy(), usados para inicializar a página JSP (ler dados de configuração, por exemplo) e liberar recursos, respectivamente.
45