Escolar Documentos
Profissional Documentos
Cultura Documentos
Arte
Diagramação - Romulo Araujo
Capa - Antonio Xavier
Produção
Gerência de Marketing - Kaline Dolabella
Atendimento ao leitor
A DevMedia possui uma Central de Atendimento on-line, onde você pode tirar suas dúvidas sobre
serviços, enviar críticas e sugestões e falar com um de nossos atendentes. Através da nossa central
também é possível alterar dados cadastrais, consultar o status de assinaturas e conferir a data de envio
de suas revistas. Acesse www.devmedia.com.br/central, ou se preferir entre em contato conosco através
do telefone 21 3382-5025.
Edições anteriores
Adquira as edições anteriores da revista Java Magazine ou de qualquer outra publicação do Grupo
DevMedia de forma prática e segura, em
www.devmedia.com.br/anteriores.
Publicidade
publicidade@devmedia.com.br
21 3382-5025
Anúncios - Anunciando nas publicações e nos sites do Grupo DevMedia, você divulga sua marca ou
produto para mais de 100 mil desenvolvedores de todo o Brasil, em mais de 200 cidades. Solicite nossos
Media Kits, com detalhes sobre preços e formatos de anúncios.
Java, o logotipo da xícara de café Java e todas as marcas e logotipos baseados em ou referentes a Java
são marcas comerciais ou marcas registradas da Sun Microsystems, Inc. nos Estados Unidos e em outros
países.
ÍNDICE
[Core, Web]
JBoss Application Server 5
As principais novidades do JBoss AS 5
Bruno Rossetto Machado e
João Paulo Viragine
[Web, Tutorial]
Spring Security
Segurança simples, poderosa e portável - à moda do SpringFramework
Michel Zanini
[Vanguarda, Core]
Novos tempos: javax.time
A nova API de Data e Hora, que facilita a programação e corrige falhas históricas das suas antecessoras
Daniel Cicero Amadei e Michael Nascimento Santos
[Mobile]
Java ME Platform SDK 3
As novidades do novo SDK para Java ME, que substitui o antigo Wireless Toolkit
Ernandes Mourão Júnior
[Boas Práticas]
Estratégias de Integração de Aplicações Java EE
Melhores práticas de interoperabilidade Java para o desenvolvimento de aplicações robustas
Marco Aurélio de Souza Mendes
[Core, Expert]
Estratégias de Integração de Aplicações Java EE
Desenvolvendo aplicações concorrentes estáveis e escaláveis com a java.util.concurrent
Ronaldo Blanch Rocha
Um pouco de Groovy
Marcelo Castellani
Marcelo Castellani (castellani@itautec.com) é analista de sistemas sênior na Itautec S/A e atua na área
de desenvolvimento desde 1996, passando por linguagens como Visual Basic, C, C++ e Java. Mantém o
blog hype quino (www.hypequino.com) aonde fala de novas tecnologias em matérias curtas.
Apresenta uma introdução a linguagem dinâmica Groovy, que possibilita ter uma produtividade de
linguagens dinâmicas como Python e Ruby dentro da máquina virtual Java, sem emendas.
Tornar o desenvolvimento em Java mais rápido e produtivo, além de apresentar novos conceitos, como
closures, não disponíveis na linguagem Java, que podem facilitar a vida do desenvolvedor em tarefas
corriqueiras.
Em que situação o tema é útil: Para agilizar o desenvolvimento de aplicações Java, otimizando situações
onde a mesma é muito verbosa, como manipulação de XML por exemplo. Groovy possibilita, através de
mecanismos como closures, uma sintaxe mais limpa e produtividade em alto nível.
Um pouco de Groovy:
É possível programar para a plataforma Java sem usar a linguagem Java, e ainda por cima ser
extremamente produtivo. Groovy é uma linguagem dinâmica, flexível e que se integra com Java sem
emendas, além de fornecer recursos como closures e atribute accessors, não disponíveis na linguagem
padrão da JVM. Groovy, ao contrario de outras linguagens dinâmicas, possibilita gerar bytecodes Java e
possibilita o uso de toda a infra-estrutura já desenvolvida para suas aplicações.
Introdução
Linguagens dinâmicas estão na moda já há algum tempo. Desde o advento de frameworks de alta
produtividade como o Ruby on Rails e o Django, linguagens como Ruby e Python saíram de seus nichos e
passaram a fazer parte das rodinhas de conversa de desenvolvedores Java, outrora um tanto quanto
seletivos. Esses, então, descobriram um admirável novo mundo, com closures, tipos de dados complexos
e facilidades que não existem na sua linguagem preferida. Foi mais ou menos nesse meio que surgiu o
embrião do que viria a ser o Groovy.
No dia 29 de agosto de 2003 James Strachan publicou em seu blog o primeiro artigo sobre aquilo que
viria a ser o Groovy (veja em Links o endereço do post). Ele deixava bem claro as suas intenções na
época: “minha idéia inicial é fazer uma pequena linguagem dinâmica, que seja compilada diretamente em
classes Java e que tenha toda a produtividade elegante encontrada em Ruby e Python, mas que permita
reusar, estender, implementar e testar código Java já existente”. James procurava uma linguagem
dinâmica para desenvolver em plataforma Java, e em seu post ele deixava claro que as opções da época
não eram interessantes. Ele não queria apenas uma linguagem dinâmica, mas sim algo que pudesse ser
integrado ao que ele já tinha pronto em Java, algo que acelerasse seu desenvolvimento e que não o
obrigasse a jogar tudo o que tinha de código Java já pronto e testado (e em produção) no lixo. Enfim, ele
queria algo que não existia na época. Mas como querer é poder, James uniu-se a Bob McWhirter e juntos
fundaram o projeto Groovy em 2003.
Logo, com um grupo de pessoas que compartilhavam da mesma idéia, iniciaram o desenvolvimento da
linguagem. Foi em 2004, com a fundação do GroovyOne e a entrada de outros desenvolvedores (entre
eles Guillaume Laforge – hoje o mantenedor do projeto) que a coisa decolou. Foi criada a Groovy
Language Specification (GLS) e o kit para testes de compatibilidade (o TCK), além do parser básico da
linguagem. O embrião do projeto estava pronto e a partir daí não teria mais como voltar atrás.Groovy
evoluiu desconhecido por algum tempo, e até dezembro de 2007 várias versões foram lançadas sob o
número 1.1.x. Em 7 de dezembro de 2007 a versão final da família 1.1 foi lançada, e então nomeada
Groovy 1.5 devido às diversas modificações realizadas na mesma. Hoje a linguagem é uma especificação
do JCP (JSR 241) e é considerada a segunda linguagem oficial da plataforma.Ao contrario do que alguns
pensam, Groovy não é um concorrente do Java, mas uma ferramenta de apoio, como veremos neste
artigo.
O que é Groovy?
O web site oficial da linguagem possui uma das melhores definições sobre a linguagem, a qual reproduzo
a seguir: “Groovy é uma linguagem ágil e dinâmica para a Plataforma Java com recursos que são
inspirados em linguagens como Python, Ruby e Smalltalk, tornando-os disponíveis aos programadores
Java, usando uma sintaxe mais próxima do Java”. Ou seja, Groovy possui uma sintaxe semelhante ao
Java, mas com o poder de linguagens dinamicamente tipadas. Mas Groovy não é apenas uma linguagem
de script. Groovy pode ser compilada, gerando bytecodes Java, ou seja, um arquivo de código fonte
Groovy pode virar um arquivo .class (e esse recurso você não encontra por exemplo no JRuby). Isso
garante que a única coisa que você precisa para rodar seus códigos Groovy no ambiente de produção é a
máquina virtual Java (ou seja, nada mais do que usualmente já precisaria) e o jar com o runtime e API`s
do Groovy. É comum dizer que Groovy roda integrado com o Java sem emendas, o que não acontece com
seus “concorrentes”, por assim dizer.
Mais do que isso, o Groovy é totalmente integrado ao Java no mais baixo nível. Por exemplo, se você
instanciar um objeto do tipo Date em Groovy esse nada mais é do que uma instância de java.util.Date. E
tudo funciona de maneira transparente por que, por debaixo dos panos, tudo é bytecode Java.
Aí você me pergunta “ok, então pra que eu preciso disso?”, e a resposta é simples: para facilitar sua vida.
Groovy possui uma sintaxe mais simples e enxuta do que o Java, apesar de ainda parecer Java, e possui
recursos poderosos que não são encontrados na linguagem Java, como closures.E Groovy possui um
grande aliado, o Grails, um framework de produtividade baseado em Spring e Hibernate que permite o
desenvolvimento de aplicações Java EE com a mesma agilidade do pessoal do Ruby on Rails (e sem
configurar centenas de arquivos XML!).
Outro ponto importante é que ambos, Groovy e Grails, são hoje propriedades da SpringSource, empresa
que mantém o framework Spring. A empresa adquiriu a G2One, que cuidava de ambos, em 11 de
novembro de 2008 e em seu site (novamente na seção Links) é possível ver uma nota interessante sobre
o assunto.
Como qualquer outra linguagem, para desenvolver em Groovy você precisa de um editor de textos, de
um interpretador/compilador e outras ferramentas. Felizmente o NetBeans nos fornece tudo o que
precisamos (e até mais) num único pacote.O NetBeans é uma excelente IDE mantida pela Sun e
desenvolvida por uma grande comunidade ao redor do mundo. Ela possui suporte nativo a Java, C/C++,
PHP, JavaScript, Ruby e Groovy.
Para este artigo vou usar a versão 6.5 da IDE rodando no Mac OS X versão 10.5.6. Você pode baixar
gratuitamente o NetBeans de seu site (veja o endereço no quadro Links). Caso deseje usar o Eclipse (que
também suporta Groovy via plugins) ou outra IDE para testar os códigos deste artigo, ou mesmo um
editor de textos simples como o GEdit do Gnome ou o Notepad do Windows, você deverá configurar o
ambiente. Para isso recomendo que acesse a página do projeto na internet. Porém, caso ainda não use o
NetBeans, dê uma chance a ele. Essa é a oportunidade de conhecer duas ferramentas novas e de alta
qualidade ao mesmo tempo.
Alô Mundo
Vamos começar nossa viagem pelo admirável mundo novo do Groovy com um exemplo extremamente
simples. Sou fã do tradicional Alô mundo, apesar de ser um exemplo simples para iniciar numa
linguagem. No nosso caso ele será suficiente para apresentar muitos recursos, como attribute accessors,
como usar Groovy dentro do Java e muito mais.
Abra o NetBeans (ou o ambiente de sua escolha) e crie uma nova aplicação Java. Você verá uma
categoria Groovy na janela de Novo Projeto, mas ela tem apenas um template para a criação de um
projeto Grails. Como este não é o escopo deste artigo vamos ignorar essa categoria de projetos. Para isso
use a categoria Java, como na Figura 1. Clique no botão Próximo e então informe um nome para seu
aplicativo (eu usei o singelo nome “AloMundoGroovy”) e selecione onde o deseja salvar. Clique em
Finalizar e seu aplicativo será criado. Até agora não colocamos nem um pouco de Groovy no projeto, ele
é um tradicional e simples projeto de aplicação Java que você já conhece.
Vamos adicionar então o Groovy ao projeto. Para isso clique com o botão de atalho do mouse sobre o
projeto e selecione o menu Propriedades. Na janela que será aberta selecione o item Groovy na lista de
categorias e marque a opção Ativar Groovy, como na Figura 2.
Por padrão o NetBeans criará um arquivo .java no nosso projeto. Remova-o pois não o usaremos, e então
crie um pacote chamado org.groovy.maonamassa para adicionarmos os códigos-fonte de nosso projeto.
Dentro desse pacote adicione um novo JFrame e, na janela de design do NetBeans, adicione uma caixa
de texto ao JFrame, como na Figura 4.
Agora vamos adicionar um arquivo .groovy em nosso projeto. Para isso clique sobre o pacote criado e
clique com o botão de atalho do mouse. Selecione então a opção Novo>Outro, e na janela que será
aberta, na categoria Groovy, selecione Classe do Groovy. Nomeie-a como considerar mais interessante,
sem a necessidade de ser o nome da classe principal, pois Groovy não faz essa diferenciação como o
Java.
Vamos adicionar o código à nossa classe Groovy. Para isso, digite o código da Listagem 1.
package org.groovy.maonamassa
class AloMundoGroovy {
def saudacao = "Alô mundo, em Groovy !!!!"
}
Perceba que não usamos ponto-e-vírgula no final de nossa declaração, e que usamos a palavra reservada
def na criação de nosso objeto (sim, objeto, da classe Object). O restante deve ser conhecido para
programadores Java. Nossa classe Groovy está pronta para uso. Basta editarmos o código de nosso
JFrame para colocar tudo em funcionamento. O código do JFrame deverá ficar como apresentado na
Listagem 2.
package org.groovy.maonamassa;
jTextField1 =
new javax.swing.JTextField();
setDefaultCloseOperation(javax.swing.
WindowConstants.EXIT_ON_CLOSE);
getContentPane().add(jTextField1,
java.awt.BorderLayout.CENTER);
pack();
}//
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
java.awt.EventQueue.invokeLater
(new Runnable() {
public void run() {
new MostrarJFrame().setVisible(true);
}
});
}
Repare na linha em negrito, é nela que chamamos o método getSaudacao() de nossa classe Groovy. Se
você olhou o código da Listagem 1 e não achou esse método não se preocupe, ele é criado através de um
recurso comum no Groovy e no Ruby, que é chamado attribute accessor. Esse recurso cria os getters e
setters para propriedades de uma maneira simples e eficaz, sem excesso de código. Isso acontecerá
sempre que você definir um membro de sua classe sem especificar a visibilidade (protected, public ou
private): o Groovy o considera como uma propriedade e define automaticamente os getters e setters.
A mesma classe da Listagem 1, escrita em Java, teria o dobro do tamanho. Veja na Listagem 3.
return saudacao;
}
this.saudacao = saudacao;
}
}
Note que parte de nosso projeto foi feito em Java (para tirar proveito do editor visual do IDE), mas uma
parte foi feita em Groovy (e isso deixou as coisas mais simples - uma linha ao invés de sete), e ambos
funcionam bem juntos, como é possível ver na Figura 5.
O leitor mais atento deve ter reparado no .toString() junto ao nosso método getSaudacao(), mostrado na
Listagem 2, o que mostra que tanto o getter como o setter de saudacao trabalham com um Object.
Isso se deve ao fato de que não definimos o tipo de nosso objeto no momento da sua criação com o def.
Listagem 4 e remova o .toString() da Listagem 2. Verifique que agora ambos os métodos getter e setter
trabalham com String e não com Object. É possível ser menos verboso ainda e deixar de utilizar o def,
pois ele pode ser omitido quando especificamos o tipo do objeto.
package org.groovy.maonamassa
class AloMundoGroovy {
// aqui você pode omitir o def
def String saudacao = "Alô mundo,
em Groovy !!!!"
}
GDK
Assim como o Java possui a JDK, o Groovy possui a GDK. É fácil confundir-se quando pensamos nisso
pois todo objeto Java pode ser usado dentro do Groovy, mas o contrario não é verdade. A GDK estende a
JDK adicionando métodos que não existem originalmente nos objetos Java.
Um exemplo seria como obter o tamanho de uma determinada instancia de uma classe.
Em Java isso é bem confuso pois existem objetos que usam o método length() (como os que são
instancias de arrays e Strings), outros usam o método size() (como os que são instancias de Collections e
Maps). Temos até um getLength()(para java.lang.reflect.Array) e o método groupCount(), para instancias
da classe Matcher.
Groovy, através de um dispositivo chamado MetaClass, possibilita que todos esses objetos utilizem um
mesmo método para obter o tamanho de um objeto: o size(). Isso possibilita termos o código da
Listagem 5, onde temos tanto um ArrayList quanto uma String usando o mesmo método, o size().
class TestaSize {
meuArray.add(teste)
println teste.size()
println meuArray.size()
Outra vantagem do GDK sobre a biblioteca padrão do Java é que Groovy não diferencia entre tipos
primitivos e objetos. Em Groovy a expressão valor01 + valor02, considerando valor01 e valor02 do tipo
java.lang.Integer, retorna a soma dos dois inteiros. Mas o Groovy vai além, definindo uma equivalência
entre operadores e métodos: no caso, a expressão acima será interpretada como valor01.plus(valor02).
Se você quiser criar novas classes com suporte a operadores, basta definir nela o método que será
chamado para cada operação (para + o método é o plus(), para - o método é o minus(), para ++ é
next() e por aí vai - consulte a documentação do Groovy para a lista de métodos e operações
correspondentes).
Pense na utilidade prática disso. Quantas vezes você já não teve uma classe em que seria conveniente
usar o operador + para somar uma instância com outra e teve que apelar para métodos mirabolantes
como somaClasse() ou append()? Em Groovy basta definir o método correto e, ao avaliar a operação, o
Groovy procura o método correspondente.
Isso só é possível por que o Groovy usa o conceito de MetaClass apresentado acima. Quando o
compilador encontra uma expressão como valor01 + valor02 num código Groovy, ele gera um bytecode
diferente do que gera quando encontra essa mesma expressão num arquivo de código Java, pois
chamadas de métodos são redirecionadas pelo MetaClass do objeto, o que possibilita ao Groovy
interceptar, adicionar, redirecionar e remover código em tempo de execução. É esse truque que possibilita
valor01 + valor02 tornar-se valor01.plus(valor02) em tempo de execução.
Claro que todo esse poder tem um preço: um código Groovy é ligeiramente mais lento do que um código
Java durante a execução, mas nada que torne proibitivo seu uso. Fazendo uma analogia, num
comparativo com o desenvolvimento para sistemas operacionais, a JVM seria nosso sistema operacional,
e ao desenvolver optaríamos por Java quando precisamos de velocidade, e por Groovy quando
precisamos de facilidades e performance não é um dos requisitos principais. Pode parecer, com todo esse
parágrafo, que Groovy é um elefante pesado e lento, mas Groovy é apenas um pouco mais lento que
Java.
Criei uma classe chamada SomaODobro que, quando tem duas instâncias somadas, realiza a adição e
depois a multiplicação para retornar o resultado. Veja o código.
package org.groovy.maonamassa
class SomaODobro {
class AloMundoGroovy {
valor01.valorInicial = 10
valor02.valorInicial = 20
É importante ressaltar que sim, é possível, em Groovy, ter mais de uma classe dentro de um arquivo de
código fonte como visto acima. As classes podem, inclusive, ser públicas, ao contrário do Java, onde
podemos ter apenas uma classe pública por arquivo. Veja nas Listagens 7 e 8 um exemplo simples.
package groovytest
}
}
package groovytest;
meuCarro.imprime();
minhaMoto.imprime();
Closures
Um dos recursos mais úteis e apaixonantes de linguagens dinâmicas são os closures, ou fechamentos, ou
blocos, que dão um poder de fogo enorme no desenvolvimento. Closures nada mais são do que pedaços
de código tratados como objetos, e como tal podem receber parâmetros e retornar valores. E como a JVM
não sabe se o código que está rodando é Groovy ou Java, é perfeitamente possível dizer que um closure
é apenas mais objeto para a JVM, tal qual uma String ou um Integer. Uma aplicação robusta e poderosa
de closure é na classe File da GDK. O Groovy estende o objeto File padrão do Java e adiciona um método
eachLine(), que recebe como parâmetro um closure:
O closure nada mais é do que o bloco { println linha }, que será executado para cada iteração de
eachLine(), ou seja, essa única linha abrirá um arquivo de texto chamado arquivo.txt, lerá linha por linha
até o final e passará cada linha ao closure, que imprimirá o que recebeu na saída padrão usando o
println() (o it representa a linha recebida). Tudo isso em apenas uma linha de código.
Outro exemplo bem interessante é apresentado a seguir, onde temos um tipo de dado do Groovy (o
Range, que é um intervalo representado com dois números separados por dois pontos simples, como em
1..100, que inclui os números de 1 a 100); e o método each(), que varre cada membro do Range, e
depois executa a closure que o imprime.
(1..10).each { println it }
Ok, mas o que é esse tal de it no meu código? Ele é uma variável conhecida como “Magic variable”, ou “
variável mágica”, algo que não precisa ser declarado pra existir. it pode ser visto como um membro da
classe que define os closures do Groovy, é ele quem recebe o parâmetro padrão quando esse não é
declarado. Veja como fica a opção de uso de uma variável explícita no lugar da variável mágica.
É o sinal de -> que separa a declaração de variáveis do corpo do closure. Mas closures não são úteis
apenas para imprimir um valor na saída padrão, eles podem ser usados para operações complexas, sua
imaginação é o limite.
Vamos criar um exemplo pra demonstrar um pouco do poder e da elegância do uso de closures. Crie um
novo projeto Java e adicione Groovy a ele como visto anteriormente. Adicione uma nova classe Groovy
chamada Aluno e então insira o código da Listagem 9.
class Aluno {
// definimos as propriedades
String nomeAluno
Integer matriculaAluno
// Construtor
Aluno(String nome) {
matricula = matricula + 1
this.setNomeAluno(nome)
this.setMatriculaAluno(matricula)
Nessa classe temos duas propriedades, o nome do aluno e o número de sua matrícula. O nome deverá
ser informado no momento da criação do objeto, através de nosso construtor e o número da matrícula é
sugerido através do uso de um membro estático que é incrementado a cada nova instancia criada
(perceba que esse recurso não deve ser usado em situações reais, mas encaixa como uma luva nesse
exemplo).
Redefinimos também o método toString(), para possibilitar uma impressão simplificada de nossos
valores, quando necessário. Perceba que faço uso dos getters e setters sem os declarar pois, como foi
visto antes, eles são criados automaticamente quando definimos membros de classe sem especificar sua
visibilidade.
Na Listagem 10 instanciamos, em um arquivo main.java três objetos de nossa classe e imprimimos seus
dados, usando o toString().
package groovy2;
System.out.println(aluno01.toString());
System.out.println(aluno02.toString());
System.out.println(aluno03.toString());
Vamos agora criar, na Listagem 11, uma nova classe Groovy chamada Aula. Vamos utilizá-la para agrupar
nossos alunos em aulas.
package groovy2
class Aula {
alunos.add(nomeAluno)
alunos.remove(nomeAluno)
def imprimeListaAlunos() {
package groovy2;
historia.adicionaAluno(aluno01);
historia.adicionaAluno(aluno02);
historia.adicionaAluno(aluno03);
historia.imprimeListaAlunos();
A maior parte das linguagens e recursos que se propõe a enxugar o código o faz às custas de clareza.
Isso não acontece em Groovy: o código continua perfeitamente legível, mesmo com linhas a menos. Em
alguns casos, como no exemplo acima, é até mais fácil ler um código Groovy do que um código Java.
Groovy é extremamente poderosa e pode lhe ajudar muito em seus projetos Java. Ela é particularmente
útil para processamento de texto graças a um poderoso suporte a expressões regulares, para
processamento de listas e de arquivos graças ao uso de closures e principalmente para o processamento
de XML, onde o Java é deveras confuso e prolixo.
Além disso Groovy é a base do Grails, framework de produtividade para o desenvolvimento de aplicações
web que une a poderosa infra-estrutura do Spring e Hibernate à simplicidade das idéias do Ruby on Rails.
Grails possibilita o desenvolvimento de aplicações complexas de maneira rápida e indolor, gerando uma
aplicação web totalmente compatível com Java EE em um arquivo .war.
Para aprender mais sobre Groovy dê uma olhada na página do projeto na web. Existem dúzias de
exemplos e informação relevante para que você torne-se produtivo rapidamente.
Além disso, é interessante dar uma olhada no livro Groovy em ação, escrito por Dierk König junto a
Andrew Glover, Paul King, Guillaume Laforge (o mantenedor do projeto) e Jon Skeet, publicado pela
editora AltaBooks. (O original, Groovy in action, é publicado pela Manning, caso inglês não seja um
problema). Groovy é simples e poderoso e merece sua atenção. O tempo que irá ganhar quando começar
a usá-lo compensará e muito seu aprendizado.
Links
http://radio.weblogs.com/0112098/2003/08/29.html
Post de James Strachan, onde surgiu a idéia sobre o Groovy
http://www.springsource.com/node/836
Página da SpringSource comentando a compra da G2One
http://www.netbeans.org
Site do NetBeans, a única IDE que você precisa
http://grails.codehaus.org
Site do Grails, framework para aplicações web que usa o Groovy como base
http://groovy.codehaus.org
Site do Groovy
Saiba Mais
www.devmedia.com.br/articles/viewcomp.asp?comp=11412
Auditório Virtual DevMedia - Groovy na web com Struts 2
www.devmedia.com.br/cursos/listcurso.asp?curso=55
Curso Online - Introdução ao Groovy
www.devmedia.com.br/articles/viewcomp.asp?comp=8982
Java Magazine 32 - Groovy: Java Através de Scripts
JBoss Application Server 5
Bruno Rosseto
É bacharel em Sistemas de Informacão pela Universidade Mackenzie e atua com desenvolvimento de
software com a plataforma Java há quatro anos. Possui experiênciaem projetos Java EE nas áreas
automotivas, e-commerce,e outros projetos de missão crítica. Participou do projeto ganhador do Duke
s
Choice Awards de 2005 - SIGA Saúde. Atualmente é consultor da Summa Technologies do Brasil.
O JBoss Application Server 5 é resultado de três anos de pesquisas e desenvolvimento, que culminou em
um total redesenho da arquitetura interna do servidor de aplicações.
A interface gráfica do novo JBossAS também foi atualizada, e foi criado o projeto Jopr - uma interface
web para monitoração e controle de toda a infraestrutura JBoss, o que possibilita, entre outras coisas: a
criação de novos datasources, deploy de pacotes, monitoração de pools de conexão, mantendo um
histórico de ações e gráficos para futuras consultas. Dessa forma, ficou muito mais fácil cuidar dos seus
JBosses.
O lançamento do JBoss AS 5 é apenas o começo de uma nova era para os projetos JBoss, uma vez que
outras soluções, como a Plataforma SOA, Portais, etc., poderão usufruir desse robusto e performático
servidor de aplicações.
A versão 5 do JBoss Application Server, que a partir desse momento chamaremos simplesmente AS5, foi
resultado de uma maratona de três anos de pesquisas e desenvolvimento que culminou em um total
redesenho da arquitetura interna do servidor de aplicações.
Essa versão marca o início de uma nova era para o servidor de aplicações mais popular, querido e
utilizado do mundo. Não se trata apenas de uma nova versão do servidor de aplicações, mas de todo um
ambiente em estado da arte para execução da próxima geração de projetos desenvolvidos pela JBoss.
Sobre a JBoss
JBoss, apesar de ser sinônimo de servidor de aplicações, não está restrito apenas a isso. A comunidade
JBoss (www.jboss.org) possui hoje mais de 35 projetos. Entre eles, podemos destacar:
Hibernate - o framework de persistência ORM (Object Relational Mapping) mais utilizado do mundo e que
muito influenciou a especificação de EJB 3.0.
JBoss Seam - o framework que combina o que há de melhor em desenvolvimento Web 2.0 com o novo
modelo de componentes de negócio EJB 3.0, aumentando a produtividade e disponibilizando inúmeros
componentes para facilitar e acelerar o desenvolvimento de aplicações corporativas em Java. O
reconhecimento do poder do JBoss Seam por parte da comunidade deu origem à JSR 299 - Web Beans.
Ver Edição 58.
Além de influenciar o futuro da plataforma Java EE, seja com participações nas JSRs, seja com a
implementação de referência de JSRs, a JBoss conta hoje com a participação de brasileiros, funcionários
da Red Hat Brasil, como principais desenvolvedores em diversos projetos da comunidade JBoss,
dedicando-se em tempo integral a atividades de desenvolvimento e suporte a clientes. Dentre esses
projetos, podemos destacar:
o próprio JBoss AS, o JBoss Rules - motor de regras e BRMS (business rule management system),
JBoss AOP - framework para AOP (programação orientada a aspectos), JBoss SX (framework de
segurança da JBoss), JBoss Profiler (ferramenta de profiling baseada em log), JBoss Messaging (JMS
provider), JBoss ESB (para integração SOA, ver Edição 59) e JBoss Portal (solução para portais
corporativos).
Mais detalhes sobre esses e outros projetos JBoss (como o JBoss jBPM, JBoss Tools, Teiid) podem ser
encontrados no site: www.jboss.org.
Apesar de os projetos JBoss serem conduzidos, em sua maior parte, por funcionários da Red Hat/JBoss, é
inegável a contribuição da comunidade durante todos esses anos de existência da jboss.org. Essa
contribuição é de extrema importância para a sobrevivência e qualidade dos projetos.
A contribuição não é feita apenas com desenvolvimento de código fonte, mas também com participação
em fóruns de discussão, relato de bugs, pedido de novas funcionalidades, elaboração/tradução de
documentação, blogs pessoais, eventos organizados pela comunidade, etc.
Veja os links de referência no final do artigo para saber mais informações sobre como contribuir com a
comunidade JBoss
Novidades
O suporte ao JDK 6 é também uma novidade dessa versão. Apesar de suportar o Java 6 desde a versão
4.2, é na versão 5 que esse suporte foi aprimorado e tornou-se padrão para execução do servidor de
aplicações.
Além da certificação Java EE 5 e do suporte ao Java 6, o destaque dessa versão, sem dúvida nenhuma,
fica a cargo do JBoss Microcontainer. O AS5 faz parte de uma nova geração do servidor de aplicações,
construído com base no novo JBoss Microcontainer.
O JBoss Microcontainer é resultado de uma completa reescrita do JBoss JMX Microkernel (utilizado nas
versões das séries 3.x e 4.x do JBoss AS) e o substitui completamente para suportar a utilização direta
de POJOs e o uso como um projeto independente do servidor de aplicações JBoss, seguindo a tendência e
evolução do desenvolvimento Java EE com a utilização de novos paradigmas como AOP, injeção de
dependência e inversão de controle, e o foco na utilização de POJOs (como EJB 3.0, JPA, Spring, Guice,
entre outros).
O AS5 utiliza o JBoss Microcontainer para fazer a integração dos serviços disponibilizados pelo servidor
de aplicações, entre eles: container Servlet/JSP; container EJB; gerenciador de deploy, entre outros,
disponibilizando, assim, um ambiente Java EE padrão. O JBoss AS não é um servidor de aplicações
monolítico - com um único kernel fornecendo os serviços requeridos pela especificação Java EE - mas
sim, uma coleção de componentes independentes e interconectados, cada um deles com foco em uma
funcionalidade específica requerida pela especificação Java EE. Essa arquitetura flexível possibilita que
novos serviços possam ser facilmente adicionados e os serviços desnecessários possam ser removidos.
Se houver necessidade de um serviço adicional, simplesmente fazemos o deploy do serviço desejado. De
maneira análoga, se não precisamos de um serviço, podemos simplesmente removê-lo. A Figura 1
mostra uma visão geral de como os serviços são associados e disponibilizados pelo Microcontainer. Como
resultado, temos um servidor de aplicações totalmente customizado às nossas necessidades, com o uso
eficaz de recursos do servidor físico (CPU, memória, disco).
A flexibilidade é tamanha, que os serviços construídos com base no JBoss Microcontainer podem ser
utilizados de maneira independente em outros ambientes/servidores de aplicações não JBoss, como o
GlassFish, ou até mesmo o Tomcat. Como exemplo, podemos citar o suporte total ao EJB 3.0 no Tomcat
(bastando para isso, fazermos o deploy do JBoss EJB 3.0 container no Tomcat).
Como o JBoss Microcontainer é extremamente leve, pode ser utilizado para disponibilizar serviços até
mesmo em um ambiente Java ME. Desse modo, abre-se um novo horizonte de possibilidades para
aplicações móveis que passam a usufruir dos serviços “enterprise”, sem a necessidade de utilização de
um ambiente/servidor Java EE completo. O JBoss Microcontainer utiliza o conceito de injeção de
dependências (IoD) para interconectar e disponibilizar os serviços do servidor de aplicações. Além disso,
pode ser utilizado como container de injeção de dependências de propósito geral ao estilo Pico Container
e Spring. Como amplamente consolidada no Java 5, toda configuração do JBoss Microcontainer pode ser
realizada através de anotações ou XML, dependendo do local onde a informação de configuração está
localizada (classes Java ou arquivos de configuração).
Além de tudo isso, o JBoss Microcontainer possui classes de testes utilitárias que estendem o JUnit e
tornam a configuração e a execução de testes unitários uma tarefa extremamente simples, permitindo
que o desenvolvedor acesse POJOs e serviços nas classes de testes com apenas algumas linhas de
código.
Todo o mecanismo de classloader do JBoss AS também foi modificado para utilizar o avançado conceito
do Virtual Deployment Framework (VDF). O VDF foi desenvolvido para abstrair, simplificar e unificar a
manipulação de arquivos pelo servidor de aplicações.
Denominado Virtual File System (VFS) Class Loader, esse novo mecanismo de classloader, além do uso do
VDF para localizar bibliotecas e classes, faz uso extensivo de AOP para “aspectizar” o deploy de
aplicações/serviços no servidor de aplicações.
O VFS faz a análise dos deploys produzindo meta-informações que serão utilizadas pelo JBoss
Microcontainer para instanciar, interconectar e controlar o ciclo de vida e dependência dos deploys. O
JBoss Microcontainer utiliza uma máquina de estados para garantir que toda e qualquer dependência do
deploy seja satisfeita antes de disponibilizar o serviço.
Um grande avanço do AS5 foi a modularização dos serviços internos do servidor de aplicações em
projetos independentes.
Essa modularização não traz impactos diretos para o usuário final, mas faz parte de uma importante
estratégia da JBoss em disponibilizar os vários serviços Java EE como projetos independentes. Assim,
esses serviços podem ser consumidos a la carte em diferentes ambientes, e não apenas no próprio
servidor de aplicações, o que permite grande flexibilidade e liberdade de escolha. Remover serviços é tão
simples quanto adicionar novos.
Muitas das principais funcionalidades do AS5 são providas da integração de vários outros projetos JBoss,
entre eles: JBoss EJB 3.0 - Implementação da última versão da especificação EJB 3.0 - A especificação
de EJB 3.0 faz parte de uma total reestruturação da especificação EJB. E tem por objetivo a simplificação
do desenvolvimento;
JBoss Messaging - Reescrita completa do antigo JBossMQ (que é o provedor JMS padrão no JBoss AS das
séries 4.x).
É uma implementação de JMS de alta performance compatível com a JSR-914, com suporte out-of-the-
box a cluster de filas e tópicos, com tolerância a falhas transparente e um sistema de redistribuição de
mensagens inteligente. É hoje o provedor de mensageria padrão do AS5, além de ser parte integrante da
infraestrutura do JBoss ESB;
JBoss Cache - É a implementação de cache utilizada no AS5. É utilizado principalmente em conjunto com
o JGroups para fornecer uma solução completa de cluster. Entre as diversas características está o buddy
replication: permite que o cache seja replicado apenas para um “buddy” (companheiro) no cluster, evita a
sobrecarga de outros nós no cluster sem necessidade, realiza uma espécie de backup do estado do nó;
JBoss WS - Implementação da pilha de web services compatível com a especificação JAX-WS 2.0/JAX-
RPC.
Além de implementar toda a especificação de Web Services, foi criada uma camada de abstração que
possibilita que se pluguem outras implementações. Por exemplo: podemos utilizar a implementação
nativa do JBoss WS, o Metro (implementação de web services da Sun), ou ainda o CXF (implementação
da Apache), sem qualquer tipo de impacto sobre o JBoss AS. Assim, o usuário tem total liberdade para
escolher a implementação que melhor se adapta às suas necessidades.
No final do mês de março, foi anunciado que os esforços serão focados em uma única implementação:
JBossWS-CXF, ou seja, as próximas versões do JBoss AS virão com o CXF nativo. Aqueles que usam o
JBoss WS nativo não precisam se preocupar, pois a transição será realizada gradativamente e trará
grandes benefícios, principalmente no que diz respeito a soluções SOA;
JBoss Transactions - é o gerenciador de transações padrão no AS5. O JBoss Transactions foi adquirido em
2005 da Arjuna/Hewlett-Packard - um gerenciador de transações extremamente rápido e robusto.
Resultado de mais de 18 anos de experiência dos seus criadores em gerenciamento de transações, foi a
primeira implementação de JTA e JTS do mercado;
JBoss Web - É o container Web/Servlet do AS5, comumente conhecido como "Tomcat on stereoids". Tem
sua implementação baseada no Apache Tomcat 6.0 e inclui suporte a conectores baseados no Apache
Portable Runtime (APR) para alcançar maior performance e escalabilidade, podendo, em alguns casos,
equiparar-se ao Apache HTTP Server ou até superá-lo;
JBoss Security - Além do suporte a JAAS, foi atualizado para suportar mecanismos de autorização
plugáveis: SAML, XACML e SSO.
A Tabela 1 compara os principais serviços utilizados no AS5 e no seu sucessor (JBossAS 4.2.3), para
facilitar a visualização da evolução de versões entre os dois.
O AS5 suporta nativamente POJOs, MBeans e OSGi bundles (com ajuda do Apache Felix). Suportar um
novo modelo de componentes é tão simples quanto implementar uma nova fachada para o JBoss
Microcontainer. Isso permite que o JBoss AS suporte qualquer outro modelo de componentes existente ou
que ainda está por vir; já está preparado, portanto, para o sistema de módulos que será implementado
no Java 7.
Novidades em Cluster
Uma das melhorias no AS5 foi a criação de um novo mecanismo de integração entre o JBoss Cache e o
Hibernate/JPA para a utilização de Second Cache Level (ou cache L2, introduzido no Hibernate 3.3).
Existem basicamente quatro tipos de objetos que podem participar de um Second Cache Level:
entidades, collections, resultados de query e timestamps. Nas versões 4.x, apenas um único cache podia
ser utilizado para armazenar todos os tipos de objetos, causando dificuldades na consulta. Com o AS5
existe um cache diferenciado para cada tipo de objeto, evitando assim sobrecarga e lentidão na busca de
objetos na árvore de cache. Essa versão também permite buddy replication de Stateful Session Beans.
Com buddy replication, é possível diminuir a carga de memória e o tráfego na rede. A Figura 2 mostra
uma arquitetura com seis nós, onde cada nó possui os seus dados e um cache do nó anterior.
Quando ocorre queda de um dos nós, automaticamente o nó que possuía o cache do nó em queda
assume o comando, guardando também o cache do nó anterior, fechando o círculo. A Figura 3 mostra a
queda do servidor nó 1 e o servidor nó 2 assumindo o cache do nó 1 (em queda) e 6.
Listagem 1.
$JBOSS_HOME/server/all/deploy/cluster/jboss-cache-manager.sar
<mbean code="org.jboss.jms.server.destination.
QueueService"
name="jboss.messaging.destination:service=Queue,
name=testDistributedQueue"
xmbean-dd="xmdesc/Queue-xmbean.xml">
<depends optional-attribute-name=
"ServerPeer">jboss.messaging:service=
ServerPeer</depends>
<depends>jboss.messaging:service=PostOffice
</depends>
<attribute name="Clustered">true</attribute>
</mbean>
A configuração do serviço de portas também mudou. Com a utilização de POJOs e AOP no Microcontainer,
as portas agora são injetadas conforme a necessidade da configuração.
O serviço ServiceBindingManager é o responsável por fazer a associação entre as portas e cada um dos
serviços, por exemplo, HTTP na porta 8080, AJP na porta 8009, etc. Na versão 5.0, ocorre uma chamada
ao arquivo bootstrap.xml (Listagem 2), que é o responsável por carregar algumas configurações em
tempo de inicialização. O bootstrap.xml está configurado para utilizar o arquivo de configuração de portas
bindings.xml, o qual já possui algumas configurações pré-definidas que podem ser utilizadas e
customizadas conforme a necessidade do usuário.
Listagem 2.
$JBOSS_HOME/server/$PROFILE/conf/bootstrap.xml
<bootstrap xmlns="urn:jboss:bootstrap:1.0">
<url>bootstrap/vfs.xml</url>
<url>bootstrap/classloader.xml</url>
<url>bootstrap/aop.xml</url>
<url>bootstrap/jmx.xml</url>
<url>bootstrap/deployers.xml</url>
<url>bootstrap/bindings.xml</url>
<url>bootstrap/profile-repository.xml</url>
</bootstrap>
A Listagem 3 mostra a injeção das configurações de portas disponíveis. No caso, estão sendo injetadas as
configurações de PortsDefaultBindings, Ports01Bindings, Ports02Bindings e Ports03Bindings.
<bean name="ServiceBindingStore"
class="org.jboss.services.binding.impl.
PojoServiceBindingStore">
<bean name="PortsDefaultBindings"
class="org.jboss.services.binding.
impl.ServiceBindingSet">
<constructor>
<!-- The name of the set -->
<parameter>ports-default</parameter>
<!-- Default host name -->
<parameter>${jboss.bind.address}</parameter>
<!-- The port offset -->
<parameter>0</parameter>
<!-- Set of bindings to which the
"offset by X" approach cant be applied -->
<parameter><null/></parameter>
</constructor>
</bean>
<bean name="Ports01Bindings"
class="org.jboss.services.binding.impl.
ServiceBindingSet">
<constructor>
<!-- The name of the set -->
<parameter>ports-01</parameter>
<!-- Default host name -->
<parameter>${jboss.bind.address}</parameter>
<!-- The port offset -->
<parameter>100</parameter>
<!-- Set of bindings to which the
"offset by X" approach cant be applied -->
<parameter><null/></parameter>
</constructor>
</bean>
O trecho referente a port offset atua diretamente na configuração de StandardBindings (Listagem 6),
realizando uma somatória no valor de cada porta. No caso, o PortsDefaultBindings irá manter as
configurações conforme estão no arquivo, pois seu port offset está definido com valor 0, porém a
configuração Ports01Bindings irá somar 100 em cada uma das portas. Portanto, para o Ports01Bindings,
a porta 1099 será 1199, a porta 1098 será1198, a porta 8080 será 8180, e assim por diante.
<bean name="StandardBindings"
class="java.util.HashSet"
elementClass="org.jboss.
services.binding.ServiceBindingMetadata">
<constructor>
<parameter>
<set>
<!-- *********************
conf/jboss-service.xml
****************** -->
<!-- Naming Service -->
<bean class="org.jboss.services.
binding.ServiceBindingMetadata">
<property name="serviceName">
jboss:service=Naming</property>
<property name="bindingName">
Port</property>
<property name="port">1099</property>
</bean>
<bean class="org.jboss.services.
binding.ServiceBindingMetadata">
<property name="serviceName">
jboss:service=Naming</property>
<property name="bindingName">
RmiPort</property>
<property name="port">1098</property>
</bean>
.
.
.
</set>
</parameter>
</constructor>
</bean>
$PROFILE é o diretório referente ao profile/configuração, por exemplo: default, all, web, etc.
As portas padrão do JBoss Application Server são definidas no bean StandardBindings, o qual realiza a
associação de cada porta a cada serviço específico. A Listagem 6 mostra um trecho da declaração do
StandardBindings, em que o serviço Naming (JNDI) está sendo associado à porta 1099 e à porta RMI
1098.
Com a utilização do ServiceBindingManager, torna-se mais fácil a utilização de várias instâncias de JBoss
AS no mesmo servidor físico. Desse modo, a configuração de portas é mantida em um único lugar, o que
facilita a manutenção.
Os arquivos que terminam com jboss-beans.xml são utilizados pelo Microcontainer para realizar a injeção
de POJOs. São semelhantes aos -service.xml que eram utilizados nas versões 4.x. A maioria dos serviços
do AS5 já foi convertida para POJOs.
Porém, ainda restam alguns MBeans das versões antigas que utilizam os -service.xml, os quais serão
substituídos em versões futuras.
Para facilitar o entendimento da injeção de POJOs realizada pelo Microcontainer, criamos a classe
PojoServiceExample (Listagem 7).
package jm.exemplos.pojo;
public PojoServiceExample() {
System.out.println("Construtor de PojoServiceExample()");
É uma classe muito simples, com apenas um método construtor que imprime uma mensagem na tela. A
Listagem 8 mostra o arquivo jboss-beans.xml, o qual possui a declaração do POJO com o nome
PojoServiceExample.
<deployment xmlns="urn:jboss:bean-deployer:2.0">
<bean name="PojoServiceExample"
class="jm.exemplos.pojo.PojoServiceExample"/>
</deployment>
Listagem 9.
$JBOSS_HOME/server/$PROFILE/log/server.log
PojoServiceExample to vfszip:/opt/downloads/jboss-5.0.1.GA/server
/default/deploy/jboss5-servico-exemplo.beans
Esse exemplo mostra a forma mais simples de disponibilizar um POJO utilizando o Microcontainer.
Basicamente todos os serviços, módulos e suas dependências são injetados como POJOs e manipulados
dessa forma, sem a necessidade de ter de implementar uma interface ou estender uma classe abstrata.
Instalação
Para a instalação, utilizaremos a versão binária da distribuição do JBoss AS. Para fazer o download do
AS5, basta acessar a página de download (ver Links) e selecionar o arquivo jboss-5.0.1.GA.zip. O site
JBoss.org disponibiliza também versões anteriores e um Release Notes de cada uma das versões.
O único pré-requisito para execução do JBoss AS é uma máquina virtual Java (JRE) 1.5 ou superior
instalada. Na versão 5, não há mais a necessidade de instalação do JDK, pois o AS5 já vem com o ECJ
(compilador do Eclipse JDT), necessário para compilar JSPs.
O AS5 pode ser instalado e executado utilizando uma máquina virtual Java 5 ou 6 em qualquer Sistema
Operacional. Os binários compilados com as duas JDKs estão disponíveis no site www.jboss.org. Apesar
de o número de downloads do binário compilado com Java 6 ser superior e ser considerado uma versão
experimental, a versão com Java 5 é a chamada versão primária e é recomendada para utilização em
produção. Caso queira rodar o AS5 compilado com JDK 5 utilizando JRE 6, quatro bibliotecas devem ser
copiadas do diretório /client para o diretório /lib/endorsed.
São elas:
• jbossws-native-saaj.jar;
• jbossws-native-jaxrpc.jar;
• jbossws-native-jaxws.jar;
• jbossws-native-jaxws-ext.jar.
Porém, utilizando o binário compilado com JDK 6 em uma JRE 6, nenhuma alteração é necessária.O que
muda em geral é o script de inicialização de cada ambiente. Por exemplo: no Windows utilizamos run.bat,
no Unix/Linux, run.sh (para simplificar, omitiremos a extensão a partir deste ponto). O processo de
instalação básica do JBoss AS é extremamente simples: basta descompactá-lo em algum diretório de sua
escolha.
Todo o JBoss AS e sua configuração estão contidos em uma única estrutura de diretório. Então para
desinstalar o JBoss AS basta removermos todo o diretório.
Inicializando o servidor
Após a descompactação do arquivo, iremos iniciar o Application Server. Os scripts de inicialização estão
localizados no diretório /bin; execute o script run. Caso esteja utilizando o Windows, execute o arquivo
run.bat. O prompt de comando deverá exibir algo semelhante à Figura 4.
Os arquivos de inicialização utilizarão por padrão o profile default. Veja o quadro “Explorando a nova
estrutura de diretórios” para entender melhor os profiles existentes no AS5. Com o parâmetro -c, é
possível inicializar diferentes profiles:
# ./run -c allP
Por questões de segurança, o JBoss AS, quando executado, atende às requisições apenas na interface
local (127.0.0.1). Esse comportamento faz com que o servidor não seja acessível remotamente. Para
habilitar o acesso remoto ao servidor de aplicações, basta utilizarmos a opção -b no script de inicialização
do servidor. O comando abaixo fará o JBoss AS executar os serviços em todas as interfaces de rede do
servidor onde está sendo executado.
# run.sh -b 0.0.0.0
A estrutura de diretórios do AS5 é muito semelhante à dos seus antecessores (Figura 5). Iremos detalhar
as principais diferenças entre a estrutura de diretórios de uma versão da série 4.x para a versão 5.
Muitos usuários se perguntam qual profile devem utilizar para executar suas aplicações. A Tabela 2
mostra as principais diferenças entre os cinco profiles.
Tabela 2: Serviços disponíveis em cada profile.
Os profiles mais utilizados nas versões anteriores eram o default e o all. No AS5, além destes, o profile
web deverá ser utilizado em grande escala, substituindo a utilização do Tomcat e jboss-web stand-alone.
Se a aplicação necessita de alta-disponibilidade (HA), utiliza-se o profile all. A maioria dos outros casos
será atendida com o profile default.
Cada profile possui um diretório conf. Assim como nas versões anteriores do JBoss AS, possui as
configurações gerais do servidor de aplicação, como controle transacional, log4j, diretórios de deploy, etc.
O diretório deploy é o local utilizado para publicar aplicações e disponibilizar serviços no JBoss AS.
No AS5, houve a separação dos serviços “deployáveis” com a criação do diretório deployers (Figura 6).
Figura 6: Serviços disponíveis em cada profile.
Assim, não ficam mais misturados no diretório deploy, sendo armazenados no diretório deployers(a
exemplo do jbossweb.deployer).
O diretório lib contém bibliotecas que serão compartilhadas por todos os serviços e aplicações disponíveis
para o profile. Caso queira compartilhar um driver JDBC entre várias aplicações no mesmo profile,
adicione a biblioteca do drive no diretório lib.
Para o profile all, foi criado o diretório cluster, o qual armazena uma total reestruturação dos arquivos de
configuração de cluster. O arquivo cluster-service.xml não existe mais, e seu equivalente é o
$PROFILE/deploy/cluster/jgroups-channelfactory.sar/META-INF/jgroups-channelfactory-stacks.xml.
JBoss AS de cara nova
O design do JMX Console e Web Console também foi atualizado para facilitar a busca de serviços. Agora,
há um menu lateral esquerdo com filtro para facilitar a identificação dos serviços. Ao clicar no link,
apenas os serviços relacionados serão exibidos (Figura 7).
O Web Console também foi modificado, incorporando o design do JMX Console (Figura 8).
Acabando com o mito: O JBoss AS não tem um console unificado de administração, monitoração e
controle, tudo tem de ser feito “à mão”.
Não basta ser um servidor moderno, robusto e performático se a administração não for algo simples e
intuitivo. Pensando nisso, foi criado o projeto Jopr (Figura 9). O nome Jopr (pronuncia-se jopper) foi
escolhido baseado no filme Jogos de Guerra (WarGames), em que havia um super computador chamado
WOPR (Whopper) - War Operation Plan Response - o qual era responsável por responder a todo e
qualquer ataque nuclear inimigo.
Figura 9: Jopr
O propósito desse projeto é disponibilizar ao administrador uma interface Web integrada e intuitiva para a
administração, monitoração, configuração e controle da Plataforma JBoss.
Para completar seu estudo sobre o JBoss AS 5, veja a Apresentação “Mergulhando fundo no JBoss AS 5 e
Jopr”.
Conclusão
Após três anos de pesquisas e desenvolvimento, o JBoss Application Server 5 está pronto para sua
grande estréia.
Com um novo kernel construído a partir do zero, substitui a utilização de MBeans por POJOs, utiliza
extensamente AOP, unifica a manipulação de metadados, altera o mecanismo de classloading, “aspectiza”
os deployers, tudo isso mantendo a compatibilidade com a maioria dos serviços existentes. Essas são
algumas das novidades dessa nova versão.
O lançamento do AS5 é apenas o começo de uma nova era para os projetos JBoss.
A flexibilidade proporcionada pelo Microcontainer permitirá à Red Hat/JBoss, além de inovar, estar up-to-
date com as especificações do JCP.
O objetivo da Red Hat/ JBoss é ter o mais inovador, robusto e performático servidor de aplicações do
mercado.
As possibilidades são infinitas e a Red Hat/JBoss precisa da participação da comunidade para ajudar na
definição do futuro e no desenvolvimento do JBoss AS e de todos os outros projetos. Essa contribuição é
essencial para manter os projetos JBoss como os mais populares, queridos e utilizados do planeta. A
comunidade é o poder do open source. Não seja apenas usuário: participe, teste, reclame, sugira,
contribua.
Links
www.jboss.org
Página principal da JBoss
http://www.jboss.org/community/docs/DOC-9938
Como contribuir com a comunidade JBoss
http://java.sun.com/javaee/overview/compatibility.jsp
Listagem de Servidores de Aplicação compatíveis com Java EE 5.0
http://jcp.org/en/jsr/detail?id=244
Página da especificação Java EE 5.0
http://www.jboss.com/products/platforms/application/standards
Página de padrões suportados pelo JBoss AS
http://www.jboss.org/jbossas/downloads/
Página de download do JBoss AS
http://www.jboss.org/jopr/
Página principal do Jopr
http://felix.apache.org
Página principal do Apache Felix
http://magazine.redhat.com/2008/10/31/interview-chris-morgan-on-jopr/
Entrevista com Chris Morgan sobre Jopr
Livros
JBoss in Action, Javid Jamae e Peter Johnson, Manning Publications, 2009
Nova versão do livro JBoss in Action a qual foca nas principais configurações do JBoss Application Server
5
Saiba Mais
www.devmedia.com.br/articles/viewcomp.asp?comp=10118
Java Magazine 46 - JBoss: Instalação, Arquitetura , Configuração, Tuning e Administração
www.devmedia.com.br/articles/viewcomp.asp?comp=8453
Java Magazine 51 - Portlets com JBoss Portal
www.devmedia.com.br/articles/viewcomp.asp?comp=8495
Java Bagazine 53 - Drools: Regras na Prática
www.devmedia.com.br/articles/viewcomp.asp?comp=9488
Java Magazine 58 - JBoss Seam
www.devmedia.com.br/articles/viewcomp.asp?comp=10202
Java Magazine 59 - JBoss ESB
Spring Security
Michel Zanini
Formado em Ciências da Computação pela Universidade Federal de Santa Catarina (UFSC) e possui as
certificações SCJP, SCWCD, SCBCD e SCDJWS.
O artigo apresenta o projeto Spring Security como uma alternativa na área de segurança à tradicional
especificação Java EE, através de um exemplo prático e realista.
Com o Spring Security é possível criar um mecanismo de autenticação e autorização para sua aplicação
web em questão de minutos. O framework foca em facilitar a implementação dos casos de uso mais
freqüentes, porém oferece valiosos pontos de extensão para requisitos mais complexos. Por fim,
disponibiliza suporte a inúmeros diferentes tipos de autenticação e integração com as mais usadas
tecnologias na área de segurança.
Para qualquer aplicação web que necessite restringir seus recursos para diferentes tipos de usuário,
bem como assegurar que se autentiquem de forma prática e segura.
Spring Security:
O Spring Security surgiu da necessidade de melhorar o suporte à segurança oferecido pela especificação
Java EE. O framework centraliza a configuração em um único XML, dispensando configurações do
container e tornando a aplicação web um arquivo WAR auto contido.
Para começar a utilizá-lo basta adicionar seus JARs ao classpath, configurar um filtro e um listener no
web.xml e criar um application context (XML de configuração). O XML centraliza todas as configurações
de autenticação e autorização. As tags definem quais roles podem acessar cada grupo de URLs. A tag
define a fonte de dados para as informações de usuários (banco de dados, arquivo de propriedades,
LDAP, etc.).
Quando necessário, é possível utilizar os eventos publicados pelo framework a cada sucesso ou falha na
autenticação ou autorização. Ouvir os eventos permite criar complexos casos de gerenciamento de
usuários. O Spring Security ainda oferece integrações com a API de Servlets, taglibs para facilitar a
codificação de JSPs, suporte à HTTPS, segurança em métodos com uso de anotações e suporte a
autenticação com LDAP ou certificados X509.
Com o objetivo de preencher a lacuna deixada pela especificação, em 2003 surgiu o Acegi Security
System for Spring. O Acegi Security é conhecido por ser extremamente configurável e poderoso, porém
difícil de utilizar devido à enorme quantidade de configuração XML necessária. Em 2007 o projeto Acegi
foi incorporado dentro do guarda-chuva de projetos do Spring Framework Portifolio, e então, renomeado
como Spring Security.
Em abril de 2008 a versão 2.0.0 do Spring Security foi lançada tomando como proveito a configuração
baseada em namespaces do Spring 2.0. Hoje, o Spring Security é extremamente fácil de configurar, sem
perder a flexibilidade e o poder do antigo Acegi.
O Spring Security depende de alguns JARs do Spring Framework “core”. Entretanto, não é necessário que
sua aplicação seja construída com o modelo de programação do Spring Framework. Ou seja, uma
aplicação pré-existente que não usa Spring pode passar a utilizar o Spring Security sem grandes
modificações. Para aprender mais sobre o Spring Framework consulte o artigo de capa da Edição 65.
Assim como o Java EE o Spring Security possui uma abordagem declarativa para segurança, baseada em
roles (papéis). A abordagem é declarativa, pois a aplicação não precisa chamar nenhum método para
realizar autenticação ou autorização, tudo é feito através de configuração XML.
Para configurar o Spring Security de forma declarativa, assim como no Java EE, é necessário declarar
quais serão os roles envolvidos, quais os recursos que serão protegidos, e quais roles podem acessar
cada recurso. Além disso, declara-se como a autenticação será feita (basic, digest, form login, LDAP,
etc.).
Este artigo apresenta as características do Spring Security, mostrando alguns recursos importantes não
presentes na especificação Java EE. Exemplos práticos serão construídos, abordando cenários frequentes
que são requisitos de grande parte das aplicações web.
Os conceitos básicos sobre segurança não são abordados no artigo. Entretanto, o artigo de capa da
Edição 22 descreve tais conceitos e demonstra exemplos práticos utilizando a especificação Java EE.
Primeiro exemplo
Como primeiro exemplo vamos criar uma aplicação web simples com duas áreas de acesso restrito: uma
permitida para qualquer usuário autenticado (/usuarios/index.jsp) e outra apenas para usuários
administradores (/admin/index.jsp). As páginas restritas apenas exibem uma mensagem e possuem um
link de retorno à página principal. Espera-se que um login seja solicitado ao acessar qualquer uma destas
áreas. A página inicial da aplicação (/index.jsp), ilustrada na Figura 1, tem acesso livre e possui links
para as duas áreas restritas.
Configurando o web.xml
O Spring Security utiliza-se de um filtro HTTP, declarado no web.xml (Listagem 1), para interceptar todas
as URLs acessadas e conferir suas permissões de acesso.
Por isso, o filtro é aplicado com o url-pattern “barra asterisco”. No Java EE tal filtro não é necessário, pois
o controle de acesso é realizado pelo próprio container.
É importante notar que o nome do filtro é ‘springSecurityFilterChain´ e não deve ser alterado, pois o
Spring Security já espera que o filtro esteja com este nome, por convenção.
No Java EE as configurações de autenticação e autorização são feitas no web.xml. No Spring Security são
feitas em um application context padrão do Spring Framework. Dessa forma, precisamos do listener
ContextLoaderListener declarado no web.xml para carregar o application context na inicialização da
aplicação web.
<web-app>
<filter>
<filter-name>springSecurityFilterChain
</filter-name>
<filter-class>org.springframework.web.filter.
DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain
</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.
context.ContextLoaderListener
</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:
spring-security-config.xml
</param-value>
</context-param>
</web-app>
Application context é o nome dado aos XMLs de configuração do Spring Framework. Esses arquivos são
genéricos o suficiente para configurar qualquer tipo de aplicação. Em casos específicos, como do Spring
Security, namespaces são utilizados para reduzir a quantidade de XML necessária.
<beans:beans xmlns="http://www.springframework.org/
schema/security"
xmlns:beans="http://www.springframework.org
/schema/beans"
xmlns:xsi="http://www.w3.org/2001/
XMLSchema-instance" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/
beans/spring-beans-2.5.xsd
http://www.springframework.org/
schema/security
http://www.springframework.org/
schema/security/
spring-security-2.0.2.xsd">
<http auto-config="true">
<intercept-url pattern="/usuarios/**"
access="ROLE_USUARIO,ROLE_ADMIN" />
<intercept-url pattern="/admin/**"
access="ROLE_ADMIN" />
</http>
<authentication-provider>
<user-service>
<user name="joao" password="123"
authorities="ROLE_USUARIO" />
<user name="admin" password="123"
authorities="ROLE_ADMIN" />
</user-service>
</authentication-provider>
</beans:beans>
Se o leitor utiliza a IDE Eclipse, aconselhamos a instalação do plugin Spring IDE (veja seção de links)
para facilidades na edição de application contexts. Outras IDEs como NetBeans e IntelliJ IDEA também
possuem excelentes plugins de integração com o Spring, vale à pena conferir.
O controle de acesso é definido pela tag interna à tag . No atributo pattern definimos uma expressão que
atenderá as URLs acessadas e no atributo access definimos os roles de usuários que poderão acessar as
URLs, separados por vírgulas. Definimos que todas as URLs com prefixo /usuarios serão acessadas por
usuários normais e administradores e URLs com prefixo /admin apenas por administradores.
Por default a sintaxe das expressões utilizadas no atributo pattern é a mesma utilizada no Ant. Porém, se
desejado, pode-se alterá-la para seguir a sintaxe de expressões regulares. Para tal, basta adicionar o
atributo path-type="regex" na tag . Dessa forma é possível criar expressões tão complexas quanto
necessário para atender às URLs, de acordo com os requisitos do desenvolvedor.
As tags serão interpretadas em ordem de definição e a primeira a atender será usada. Dessa forma, os
patterns mais específicos devem vir primeiro. Por exemplo, a expressão ‘/usuarios/vip/**´ deve ser
declarada acima da expressão ‘/usuarios/**´, caso contrário a expressão ‘vip´ nunca será avaliada, pois
a expressão ‘/usuarios/**´ também atende a URLs do tipo ‘/usuarios/vip/**´.
O atributo auto-config=“true” da tag configura automaticamente a aplicação para utilizar login baseado
em formulário. O JSP do formulário nem mesmo precisa ser codificado, o Spring Security irá gerá-lo
dinamicamente conforme a Figura 2. Para mais informações sobre o auto-config=“true” veja o quadro
“Entendendo o auto-config”.
Entendendo o auto-config
O atributo auto-config da tag na configuração do Spring Security ativa as opções mais usadas do
framework, ajudando a diminuir a quantidade de XML necessário. Quando setamos seu valor para “true”
o Spring Security considera o XML como a seguir:
<http>
<form-login />
<anonymous />
<http-basic />
<logout />
<remember-me />
</http>
Isso configura o framework para utilizar autenticação por formulário e http-basic, bem como tratamento
de usuários anônimos e previamente autenticados (remember-me).
Cada uma das tags possui atributos. Para modificá-los é necessário apenas escrever a tag específica, e o
auto-config=true substituirá esta parte da configuração.
As tags omitidas continuam sendo consideradas. Por exemplo, o código abaixo muda o JSP do formulário
de login e o restante das configurações automáticas continuam aplicadas.
Isso configura o framework para utilizar autenticação por formulário e http-basic, bem como tratamento
de usuários anônimos e previamente autenticados (remember-me).
Cada uma das tags possui atributos. Para modificá-los é necessário apenas escrever a tag específica, e o
auto-config=true substituirá esta parte da configuração. As tags omitidas continuam sendo consideradas.
Por exemplo, o código abaixo muda o JSP do formulário de login e o restante das configurações
automáticas continuam aplicadas.
<http auto-config=true>
<form-login login-page=/login.jsp/>
</http>
Desta forma, a última coisa que nos resta é definir os usuários possíveis e seus papéis. No primeiro
exemplo, para facilitar, iremos utilizar a tag que permite definir usuários, senhas e roles no próprio
arquivo XML (também é possível referenciar um arquivo de propriedades). Normalmente, em uma
aplicação real, não é viável definir os usuários em arquivo. Nos próximos exemplos iremos apresentar
alternativas.
Últimos passos
O esqueleto do projeto, juntamente com os JARs necessários para rodar os exemplos, podem ser vistos
da Figura 3. Para ajudar na depuração da aplicação, através de log, criamos o arquivo log4j.properties e
adicionamos o log4j.jar ao classpath. O JAR do banco de dados HSQL-DB está presente pois será utilizado
em breve, nos próximos exemplos. O resto das bibliotecas são dependências do Spring Security. Os JARs
estão disponíveis no download dessa edição no site da Java Magazine.
Executando o exemplo
Para gerar rapidamente um hash md5 para utilizar nos exemplos o leitor pode implementar uma classe
Java simples de utilidade ou acessar um gerador online (veja Links).
Formulário personalizado
A geração automática de formulário é útil apenas para testes. Uma aplicação real necessita de uma
página de formulário customizada. Iremos criar um JSP (/login.jsp) com um formulário para o nosso
exemplo. Para isso acrescentamos este trecho dentro da tag no spring-security-config.xml:
O atributo authentication-failure-url configura o JSP que será apresentado caso o login falhe. Neste caso
configuramos o mesmo JSP do formulário de login com um parâmetro login_error=true. Esse parâmetro
é utilizado pelo JSP de login da Listagem 4.
<html>
<head>
<title>Spring Security</title>
</head>
<body>
<h1>Spring Security</h1><hr/>
<p>
<% if (request.getParameter
("login_error") != null) { %>
<font color="red">
Não foi possível se autenticar.<br/>
Motivo:
${SPRING_SECURITY_LAST_
EXCEPTION.message}.
</font>
<% } %>
</p>
<form action=
"j_spring_security_check"
method="POST">
Login: <input type=text name=j_username
value="${not empty login_error ?
SPRING_SECURITY_LAST_USERNAME : }" />
Senha: <input type=password name=j_password>
<input type="checkbox"
name="_spring_security_remember_me" />
Salvar as minhas
informações neste computador?
<input name="submit"
type="submit" value=”Login” />
<input name="reset"
type="reset" value=”Limpar” />
</form>
<a href="index.jsp">Voltar...</a><br>
</body>
</html>
Os exemplos demonstrados utilizam Scriplets (código Java no JSP) apenas por questões didáticas. Uma
aplicação real estaria utilizando a taglib JSTL.
No topo do JSP, abaixo do título, testamos se o parâmetro login_error é igual a true. Caso verdade (o
login falhou) então mostramos uma mensagem de erro. A expressão $
{SPRING_SECURITY_LAST_EXCEPTION.message} recupera a última mensagem de erro gerada pelo
framework. Essa mensagem por default está em inglês, mas pode ser internacionalizada.
Em seguida codificamos o formulário. Por default a action do formulário de login deve ser
j_spring_security_check e o “name” dos inputs de usuário e senha j_username e j_password,
respectivamente. No nosso exemplo, para habilitar o remember-me, adicionamos um checkbox com o
“name” _spring_security_remember_me. Esse JSP gera a imagem da Figura 4.
O atributo auto-config=true da tag <http> já configura uma URL default para realizar o logout,
/j_spring_security_logout. Ao clicar em um link apontando para esta URL o logout é realizado. Caso
necessário, é possível modificar a URL acrescentado a tag <logout> dentro da tag <http>:
Quando um usuário normal acessa a página protegida dos administradores o container apresenta uma
mensagem padrão para o erro 403 (access denied). Essa página pode ser personalizada pelo Spring
Security acrescentando o atributo access-denied-page à tag :
<html>
<body>
<h1>Spring Security</h1><hr/>
<p><font color="red">Acesso negado.
O usuário não tem permissão para acessar
essa página.</font><p>
<p>Remote user....:
<%= request.getRemoteUser() %></p>
<p>User principal....:
<%= request.getUserPrincipal() %></p>
<a href="../">Voltar...</a><br>
<a href="../j_spring_security_logout">
Logout</a><br>
</body>
</html>
Quem tem experiência com a segurança tradicional Java EE sabe que não é possível acessar o JSP de
login diretamente pela aplicação. O container deve apresentar este JSP quando uma página protegida é
acessada. Essa limitação cria outros problemas, por exemplo, quando um portal deseja que o formulário
de login esteja contido em todas as áreas do site. Nesse caso, os desenvolvedores se obrigam a criar
algum mecanismo utilizando JavaScript para contornar a situação.
No Spring Security tais limitações simplesmente não existem. Quando o usuário acessa diretamente o
JSP de login, ou quando um formulário de login embutido é utilizado, o Spring Security utiliza uma URL
default para redirecionar o usuário. A tag <form-login> possui um atributo default-target-url que indica
esta URL (se não informada o default é a raiz da aplicação). Por exemplo, se o formulário for configurado
desta forma:
Sempre que o formulário de login for acessado diretamente, após logar-se, o /index.jsp será carregado.
Caso o usuário acessar uma página protegida, após logar-se, esta será a página exibida e não o
/index.jsp. Se o atributo always-use-default-target for true o usuário sempre será redirecionado para o
/index.jsp.
A última modificação necessária para tornar o exemplo realístico é adicionar um banco de dados,
substituindo a configuração dos usuários e roles do XML. Um modelo de dados típico para uma aplicação
web, de maneira simplificada, é algo parecido com as tabelas da Figura 5.
O campo chamado ativo na tabela de usuários serve para impedir que usuários bloqueados autentiquem-
se. Caso este campo for false o usuário está com o acesso bloqueado. O campo tentativas_login
armazena o número de vezes que o usuário errou a senha consecutivamente. Esse campo será utilizado
apenas em exemplos posteriores.
O download do artigo traz dois scripts para serem executados no banco de dados HSQL. Um script cria as
tabelas e outro cria alguns usuários para teste. Para rodar o HSQL basta executar este comando no
prompt do sistema operacional (considerando que o jar do HSQL está no diretório corrente):
Logo após, abra outro prompt e execute o comando para abrir o console SQL com interface gráfica:
Selecione a opção “HSQL Database Engine Server” e pressione OK. Ao abrir o console execute os dois
scripts para criar e popular as tabelas. Feche o console, mas mantenha sempre o prompt do servidor
HSQL rodando.
Alterar a configuração do Spring Security para considerar as tabelas é algo muito simples, pressupondo
que o banco de dados está corretamente configurado. Duas modificações são necessárias: substituir a
tag por uma tag e acrescentar um spring bean para a configuração do banco de dados (data-source). A
Listagem 6 demonstra o novo spring-security-config.xml.
<beans:beans (...)>
<http auto-config="true" access-denied-page=
"/accessDenied.jsp">
<intercept-url pattern="/usuarios/**"
access="ROLE_USUARIO,ROLE_ADMIN" />
<intercept-url pattern="/admin/**"
access="ROLE_ADMIN" />
<form-login login-page="/login.jsp"
authentication-failure-url=
"/login.jsp?login_error=true"
default-target-url="/index.jsp" />
</http>
<authentication-provider>
<password-encoder hash="md5" />
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="select login as username,
senha as password, ativo as enabled
from usuario where login = ?"
authorities-by-username-query="select u.login as username,
p.descricao as authority from usuario u join usuario_perfil
up on u.login = up.login join perfil p on up.id_perfil =
p.id_perfil where u.login = ?" />
</authentication-provider>
<beans:bean id="dataSource"
class="org.springframework.jdbc.
datasource.DriverManagerDataSource">
<beans:property name="url"
value="jdbc:hsqldb:hsql://localhost" />
<beans:property name="driverClassName"
value="org.hsqldb.jdbcDriver" />
<beans:property name="username" value="sa" />
<beans:property name="password" value="" />
</beans:bean>
</beans:beans>
Precisamos de três atributos para configurar a tag . O primeiro atributo é uma referência para a
configuração do banco de dados que será utilizado (data-source-ref). O segundo é a query que será
utilizada para buscar os usuários dado um username (users-by-username-query). Essa query necessita
retornar três colunas esperadas pelo Spring Security: username, password e enabled. Mapeamos as
colunas do nosso modelo para as esperadas pela query. Por último, uma query para buscar os roles
(authorities) do usuário, dado um username (authorities-by-username-query). Essa query retorna um
username e a authority (role). Isso permite ao framework realizar as queries no banco e utilizar as
informações retornadas para autenticação.
Repare que utilizamos a classe DriverManagerDataSource que serve apenas para propósitos de teste. Em
uma aplicação real configuraríamos um pool de conexões como data-source, normalmente oferecido pelo
container através de um nome JNDI.
O Spring Security utiliza a infra-estrutura do application context do Spring para publicar eventos
referentes a momentos importantes em seu fluxo de execução. Existem duas categorias de eventos:
eventos de autenticação e eventos de autorização. Para cada situação existe um evento correspondente.
Por exemplo, quando uma autenticação ocorre com sucesso, um evento AuthenticationSuccessEvent é
publicado; quando a autenticação falha porque a senha está errada, um evento
AuthenticationFailureBadCredentialsEvent publicado; se a autenticação falha porque o usuário está
inativo, um evento AuthenticationFailureDisabledEvent ocorre; e assim por diante. O Spring Security
utiliza a infra-estrutura do application context do Spring para publicar eventos referentes a momentos
importantes em seu fluxo de execução. Existem duas categorias de eventos: eventos de autenticação e
eventos de autorização. Para cada situação existe um evento correspondente. Por exemplo, quando uma
autenticação ocorre com sucesso, um evento AuthenticationSuccessEvent é publicado; quando a
autenticação falha porque a senha está errada, um evento AuthenticationFailureBadCredentialsEvent é
publicado; se a autenticação falha porque o usuário está inativo, um evento
AuthenticationFailureDisabledEvent ocorre; e assim por diante.
Quando uma URL é acessada e o filtro do Spring Security verifica se o usuário tem autorização para
acessar a URL, eventos de sucesso ou falha também são publicados. Veja na Figura 6 uma hierarquia de
eventos de autenticação e autorização. Consulte o Javadoc de cada classe para entender o momento em
que cada evento é publicado.
<beans:bean class="br.com.jm.security.
TestEventListener" />
Essa funcionalidade permite implementar complexos casos de uso, com baixo acoplamento entre a
aplicação e o Spring Security, como veremos em um exemplo adiante. Para facilitar o aprendizado de
quando os eventos ocorrem, registre dois listeners que vêm juntos com a distribuição do Spring Security,
como a seguir:
<beans:bean class="org.springframework.security.event.
authorization.LoggerListener" />
<beans:bean class="org.springframework.security.event.
authentication.LoggerListener" />
Estes listeners efetuam log de todos os eventos que ocorrem, de autenticação e autorização. Uma ótima
forma de aprender o funcionamento do framework é a análise do log gerado por estes listeners.
Escolhemos um caso de uso para demonstrar como os eventos de autenticação são úteis e como é fácil
customizar o framework. Cada vez que um usuário errar a senha por três vezes consecutivas, a sua conta
será bloqueada. Essa funcionalidade é muito comum em sites que exigem altos padrões de segurança,
como bancos por exemplo. A cada erro de autenticação por informar a senha incorreta (evento
AuthenticationFailureBadCredentialsEvent) a coluna tentativas_login será incrementada. A cada
autenticação com sucesso (evento AuthenticationSuccessEvent) a coluna tentativas_login será zerada. A
cada tentativa de login, caso a coluna tentativas_login for igual ou maior que três, a autenticação será
bloqueada. Veja na Listagem 8 o listener que atualiza a coluna tentativas_login. Escolhemos um caso de
uso para demonstrar como os eventos de autenticação são úteis e como é fácil customizar o framework.
Cada vez que um usuário errar a senha por três vezes consecutivas, a sua conta será bloqueada. Essa
funcionalidade é muito comum em sites que exigem altos padrões de segurança, como bancos por
exemplo. A cada erro de autenticação por informar a senha incorreta (evento
AuthenticationFailureBadCredentialsEvent) a coluna tentativas_login será incrementada. A cada
autenticação com sucesso (evento AuthenticationSuccessEvent) a coluna tentativas_login será zerada. A
cada tentativa de login, caso a coluna tentativas_login for igual ou maior que três, a autenticação será
bloqueada. Veja na Listagem 8 o listener que atualiza a coluna tentativas_login.
Para completar o exemplo ainda é necessário bloquear o login caso a coluna seja maior ou igual a três.
Para isso iremos explorar um ponto de extensão comumente utilizado no Spring Security, a interface
UserDetailsService. Essa interface possui o método loadUserByUsername() que recupera os dados de um
usuário dado seu username. Veja a Listagem 9.
Uma implementação dessa interface substitui as tags (usuários em XML ou arquivo de propriedades) ou
(usuários em banco de dados) que vimos até agora. Sendo assim, através dessa implementação, o
usuário do framework tem liberdade para buscar as informações de onde necessitar e da forma que
quiser. Entretanto, o motivo mais comum para implementar a interface não é a escolha de uma nova
fonte de dados e sim, a personalização do objeto UserDetails de retorno.
A interface UserDetails é implementada por objetos que representam o usuário no seu modelo de
domínio. Ela possui métodos para retornar o username, o password, as authorities e quatro booleans
representando diferentes motivos para bloqueio do login: enabled, accountNonLocked,
accountNonExpired e credentialsNonExpired. Sendo assim, criamos uma classe Usuario para implementar
a interface, veja a Listagem 10.
return this.login;
return this.senha;
return this.authorities;
return this.ativo;
}
public boolean isAccountNonExpired() {
return true;
return true;
Caso qualquer um dos métodos que retornam boolean retornar false o login será bloqueado e uma
mensagem adequada será apresentada. Nesse caso não iremos utilizar as propriedades de conta ou
senha expirada, então retornarmos sempre true nos métodos isAccountNonExpired() e
isCredentialsNonExpired(). Para o método isEnable() utilizamos o valor da coluna ativo no banco de
dados e para o método isAccountNonLocked() utilizamos nossa regra de negócios: bloquear o login caso
três ou mais tentativas de login tenham sido feitas com a senha incorreta.
Para não duplicarmos código sem necessidade, iremos estender uma classe do Spring Security que já nos
oferece um ponto de extensão justamente para estes casos. A classe JdbcDaoImpl é a classe por trás da
tag . Essa classe já implementa o método loadUserByUsername() e nos deixa uma extensão valiosa, o
método createUserDetails(). Esse método é um gancho para retornar um UserDetails customizado. Veja a
Listagem 11.
O método createUserDetails() apenas copia propriedades que já estão disponíveis e chama o método
carregarInformacoesAdicionais() que faz uma query para preencher as propriedades restantes: email e
tentativas_login. A implementação do caso de uso está completa. A classe CustomUserDetailsService é
utilizada para buscar as informações de um usuário e retornar um UserDetails do nosso domínio. Após a
execução do método loadUserByUsername() as propriedades booleanas do Usuario são testadas. Nesse
caso, o getter da propriedade accountNonLocked testa se a coluna tentativas_login é igual ou maior que
três. Por fim, para controlar o incremento da coluna, os eventos de sucesso e falha na autenticação são
tratados pela classe IncorrectPasswordEventListener. O novo spring-security-config.xml é demonstrado na
Listagem 12.
A principal diferença do novo XML é a tag . Retiramos o e fizemos uma referência para o bean
“customUserService”. Como a classe CustomUserServiceDetails estende JdbcDaoImpl as propriedades
das queries continuam existindo. Sendo assim, copiamos as propriedades da antiga para o novo bean.
Outra diferença importante é a adição do IncorrectPasswordEventListener no final do arquivo.
O filtro do Spring Security substitui a implementação original da interface ServletRequest por um wrapper
(extensão da classe ServletRequestWrapper). Este wrapper implementa os métodos relacionados a
segurança na interface ServletRequest: getRemoteUser(), isUserInRole() e getUserPrincipal(). Dessa
forma, é possível utilizar os métodos tradicionais do Java EE como de costume, sem diferenças. Por
exemplo, veja o trecho de código a seguir:
System.out.println
(request.isUserInRole
("ROLE_USUARIO"));
System.out.println
(request.isUserInRole
("ROLE_ADMIN"));
Este código funciona perfeitamente com o Spring Security. Se o usuário “admin” (com perfil de
administrador) estiver logado, então a seqüência a ser impressa no console será:
Role: admin
false
true
Outra forma de se obter o usuário logado, porém sem necessitar do HttpServletRequest, é através da
classe SecurityContextHolder. Essa classe mantém o objeto Authentication em uma variável thread-local.
O método a seguir pode ser implementado em uma classe de utilidade e pode ser chamado em qualquer
ponto da aplicação:
Utilizando as taglibs
O Spring Security fornece duas tags úteis para codificação de JSPs: e . Ambas podem ser importadas
com a declaração a seguir:
A tag <authorize> é utilizada para mostrar/esconder informações de acordo com as permissões (roles)
do usuário. Veja o código a seguir:
<sec:authorize ifAllGranted="ROLE_ADMIN">
<p>Você é um administrador e também pode acessar esta <a
href="../admin/index.jsp">página</a>.<p>
</sec:authorize>
A tag possui três atributos exclusivos: ifAllGranted, ifAnyGranted e ifNotGranted. Todos os atributos
suportam um ou mais roles separados por vírgula. O atributo ifAllGranted informa que o usuário tem que
<Authentication>permite acessar as propriedades do objeto Authentication do usuário logado no JSP. Por
exemplo, o trecho de código a seguir, codificado no index.jsp, irá exibir as informações do usuário
utilizando a tag <Authentication>:
<sec:authorize ifAnyGranted="ROLE_ADMIN,ROLE_USUARIO">
<b>Informações do usuário logado:</b><br>
Login: <sec:authentication property="principal.login" /><br>
Senha: <sec:authentication property="principal.senha" /><br>
E-mail: <sec:authentication property="principal.email" /><br>
<a href="j_spring_security_logout">Logout</a><br>
</sec:authorize>
O vídeo “Spring Security” apresenta como realizar o controle de sessões, trabalhar com HTTPS e
autenticação através de certificados digitais.
Conclusões
Com os exemplos demonstrados podemos ver alguns dos principais casos de uso de segurança em uma
aplicação web. O Spring Security centraliza as configurações em apenas um arquivo que será
empacotado juntamente com o war da aplicação, dispensando configurações externas do container. A
aplicação não fica apenas mais enxuta, como também mais extensível, como vimos através da interface
UserDetailsService e dos eventos de autenticação e autorização. Dessa forma é possível implementar
casos complexos de gerenciamento de usuários, de forma desacoplada do domínio de negócios da
aplicação.
Alguns assuntos não foram abordados no artigo por motivo de espaço, porém o leitor pode consultar a
documentação no site do framework.
Algumas funcionalidades oferecidas pelo Spring Security e não apresentadas são: segurança a nível de
métodos (utilizando anotações e AOP), suporte a HTTPS, controle de sessões concorrentes e diferentes
formas de autenticação, como certificados X509, LDAP, etc. Para mais informações sobre single sign on
consulte o quadro “Single sign on com CAS e OpenID”.
Autenticação single sign on significa logar-se em uma aplicação e permanecer logado ao acessar outra
aplicação do mesmo grupo. Essa funcionalidade é comumente oferecida por containers para as aplicações
as quais ele gerencia. O Spring Security oferece suporte a single sign on para aplicações intranet através
do Central Authentication Service (CAS) e aplicações internet através do OpenID.
O CAS é uma aplicação web (arquivo WAR) que é responsável por apresentar as telas de login das
aplicações do mesmo grupo. Basicamente, todas as aplicações do grupo irão redirecionar o browser para
a aplicação central na hora da autenticação, e após o login, o CAS irá retornar a URL para a aplicação que
solicitou o login. Dessa forma, as configurações de autenticação ficam no WAR do CAS e nos demais
WARs o Spring Security é usado para realizar a integração.
Uma vantagem do CAS é o fato de existirem clientes para diversas linguagens como Java, .Net, PHP, Perl,
etc. permitindo integração entre aplicações heterogêneas.
O OpenID, por sua vez, elimina a necessidade de ter vários logins em diferentes sites na internet. Ao
criar um login em um dos providers de OpenID (veja Links) é possível utilizar o username e senha para
acessar todos os sites que o suportam, sem necessitar logar-se múltiplas vezes. Grandes empresas já
suportam o padrão como Sun, Google, IBM, Microsoft, etc. O Spring Security facilita o processo de
habilitar sua aplicação para suportar logins provenientes de providers OpenID.
Um objeto wrapper é uma implementação do padrão de projeto Decorator. O objetivo deste desing
pattern é adicionar ou substituir o comportamento de classes existentes de forma dinâmica. Isto é feito
criando uma classe base, normalmente com o sufixo Wrapper ou Decorator, que implementa a interface a
ser decorada e também recebe como parâmetro no construtor um objeto da mesma interface. Em
seguida, todos os métodos da classe são implementados delegando para o objeto recebido no construtor.
Por fim, quando se deseja adicionar ou mudar o comportamento da interface, basta estender o Wrapper e
sobre-escrever os métodos desejados.
Links
www.springsource.org
Portal do Spring Framework e sub-projetos
static.springframework.org/spring-security/site/index.html
Acesso direto à home page do Spring Security
springide.org/blog
Download do Spring IDE (plugin para o Eclipse)
www.jasig.org/cas
Homepage do projeto CAS
openid.net
Homepage do OpenID
www.myopenid.com
Provider de logins OpenID
md5-hash-online.waraxe.us
Gerador online de hash md5
Saiba Mais
www.devmedia.com.br/cursos/listcurso.asp?curso=46
Curso Online - Trabalhando com Struts 2 em conjunto com Sitemesh, Spring e JPA
www.devmedia.com.br/cursos/listcurso.asp?curso=113
Curso Online - Java Web: Saiba como Desenvolver Aplicações utilizando Spring, Hibernate e JSF na
Prática
www.devmedia.com.br/articles/viewcomp.asp?comp=10225
Java Magazine 40 - Começando com Spring
www.devmedia.com.br/articles/viewcomp.asp?comp=8573
Java Magazine 45 - Indo além com Spring
www.devmedia.com.br/articles/viewcomp.asp?comp=10170
Java Magazine 46 - Integrando Spring com JSF e JDBC
www.devmedia.com.br/articles/viewcomp.asp?comp=8369
Java Magazine 50 - Spring com Struts e JPA
www.devmedia.com.br/articles/viewcomp.asp?comp=8647
Java Magazine 53 - Spring Remoting
www.devmedia.com.br/articles/viewcomp.asp?comp=10210
Java Magazine 60 - Enviando e-mails com Spring 2.5
www.devmedia.com.br/articles/viewcomp.asp?comp=11678
Java Magazine 65 - Spring e Java EE 6 estão na mesma direção?
www.devmedia.com.br/articles/viewcomp.asp?comp=11677
Java Magazine 65 - Criando uma aplicação web com Spring Framework
JavaFX Script
Apresentamos uma referência da linguagem JavaFX Script, que faz parte da plataforma JavaFX.
JavaFX é a nova plataforma RIA da Sun, compatível com Java SE e Java ME, já apresentadas em artigos
anteriores desta série. Alguns leitores podem encarar a exigência de aprender uma nova linguagem de
programação como um obstáculo, mas este aprendizado é facilitado pela semelhança entre JavaFX Script
e Java - tiramos proveito desta semelhança para explicar a nova linguagem de forma concisa. Mesmo
para quem já aprendeu JavaFX Script, o artigo serve como uma referência bem organizada e pragmática
(ao contrário da referência oficial, que tem uma estrutura mais formal, certamente mais detalhada, mas
não adequada à facilidade de consulta e sem pretensões didáticas).
A linguagem JavaFX Script é um pré-requisito para desenvolver para a plataforma JavaFX, a qual
promete grande produtividade, mas (com qualquer plataforma) somente após vencer uma curva inicial
de aprendizado. Mesmo para quem não tiver grande interesse na JavaFX, o artigo apresenta muitas
inovações da JavaFX Script que são um aprendizado valioso para qualquer interessado em linguagens de
programação.
Agora, encerrando esta cobertura inicial da JavaFX, vamos nos focar apenas na linguagem JavaFX Script,
cobrindo-a de uma forma mais formal e abrangente. (Para o leitor ainda pouco motivado a aprender
JavaFX Script, seria boa idéia começar pelo quadro “Por que uma nova linguagem?”.)
Não é meu objetivo repetir o documento de Referência da Linguagem de Programação JavaFX Script que
está disponível em openjfx.dev.java.net/langref. Ao invés disso, a idéia é explicar a linguagem de uma
forma concisa e pragmática, tendo como alvo não alguém que vai escrever um compilador ou outra
ferramenta que exige conhecimento formal da linguagem, mas um desenvolvedor que deseja vencer a
curva de aprendizado da sua sintaxe ou então ter uma referência de fácil consulta.
Ainda mais especificamente, escrevo pensando no programador que já conhece bem a linguagem Java, o
que permitirá resumir ou omitir explicações dos pontos onde ambas as linguagens forem iguais ou muito
parecidas (e são muitos pontos). Assim, podemos concentrar nosso tempo e neurônios nas partes que
são diferentes.
O artigo é organizado como uma referência da linguagem, agrupando suas funcionalidades em seções
como Valores, Operadores, Classes etc., de forma similar a uma referência formal. No entanto, fiz este
agrupamento de uma forma “relaxada” segundo critérios conceituais e não de gramática formal. Por
exemplo, a seção de Operadores não possui todos os operadores, pois alguns deles são cobertos em
outras seções (ex.: os diversos operadores específicos para sequences são mostrados na seção
Sequences).
No decorrer do artigo, uso o termo “JavaFX” no lugar de “JavaFX Script”, por simplicidade.
Tipos e Valores
O sistema de tipos da JavaFX é inspirado no Java, mas com melhorias importantes. Na Tabela 1,
classifiquei os tipos da JavaFX em cinco categorias e comparei-os aos tipos equivalentes em Java. Vamos
comentar apenas as novidades desta tabela.
Valores
A maior novidade é que o typesystem de JavaFX é OO-puro: não existem tipos primitivos. Por outro lado,
existem “tipos de valor”, que são aqueles cuja igualdade de referência é equivalente à igualdade de valor
(em outras palavras: x == y se e somente se x.equals(y)). Os tipos primitivos do Java, como int, são
tipos de valor, por isso não há dois valores 17 distintos, por exemplo. O mesmo vale para o Integer da
JavaFX. Além disso, os valores são imutáveis, e não admitem null.
A diferença entre esses tipos da JavaFX e os tipos primitivos do Java é que os tipos de valor são objetos.
Em JavaFX, todos os tipos, sem exceção, são derivados de Object.
Em termos de implementação, os valores de JavaFX são equivalentes aos primitivos de Java, evitando o
custo de desempenho de usar objetos. Por exemplo, se você olhar o arquivo .class gerado pelo javafxc
para uma função que recebe um parâmetro Integer, verá que o bytecode compilado irá conter um
método que recebe um int primitivo. Somente quando um valor é utilizado em alguma operação que
exige sua faceta OO, a JavaFX faz uma conversão automática para um objeto. Você pode considerar isso
uma versão melhorada do autoboxing de Java 5. As melhorias incluem a simplificação (não há tipos
paralelos como int/Integer) e desempenho (o compilador, runtime e frameworks usam várias técnicas
para representar valores na forma primitiva, e reduzir ao mínimo as conversões de/para a forma OO).
Strings
Menção honrosa vai para o tipo String da JavaFX Script, que ao contrário de Java, é definido como um
value type. Além disso, carrega um lote de funcionalidades extra:
O valor default de uma String não inicializada é "" (string vazia). É impossível ter uma string null; se você
fizer esta atribuição, fica com "". Nunca acontecerá uma NullPointerException envolvendo strings (ou
qualquer value type);
O operador == equivale a equals(), não há distinção entre igualdade e identidade (novamente, como
todos value types);
Pode-se mesclar strings com expressões, por exemplo: "Alô, {nome}!" será expandido conforme o valor
dinâmico da variável nome. Ou então, "Temperatura: {%2.2f temp}", a variável temp será formatada no
estilo de System.printf() (ex.: %2.2f 32.257 → 32,25);
Pode ser delimitada por aspas simples ou duplas, ex.: String com "aspas duplas" dentro;
Internacionalização é simples: def msg = ##"ERRO" irá atribuir a msg o valor da string associada à
chave ERRO no resource bundle da aplicação; se não existir este resource, o valor da string será "ERRO"
mesmo. Para separar a chave do valor default, use ##[ERRO]"Deu zebra!";
É o único tipo de JavaFX Script para manipular caracteres. Não existe um tipo específico para um
caractere único, como o char do Java. Conversões automáticas são feitas ao invocar classes/APIs de Java
que utilizam char; ex.: "Osvaldo".charAt(0) = "O".
Duration
O tipo de valor Duration representa uma quantidade de tempo. Exemplos de literais: 15ms (15
milissegundos), 2.3s (2.3 segundos), 55m (55 minutos), 10h (10 horas), 10h + 5m (10 horas e 5
minutos). Este tipo não passa de um açúcar sintático para um long contendo o tempo normalizado para
milissegundos, mas ao usar o framework de animação da JavaFX, você verá que é muito conveniente ter
uma sintaxe especial para isso.
Void
O Void do Java FX significa o mesmo que o do Java, mas é mais útil, ver seção Controle.
Referência
Os reference types da JavaFX também são familiares. Ver seções sobre funções e sequences.
Funções
var validador: function (o:Object): Boolean O tipo da variável validador é uma função que possui um
parâmetro do tipo Object e retorna um Boolean. Portanto, funções podem ser atribuídas a variáveis,
passadas como parâmetro, etc. como quaisquer outros tipos. A linguagem Java possui uma aproximação
desta facilidade, combinando interfaces com inner classes - mas o resultado é uma sintaxe confusa. Com
a versão muito melhorada de JavaFX, podemos escrever por exemplo um tratador de eventos assim:
Stage {
onClose: function() { FX.exit() }
}
Sequences
As sequences da JavaFX Script possuem sintaxe similar, mas significado e capacidades diferentes dos
arrays do Java. Em comum com os arrays, as sequences são coleções de objetos de um tipo homogêneo
e com tamanho fixo, indexadas a partir da base 0.
As diferenças começam com algumas sintaxes facilitadas (syntax sugar), mas vão além. A Tabela 2
exemplifica todas as capacidades e sintaxes específicas para sequences. Algumas coisas são simples - por
exemplo, o operador sizeof equivale ao pseudo-atributo length dos arrays do Java; e com o ‘..´ você
pode especificar ranges (intervalos), que podem ser usados tanto para determinar quais elementos da
sequence serão manipulados (como em delete dias[1 .. 2]), quanto para criar uma sequence contendo
todos os números inteiros do range (como em [1 .. 100]).
Tabela 2: Sintaxes de uso de sequences.
Uma característica importante das sequences é que elas não admitem aninhamento; qualquer tentativa
de colocar uma sequence dentro de outra irá gerar uma sequence única com todos os elementos.
Por exemplo, o resultado de [ dias, ["Qua, "Qui"] ] não é [ "Dom", "Seg", "Ter", ["Qua, "Qui"] ], e sim
[ "Dom", "Seg", "Ter", "Qua, "Qui" ]. Esta transformação é conhecida como flattening (achatamento), e
embora possa parecer uma limitação, é uma característica importante do estilo de programação da
linguagem. Este estilo incentiva o uso de expressões “fluentes” - onde o output de cada expressão é
usado como input da seguinte, algo similar ao uso de pipes em shell scripts. Para isso funcionar, é preciso
que todas as operações utilizem uma estrutura de dados padronizada; no Unix essa estrutura é texto
ASCII, em Java FX Script (e outras linguagens) é a lista ou sequence.
O leitor expert em outras linguagens que fazem uso pesado de listas/sequences, como as da família LISP,
sabe que tais linguagens permitem o uso de estruturas aninhadas. Mas JavaFX Script não pretende ser
uma linguagem de programação funcional sofisticada, para quem curte técnicas do tipo “map/reduce”; ao
invés disso, foi feita a opção por sequences com flattening automático, o que simplifica os cenários de
uso mais importantes para JavaFX, por exemplo a inicialização de estruturas complexas como o Scene
Graph.
Observe a tentativa da JavaFX Script de usar uma sintaxe SQL-like para a manipulação de dados:
sintaxes como insert..into, delete..from, o in e o where do for.
Num detalhe importante, o leitor pode estar confuso com minha afirmação que as sequences são
imutáveis, seguida por uma tabela que indica várias sintaxes para sua alteração. Explico: todas essas
sintaxes criam uma nova sequence. Por exemplo:
No exemplo acima, após a atribuição diasUteis = dias, ambas variáveis apontam para a mesma
sequence. Mas em seguida, o delete cria uma nova sequence e a atribui à variável diasUteis; a sequence
original continua existindo pois ainda é referenciada por dias. Este truque sintático - operadores como
delete e insert, que executam uma atribuição ‘escondida´ - mantêm a sintaxe familiar para quem não
está acostumado ao estilo funcional (no qual nunca, ou raramente, se altera o valor de variáveis
preexistentes). Para mais detalhes, veja o quadro “Imperativo ou Funcional?”.
Declarações
Nesta seção veremos como JavaFX Script permite declarar variáveis e funções.
Na Tabela 3, podemos ver que as variáveis de JavaFX podem ser declaradas com var ou def. A diferença
é que o def é usado para constantes (similar às variáveis final do Java), enquanto var indica variáveis
propriamente ditas (i.e., cujo valor pode variar).
Tabela 3: Declarações de variáveis e funções.
As declarações estáticas de tipo são opcionais, mas isso não significa que JavaFX Script seja uma
linguagem dinamicamente tipada. Ao contrário, JavaFX é tão estaticamente tipada quanto Java; a
diferença é que contamos com inferência de tipos, ou seja, a capacidade do compilador de determinar
automaticamente o tipo. No exemplo var x = 5, o tipo de x é estabelecido como Integer, pois este é o
tipo do valor 5 com o qual a variável é inicializada. No caso específico das declarações def, como o valor
inicial é mandatório, a declaração de tipo jamais é necessária.
Também salta aos olhos que JavaFX Script utiliza o “estilo Pascal” de declarações de tipos, ou seja nome :
Tipo ao invés de Tipo nome como em Java. Apesar de parecer uma diferença gratuita e desagradável de
Java, você se acostumará e verá que existem bons motivos para isso (o mesmo vale para Scala, que -
não coincidentemente - também usa inferência de tipos).
class Y {
var x = 6;
function z () {
var x = 7;
JavaFX suporta variáveis e funções locais, de instância, e globais. Esta última opção parece ser uma
novidade, mas de fato não é, pois ocupa o lugar dos static do Java. Se num arquivo Y.fx você tiver um
elemento global x, este será acessível externamente com a sintaxe Y.x, ou seja, igual às statics. Em
termos de compilação e desempenho, a equivalência também permanece: variáveis e funções globais são
transformadas pelo javafxc em variáveis e métodos static.
A inferência de tipos também funciona para funções, especialmente o tipo de retorno: raramente é
preciso especificá-lo explicitamente, pois ao encontrar por exemplo uma operação return a + b no corpo
da função, o javafxc determina que o tipo desta expressão é o tipo estático de retorno a função. Mas
alguns programadores podem preferir especificar o tipo de retorno, para ter certeza que este será um
tipo específico (neste caso, um return e onde e não possui exatamente o mesmo tipo declarado para
retorno, sofrerá uma conversão implícita se possível).
O compilador javafxc irá transformar as funções em métodos idênticos aos do Java, com uma
“assinatura” no bytecode que inclui os tipos dos parâmetros e do retorno, sendo que esta assinatura influi
na compilação e na compatibilidade binária de outras classes compiladas com invocações ao método
(função) em questão. Assim, as funções de JavaFX Script podem ser também invocadas a partir de
classes Java, de maneira direta e tipicamente sem nenhum overhead.
Os tipos dos parâmetros, surpreendentemente, também podem ser inferidos. Isso é possível se houver
pelo menos uma invocação à função no mesmo script, ou em atribuições ou inicializações para uma
propriedade tipada como função:
No primeiro exemplo, o compilador determina que os parâmetros a e b de teste() são do tipo Number,
devido à presença da invocação teste(10, 15). No segundo exemplo, ao compilar a última linha onde
instanciamos um objeto do tipo A, a function (x) {...} não exige declarar que x é uma String pois esta
função está sendo atribuída à propriedade A.b, e para esta atribuição ser válida, a função no lado direito
deve ter a mesma assinatura da propriedade no lado esquerdo.
Visibilidade
JavaFX suporta um conjunto completo de modificadores de visibilidade, de fato, mais completo que Java.
Veja a relação completa na Tabela 4. Mais explicações (sobre a necessidade dos novos níveis) na próxima
seção. Mas antes de chegar lá, podemos destacar um fato interessante: JavaFX não possui um nível de
visibilidade private. Você logo entenderá o motivo.
Propriedades
JavaFX Script utiliza o termo “propriedade” ao invés de atributo. Propriedades são atributos de classes,
mas com alguns recursos adicionais em comparação com os atributos do Java.
Para quem vem de uma linguagem OO tradicional como C++ ou Java, poderão parecer “heréticas” idéias
como não encapsular atributos com getters e setters, ou não dispor de um nível de visibilidade private.
Mas analisando a linguagem como um todo, podemos entender seu design. A soma de recursos como
visibilidade mais refinada (especialmente public-init e public-read), instanciação “estilo JSON”, triggers e
binding, substitui 99% das necessidades de getters, setters e construtores. Ou seja, seus atributos (opa,
propriedades!) continuarão tão bem encapsulados quanto antes, só que com um código bem mais
simples - e, de quebra, algumas novas facilidades.
Quanto à visibilidade private, esta não existe de fato nem em Java - um membro private de uma classe
pode ser acessado por suas classes aninhadas ou inner classes. Classes aninhadas/inner costumam ser
fortemente acopladas à sua enclosing class, justificando o acesso privilegiado. Em JavaFX a lógica é
igual: todo o código contido por um script .fx é fortemente acoplado (se não for, divida-o em vários
scripts). JavaFX só torna explícito e homogêneo um design que, em Java, é acidental e imperfeito.
Acidental, pois não era assim no Java 1.0 - mudou no 1.1 quando as classes aninhadas/inner foram
criadas. Imperfeito, pois Java permite definir várias classes no mesmo arquivo .java (desde que só uma
seja pública), mas não há privilégios de acesso a membros privados entre estas classes - embora o
mesmo argumento de alto acoplamento seja válido.
scene: Scene {
content: Text {
x: 10, y: 30
O código acima constrói a aplicação completa (ok, faltando só as declarações package e import). Este
código instancia (e retorna - ver seção Controle) um objeto Stage. Este objeto é inicializado com o valor
250 para a propriedade width, etc. No caso da propriedade scene, seu valor é um objeto Scene que é
inicializado da mesma forma, idem para a propriedade content de Scene. Finalmente, a propriedade
onClose tem o tipo function (): Void, sendo inicializada com uma função deste tipo.
Teoricamente, você poderia fazer o mesmo em Java, com expressões new aninhadas. Mas há três
problemas. Primeiro, os construtores do Java são rígidos - se um objeto com os atributos a, b, ca,b),
inicializar apenas a exige passar explicitamente o valor default de b, e inicializar c exige invocar setC()
após a construção (saindo além da inicialização hierárquica).
O segundo problema é que os frameworks Java não costumam trabalhar com “atributos funcionais” como
onClose, preferindo design patterns como Template Method (um método onClose() que pode ser
redefinido) ou Observer (um método como addCloseListener()); ambos suportam uma expressão única
de inicialização, mas ao custo de código bem mais confuso devido à horrível sintaxe das inner classes.
Terceiro, a invocação de construtores não indica o nome dos argumentos, de forma que expressões de
construção longas e/ou aninhadas tendem a ficar difíceis de ler se você não souber de cor qual
argumento que vai em cada posição (ou se isso não for evidente pelos valores passados).
Alguns frameworks Java modernos adotam o pattern de “APIs fluentes” no qual métodos terminam com
um return this, o que permite encadear expressões como: new X(a).setB(b).setC(c), etc. Um método
addXxxListener() também poderia seguir este design. Mas o resultado, mais uma vez, é bem pouco
elegante. Na minha opinião, é uma tentativa pobre de imitar a linguagem Smalltak, na qual o estilo
“fluente” é natural devido à sua sintaxe de mensagens (não há parâmetros posicionais) e ao fato de
todos os métodos retornarem this por default (não existe o tipo void). E como, na definição do Java SE 7,
os conservadores venceram a briga e ficaremos sem closures, o Java continua condenado a gambiarras
pavorosas (ainda que sejam melhor-que-nada) como “APIs fluentes”.
Por que tudo isso é importante - não é muito oba-oba em cima de uma economia de algumas linhas de
código para inicializar objetos complexos? Acontece que esta facilidade sintática possui outros “efeitos
colaterais” positivos. Por exemplo, você pode usar a sintaxe de notação de objetos literais do JavaFX para
muitas tarefas onde tipicamente usaria XML, resultando num código que é mais enxuto e legível, fácil de
emitir e interpretar (como sabe qualquer programador JavaScript que prefere JSON a XML). Um exemplo
concreto disso é o formato gráfico FXD (ver Edição 67), nada mais que código JavaFX com inicializações
de classes gráficas. Outro exemplo é a construção da árvore de componentes e eventos da GUI, ilustrada
acima: este código, além de enxuto e legível, é ideal para manipulação de ferramentas. Por tudo isso,
JavaFX dispensa a necessidade de linguagens de descrição de GUI como XAML (Silverlight), MXML (Flex),
XUL (Mozilla), “layouts” do Android, etc.
A falta de algo equivalente (uma DSL para GUI & gráficos) não é uma lacuna de JavaFX, pelo contrário,
sua presença em plataformas competidoras revela inadequações das linguagens de programação
principais destas plataformas. Na JavaFX, o FXD é inclusive bom o suficiente para substituir o formato
SVG de gráficos vetoriais.
Binding
Binding é uma das características mais “famosas” de JavaFX Script, pois além de ser um poderoso
recurso de programação em geral, é uma peça essencial para os frameworks e o estilo de programação
da plataforma JavaFX.
Se você (como eu) começou aprendendo sobre binding examinando os fontes de diversos demos que só
utilizam as duas variantes mais simples deste recurso (bind x = y e criação de objetos com binding
igualmente simples em propriedades), poderá se surpreender com a Tabela 6, que mostra uma
sofisticação extraordinária. Vamos demonstrar esta capacidade com um exemplo que explora uma das
sintaxes complexas, combinado com triggers para logar o que acontece:
-> DST
As duas primeiras linhas são efeito das atribuições dos valores iniciais a dias, e por conseqüência do
binding, a iniciaisDias - pois a trigger é disparada inclusive para a atribuição inicial. (Note que ao exibir
sequences com o recurso de mesclagem de strings, os elementos são concatenados.) Na terceira linha,
que é o que mais nos interessa, “SegTer -> TERQUA” vemos que o binding de iniciaisDias foi acionado
pela atribuição a dias; sendo que esta atribuição modificou apenas dois dos seus elementos. Na quarta e
quinta linhas, vemos que o for (d in dias) é reexecutado somente para estes elementos (dias[1..2]), e as
alterações de iniciaisDias também são feitas de maneira incremental: repare que são geradas duas
alterações independentes para os elementos de índice 1 ("S"®"T") e 2 ("T"®"Q"), ao invés de uma
alteração única que causaria um log "DST -> DTQ".
Podemos concluir várias coisas. Primeiro, como já disse, o binding do Java vai bem além daqueles casos
simplórios que você viu em javafx.com/samples; é um recurso que pode ser explorado de inúmeras
formas. Segundo, as capacidades das sequences do Java são ainda mais incrementadas pelo tratamento
especial de outros recursos da linguagem, como triggers e binding - de uma forma geral, a linguagem
tenta otimizar o esforço destas operações tornando-as o mais incrementais possíveis (se você tem uma
sequence de 1000 elementos e altera apenas um elemento, isso não irá gerar execuções de triggers ou
bindings considerando todos os 1000 elementos).
Como nada é perfeito, um alerta: binding é pesado, especialmente devido às capacidades de avaliação
incremental de sequences e expressões complexas.
Para cada variável sujeita a binding e cada expressão bind, o runtime é obrigado a manter na memória
estruturas de dados relativamente grandes. É por isso que você pode encontrar na internet algumas
pessoas comentando que ficaram horrorizadas ao ver que uma variável Boolean ocupava 100 bytes ou
mais. Use este recurso apenas quando necessário (especialmente na JavaFX Mobile).
Operadores
A Tabela 7 revela algumas das minhas poucas discordâncias do design de JavaFX Script. Alguns
operadores desviam de Java, a meu ver gratuitamente: por que, por exemplo, and ao invés de &&? O
argumento é que operadores nomeados são mais “simples” que símbolos misteriosos como &&, mas não
engulo isso, lembrando que linguagens RIA competidoras, como JavaScript e ActionScript, utilizam os
operadores simbólicos tradicionais da família C.
Os operadores simbólicos são mais legíveis pois se destacam de nomes de variáveis e funções; ao ler um
código como aa || bb && cc, a distinção entre operadores/operandos é imediatamente clara, o que não
ocorre para aa or bb and cc - que exige um “parsing mental” para reconhecer or e and como operadores
e as demais palavras como identificadores de variáveis.
Pior que isso, vejo algumas inconsistências: a exclamação ! foi substituída por not como operador de
negação, mas no operador diferente-de !=, continuamos vendo a exclamação indicando negação. Mais: o
‘|´ de [a | a > 5] e o where de for (a in b where a > 5) têm exatamente o mesmo significado... nada é
perfeito, e na minha opinião, a sintaxe de operadores não-simbólicos é um lugar onde o design de JavaFX
Script derrapou.
Outras divergências me parecem OK. O operador de typecast as é mais elegante que o do Java, pois
sendo posfixado, é coerente com a ordem de avaliação, ex.: dVar * 2.0 as Integer, temos primeiro uma
multiplicação entre variáveis Double, depois sua conversão para Integer, e observe também que não
precisamos usar parênteses - (dVar * 2.0) as Integer - pois a precedência do as é mais fraca (i.e., o
typecast é avaliado depois da maioria dos outros operadores). E o sizeof é um conceito bastante
interessante, coerente com o design orientado a expressões de JavaFX Script.
JavaFX não possui operadores de manipulação de bits, como |, &, ^, ~, <<, >> e >>> do Java. O
veterano de C/C++/Java dentro de mim também não gostou disso à primeira vista, mas acabei
entendendo, pois estes operadores são de uso muito raro na enorme maioria das aplicações, e JavaFX
Script é uma linguagem dedicada à camada de aplicação - não se supõe que alguém vá usá-la para
“escovações de bits” como algoritmos de compressão ou criptografia.
Principalmente lembrando que JavaFX é uma linguagem “parceira” de Java: é trivial invocar métodos de
casses Java a partir de JavaFX e vice-versa, misturar ambas classes no mesmo projeto/IDE etc.
Além disso, JavaFX reserva os tokens << e >> para outro propósito: escape de literais externas. Por
exemplo, se você tiver que invocar um método bind() de uma classe Java, pode fazê-lo com
obj.<>(argumentos). Sem o escape isso seria ilegal por que bind é uma palavra-chave em JavaFX. Java
7 também terá um mecanismo de escape similar.
Controle
JavaFX Script, como qualquer linguagem de programação, precisa de estruturas de controle como
decisões e loops. Você poderia imaginar que pelo menos nesta área, a linguagem seria praticamente igual
a Java. Engano, também aqui JavaFX apresenta inovações poderosas, muito embora mantenha uma boa
similaridade sintática com Java. Veja a Tabela 8.
Isso pode parecer malandragem, qual é a diferença entre um statement e uma expressão Void? A maior
diferença é técnica: a gramática da linguagem fica mais simples e unificada, o único efeito do Void é não
permitir o uso de determinada expressão no lado direito de lugares que exijam um valor, como uma
atribuição. Porém, podemos ver que a linguagem faz um grande esforço para que quase tudo seja uma
expressão normal (não-Void). Notavelmente, as estruturas de controle if e for são expressões que
retornam valores. Vemos também que num bloco de código, o return é geralmente desnecessário para
retornos com valor - basta terminar o bloco com um valor, como:
O bloco {} em si é uma expressão que possui valor, o qual pode ter tipo Void (se termina por um return
sem valor ou outra expressão de tipo Void) ou outro tipo qualquer.
Cadê o switch?
Se o leitor viu a Tabela 8 com as estruturas de controle de JavaFX e estiver perguntando “onde está o
switch?”, a resposta simples é: não tem. Mas a resposta mais longa é... tem algo parecido, ou poderá ter
logo.
Muitos designers de linguagens OO apontam que a estrutura de controle switch é anti-OO pois muitas
vezes pode ser substituída por polimorfismo ou outras técnicas. Em JavaFX, tiveram a coragem de deixar
o switch de fora. Isso significa que você tem que usar ou polirmorfismo, ou cascatas de ifs, onde
normalmente usaria um switch.
Mas esta parte do design da linguagem me parece incipiente pois, para realmente não sentirmos falta do
switch, precisamos de outros mecanismos (o polimorfismo nem sempre é adequado). JavaFX já possui
parte da solução (funções de primeira ordem) - é melhor ilustrar com um exemplo prático:
[
function() { println("Case 0"); }
function() { println("Case 1"); }
function() { println("Case 2"); }
function() { println("Case 3"); }
][valor]();
Ao invés de case, usamos uma sequence contendo uma função com o código correspondente para o case
do seu índice. Ao invés de switch(valor), usamos o valor para indexar a sequence, obtendo e executando
a função que contém o código deste “caso”. Note que os “casos” poderiam ter parâmetros e retornar
valores (desde que todos tenham a mesma assinatura: isso será verificado pelo compilador!), o que já
tornaria esta técnica mais poderosa que o switch/case do Java.
Observe mais uma vez a inferência de tipos de JavaFX: no exemplo, o tipo da sequence é function()
[].Digamos que você edite apenas um dos “casos” e coloque um parâmetro Integer na função; então o
tipo inferido será o tipo mais elementar compatível com todos os casos - no caso, um tipo Function
genérico (herdado por todos os tipos de função). Mas se você utilizar a sequence como no exemplo,
indexando-a e executando a invocação com (), o compilador exige que todas as funções possuam
assinatura compatível com a da invocação - no caso, nenhum parâmetro - e reclamará de qualquer
“caso” que seja diferente disso.
O maior problema é que nem todos os switch/case possuem um domínio de valores como 0, 1, 2..., que
se prestem ao mapeamento para índices de sequence. O ideal seria então usar mapas (sequences
associativas), o que permitiria criar uma estrutura como [ "Ouros": function() { println("Ouros"} ], etc.;
isso também permitiria o uso de objetos arbitrários, não só números, como chave. Infelizmente JavaFX
ainda não possui nenhum suporte nativo a estruturas de dado deste tipo - pode-se usar as do Java como
java.util.HashMap, mas isso não teria facilidades como uma sintaxe especializada de inicialização e
indexação.
Uma versão futura da linguagem possuirá este suporte a mapas, provavelmente a JavaFX 2.0 (ver JFXC-
642 no JIRA). Mas também seria bom permitir a declaração de funções triviais ainda mais simples (sem o
function()), e mesmo assim, faltaria o default.
Há linguagens, como Smalltalk, que sempre se viraram muito bem sem o switch. Na pior hipótese você
pode simplesmente usar ifs em cascata, o que em JavaFX talvez não seja ruim, lembrando que if é uma
expressão e você pode escrever código como:
def sobrenome =
if (nome == "Osvaldo")
"Doederlein"
else if (nome == "Eduardo")
"Spínola"
else
"Silva";
que é mais enxuto que, com um switch, ter que fazer uma atribuição separada para sobrenome em
o
cada case. (Para um exemplo tão trivial o Java permitiria usar o ternário ?:, mas não se os cases
tivessem que executar algum statement antes do valor retornado.)
Mas esta solução ainda incomoda um pouco: esteticamente, o primeiro ”caso” (if) não tem um cabeçalho
idêntico aos demais (else if); e a repetição de "nome ==" também me incomoda um pouco, ainda que
seja mais legível e genérica que os cases. Eficiência não é um problema pois o compilador poderia
reconhecer uma cascata de ifs com estrutura similar a um switch, e gerar código idêntico (com bytecodes
tableswitch ou lookupswitch).
Generators
Uma vantagem do design orientado a expressões de JavaFX, e seu uso de tipos de dados de alto nível
como sequences e estruturas de controle avançadas como generators (for), é que o javafxc tem
oportunidade para fazer otimizações importantes. Por exemplo, ao conhecer o for da JavaFX Script, você
talvez tenha se horrorizado ao imaginar que este sempre irá gerar uma sequence com os valores
produzidos por cada iteração do loop. Mas não é exatamente assim; o compilador só faz isso se
necessário. Assim, no código
def x = [1, 2, 3]
def y = for (n in x) n * 2
a sequence será de fato gerada, no caso [2, 4, 6], e atribuída a y. Porém, neste outro código:
def x = [1, 2, 3]
for (n in x) n * 2
for só irá avaliar a expressão n * 2 para cada n in x, mas nenhuma sequence será criada, pois o “valor
o
de retorno” do for não está sendo utilizado de qualquer forma (como termo de outra expressão, lado-
direito de atribuição, etc.).
Classes
JavaFX é uma linguagem OO baseada em classes, com sintaxe básica parecida com Java, mas com pelo
menos um grande desvio do design de Java.
Como JavaFX faz isso? Se você observar os arquivos gerados pelo javafxc, verá que uma classe C da
JavaFX gera um par de .class, que se você descompilar gerando arquivos .java (ou examinar com o
javap), verá que correspondem a uma interface C.Intf e uma classe C que implementa esta interface.
Junto com alguns outros truques de compilação, isso permite integrar o paradigma de herança múltipla
generalizada da JavaFX Script ao modelo OO do bytecode / JVM, que suporta herança múltipla apenas
para interfaces com métodos abstratos.
Para saber mais sobre a linguagem acesse a Apresentação “JavaFX Script - Referência Rápida”.
A Tabela 9 resume a sintaxe das classes de JavaFX. Não existem interfaces, apenas classes, e a herança
múltipla é suportada para tudo (funções abstratas e concretas, e até mesmo propriedades). Classes da
JavaFX podem inclusive herdar qualquer coisa de Java (tanto classes quanto interfaces). Ou seja, JavaFX
Script unifica nossas class e interface numa única entidade, também não exigindo distinguir entre
extends e implements.
Tabela 6: Classes
Por que uma nova linguagem?
Produtividade Competitiva
Todas as plataformas RIA competidoras utilizam alguma linguagem de programação considerada de “alta
produtividade” - pelo menos segundo alguns critérios, como alta densidade de código (mais
funcionalidade com menos linhas de código) e facilidade de prototipação. As competidoras incluem
JavaScript, ActionScript, e linguagens XML como XAML e MXML (para estas últimas, a vantagem é o uso
de ferramentas visuais).
JavaFX Script possui diversas características de alta produtividade, como: inferência de tipos, binding,
sequences, inicialização declarativa de estruturas hierárquicas de objetos, sintaxes de alto nível para
diversas necessidades de programação geral (desde o simples relaxamento na pontuação até o for
“turbinado”), e sintaxes especiais para algumas APIs.
Agilidade
Quando se fala em evolução da linguagem Java, não podemos nos esquecer que a Sun não é dona do
Java. A especificação da linguagem, bem como de todas APIs formais, é controlada pelo Java Community
Process (JCP). Embora a Sun mantenha certas prerrogativas e um alto grau de influência no JCP, isso não
inclui carta branca para fazer o que quiser com a plataforma. Em especial, a evolução de especificações
preexistentes é sempre “torturante” pois precisa levar em conta os interesses de diversos players com
voz no JCP, e possivelmente grande investimento na tecnologia em questão. A quebra de compatibilidade
retroativa é virtualmente impossível.
A Sun chegou atrasada à briga do RIA. Se fosse seguir o caminho de estender a linguagem Java (mesmo
que isso fosse tecnicamente uma boa idéia), ou criar a JavaFX sob os auspícios do JCP, o lançamento da
JavaFX 1.0 levaria pelo menos o dobro do tempo, e a sua evolução até a JavaFX 2.0 levaria 3-4 anos ao
invés de um ano. Isso liquidaria qualquer chance da JavaFX de competir. Órgãos de padrões como o JCP
são excelentes para tecnologias já razoavelmente maduras, mas são quase sempre péssimos para criar
coisas completamente novas - a inovação raramente acontece em comitês.
Imperativo ou Funcional?
Na minha mania de aprender novas linguagens mexendo em código dos outros, cheguei ao seguinte
trecho de um demo (javafx.com/samples/SmokeParticles/):
Seu funcionamento é o seguinte: primeiro um novo objeto Particle é criado e inserido ao final da
sequence parts; depois, um loop varre todos os elementos que já estavam na sequence, invocando os
métodos update() e isdead(), e deletando da sequence aqueles que estão “mortos”. Mas achei esse
código feio, confuso, então resolvi reescrevê-lo:
parts = [
Particle {
vx : 0.3 * random.nextGaussian()
vy : 0.3 * random.nextGaussian() - 1
}
Esta nova versão faz uma substituição única da sequence antiga pela nova, a qual é criada da seguinte
forma: primeiro temos um forisdead() a update()), e depois temos a nova Particle.
O ‘[...]´ mais externo serve concatena a sequence gerada pelo for com o objeto final, sendo que graças
ao flattening automático, o resultado será uma sequence simples com todos estes elementos.
A nova versão é característica do estilo funcional, evitando alterações repetitivas como insert e delete.
Sobrou uma alteração de variável (parts = ...), mas é uma só, e mesmo esta atribuição só restou por que
eu não quis reescrever o programa todo. É um código bem mais “limpo”, menor (10 linhas ao invés de
14) e mais simples (sua complexidade ciclomática - quantidade e aninhamento de estruturas de controle
- é menor.)
Mais importante, a versão funcional é idêntica a uma descrição formal / matemática do algoritmo, que
podemos enunciar como: “o novo conjunto de partículas é formado pelas partículas preexistentes que
após o update() continuem vivas, mais uma partícula nova”. É por isso que tanta gente gosta de
programação funcional: é o estilo de programação que permite traduzir, de forma direta, algoritmos
especificados de forma matematicamente rigorosa (que é quase sempre a descrição mais enxuta e
elegante possível).
Bem, agora as más notícias. Testando meu novo código, este funcionou, mas com um desempenho muito
pior. O problema é que a sequence parts é amarrada, via binding, ao scene graph da animação do
programa; porém, o binding de sequences não é otimizado para o cenário de substituição total de uma
sequence (com uma atribuição explícita), apesar de ser otimizado para manipulações pontuais como
insert e delete
A otimização em questão evita que o scene graph inteiro seja reconstruído em cada frame da animação,
o que resulta em péssimo desempenho. Ou seja, não se trata de um bug/limitação de desempenho das
sequences propriamente ditas, mas somente da combinação entre sequences e binding, ou talvez, do
runtime do scene graph. Registrei um novo bug descrevendo o problema (javafx-
jira.kenai.com/browse/JFXC-2911). É o tipo de coisa que, infelizmente, podemos esperar de um software
complexo como a JavaFX que ainda é praticamente recém-lançado no momento em que escrevo. Mas não
tira o mérito do estilo de programação funcional ou do design de sequences (o JIRA da JavaFX registra
uma grande quantidade de bugs similares, a maioria corrigidos antes mesmo do release 1.0 - mas o
trabalho obviamente ainda não terminou).
Conclusões
Este artigo encerra uma primeira “trilogia” de JavaFX, na qual cobrimos os aspectos principais desta nova
plataforma: tecnologia RIA, mobilidade, linguagem de programação, pontos principais do framework. A
partir deste ponto, minha expectativa é que um leitor que sabe programar Java e tem interesse pela
plataforma JavaFX possa andar com seus próprios pés, chegando ao ponto de desenvolver aplicações RIA
sofisticadas para desktop e dispositivos móveis.
Há duas partes da JavaFX que cobrimos de forma superficial: os recursos de mídia (ver Edição 67) e as
APIs javafx.* (ver Edição 68). Estes temas renderiam, seguramente, pelo menos mais dois capítulos da
série. Mesmo na linguagem JavaFX Script, coberta neste artigo, existem tópicos específicos que
poderíamos explorar, como a integração com Java, ou o desempenho.
Mas preferi parar por aqui, por três motivos. Primeiro, a plataforma JavaFX ainda é muito recente e
ainda é difícil avaliar o interesse dos leitores por uma cobertura tão contínua e abrangente. Segundo, o
mundo não parou por causa da JavaFX, e há outros temas que pretendo cobrir nas próximas edições.
Terceiro, a JavaFX ainda está em rápida evolução: no caso específico dos frameworks, não vou gastar
nosso tempo (meu e dos leitores) com um “artigão” sobre os frameworks da JavaFX 1.1, quando sei que
já em junho deste ano (quando nossa próxima Edição já estaria nas bancas) a Sun terá lançado a JavaFX
1.5, com muitas novidades especialmente nas APIs. Resumindo, espero que o leitor tenha apreciado
estes primeiros artigos, tanto quanto eu apreciei escrevê-los... chegou a hora de fazer uma pausa, mas
com planos de voltar à JavaFX daqui a algumas edições, com as baterias recarregadas.
Saiba Mais
http://www.devmedia.com.br/resumo/default.asp?idrev=123#1515
Java Magazine 67 - JavaFX: RIA a todo vapor
http://www.devmedia.com.br/resumo/default.asp?idrev=130#1590
Java Magazine 68 - JavaFX: Tutorial
Novos tempos: javax.time
Bacharel em Sistemas de Informação pelo Mackenzie e pós-graduado pela Fundação Vanzolini. Trabalha
com Java desde 1999 e possui as certificações SCJP, SCWCD, SCBCD, SCDJWS, SCEA, BEA Certified
Developer: Integration Solutions e BEA Certified SOA Architect. Já atuou como Desenvolvedor, Analista e
Arquiteto de Sistemas e atualmente é Consultor em Arquitetura Oracle | BEA. Blog:
http://www.amadei.com.br
É um dos spec-leads da JSR-310 e expert em outras cinco JSRs. Ganhou um JavaOne Rock Star Speaker
Award pela sua palestra sobre a JSR-310 em 2008 no JavaOne. Atua como Senior Technical Consultant na
Summa Technologies do Brasil.
O artigo aborda o resultado da JSR-310, a JSR que está definindo a nova API de data e hora que será
incorporada na plataforma Java. A arquitetura e as principais classes e interfaces da API são discutidas e
exemplificadas. Além disso, são abordadas as lacunas presentes nas classes atuais (Date e Calendar) e
como a javax.time endereça tais problemas.
Apresentar a nova API de datas e horas que será incorporada na plataforma Java, provendo discussões
sobre a sua arquitetura e exemplos de uso da API, além de comparações com as classes atuais.
O tema é importante para qualquer um que deseje estar atualizado sobre as novas tendências da
plataforma Java. A manipulação de datas e horas é peça-chave em qualquer implementação e a criação
de uma nova API tende a facilitar muito a vida do desenvolvedor.
O artigo aborda a nova API para representação de datas e horas da plataforma Java e como ela facilita a
vida do desenvolvedor Java, corrigindo bugs e endereçando problemas das classes atuais Date e
Calendar. A nova API possui representações distintas para datas que serão utilizadas para cálculos
computacionais e datas que serão utilizadas por seres humanos. Além disso, temos representações
padrão para tipos de dados que atualmente precisamos criar como durações, períodos e intervalos.
Neste artigo conheceremos a nova API de data e hora que está sendo elaborada para a plataforma Java
SE 7. O foco da nova API é resolver vários problemas que afetam os desenvolvedores Java há anos,
presentes nas classes java.util.Date e java.util.Calendar.
Por que uma nova API?
A arquitetura elaborada para as classes Date e Calendar, ambas do pacote java.util é bastante
questionável. Veremos algumas dessas decisões arquiteturais dúbias, além de “bugs” da API atual, no
decorrer do artigo.
Outro grande problema é a falta de tipos para representar unidades comuns no dia-a-dia de qualquer
desenvolvedor como, por exemplo, períodos, instantes, durações, entre outros. Sem classes que
representem essas unidades do “mundo real”, é necessária a criação de soluções paliativas por conta e
risco dos desenvolvedores. Isso resulta em mais codificação e código legado para manutenção.
Esses pontos claramente abrem espaço a uma nova abordagem para o tratamento de datas e horas
dentro da plataforma Java.
Um dos principais problemas das classes atuais é a inconsistência. A classe java.util.Date não representa
uma data, mas sim um instante na linha do tempo. Desta forma, não temos como representar somente
uma data ou somente o tempo. Outro exemplo de inconsistência é o fato da classe Date utilizar anos com
base em 1900, enquanto a Calendar requerer os anos completos, incluindo o século. O código
apresentado a seguir, apesar do uso de um construtor descontinuado (deprecated), ilustra a criação de
uma data cujo ano será 3909, ao contrário do que parece ao ler o código.
O início dos meses em 0 (Zero) na classe Calendar também é algo que tem atrapalhado a vida dos
desenvolvedores Java de forma considerável. Por exemplo, para criar uma instância de Calendar que
represente a data 01/01/2009, temos que escrever o seguinte código:
As APIs atuais exigem muito código para tarefas rotineiras de manipulação de datas e horas. Sem
mencionar ainda que muitas dessas “tarefas rotineiras” constituem casos de uso comuns e poderiam ser
implementados pelas próprias APIs.
Esses são apenas alguns dos problemas que a JSR 310 irá resolver. Os já velhos conhecidos problemas
devido às alterações no horário de verão também estão sendo endereçados. Além de tudo isso, você verá
que a API é muito mais intuitiva e irá lhe poupar muitas e muitas linhas de codificação.
A JSR 310 tem sido elaborada com alguns princípios para facilitar seu uso e entendimento e tornar seu
código mais robusto e de fácil leitura.
Baseada em padrões
A nova API está sendo construída totalmente alinhada com o ISO-8601, um padrão internacional para
representação de datas e horas.
Imutável
Os principais objetos da nova API serão imutáveis. Não será possível alterá-los após a construção.
Nesse caso, criamos uma data com a classe javax.time.calendar.LocalDate. Essa classe representa uma
data sem fuso horário (time zone) no padrão ISO-8601, resultando em 2009-01-01. Caso desejemos
alterar o ano dessa data, por exemplo, para 2010 não iremos utilizar um método set(). Neste caso,
devemos utilizar um método with(), que cria uma nova instância da classe com o ano desejado. O código
ficaria conforme apresentado a seguir:
date = date.withYear(2010);
Interfaces Fluentes
As classes e interfaces seguem o padrão de fluência para permitir uma melhor legibilidade do código. Isso
transforma as invocações a mais do que um método em sentenças de fácil leitura. O código apresentado
a seguir, além do ano, altera o mês e o dia.
date = date.withYear(2010).
withMonthOfYear(3).withDayOfMonth(2);
Clareza
Os métodos são bem definidos e indicam claramente seu propósito. Por exemplo, para subtrair dois anos
de uma data qualquer, utilizamos o seguinte código:
date = date.withYear(2010).
withMonthOfYear(3).
withDayOfMonth(2);
date = date.minusYears(2);
Isso irá resultar na data 2008-03-02. Muito mais claro do que o método roll(int field, int amount) da
classe Calendar.
Extensível
Através de pontos de extensão, utilizando conceitos do design pattern Strategy, é possível controlar e
customizar como a API se comporta em relação às datas e horas. Mesmo assim, não é necessário ser um
especialista para usar a API. São fornecidas implementações padrão para a maioria dos cenários.
Além disso, a javax.time traz consigo uma classe utilitária, a javax.time.calendar.DateAdjusters, contendo
implementações dos casos de uso mais comuns para o ajuste de datas. Essas implementações devem
satisfazer a maioria das necessidades de desenvolvimento. O código apresentado a seguir ilustra o ajuste
sendo realizado em uma data:
date = date.with(DateAdjusters.
next(DayOfWeek.MONDAY));
O código que apresentamos, além do ajuste da data, apresenta uma nova classe:
javax.time.calendar.Clock. Essa classe é um façade para o acesso à data e hora correntes. O método
today(), que invocamos na classe Clock, retorna a data corrente como uma instância de LocalDate.
Voltando a falar sobre o ajuste da data, ele ocorre na invocação ao método with(). Informamos
DateAdjusters.next(DayOfWeek.MONDAY) como parâmetro do método. O objeto retornado pelo método
next() é responsável por ajustar a data para a segunda-feira subsequente à data informada. Avaliando as
datas, caso executemos tal código no dia 05/02/2009, após o ajuste, a nova data (lembre-se da
imutabilidade!) será dia 09/02/2009.
Ainda no âmbito dos Adjusters, outro exemplo muito interessante de sua aplicabilidade seria o ajuste das
datas para dias úteis. Você pode, ainda, implementar seu próprio adjuster - digamos, para saltar feriados
obtidos de um cadastro de feriados do seu sistema.
A javax.time apresenta duas formas distintas para lidar com o tempo. Uma escala voltada para
máquinas, denominada Continuous e a outra com foco em datas para seres humanos, denominada
Human.
Continuous
Essa abordagem da javax.time é voltada para máquinas e representa o tempo na forma de um número
incremental, sem muito significado para seres humanos, porém com grande valor para uso em
processamentos que requerem cálculos envolvendo timestamps.
Instant
Duration
A classe javax.time.Duration representa uma duração de tempo. Dentro da javax.time, ela representa a
duração entre dois instantes. O instante inicial que forma uma instância de Duration é inclusivo e o final é
exclusivo. Apesar de armazenar dados provenientes de lá, a classe Duration é desconectada e
independente da linha do tempo. O código apresentado a seguir ilustra o uso da classe Duration:
System.out.println(duration);
InstantInterval
intervalBetween(agora, umMinutoMais);
(Clock.systemDefaultZone().instant());
No exemplo, por utilizarmos o método “padrão”, o intervalo inicial é inclusivo e o final é exclusivo. Existe
outro método que recebe valores booleanos indicando se desejamos que cada um dos intervalos seja
inclusivo (true) ou exclusivo (false). Além disso, podemos construir a instância de InstantInterval a partir
dos métodos “builder”: intervalFrom() e intervalTo().
Human
Essa abordagem da javax.time é voltada para seres humanos. Ela representa os valores de datas e horas
utilizando campos com classes específicas para representar cada um dos dados do calendário: ano, mês,
dia, hora, minuto e segundo. Além desses campos mais comuns, temos algumas outras classes ou
enumerações para representar, por exemplo, o dia do ano, a semana do ano, os nanossegundos de cada
segundo, entre outras.
Através da abordagem para seres humanos, temos formas (classes!) para representar datas e horas,
datas sem hora, horas sem data, offsets e time zones.
Os tipos mais simples presentes dentro da javax.time são os tipos chamados de “locais”. Esses tipos
podem representar data ou hora de forma isolada ou as duas em conjunto. São chamadas de locais por
não estarem associados a um offset ou time zone.
LocalDate
A representação dos campos de datas e horas dentro das classes é efetuada através de classes com este
propósito. Com isso, essas classes contém métodos implementando ações comuns, requeridas de cada
um desses campos que compõem nossas datas e horas. A javax.time utiliza como padrão os métodos
get() retornando valores numéricos para tais campos, por exemplo, getYear() retorna o ano como um
inteiro e os métodos to() retornam as classes específicas, no caso do ano, seria a classe
javax.time.calendar.field.Year. O exemplo apresentado a seguir ilustra o uso do método toYear().
No exemplo, obtemos o ano e, a partir do objeto criado, verificamos se ele é bissexto. Repare como isso
seria trabalhoso caso o ano fosse representado somente pelo tipo primitivo (int).
Teríamos que fazer esse cálculo manualmente, cada um em seu projeto, testar esse código e mantê-lo.
Vemos aqui também, com a obtenção do último dia do mês, o grande valor no uso de classes específicas
em comparação com tipos primitivos. O cálculo do último dia do mês é resolvido automaticamente pela
API, poupando você, desenvolvedor, desse (grande) trabalho.
LocalTime
A classe javax.time.calendar.LocalTime representa uma hora sem time zone ou offset. O trecho de código
apresentado a seguir imprime a hora corrente e, posteriormente, subtrai uma hora e imprime o valor
atualizado.
System.out.println(agora);
agora = agora.minusHours(1);
System.out.println(agora);
LocalDateTime
A classe javax.time.calendar.LocalDateTime representa uma data e hora sem time zone ou offset. O
trecho de exemplo apresentado a seguir imprime a hora corrente e posteriormente subtrai 36 horas e
imprime o valor atualizado.
System.out.println(agora);
agora = agora.minusHours(36);
System.out.println(agora);
No exemplo, caso a primeira data e hora fosse representada por “2009-02-07T11:50:08.093”, após a
subtração de 36 horas, teríamos “2009-02-05T23:50:08.093”. Repare que o cálculo ocorreu
corretamente e alterou até mesmo a data.
OffsetDate
A classe javax.time.calendar.OffsetDate representa uma data com um offset em relação ao UTC. Por
exemplo, ao criar um objeto OffsetDate aqui no Brasil, na região que segue o horário de Brasília, temos
como offset o valor -03h00min, ou seja, se em Brasília são 16h, o horário UTC estará marcando 13h.
O trecho de código apresentado a seguir ilustra a obtenção da data atual considerando o offset:
Caso esteja sob o offset de Brasília, ao imprimir o objeto, a string “2009-02-07-03:00” teria sido
impressa no console, seguindo o padrão ISO-8601 para datas e horas com offset.
Nota Devman - Thread-safe
Thread-safe ou, melhor dizendo, thread-safety é o conceito que usamos para nomear a situação em que
o acesso de múltiplas threads a um elemento no mesmo momento não apresenta resultados
inconsistentes, sem que uma thread tenha interferência sobre os dados manipulados por outra thread.
O trecho de código apresentado a seguir ilustra a obtenção do horário atual considerando o offset. Após
isso, a criação de uma nova instância de OffsetTime contendo o mesmo horário, porém com o offset
encontrado no Japão, que é +09h00min. Após isso, fazemos uma comparação dos dois horários com o
intuito de ilustrar que a comparação levará em conta o offset.
Após a execução do código apresentado, a variável horarioJapaoPosterior irá armazenar o valor true
indicando que a hora no Japão é posterior.
OffsetDateTime
Ao imprimir essa variável no console, temos uma string representando a data, horário e o offset seguindo
o padrão ISO: “2009-02-09T23:20:54.171-03:00”.
Time Zones
O código apresentado a seguir ilustra a obtenção de duas instâncias de ZonedDateTime, a primeira com o
time zone padrão da JVM, configurado como “América/Sao_Paulo” e o segundo forçando o uso do time
zone de Paris.
Ao executar o código apresentado, as datas e horas correspondentes ao time zone informado são
impressas no console:
2009-02-09T23:37:18.968-03:00 UTC-03:00
2009-02-10T03:37:18.968+01:00 Europe/Paris
Repare que a primeira string impressa apresenta o time zone como UTC-03:00, isso por que não há um
identificador correspondente para este time zone, ao contrário do que ocorreu com o time zone de Paris,
que criamos a partir do identificador, que é apresentado na impressão do objeto.
Uma forma equivalente de obter a instância de ZonedDateTime à que utilizamos é a apresentada a seguir,
uma vez que o time zone de Paris corresponde ao offset de uma hora em relação ao UTC:
Matchers e Resolvers
Como já dissemos, a javax.time recorre a conceitos do design pattern Strategy para que você possa
customizar pontos de seu comportamento. Um exemplo pelo qual já passamos foi o dos Adjusters que
permitem a realização de ajustes em datas e horas de forma bastante flexível.
Além dos Adjusters, a javax.time nos provê o conceito de Matchers e Resolvers, que veremos agora.
Matchers
Os Matchers possuem a responsabilidade de realizar consultas em datas e horas de forma muito simples
e flexível. Eles reduzem drasticamente a quantidade de código e a lógica empregada neste tipo de
operação.
O código apresentado a seguir ilustra a consulta à data (e hora) atual. Nesse caso consultamos se o ano
é 2009, valorizando uma variável booleana denominada ano2009. Como estamos realmente em 2009,
essa variável será valorizada com true.
A consulta envolvendo o horário é praticamente idêntica, conforme pode ser visto a seguir:
É possível realizar tais operações, pois cada uma dessas classes (Year, HourOfDay e as outras classes
representando os elementos do calendário) implementa as interfaces necessárias para executar o método
matches().
package br.com.jm.javax.time.human.matchers;
//imports...
System.out.println(diaImpar);
@Override
if (data.getDayOfMonth() % 2 == 0) {
return false;
} else {
return true;
O código apresentado a seguir ilustra o uso da classe DateMatchers para a verificação se a data corrente
é um final de semana.
Como visto, a classe DateMatcher pode nos auxiliar com a verificação de datas e nos auxiliar muito em
tarefas do mundo real como, por exemplo, verificar se determinada data está dentro do domingo de
páscoa ou na sexta-feira santa.
Resolvers
Assim como os matchers, os Resolvers são pontos de extensibilidade disponíveis na javax.time. Através
deles você pode indicar como deseja que uma data inválida seja tratada, por exemplo, ao criar uma data
no dia 29 de fevereiro em um ano que não seja bissexto. A classe javax.time.calendar.DateResolvers
possui algumas implementações dos casos de uso mais comuns onde possa vir a ser necessário o uso de
um Resolver.
O exemplo apresentado a seguir ilustra a criação de uma data utilizando um resolver provido pela classe
DateResolvers que resolve uma data inválida como a próxima data válida.
Ao imprimir tal data no console, temos como data apresentada 2009-03-01. Caso você tenha achado o
código verboso demais, é possível utilizar o recurso de static imports para reduzir a verbosidade. A
Listagem 2 apresenta tal opção.
Listagem 2. Uso de static imports para minimizar a verbosidade do código no uso de Resolvers
package br.com.jm.javax.time.human.resolvers;
import javax.time.calendar.DateResolvers;
import javax.time.calendar.LocalDate;
resolveDate(isoYear(2009),
monthOfYear(2), dayOfMonth(29));
System.out.println(data);
Períodos
Os períodos, dentro da javax.time, são cidadãos de primeira-classe. Isso quer dizer que existem classes
capazes de representá-los, ao contrário do que você encontra nas APIs atuais, onde não há forma padrão
para representar períodos.
Através da representação dos períodos, podemos expressar durações de tempo da maneira tratada pelos
seres humanos como, por exemplo, a duração de uma reunião ou de suas férias.
Como dito, existe uma classe para cada parte do período:
javax.time.period.field.Days
javax.time.period.field.Hours
javax.time.period.field.Minutes
javax.time.period.field.Months
javax.time.period.field.Seconds
javax.time.period.field.Weeks
javax.time.period.field.Years
A partir desse momento, o objeto representa o determinado período para o qual foi criado. Ao imprimir
tal período no console, é impressa sua representação de acordo com o padrão ISO-8106. Essa
representação para o período de duas horas é “PT2H”. Uma das grandes vantagens no uso de objetos
especializados para os períodos é permitir que você execute operações nesse objeto. Isso resultará na
criação de outro objeto para representar o novo período resultante da operação. O objeto que representa
o período também é imutável. A seguir, uma subtração de quarenta minutos de nosso período de duas
horas:
period = period.minusMinutes(40);
Ao executar tal operação, o período ainda permanece com duas horas e menos quarenta minutos e
necessita ser normalizado para representar o período real, que seria de uma hora e vinte minutos.
Normalização
O método normalize() retorna uma cópia do período normalizado para os limites padrão dos campos de
data e hora, levando em conta as seguintes regras:
12 meses em um ano;
60 minutos em uma hora;
60 segundos em um minuto;
1.000.000.000 de nanossegundos em um segundo.
Por exemplo, um período de 13 meses é normalizado para um ano e um mês e a criação de um período
de 5000 minutos com o código Period.minutes(5000) resulta em um período normalizado de 83 horas e
20 minutos.
Caso você necessite ainda realizar a normalização por dias, quebrando cada 24 horas em um dia, você
deve utilizar o método normalizedWith24HourDays(), que neste nosso exemplo resultaria em um período
de três dias, onze horas e vinte minutos.
Para revisar o que foi visto e por em prática o conteúdo apresentado, veja a Apresentação “Javax.time”.
Conclusão
Você conheceu um pouco sobre a nova especificação para representar datas e horas dentro da plataforma
Java. Essa nova API tenta trazer várias facilidades e corrigir problemas conhecidos das opções atuais,
buscando transformar seu código em algo mais legível e simples. Passamos por todos os pontos mais
importantes da nova API, fornecendo exemplos pontuais e focados, tentando ambientar você com os
novos conceitos.
A JSR 310, que está definindo a nova API, é aberta e você pode contribuir para seu desenvolvimento.
Acesse o site da JSR (veja seção de Links) e faça parte da definição do futuro da linguagem. O código
apresentado neste artigo está disponível para download no site da revista, contando com um JAR da
javax.time compilado durante a escrita do artigo.
Como a API ainda está sendo concebida, é possível que quando o artigo chegar a você, leitor, algo possa
ter sido alterado. Devido a isso, deixamos aqui a sugestão de baixar os fontes da última versão da API e
adequar os exemplos às possíveis alterações. Acesse também a lista de discussões para entender o rumo
que está sendo dado à API.
Links
jsr-310.dev.java.net
Site de desenvolvimento da JSR 310, onde você pode obter acesso a todos os artefatos relacionados à
nova API, além de participar de seu desenvolvimento, cadastrando-se nas listas de discussão.
jcp.org/en/jsr/detail?id=310
Site oficial da JSR 310, parte do JCP.
joda-time.sourceforge.net
Site da Joda Time, API que está servindo de base para o desenvolvimento da JSR 310.
Java ME Platform SDK 3
No momento em que uma ferramenta que usamos com frequência surge com uma versão nova, é
sempre válido dar uma olhada nas principais novidades que ela vem trazendo. Estas novidades,
geralmente, vêm para melhorar nossa produtividade, além de nos proporcionar mais subsídios, a fim de
desenvolvermos aplicações ainda melhores.
Assim como os seres vivos, os softwares de computador também evoluem a partir do momento em que
se inicia um novo ciclo. No caso da principal ferramenta de desenvolvimento da plataforma Java ME, o
Wireless Toolkit, este seu novo ciclo trouxe-lhe evoluções bastante expressivas. Mudanças que vão desde
o nome, passando pelo novo conceito da ferramenta, até chegar às suas novas funcionalidades.
A ferramenta que antes era considerada um simples toolkit (caixa de ferramenta), agora passa a ser um
completo SDK, contando com um ambiente integrado de desenvolvimento. Editor de código, emuladores,
nova máquina virtual e APIs são somente algumas das muitas novidades trazidas por esta nova
ferramenta. Sem falar que até o seu nome é novidade, pois agora a ferramenta é chamada de Java ME
Platform SDK 3.
Com todas essas novidades é possível constatar o grande passo que a plataforma Java ME dá em direção
a uma maior popularização da plataforma. Pois com o ambiente de desenvolvimento integrado baseado
no NetBeans, novos desenvolvedores terão mais facilidades de criar as suas primeiras aplicações, o que
garante uma maior chance de crescimento da comunidade Java ME.
Segundo a Biologia, o processo permanente de mudança que vem transformando a vida na Terra, desde
o seu princípio mais simples até a sua atual diversidade, é conhecido como Evolução. A evolução ocorre
quando um ser vivo se reproduz, e pequenas mudanças aleatórias nos seus genes fazem com que o seu
descendente seja diferente dele próprio. Essas mudanças, se benéficas, fazem com que o indivíduo
sobreviva o tempo necessário para se reproduzir, dando início a um novo ciclo de transformações, o que
resulta no surgimento de novas espécies.
Num paralelo entre o processo de evolução dos seres vivos com o de um software de computador,
verificamos como ambos são parecidos. O software, geralmente, inicia o seu clico de vida de forma bem
simples, com poucas funcionalidades, somente com o intuito de atender certa demanda do momento.
À medida que vai sendo utilizado, o software, se for bom, acaba aguçando a criatividade dos usuários,
que solicitam novas versões mais aperfeiçoadas e com mais funcionalidades. Estas solicitações induzem o
software a entrar no clico de evoluções, resultando em novos releases.
Neste artigo, falaremos sobre como este processo evolutivo dos softwares atingiu o Wireless Toolkit
(WTK). Apresentaremos algumas das principais novidades que a Sun Microsystems preparou, justificando
a mudança do seu nome para Java ME Platform SDK 3.
O fato de a Sun ter mudando o nome do WTK não deve causar muito espanto aos desenvolvedores,
tendo em vista que esta não é a primeira vez. Desde o surgimento da plataforma Java ME, por volta de
1999, esta já é a segunda vez que a Sun muda o nome da ferramenta.
O nome original, Java 2 Platform Micro Edition Wireless Toolkit, foi substituído em 2007, devido à nova
versão 1.5 (ou 5) da plataforma Java. Desde então, a Sun retirou o famigerado “2” de todos os nomes de
suas plataformas e ferramentas. Com isto, o que era J2ME virou Java ME, o que também acabou
refletindo na primeira mudança de nome do WTK para Sun Java Wireless Toolkit.
A mudança do nome na nova versão, que agora insere o termo SDK (Standard Development Kit), se deve
ao fato da ferramenta proporcionar um ambiente de desenvolvimento completo, e não somente os
emuladores, ferramentas de monitoração (e.g. monitor de memória), códigos de exemplo,
documentação, etc. O pacote agora também vem com editor de código com assistência de codificação
(veja a seção “Netbeans “Lite”). Além disso, a plataforma Java ME é suportada de forma muito mais
completa (o antigo WTK suportava apenas a configuração CLDC, exigindo toolkits separados para CDC e
outras variantes). Um arsenal completo que eleva a categoria da ferramenta, justificando o acréscimo do
termo SDK.
O nome original, Java 2 Platform Micro Edition Wireless Toolkit, foi substituído em 2007, devido à nova
versão 1.5 (ou 5) da plataforma Java. Desde então, a Sun retirou o famigerado “2” de todos os nomes de
suas plataformas e ferramentas. Com isto, o que era J2ME virou Java ME, o que também acabou
refletindo na primeira mudança de nome do WTK para Sun Java Wireless Toolkit.
A mudança do nome na nova versão, que agora insere o termo SDK (Standard Development Kit), se deve
ao fato da ferramenta proporcionar um ambiente de desenvolvimento completo, e não somente os
emuladores, ferramentas de monitoração (e.g. monitor de memória), códigos de exemplo,
documentação, etc. O pacote agora também vem com editor de código com assistência de codificação
(veja a seção “Netbeans “Lite”). Além disso, a plataforma Java ME é suportada de forma muito mais
completa (o antigo WTK suportava apenas a configuração CLDC, exigindo toolkits separados para CDC e
outras variantes). Um arsenal completo que eleva a categoria da ferramenta, justificando o acréscimo do
termo SDK.
NetBeans
Lite
Após quase dez anos desde sua chegada, a máquina virtual da Java ME, a Kilo Virtual Machine (KVM),
finalmente encerra o seu ciclo de vida. Este fim já era esperado, já que há alguns anos, a KVM já não
estava mais presente nos dispositivos móveis disponíveis no mercado, sendo esta substituída pela CLDC
HotSpot Virtual Machine. Um projeto mais moderno e robusto, que já conseguia atender melhor as atuais
demandas por desempenho.
O que prolongou um pouco mais a vida da KVM foi o fato da Sun continuar utilizando-a em seus
emuladores. Até a última versão do WTK, a 2.5.2, todos os emuladores ainda eram baseados na KVM. No
entanto, com a chegada do Java ME Platform SDK 3 a Sun finalmente “aposentou” a KVM, trazendo de
uma vez por todas, a CLDC HotSpot Virtual Machine também para os seus emuladores, inclusive para a
configuração CDC. Com esta mudança, o comportamento das aplicações no emulador ficou mais parecido
ao encontrado nos dispositivos reais.
Dentre as principais vantagens da CLDC HotSpot Virtual Machine, comparada à KVM, estão a compilação
dinâmica das instruções de bytecode em instruções nativas (veja o quadro “Compilação Just-in-time
(JIT)”), menor consumo e fragmentação de memória, maior economia da bateria, dentre outras. Em
termos de números, a execução de uma instrução compilada dinamicamente, chega a ser cinquenta
vezes mais rápida do que uma instrução interpretada.
Para conhecer um pouco mais sobre a CLDC HotSpot Virtual Machine e a sua chegada aos dispositivos
móveis, veja a seção Links, além do artigo “Java: Uma perspectiva”, da Edição 65.
Descobrindo as Diferenças
Existe na Internet uma iniciativa chamada de Wireless Universal Resource File (WURFL), aonde
desenvolvedores e entusiastas vêm tentando catalogar todos os dispositivos móveis disponíveis no
mercado, assim como suas características. O objetivo é fornecer uma base de dados centralizada para os
desenvolvedores. Neste caso, um arquivo XML, afim de que eles possam conhecer, antecipadamente, as
principais diferenças que existem de um dispositivo para outro, além de ter uma idéia da fatia de
mercado que suas aplicações estão abrangendo.
Com o objetivo de facilitar o acesso às informações providas pelo WURFL (para saber mais sobre esta
iniciativa, veja a seção Links), a Sun desenvolveu uma nova funcionalidade, no Java ME Platform SDK 3,
chamada Device Database Search, que acessa essa base de dados de uma forma fácil e rápida. O usuário
pode tanto solicitar para ver a lista completa de todos os dispositivos disponíveis, como ele também pode
aplicar alguns filtros (e.g. fabricante, modelo, JSRs suportadas, tamanho de tela, etc.), a fim de facilitar a
busca pelo dispositivo desejado. O resultado é apresentado numa lista, onde modelo e fabricante são
identificados. Para ver os detalhes de um determinado dispositivo, basta selecioná-lo na lista, que todas
as suas informações serão exibidas numa outra lista ao lado (veja Figura 2).
A equipe do projeto Lightweight User Interface Toolkit (LWUIT) deve estar orgulhosa com esta conquista.
Tendo em vista a grande popularização deste projeto entre os desenvolvedores Java ME, a Sun não
perdeu tempo em tentar transformar este projeto numa espécie de padrão dentro da comunidade de
desenvolvedores. Os engenheiros da Sun devem ter percebido a quantidade de projetos que existem na
Internet, que visam melhorar a vida dos desenvolvedores, no tocante do desenvolvimento de interfaces
gráficas mais sofisticadas. Com isso, a Sun não hesitou e incorporou o LWUIT como uma de suas
bibliotecas padrão, disponibilizou uma aplicação exemplo e integrou uma de suas ferramentas utilitárias,
o Resource Manager, dentro do Java ME Platform SDK 3. Alavancando de vez o nome LWUIT dentro da
comunidade Java ME.
Figura 2: Ferramenta
Device Database Search
retornando todos os dispositivos da Nokia que
suportam MIDP 2.0.
O LWUIT, para quem ainda não conhece, é um framework de componentes gráficos inspirado no Swing
da plataforma Java Standard Edition (Java SE), especificamente modelado e construído para ambientes
restritos em poder de processamento e memória, como o dos dispositivos móveis. O LWUIT traz para o
mundo móvel, algumas das funcionalidades para o desenvolvimento de interfaces gráficas já conhecidas
no desktop e que são bem características do Swing, como por exemplo, layouts (e.g. FlowLayout),
renders (componentes que especificam a forma como um objeto é desenhado na tela), manipuladores de
evento (e.g. ActionListener), etc.
Com toda esta integração, para um desenvolvedor acrescentar o suporte ao LWUIT à sua aplicação
dentro do Java ME Platform SDK 3, basta somente alguns passos: acessar as propriedades do projeto,
selecionar “Libraries & Resources”, clicar no botão “Add Library” e selecionar a biblioteca LWUIT, que já
aparece na lista, junto com as demais bibliotecas disponíveis na ferramenta. Além disso, a aplicação
exemplo é bem completa, o que serve como uma boa referência inicial para os desenvolvedores que
estão começando no LWUIT (para saber mais sobre LWUIT, veja a Edição 60 e a seção Links). Ela
apresenta diversos exemplos de várias partes do framework.
Encontrando o
Gargalo
Como se os problema de lógica não fossem o bastante, os problemas de desempenho também tem sido
uma constante nas aplicações atuais, principalmente nas móveis, devido às exigentes demandas por mais
funcionalidades, sem falar das restrições de processamento, inerentes aos dispositivos móveis.
A fim de também facilitar mais esta tarefa dos desenvolvedores, já que por muito tempo esta também foi
executada com o auxilio de APIs como System.out.println() e System.currentTimeMillis(), foram criados
os monitores de processamento (profiler). São ferramentas que monitoram toda a execução da aplicação,
registrando os tempos de execução e a quantidade de vezes que cada método foi executado. Informações
muito valiosas quando se está à procura do “gargalo” de um determinado processo. No Java ME Platform
SDK 3, esta ferramenta de monitoração é a mesma encontrada em outras distribuições do NetBeans,
também usada para testes em aplicações Java SE e Java EE.
Para ativar o profiler para determinada aplicação no Java ME Platform SDK 3, basta habilitar a opção
Enable profiler nas propriedades do emulador utilizado. Durante a execução, o profiler registra num
arquivo (e.g. data.prof), todas as informações sobre o código que está sendo executado. Ao final da
execução, é preciso informar ao SDK, através da opção Profile>Import Java ME SDK Snapshot, o caminho
do arquivo gerado pelo profiler, a fim de que a informação coletada seja apresentada. Nesta nova tela
que é mostrada (veja Figura 3), o desenvolvedor pode ver o tempo gasto para executar cada método e a
quantidade de vezes em que ele foi executado, além de ainda poder agrupar todas estas informações por
classe ou por pacote. Dando uma visão mais macro dos tempos de execução de cada componente da
aplicação.
Figura 3: Tela do profiler que mostra os tempos de execução dos métodos da aplicação.
E mais JSRs
A cada dia novas APIs são finalmente especificadas, tornando-se aptas a serem implementadas por
algum fabricante, que suporte a plataforma Java ME em seus dispositivos móveis. O que normalmente
acontece é a implementação dessas APIs chegarem primeiro às ferramentas de desenvolvimento, tendo
em vista uma maior facilidade de implementá-las em plataformas Windows ou Linux, por exemplo, para
depois chegarem aos dispositivos.
No Java ME Platform SDK 3, três novas APIs estão finalmente disponíveis para os desenvolvedores:
Mobile Sensor API (JSR 256) (veja o artigo da Edição 55), XML API for Java ME (JSR 280) e a Java
Binding for the OpenGL ES API (JSR 239). A primeira é responsável por fornecer acesso a alguns
sensores (e.g. acelerômetro) disponíveis em certos dispositivos móveis (veja o quadro “Trabalhando com
Sensores no Emulador”).
A JSR 280, por sua vez, define uma API exclusiva para manipular arquivos XML com parsers SAX2 e
DOM. A idéia é acabar com a fragmentação que existe atualmente, onde cada API que precisa manipular
XML define o seu próprio mecanismo para desempenhar este trabalho (e.g. Web Services Specification
(JSR 172)). E finalmente, a JSR 239 vem para prover o suporte ao desenvolvimento de gráficos 3D
através da biblioteca OpenGL ES, a qual é um subconjunto da OpenGL 1.3.
Para fechar o pacote das novas API, o Java ME Platform SDK 3 também disponibiliza aplicações exemplo
para cada nova API suportada, inclusive as que já eram suportadas. Um ótimo “pontapé inicial” para
quem está começando a trabalhar com as APIs mais recentes.
É inquestionável a qualidade dos emuladores disponibilizados pelo Java ME Platform SDK 3, assim como
os de suas versões anteriores. Todos eles seguem à risca cada ponto das especificações das APIs, dando
maior segurança ao desenvolvedor sobre a corretude do seu código. Se nenhuma exceção for lançada,
alertando sobre alguma operação indevida, é porque ele está no caminho certo.
No entanto, por mais que as especificações sirvam para definir uma base comum, além de diminuir as
chances de incompatibilidade entre as diferentes implementações, no mundo real as coisas não são tão
perfeitas assim. É comum encontrar problemas de incompatibilidade entre uma implementação feita para
o Windows, que não funciona no Linux, e vice-versa. No mundo móvel então, é ainda pior. Aplicações
feitas para rodar num dispositivo da Nokia podem precisar de ajustes para rodar perfeitamente num
Motorola ou Sony Ericsson, por exemplo.
Por mais que sigam a risca o que está na especificação, sempre é possível ter algo na implementação
(inclusive bugs) que gera um comportamento diferente. A complexidade e as restrições dessas
plataformas móveis potencializam ainda mais este problema.
Tendo em vista essas possíveis diferenças, os testes em outras plataformas se tornam muito importantes
para uma aplicação Java ME, que deseja rodar em dispositivos de mais de um fabricante. Nesta versão do
Java ME Platform SDK 3 é possível importar outros emuladores desenvolvidos, por exemplo, pela Nokia,
Samsung, Motorola, Sony Ericsson, dentre outros, para dentro da ferramenta e usá-los para testar suas
aplicações. Uma funcionalidade já encontrada no NetBeans Mobility Pack.
Para importar novos emuladores, é preciso informar o caminho do SDK do fabricante, o qual os
emuladores pertencem. Para isto, deve-se acessar Tools>Java Platforms, selecionar o tipo de plataforma
J2ME, clicar em Add Plaform, selecionar Custom Java ME MIDP Platform Emulador e informar os caminhos
solicitados pelo restante do wizard. Após ter configurado a nova plataforma, basta acessar as
propriedades do projeto e selecionar a nova plataforma e o emulador a ser utilizado.
Testes em emuladores é realmente uma “mão na roda”, pois facilitam muito o desenvolvimento, tendo
em vista a rapidez que é por em execução a aplicação no emulador. Entretanto, eles nunca vão substituir
o teste no dispositivo real, pois, se de um emulador para outro já existem diferenças, imagine do
emulador para o hardware.
Suporte Oficial
Com exceção do sistema operacional Symbian, outros sistemas como Palm OS e Windows Mobile,
também bem populares no mercado, nunca foram referência pelo seu suporte à plataforma Java ME, pelo
contrário. Esta questão, acredito, deve-se ao fato que ambos possuem, desde muito cedo, suas próprias
plataformas nativas (baseadas em C) de desenvolvimento de aplicações. Ambas muito bem difundidas e
poderosas, e que acabou gerando uma comunidade de desenvolvedores duradoura.
Com relação ao Palm OS, a Sun até que se esforçou na época do lançamento da primeira versão do
Wireless Toolkit (ainda MIDP 1.0), disponibilizando uma máquina virtual que podia ser instalada nos
dispositivos da empresa Palm.
No entanto, a investida não vingou, talvez pelo desinteresse da própria Palm, e o projeto ficou pelo
caminho. A IBM também tentou, lançando uma máquina virtual chamada Websphere Everyplace Micro
Environment (WME), com suporte ao MIDP 2.0. A Palm dessa vez até esboçou um incentivo, publicando a
VM da IBM no seu site como um produto homologado. Porém, em 2008, o incentivo acabou e a Palm
retirou o apoio. Hoje este projeto está parado no tempo, assim como o próprio Palm OS, que vem
sumindo do mercado aos poucos.
A história da plataforma Java ME no sistema operacional da Microsoft não é muito diferente da vivida pela
Palm. Esta também é marcada pela dificuldade de encontrar uma máquina virtual robusta e sólida, o que
ajudaria a difundir melhor a Java ME nos populares PDAs da empresa de Bill Gates.
No entanto, por mais que o suporte seja fraco, o Windows Mobile chega ainda a ser melhor que o Palm
OS, com relação a Java ME. Nas versões para smartphones do Windows Mobile, por exemplo, até existe
uma máquina virtual que já acompanha o dispositivo. Por mais que seja restrito em recursos, ainda serve
para rodar aplicações mais simples. A IBM também investiu na Java ME para o Windows Mobile,
disponibilizando uma versão paga da sua máquina virtual, WME, voltada para versões anteriores deste
sistema.
Mas agora, as coisas parecem que vão melhorar para os desenvolvedores Java ME que querem rodar
suas aplicações no Windows Mobile.
A Sun está disponibilizando, no Java ME Platform SDK 3, uma máquina virtual para o Windows Mobile 6,
que pode tanto ser instalado em um emulador do próprio sistema operacional no PC, quanto num
dispositivo real.
Tudo o que o desenvolvedor precisa fazer é instalar a maquina virtual (veja Figura 4), através do arquivo
‘sun-java-cldc-emu.cab´, que se encontra disponível no SDK na pasta ‘JavaMESdkHome\on-
device\winmobile-arm´, no emulador ou no dispositivo. Além de executar as aplicações, o desenvolvedor
ainda tem a possibilidade de depurar suas aplicações tanto no emulador quanto no dispositivo real.
Com mais esta plataforma dando suporte à Java ME, agora com a assinatura da Sun, a plataforma Java
dá mais um passo muito importante na consolidação do seu nome nas principais plataformas móveis do
mercado.
Conclusão
A Sun, com certeza, acertou com o Java ME Platform SDK, pois a falta do ambiente de codificação do
Wireless Toolkit atrapalhava, de certa forma, os novos desenvolvedores. O problema era que estes não
conseguiam, rapidamente, codificar um “Hello, World!”. Haja vista que precisavam usar um editor de
código externo, importar o projeto para o toolkit, etc.
Com o Java ME Platform SDK tudo ficou mais integrado. A facilidade de criar um projeto, codificar,
escolher o emulador e colocar para executar são características muito importantes, que todo iniciante,
em qualquer plataforma, gosta de ter.
Entretanto, o Java ME Platform SDK é indicado somente para aplicações de teste ou pequenas, pois
algumas funcionalidades importantes no desenvolvimento de aplicações mais complexas, como
depuração e controle de versão, não estão disponíveis. Nesses casos, o recomendado mesmo é o
NetBeans Mobility Pack, que oferece o ambiente completo do NetBeans mais uma série de ferramentas
específicas para Java ME
QUADRO INFORMATIVO
A característica da plataforma Java que permite que suas aplicações sejam possíveis de serem
executadas em diferentes plataformas (e.g. Windows e Linux), sem necessidade de recompilação, é o
fato do seu código-fonte ser compilado para uma representação intermediária, conhecida como bytecode.
Mas sua execução depende de outra aplicação, esta sim dependente de plataforma: a máquina virtual,
que interpreta o bytecode.
Os ganhos em portabilidade com este código intermediário, por outro lado, gera problemas de
desempenho, pois a interpretação é bem menos eficiente que a execução de código nativo. Entretanto, o
problema pode ser resolvido com ajuda da técnica de compilação Just-in-time (JIT), que consiste na
compilação do bytecode para código nativo durante a execução da aplicação.
No momento em que um método vai ser executado, este é convertido numa representação de código de
máquina. Por isso o termo Just-in-time que, em português, significa “no momento exato”. Este código
compilado fica salvo na memória, pronto para execuções futuras.
Além da plataforma Java, a .NET também utiliza esta técnica. Neste caso, o código intermediário
convertido pela máquina virtual .NET é o Microsoft Intermediate Language (MIL). Para saber mais sobre a
JIT, veja a seção Links.
Existem algumas APIs Java ME que interagem com alguns tipos de hardware ou serviço, que vem
integrado a algumas linhas de dispositivos móveis. Por exemplo, a Mobile Sensor API (JSR 256), que
interage com sensores (e.g. acelerômetro); a Location API (JSR 179), que captura os dados de um GPS;
e a Payment API (JSR 229), que fornece serviço de pagamentos em geral.
Imaginar como se testa uma aplicação desenvolvida com estas APIs, no dispositivo real, não é difícil. Pois
neste caso, o hardware ou serviço está presente no dispositivo e a API vai acessá-lo para poder retornar
suas informações. Mas o que acontece quando não se tem o dispositivo real em mãos, para testar a
aplicação? A resposta é simples: para cada API que interage com um hardware ou serviço, os emuladores
do Java ME Platform SDK executam tais papéis, simulando-os.
No caso da JSR 256, para a qual a emulação de sensores é mais uma novidade do Java ME Platform SDK,
o desenvolvedor pode alterar as informações que o acelerômetro retorna. Desta forma, o desenvolvedor
pode informar novos valores para as coordenadas x, y e z, que representam a leitura tridimensional deste
tipo de hardware.
Para acessar as ferramentas de simulação dos emuladores, o desenvolvedor precisa acessar a opção
View>External Events Generator, disponível no próprio emulador. Depois disto, uma nova tela será
apresentada, com uma série abas nela. Cada aba, por sua vez, representa um tipo de hardware ou
serviço que pode ser simulado. No caso do acelerômetro, a aba Sensors é a que fornece os meios para
simulá-lo. Nesta mesma aba, o Java ME Platform SDK ainda fornece a simulação de um sensor de
temperatura.
Links
http://java.sun.com/javame/downloads/sdk30ea.jsp
Página de download do Java ME Platform SDK 3
http://java.sun.com/j2me/docs/pdf/CLDC-HI_whitepaper-February_2005.pdf
CLDC HotSpot™ Implementation Virtual Machine
http://wurfl.sourceforge.net/
WURFL: Wireless Universal Resource File
https://lwuit.dev.java.net/
LWUIT: Lightweight User Interface Toolkit
http://pt.wikipedia.org/wiki/JIT
JIT: Just-in-time
Saiba Mais
www.devmedia.com.br/articles/viewcomp.asp?comp=10574
Auditório Virtual DevMedia - Empreendedorismo na era dos Celulares
www.devmedia.com.br/cursos/listcurso.asp?curso=17
Curso Online - Introdução ao desenvolvimento para celulares com Java ME
www.devmedia.com.br/cursos/listcurso.asp?curso=96
Curso Online - Introdução ao uso de Web Services em Java ME
www.devmedia.com.br/cursos/listcurso.asp?curso=95
Curso Online - Criação de Interfaces Gráficas em Java ME
www.devmedia.com.br/cursos/listcurso.asp?curso=94
Curso Online - Java ME e Banco de Dados
www.devmedia.com.br/cursos/listcurso.asp?curso=93
Curso Online - Trabalhando com SMS e MMS em JavaME
www.devmedia.com.br/cursos/listcurso.asp?curso=92
Curso Online - Conectividade em Java ME
www.devmedia.com.br/cursos/listcurso.asp?curso=59
Curso Online - Desenvolvendo um Software Acadêmico para Celular
www.devmedia.com.br/articles/viewcomp.asp?comp=8585
Java Magazine 44 - A Plataforma Java ME: Parte 1
www.devmedia.com.br/articles/viewcomp.asp?comp=8569
Java Magazine 45 - Mini-curso: programação Java ME: Parte 2
www.devmedia.com.br/articles/viewcomp.asp?comp=10119
Java Magazine 46 - Programação Java ME: Parte 3
www.devmedia.com.br/articles/viewcomp.asp?comp=8547
Java Magazine 47 - Programação Java ME: Parte 4
www.devmedia.com.br/articles/viewcomp.asp?comp=8523
Java Magazine 48 - Programação Java ME: Parte 5
www.devmedia.com.br/articles/viewcomp.asp?comp=8303
Java Magazine 49 - Mini-curso de java ME: Parte 6
www.devmedia.com.br/articles/viewcomp.asp?comp=8379
Java Magazine 56 - Opinião: Android versus Java ME
www.devmedia.com.br/articles/viewcomp.asp?comp=8459
Java Magazine 56 - JSR 257: Contactless Communication API
www.devmedia.com.br/resumo/default.asp?idrev=123#1517
Java Magazine 57 - Web Feed Reader ME
www.devmedia.com.br/articles/viewcomp.asp?comp=10214
Java Magazine 60 - LWUIT: “Swing” para Java ME
www.devmedia.com.br/articles/viewcomp.asp?comp=11396
Java Magazine 64 - Google Maps em aplicações móvies
www.devmedia.com.br/articles/viewcomp.asp?comp=4895
WebMobile 11 - JavaME usando GPS
www.devmedia.com.br/articles/viewcomp.asp?comp=8057
WebMobile 15 - J2ME Polish - Desenvolvendo interfaces gráficas para aplicações JavaME
www.devmedia.com.br/articles/viewcomp.asp?comp=8275
webmobile 17 - Desenvolvimento ponta-a-ponta: cliente JavaME com servidor JavaEE
www.devmedia.com.br/articles/viewcomp.asp?comp=8274
WebMobile 17 - Criando jogos de ação em 2D com a API de jogos em JavaME
www.devmedia.com.br/articles/viewcomp.asp?comp=9353
WebMobile 18 - Desenvolvimento ponta-a-ponta: Cliente JavaME com servidor JavaEE
www.devmedia.com.br/articles/viewcomp.asp?comp=10333
WebMobile 20 - Escrevendo Jogos para Celular em JavaME
www.devmedia.com.br/articles/viewcomp.asp?comp=11440
WebMobile 22 - Construindo Interfaces Gráficas na Prática
www.devmedia.com.br/articles/viewcomp.asp?comp=9509
Vídeo - Introdução do Desenvolvimento de Jogos em Java ME - Parte 1
www.devmedia.com.br/articles/viewcomp.asp?comp=9749
Vídeo - Introdução do Desenvolvimento de Jogos em Java ME - Parte 2
www.devmedia.com.br/articles/viewcomp.asp?comp=9750
Vídeo - Introdução do Desenvolvimento de Jogos em Java ME - Parte 3
Estratégias de Integração de Aplicações Java EE
O artigo apresenta técnicas e práticas provadas de integração de aplicações Java com outras aplicações,
sistemas e bancos de dados. Projetos de integração apresentam muitas complexidades técnicas e muitas
possibilidades técnicas em Java tais como JMS, JCA, JDBC, RMI, HTTP e SOAP. O artigo mostra como
escolher estas tecnologias e como utilizá-las adequadamente através de padrões de integração de
aplicações (EIP).
O artigo é útil para que desenvolvedores possam conhecer os riscos associados à integração de sistemas
Java com outros sistemas e aplicar as melhores práticas de mercado e padrões de integração para
mitigar e eliminar estes riscos. Projetos com riscos técnicos reduzidos possuem maior garantia de
sucesso, maior qualidade, estabilidade e manutenções mais simples e facilitadas.
Em projetos de TI que requerem integrações de todo tipo, como bancos de dados relacionais, bases de
dados em outros formatos, sistemas de filas de mensagens, aplicações legadas em plataformas baixas e
altas ou aplicativos como CRM (Customer Relationship Management) e ERP (Enteprise Resource
Planning).
Integrar aplicações Java com outros sistemas não é uma tarefa trivial. Para reduzir riscos e manter
projetos sob controle, devemos usar práticas provadas de integração de sistemas.
As melhores práticas de integração são denominadas padrões EAI (EAI Patterns) e foram popularizadas
por Gregor Hohpe através do seu livro Enterprise Integration Patterns. Estas práticas permitem que um
arquiteto ou desenvolvedor escolha as estratégias mais eficientes de integração de sistemas Java com
outros sistemas e produza soluções mais perenes e econômicas.
Estas práticas de integração também são fundamentais para empresas que estejam buscando iniciativas
SOA e BPM, pois permitem gerir adequadamente a criação e evolução de serviços (ativos) em
repositórios de serviços (ESB).
Um processo simples para que você possa aprender a integrar sistemas em projetos envolve os seguintes
passos: (1) Coletar os requisitos arquiteturais de interoperabilidade; (2) Desenvolver soluções
independentes de tecnologia para cada requisito arquitetural coletado; (3) Estudar os exemplos
disponibilizados na referência [1]; (4) Estudar as tecnologias Java mais adequadas para cada requisito
(ex: WebServices ou RMI); (5) Implementar os cenários com as tecnologias Java escolhidas; (6) Testar
as soluções em ambiente de homologação.
Integrar aplicações não é uma tarefa trivial. Protocolos diversos, APIs exóticas, tecnologias e ambientes
complexos e cronogramas agressivos são alguns aspectos que desafiam diariamente analistas
desenvolvedores Java EE.
Conto aqui uma história que resume estes desafios. Uma equipe necessitava enviar emails de sua
aplicação Java. Uma tarefa aparentemente trivial, que muitos desenvolvedores vivem todos os dias. A
equipe não possuía experiência com a API JavaMail, mas com a ajuda de um recurso externo a equipe se
capacitou e desenvolveu o código necessário para suportar a integração, que neste caso ocorreria com o
servidor Microsoft Exchange. Código implementado, testes internos realizados com sucesso e aprovação
gerencial. Aparentemente o problema foi resolvido.
A vida real, entretanto, guarda surpresas. Na véspera da semana de implementação, o código foi
implantado na empresa do cliente (vamos chamá-la de empresa ACME), mas os casos de uso que
requeriam a integração com o Microsoft Exchange não funcionavam. Todos os olhos e atenções (e culpas)
foram lançados para o servidor de email da Microsoft. Após a descoberta que o servidor de email do
cliente (v2007) estava em uma diferente versão do ambiente interno da desenvolvedora (v2003),
modificações no ambiente de desenvolvimento foram realizadas para adequar o servidor. Nenhum
resultado positivo foi alcançado. O pânico se instalou!
Mais investigações foram realizadas e a equipe descobriu que o servidor de email de produção não residia
na mesma rede do servidor de aplicação do cliente, mas em um distante local remoto (em outra cidade)
em um provedor com um acesso controlado por um firewall. O analista de infra-estrutura do cliente não
foi questionado o bastante para informar este fato. Para encurtar a história, a equipe descobriu que o
envio de emails neste ambiente requeria o uso de certificados e protocolos com garantia de transporte
seguro (SMTP sobre SSL). Noites mal-dormidas, um atraso de algumas semanas no cronograma, um
cliente relativamente estressado e o desafio foi finalmente vencido.
Que lições podemos aprender com esta história? Enumero algumas abaixo:
• Certificar que as versões dos ambientes a serem integrados sejam exatamente iguais;
• Analisar cuidadosamente a topologia, isto é, a organização física do ambiente de produção do
cliente.
O leitor mais experiente pode questionar as lições aprendidas neste exemplo. “Estas lições são óbvias”.
Mas devemos lembrar que senso comum não é prática comum.
Dica: Seja um “Indiana Jones” de aplicações Java na sua empresa. Faça uma arqueologia de software nas
aplicações Java da sua empresa e colete os erros e lições aprendidas. Os erros do passado são uma
excelente fonte de aprendizado para projetos futuros.
O objetivo deste artigo é mostrar a você como tornar senso comum em prática comum, isto é, evitar
erros comuns de integrações de sistemas em projetos e evitar estresses desnecessários. Vamos abordar
estratégias e técnicas para organizar o seu trabalho, organizados da seguinte forma nas seções seguintes
deste artigo:
“Se você se conhece, mas não o inimigo, para cada vitória sofrerá uma derrota”, Sun Tzu, A Arte da
Guerra.
A interoperabilidade de aplicações pode se apresentar em quatro estilos, conforme Gregor Hohpe [1].
Pare um minuto e responda: Qual o estilo requerido no exemplo citado de interoperabilidade com o
Microsoft Exchange? (Resposta no final deste artigo).
1. Nível de dados. Como integrar fontes de dados relacionais ou em outros formatos e como reduzir
a sua redundância? A integração em nível de dados foca na movimentação de dados entre
aplicações com o objetivo de compartilhar o mesmo dado entre aplicações diferentes;
2. Nível de Interfaces de Aplicação (API). Como integrar APIs de aplicação em outras tecnologias
que não sejam Java? A integração via APIs passa pela chamada de funções através de protocolos
síncronos (RPC) ou assíncronos (mensagens);
3. Nível de Processos de Negócio. Domínio do mundo BPM/SOA, a integração em nível de processos
de negócio foca no desenvolvimento de componentes de alto nível que irão fornecer interfaces de
alto nível que podem ser considerados serviços;
4. Nível de Apresentação. Popularmente conhecidos como mash-ups, integram aplicações através de
um conjunto de vários portlets (janelas visuais independentes) que residem em um determinado
portal. A Figura 1 mostra um exemplo de integração neste nível.
Um novo teste: Qual o nível associado ao exemplo citado de interoperabilidade com o Microsoft
Exchange? (Resposta no final deste artigo).
Finalmente, devemos considerar também a questão da topologia da aplicação. Dois modelos clássicos
existem:
Dica: Colete os requisitos arquiteturais de interoperabilidade na sua aplicação. Para cada requisito
arquitetural, defina o estilo, nível e topologia associada.
“Um chefe deve empregar táticas variadas de acordo com os tipos de terreno”, Sun Tzu.
A plataforma Java foi desenhada e evoluída nos últimos anos com um grande suporte para
interoperabilidade. Considere como exemplo simples a especificação JDBC. Projetada de forma elegante
para suportar a portabilidade de sistemas operacionais e bancos de dados, ela é um exemplo que suporta
a integração do estilo (ver Tabela 1) banco de dados compartilhados, em nível (ver Tabela 2) de dados.
Diversas outras tecnologias foram projetadas para suportar outros estilos e níveis. Apresentamos alguns
exemplos nas Tabelas 1, 2 e 3, sem nos ater aos detalhes da API, que podem ser encontrados em artigos
anteriores da revista Java Magazine.
“Um soberano iluminado estuda deliberadamente a situação e um bom general lida cuidadosamente com
ela. Se não é vantajoso, nunca envie suas tropas; se não lhe rende ganhos, nunca utilize seus homens;
se não é uma situação perigosa, nunca lute uma batalha precipitada.”, Sun Tzu.
Um padrão EIP é uma solução provada aplicável ao contexto de integração de aplicações corporativas.
O livro Enterprise Integration Patterns [1] apresenta 65 padrões de integração de sistemas. Cada padrão
resolve um problema particular no contexto de integração de aplicações corporativas e pode ser usado
junto com outros padrões EIP para solucionar um problema do mundo real.
Consideremos, para ilustrar o conceito, um problema similar ao citado no começo deste artigo.
Poderíamos formalizar os requisitos arquiteturais da seguinte forma:
R1. A aplicação ACME deve interoperar com o servidor Microsoft Exchange 2007 através de
protocolos SMTP para envio de emails.
R2. O transporte para interoperabilidade com o servidor de emails deve garantir confidencialidade
e integridade das informações enviadas através do uso do protocolo SMTP sobre SSL. Ao lermos
o livro EAI Patterns, capturamos alguns padrões candidatos para o problema acima. Estes
padrões são documentados na Tabela 4.
Tabela 4: Padrões EAI usados para resolver a interoperabilidade com o Microsoft Exchange
O primeiro padrão da Tabela 4 nos diz que a solução provavelmente será resolvida com um mecanismo
assíncrono. Dadas as restrições de protocolos no requisito R1, vemos que a especificação Java nos sugere
a especificação JavaMail.
O segundo e terceiro padrões nos diz que teremos um canal para o envio e recebimento de informações e
este canal deve se conectar a um sistema de email como o Microsoft Exchange. Ao examinarmos a classe
Transport, da API do JavaMail, veremos que ela possui esta função. Naturalmente, um Message Endpoint
requer uma implementação da especificação JavaMail. Assumamos neste exemplo que usamos o JBoss
AS para esta solução.
O quarto padrão nos diz que devemos enviar um documento textual. Ao observamos novamente a classe
Transport, notamos que ela possui um método send() que espera como argumento um objeto do tipo
Message, que modela um Document Message.
O último padrão está fora do escopo Java, mas ele indica que alguém deve configurar o servidor
Exchange para não aceitar mensagens SMTP, mas somente mensagens SMTP sobre SSL. O padrão
Consumidor Seletivo representa este mecanismo.
Diagramas de casos de uso são visualizações de negócio usadas por analistas de requisitos para o auxílio
na coleta e entendimento de requisitos. Estes diagramas possuem atualmente grande popularidade na
comunidade de analistas e podem ser ferramentas poderosas para a coleta de interoperabilidades. No
nosso exemplo, poderíamos representar a interoperabilidade do envio do e-mail com o seguinte
diagrama.
Note que neste diagrama o Microsoft Exchange suporta a operação de envio de email e por isso é
chamado de Ator Secundário.
Este fragmento de código pode ser entendido como a realização do esquemático da Figura 3.
Para um problema simples, naturalmente, podemos até ir intuitivamente para uma tecnologia ou um
código, mas isso é como começar a correr uma maratona sem preparação física adequada. Os padrões
representam esta preparação física. Eles permitem que você pense nos aspectos da solução sem se ater
aos detalhes da tecnologia. Ao escolheremos uma tecnologia Java determinada, ela será a conseqüência
dos padrões, que naturalmente devem suportar os requisitos arquiteturais especificados.
Para problemas complexos de integração do mundo real, entretanto, devemos conhecer e aplicar os
padrões EIP. Outra excelente fonte de padrões é o livro POSA [2], que contém mais de uma centena de
padrões arquiteturais, entre eles diversos padrões EIP.
Como exemplo de padrões EIP usados para modelar aplicações com integração complexa, dita aplicações
EAI, poderíamos citar os padrões descritos na Tabela 5.
“Se um general sabe o lugar e a hora de uma batalha, ele pode conduzir as suas tropas para até mil
milhas, mesmo para uma batalha decisiva. Se ele não sabe nem o lugar nem a hora de uma batalha,
então o seu lado esquerdo não pode ajudar a sua direita e a ala direita não pode salvar a esquerda; a
tropa da frente não pode auxiliar a tropa da retaguarda, nem a tropa da retaguarda pode ajudar a tropa
da frente.”, Sun Tzu
1. Colete os requisitos arquiteturais de interoperabilidade. Uma boa fonte para isso são atores
secundários em diagramas de casos de uso. Eles normalmente revelam sistemas e aplicações que
devem ser integrados. Especifique detalhadamente estes requisitos para que não existam dúvidas
sobre as versões de servidores, topologias e protocolos exatos a serem utilizados;
2. Desenvolva soluções independentes de tecnologia para cada requisito arquitetural de
interoperabilidade. Para isso, identifique os estilos, níveis e topologias de cada requisito
arquitetural de interoperabilidade. Expresse então a sua solução através dos padrões EIP
encontrados em [1] e [2];
3. Estude os exemplos (mais complexos e além do escopo deste artigo) disponíveis em [1];
4. Para cada solução independente de tecnologia, escolha as tecnologias Java mais adequadas para
resolver o seu problema. Use as Tabelas 1, 2 e 3 como ponto de partida e então estude as
especificações de cada tecnologia apresentada e produtos Java que suportem estas
especificações;
5. Prove a sua solução. Talvez a dica mais valiosa, a sua solução deve ser provada com um código
real e que possa capturar um cenário de utilização do seu sistema;
6. Teste a sua solução em ambiente idêntico ao de produção. Envolva o usuário e faça testes reais e
significativos, pois este é o passo mais complexo. Aplicações não testadas adiam riscos e podem
levar a problemas graves no fim do projeto. Em cenários mais complexos onde você não tem
acesso ao aplicativo sendo interoperado, considere o uso de ferramentas como o jMOCK ou
EasyMock. Recomendo, neste particular, o excelente artigo Mocks Aren´t Stubs, de Martin Fowler,
e o artigo “Testando com Mocks de forma fácil”, Edição 62;
7. Garanta que você realizou os passos 1-5 no começo do projeto. Requisitos de interoperabilidade
são complexos e devem ser endereçados no INÍCIO do projeto. Se você usa um processo
derivado do UP (Unified Process), os itens 1-5 para todos os requisitos arquiteturais devem estar
finalizados até o 3/10 temporal do seu projeto. Se você usa processos ágeis, enderece estes
requisitos nos primeiros sprints/iterações.
Conclusões
Este artigo apresentou, de forma introdutória, princípios que devem nortear aplicações que requerem
integrações de sistemas. Coloco abaixo um guia de estudo para os mais interessados no assunto.
O leitor curioso deve ter notado o uso de ícones diferenciados para representar padrões EIP. Estes ícones
estão disponíveis gratuitamente em formato Visio no próprio portal de Gregor Hohpe (EAI Patterns).
Para o leitor ainda mais curioso que busca aqui as respostas para as perguntas feitas ao longo do artigo,
o problema tem como solução: o estilo Passagem de Mensagens, integração em Nível de Aplicação e
topologia Ponto a Ponto.
Boas integrações!
Links
martinfowler.com/articles/mocksArentStubs.html
Descreve o conceito de Mocks e que ele pode ser usado para simular aplicações externas no contexto de
projetos de integração de aplicações.
eaipatterns.com/
Site central dos padrões EIP e que resume muito material interessante sobre integração de aplicações.
eaipatterns.com/downloads.html
Recursos sobre EIP e em particular o stencil (plugin) Microsoft Visio para desenhar EIP.
epf.eclipse.org/wikis/openup/
Open UP. Processo de desenvolvimento gratuito baseado nos conceitos do UP.
Livros
[2] Pattern Oriented Software Architecture - vol 05, Buschmann e al., Wiley and sons, 2007
Continuação do clássico livro POSA - vol 01, de 1996. O primeiro livro sobre padrões arquiteturais de IT e
referência obrigatória para sistemas que requeiram integrações complexas.
Saiba Mais
www.devmedia.com.br/articles/viewcomp.asp?comp=8984
Java Magazine 32 - Integração com JBI
www.devmedia.com.br/articles/viewcomp.asp?comp=8375
Java Magazine 50 - Integração Simples com XML-RPC
www.devmedia.com.br/articles/viewcomp.asp?comp=8380
Java Magazine 56 - Web Service REST
www.devmedia.com.br/articles/viewcomp.asp?comp=9422
Java Magazine 57 - Oracle BPEL: orquestra afinada de sistemas
www.devmedia.com.br/articles/viewcomp.asp?comp=10203
Java Magazine 59 - EAI na prática
www.devmedia.com.br/articles/viewcomp.asp?comp=10202
Java Magazine 59 - JBoss ESB
www.devmedia.com.br/articles/viewcomp.asp?comp=10816
Java Magazine 62 - SOA na prática - Parte 1
www.devmedia.com.br/articles/viewcomp.asp?comp=11057
Java Magazine 63 - SOA na prática - Parte 2
Aplicações Concorrentes em Java
Consultor Java EE pela Summa Technologies do Brasil, atuando há 9 anos com Java/Java EE.
A evolução dos processadores com um único núcleo baseada em clocks com maior freqüência atingiu o
limite do aquecimento suportado e um custo/beneficio inviável. Esses processadores enfrentavam ainda
outros problemas, como alto consumo de energia e gargalo no acesso à memória. Portanto, novas
alternativas para a melhoria de desempenho e consumo ganharam destaque, entre elas o paralelismo de
threads e cores. Os processadores de múltiplos núcleos (MC) e a tecnologia SMT (Simultaneous
Multithreading) permitem que tarefas sejam executadas em paralelo melhorando o aproveitamento dos
recursos. Para tirar-se maior proveito desses avanços, é necessário mudar o modo de pensar e escrever
aplicações, substituindo-se o paradigma seqüencial pelo concorrente. Dentre os desafios que esse
paradigma apresenta, alguns merecem destaque: identificar o que pode ser executado em paralelo
(vamos chamar de thread cada parte do código com essa característica) e a sincronização dos threads.
Os sistemas operacionais, em sua maioria, suportam processos, isto é, programas independentes que
rodam com algum grau de isolamento. Thread é um recurso que permite múltiplas atividades ocorrendo
ao mesmo tempo em um mesmo processo. O Java foi uma das primeiras linguagens, com boa aceitação
no mercado, que trouxe as threads como parte da linguagem, não as tratando apenas como um recurso
controlado pelo sistema operacional.
As threads assim como os processos, são independentes e concorrentes, tendo variáveis locais e pilha
(stack) próprias, mas compartilham a memória e estado do processo em que participam. Dessa forma,
elas têm acesso aos mesmos objetos alocados no heap. Apesar de facilitar a troca de dados, deve-se
tomar cuidado para garantir que isto não resulte em erros de concorrência (data races).
Todos os programas escritos em Java rodam threads, pelo menos uma (main). A JVM cria outras threads
que geralmente não são percebidas, como: Garbage Collection (GC), threads da AWT/Swing; em
servidores de aplicação conectores de HTTP, EJB etc.
A API do Java para threads é muito simples. Entretanto, escrever programas que as utilizem com
eficiência é um trabalho complexo. Então, porque utilizá-las se são tão complicadas? Algumas das
principais razões são:
Antes da API java.util.concurrent, trabalhar com threads em Java exigia um grande esforço, pois os
recursos disponíveis são muito simples e de baixo nível.
package br.com.jm.concurrent;
if (!this.firstThousand)
this.firstThousand = true;
return true;
else {
return false;
if (this.itens <= 0) {
return true;
else {
return false;
}
while (isFull()) {
try {
synchronized (this) {
wait();
System.out.println(Thread.currentThread()
catch (InterruptedException e) {
e.printStackTrace();
itens++;
System.out.println(Thread.currentThread()
.getName()
synchronized (this) {
notifyAll();
while (isEmpty()) {
try {
synchronized (this) {
wait();
System.out.println(Thread.currentThread().
}
catch (InterruptedException e) {
e.printStackTrace();
itens--;
System.out.println(Thread.currentThread().
getName()
synchronized (this) {
notifyAll();
return this.firstThousand;
package br.com.jm.concurrent;
this.queue = queue;
this.setName(string);
while (true) {
try {
sleep(1);
catch (InterruptedException e) {
e.printStackTrace();
queue.add();
package br.com.jm.concurrent;
this.queue = queue;
this.setName(string);
while (true) {
try {
sleep(random());
catch (InterruptedException e) {
e.printStackTrace();
if (queue.isRemovable())
queue.remove();
}
private long random() {
package br.com.jm.concurrent;
// Producers
producer.start();
// Consumers
consumer.start();
A classe principal ProducerConsumer instancia alguns Producers, que geram dados, e Consumers, que os
utilizam. Ambos se comunicam por intermédio da fila customizada (representada na classe Queue),
implementada utilizando recursos primitivos do Java como synchronized, além dos algoritmos específicos
a este tipo de fila. Por exemplo, o método add() não pode inserir elementos se a fila já estiver “cheia”,
neste caso o recurso de bloqueio seguido de retry (ver o loop while(isFull()) com um wait()) garante a
integridade; assim, se a fila estiver cheia, a thread-produtor que está tentando inserir ficará em wait()
até que alguma thread-consumidor remova um elemento com remove(), que ao final faz um notifyAll(),
acordando o produtor para o próximo retry. Todo este código é bastante trabalhoso e complexo, além de
limitado, em escalabilidade e outras qualidades.
O conceito Thread-safe
Como é difícil definir se uma classe é thread-safe , alguns pontos devem ficar claros:
Para uma classe ser considerada thread-safe, primeiro ela deve funcionar bem em um ambiente não
concorrente, ou seja, deve ser implementada de acordo com as especificações e qualquer operação
(leitura ou alteração de valores utilizando membros ou métodos públicos) realizada em objetos dessas
classes não devem colocá-los em estado inválido. Além disso, a classe deve funcionar bem em ambientes
multi-thread. Nesse cenário, a estratégia de agendamento e tempo de execução, ou sincronizações
adicionais necessárias ao código, não devem influenciar o bom funcionamento da classe. Para que as
operações executadas por um objeto thread-safe atendam esses critérios, elas devem estar bem
encapsuladas e a integridade dos dados deve ser mantida de forma transparente.
Antes do Java 5, o único mecanismo para garantir que as classes atendiam o conceito thread-safe era a
primitiva synchronized. Assim, as variáveis compartilhadas entre múltiplas threads precisavam ser
sincronizadas para que as operações de leitura e alteração de valores fossem coordenadas.
Mesmo que não exista nenhuma linha de código que indique explicitamente o uso de threads em sua
aplicação, o uso de frameworks e alguns recursos podem exigir que as classes que os utilizam sejam
thread-safe. Mas desenvolver classes thread-safe exige muito mais atenção e análise do que classes não
thread-safe.
Servlets containers, por exemplo, criam muitas threads e uma determinada Servlet pode ter acessos
simultâneos para atender múltiplas requisições, por isso, uma classe Servlet deve ser thread-safe.
A Listagem 5 mostra o exemplo de uma Servlet não thread-safe (a princípio o código parece correto) que
salva o endereço das máquinas que acessam o servidor. Entretanto, a Servlet não é thread-safe, pois a
classe HashSet também não é. Nessas condições, poderíamos perder algum dado ou corromper o estado
da coleção. O modo simples de corrigir esse problema, utilizando a nova API, seria usar uma das coleções
seguras, criando o objeto ipAdressSet da seguinte forma:
private Set<String> ipAddressSet = Collections.synchronizedSet(new HashSet<String>());
É muito interessante observar que a nova API simplifica o trabalho do desenvolvedor, ao mesmo tempo
em que exige um maior conhecimento sobre concorrência, mesmo para um exemplo tão simples quanto
esse.
IOException {
if (ipAddressSet != null)
ipAddressSet.add(ipAddress);
Coleções Seguras
Na plataforma Java (desde a JDK 1.0) existem coleções thread-safe como Hashtable e Vector. A API de
Collections (introduzida no JDK 1.2) introduz muitas coleções sem esta característica (como ArrayList,
HashSet etc.), mas podemos transformá-las em thread-safe, com o uso das factories:
Collections.synchronizedSet;
Collections.synchronizedMap;
Collections.synchronizedList.
Essas coleções geralmente têm baixa performance e alguns problemas, principalmente quando o
processamento depende de dados processados anteriormente. Chamadas aos métodos Iterator.hasNext()
e Iterator.next() podem lançar uma ConcurrentModificationException, pois alguma thread pode ter
alterado a coleção. Analogamente, testar se um objeto existe na lista antes de inseri-lo, ou recuperar o
tamanho da coleção (por exemplo um List.size()), pode provocar “data races”. Esses cenários são
conhecidos pelo nome "conditionally thread-safe".
Desde a introdução da java.util.concurrent, você deve encarar estes recursos como meros quebra-galhos
- talvez para código legado que você não tenha tempo de revisar mais profundamente com a nova API.
As classes CopyOnWriteArrayList e CopyOnWriteArraySet sempre criam uma cópia da lista quando ela é
alterada (add(), set() ou remove()), assim as threads que estão iterando sobre a lista continuarão
trabalhando sobre a mesma lista que começaram. Mesmo com o custo de copiar a lista, na maioria dos
casos, o numero de iterações é bem maior que o de modificações. E o custo de tais cópias é menor do
que o normal devido à otimização copy-on-write: grosso modo, cada thread só copia de forma
incremental os elementos que alterou, não a coleção inteira. Para esses cenários as novas classes
oferecem melhor performance e permitem maior concorrência que as outras.
As listas baseadas em arrays (como Vector) ou criadas com uma das factories disponíveis na nova API
(como Collections.synchronizedList()) retornam “fast-fail iterators” e para evitar o cenário “conditionally
thread-safe” a thread que está iterando sobre a lista teria que copiar toda a lista ou bloqueá-la, sendo
qualquer uma dessas soluções muito custosa.
A classe ConcurrentHashMap permite maior concorrência que as outras alternativas de mapas thread-
safe. O modo utilizado pelas outras implementações é sincronizar todos os métodos, o que nos leva a
dois problemas:
1. Falta de escalabilidade, pois uma única thread pode acessar a coleção durante o mesmo período;
2. A possibilidade de falha na segurança do acesso (“conditionally thread-safe”) que exige
sincronização externa às classes.
Com a técnica de sincronizar tudo, independente das operações que outras threads queiram realizar na
coleção, estas precisam esperar pela primeira thread terminar sua execução. Já a ConcurrentHashMap
permite várias operações de leitura quase concomitantes, operações de leitura e gravação simultâneas
(pode-se ler e gravar dados ao mesmo tempo) e múltiplas gravações concorrentes (várias threads
gravando ao mesmo tempo).
A JDK 5 incluiu uma nova estrutura de dados, a Queue, que é uma interface bem simples.
Simplesmente bloquear a thread também permite maior controle sobre o fluxo de dados (se os
produtores estão colocando objetos na fila muito mais rápido que os consumidores, bloquear os
produtores tornará o consumo mais rápido). As implementações dessa interface são:
LinkedBlockingQueue - Fila FIFO com ou sem limites baseada em nós ligados (como se fosse uma
LinkedList);
PriorityBlockingQueue - Fila sem limites e com prioridades(não FIFO);
ArrayBlockingQueue - Fila FIFO com limites baseada num array;
SynchronousQueue - Uma fila “degenerada” que não comporta nem um único elemento, mas
facilita a sincronização entre threads (permite o hand-off, ou transferência direta, de elementos
diretamente do produtor para o consumidor).
package br.com.jm.concurrent.bq;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//2 produtores
executor.execute(new Producer(q));
executor.execute(new Producer(q));
//6 consumidores
executor.execute(new Consumer(q));
executor.execute(new Consumer(q));
executor.execute(new Consumer(q));
executor.execute(new Consumer(q));
executor.execute(new Consumer(q));
executor.execute(new Consumer(q));
package br.com.jm.concurrent.bq;
import java.util.concurrent.BlockingQueue;
public Producer(BlockingQueue<String> q) {
queue = q;
while (true) {
try {
queue.put("Java Magazine");
catch (InterruptedException e) {
e.printStackTrace();
package br.com.jm.concurrent.bq;
import java.util.concurrent.BlockingQueue;
public Consumer(BlockingQueue<String> q) {
queue = q;
while (true) {
try {
System.out.println(queue.take());
catch (InterruptedException e) {
e.printStackTrace();
}
Esse exemplo tem praticamente o mesmo nível de complexidade que o das Listagens 1 a 4 , mas o
código em si é muito mais simples. Na classe principal ProducerConsumer temos uma
ArrayBlockingQueue com tamanho fixo que controla os números máximos e mínimos de elementos na fila
(assim como a fila da Listagem 1), temos ainda um Executor (CachedTheadPool) que lida com a
concorrência, utilizando um pool de threads que podem ser reutilizadas, maximizando assim a
performance, pois alocar recursos para uma thread não é um processo simples. A fila comporta no
máximo 10 elementos e o produtor só pode inserir dados se existir espaço livre (assim como o primeiro
exemplo), a própria BlockingQueue controla esses limites. O consumidor utilizando o método take() não
corre riscos de exceções por falta de objetos na fila, pois esse método espera até que exista um elemento
na fila para retornar.
Todo o processo ocorre sem a utilização de qualquer primitiva de concorrência, muito utilizadas em todas
as classes das Listagens 1 a 4, deixando claro como a nova API pode facilitar o nosso trabalho como
desenvolvedores.
FIFO - Define a ordem de entrada e saída dos elementos da fila, nesse caso o primeiro a entrar é o
primeiro a sair (first in, first out).
As implementações de thread pools disponíveis na API são bem flexíveis e existe um framework para
controlar a execução de tasks que implementam a interface Runnable (ou uma nova variante, Callable).
No framework Executor existe a interface Executor, que é bem simples. O contrato dessa interface define
um método para executar implementações de Runnable. Em qual thread a task será executada não faz
parte do contrato, dependerá da implementação de Executor sendo utilizada, ou seja, a forma de
execução é desacoplada da forma de chamada. Como a interface Executor se preocupa com o modo da
chamada, fica muito mais fácil ajustar os parâmetros de execução (alterando valores, como: tamanho do
pool, prioridade, timeouts, etc.) com pequenas alterações no código.
Dica: Thread pool é uma boa solução para os problemas de alocação e liberação de recursos enfrentados
em aplicações multi-thread. Como nos thread pools as threads são reutilizadas, o custo de alocação de
cada thread é dividido entre as tarefas que ela executará. Como a thread já está criada a aplicação pode
responder uma requisição (ou seja, iniciar uma tarefa) imediatamente.
Existem várias implementações de Executors, cada uma com sua estratégia de execução. Elas são
criadas por factories disponíveis em métodos estáticos da classe Executors, os principais são:
A estratégia de execução define em qual thread uma task será executada. Define o modo de execução,
ou seja, como os recursos serão utilizados (ex.: memória) e como se comportar em casos de sobrecarga.
Dica: Uma pergunta muito comum é: Qual tamanho deve ter o Thread pool?
A resposta depende, principalmente, do hardware e o tipo de tarefa (task) que será executada. Existe
uma lei que pode ajudar bastante: A lei de Amdahl. Para aplicá-la, precisa-se do número de
processadores (N) existentes na máquina que executará a tarefa.
Precisa-se ainda de aproximações dos tempos médio de processamento (TP) e de espera (TE) para a
tarefa.O TP é o tempo gasto para a conclusão da tarefa e o TE é o tempo gasto pela thread esperando
para iniciar a execução. Assim, o tamanho do Thread pool deve ser algo em torno de:
N * (1 + TE / TP)
A interface Future
Essa interface pode representar uma task que completou a execução, que está em execução, ou que
ainda não começou. Pode-se ainda cancelar uma task que ainda não terminou, descobrir quando a task
terminou ou foi cancelada e receber ou esperar pelos valores retornados.
Uma implementação dessa interface é a FutureTask, que encapsula Runnable ou Callable obedecendo aos
contratos das interfaces Future e Runnable, assim, pode ser executada facilmente por um Executor.
Alguns métodos do Executor (como ExecutorService.submit()) retornam Future além de executar a task.
Para recuperar os resultados de uma task, usamos Future.get(), que lança uma ExecutionException caso
a execução da task tenha provocado uma exceção; caso a task ainda não tenha terminado o método
ficará esperando o retorno do resultado, se a execução já acabou retorna o valor imediatamente.
As diferenças entre as interfaces Runnable e Callable são: Na interface Callable o método T call(), além
de devolver um resultado do tipo definido T, lança exceção. O método void run() da interface Runnable
não retorna valores e não lança exceção.
A interface CompletionService
Permite que o processamento dos resultados seja desacoplado da execução da task. Sua interface define
o método submit() para indicar as tasks que serão executadas e define o método take() para recuperar
as tasks terminadas. Os dois métodos retornam uma implementação de Future, o que facilita o processo
de recuperação de resultados. Usando a interface CompletionService a aplicação pode ser estruturada
facilmente com o pattern Produtor/Consumidor (utilizado como exemplo nas Listagens 1 a 4 e 6 a 8),
onde o produtor cria e submete as tasks e o consumidor recupera os resultados para processá-los.
Uma implementação dessa interface é a ExecutorCompletionService, que usa um Executor para processar
as tasks. No exemplo das Listagens 9 a 11 apresenta-se a implementação de um produtor/consumidor
utilizando essa interface.
Esse exemplo usa muitos recursos da nova API para resolver praticamente o mesmo problema que os
outros exemplos, mas nele as interfaces ExecutorCompletionService, Future e Callable deixam bem claro
como o código pode ser bem simples e executar tarefas complexas, como submeter tarefas e recuperar
os valores ao final da execução das mesmas. Para esse exemplo foi escolhido o Executor
FixedThreadPool, com tamanho máximo de 10 threads. O produtor submete uma tarefa para a
ExecutorCompletionService chamando o método addTask(), enquanto o consumidor espera para obter o
resultado com o método getResult(). O getResult() retorna uma instância da interface Future, onde o
método get() retorna o resultado imediatamente, se ele existir, ou espera até o final da execução tarefa
para retornar.
package br.com.jm.concurrent.cs;
ExecutionException
Executors.newFixedThreadPool(10));
Producer producer = new Producer(completionService);
while (true) {
System.out.println(consumer.getResult().get());
if (count.equals(Integer.MAX_VALUE)) {
return 0;
else {
return count.addAndGet(1);
package br.com.jm.concurrent.cs;
this.completionService = completionService;
completionService.submit(new Callable<String>() {
return value;
});
}
package br.com.jm.concurrent.cs;
this.completionService = completionService;
return completionService.take();
Conclusões
Pode-se perceber que desenvolver com a nova API é muito mais simples, apesar de exigir ainda
conhecimentos sólidos sobre programação concorrente e a necessidade de códigos específicos que
indiquem para a JVM como lidar com essas situações. A mesma segurança que existe em um código não
concorrente em Java (ou seja, que a memória será alocada e desalocada de modo correto e eficiente, as
melhorias que o JIT Compiler proporciona, entre outros) é o que a java.util.concurrent proporciona para
os códigos concorrentes. Claro que seria melhor não existir nenhuma preocupação em deixar explícitos os
códigos para o controle da sincronização e concorrência e deixar para a JVM resolver, assim como ela
resolve os problemas de alocação de memória, mas não é tão simples (como alocação de memória),
controlar a sincronização e concorrência de um processo. Esse é um dos motivos para o assunto ser um
dos mais discutidos hoje em dia. Com essa API podemos substituir a maior parte das primitivas de
concorrência por classes de alto nível, bem testadas e de bom desempenho.
Livros
Links
http://www.jcp.org/jsr/detail/166.jsp
JSR 166: Concurrency Utilities
http://java.sun.com/docs/books/tutorial/essential/concurrency/index.html
Tutorial da Sun sobre concorrência em Java
http://gee.cs.oswego.edu/dl/concurrency-interest/
Site do Doug Lea sobre concorrência
http://www.ibm.com/developerworks/views/java/libraryview.jsp?search_by=practice:
Coluna do Brian Goetz no developerWorks
Saiba mais
www.devmedia.com.br/articles/viewcomp.asp?comp=10017
Vídeo - Novidades do NetBeans 6.5 - Parte 1
www.devmedia.com.br/articles/viewcomp.asp?comp=8471
Java Magazine 53 - Perspectivas para um Mundo Paralelo
www.devmedia.com.br/articles/viewcomp.asp?comp=9420
Java Magazine 57 - Programando com Pools
www.devmedia.com.br/articles/viewcomp.asp?comp=10197
Java Magazine 59 - Quick Update: A Crise dos Threads