Groovy: Linguagem de Script para Java
From Fragmental Bliki
Introdução
Ao contrário do Microsoft .Net, Java (linguagem e plataforma) não foi criada visando o uso de várias linguagens para gerar código interpretado (bytecodes em Java, MSIL - Microsoft Intermediate Language - em .Net). Com os anos, a comunidade Java criou suas próprias ferramentas para compensar este fato, na forma de linguagens que geram bytecodes compatíveis com os produzidos pelo compilador da linguagem Java.
Groovy é uma destas linguagens, talvez a mais conhecida. É um projeto de Software Livre hospedado na Codehaus responsável por outros projetos como XStream, Pico/Nano Container, AspectWerkz, ActiveMQ, JMock, Drools e tantos outros. A linguagem Groovy é padronizada pela JSR 241.
O grande foco de Groovy é a produção de scripts, como os feitos em Bash ou Perl, mas a linguagem é poderosa o suficiente para ir muito além. Alguns programas podem exigir que rotinas sejam configuráveis e a maioria dos grandes sistemas empresariais, como ERPs e outros sistemas vendidos como produtos, permite um alto nível de personalização por cliente. Imagine um software de frente de caixa que precisa aceitar promoções definidas pelo time de marketing várias vezes por ano.
Na maioria das vezes estas configurações são implementadas com algum nível de parametrização em arquivos de configuração, mas quando existe uma mudança grande (como uma promoção "compre dois produtos do lote ABC-001 e leve mais um") geralmente o desenvolvedor precisa codificar esta em Java e fazer outro deploy da aplicação. Com linguagens de script, isso pode ser alterado dinamicamente, como veremos nas seções abaixo. Neste artigo, veremos suas principais características.
Instalando Groovy
Para instalar, baixe a versão mais recente no site oficial (na ocasião da escrita deste artigo, a versão mais recente era a groovy-1.0-jsr-03). Descompacte o conteúdo do arquivo num diretório.
Crie uma variável de ambiente chamada GROOVY_HOME apontando para o diretório onde você descompactou o arquivo baixado. Adicione $GROOVY_HOME/bin ao seu PATH. Coloque o jar do Groovy no seu CLASSPATH.
Para testar a instalação, digite:
$ groovy --version
Compilada (para bytecodes) ou Interpretada (como um script)
Groovy pode ser interpretado ou compilado. Após seguir as instruções para a instalação, crie um arquivo de texto chamado teste.groovy e coloque dentro dele o seguinte conteúdo:
println "Olá Groovy!"
Após isso, vamos executar este pequeno texto como um script, digite no seu Shell:
$ groovy teste.groovy
Isso irá chamar o interpretador e executará o código, após a execução verifique que nenhum arquivo foi gerado no diretório pelo interpretador.
Para compilar o código, utilize o compilador que vem com o Groovy:
$ groovyc teste.groovy
Note que para isso o jar do Groovy deve estar no CLASSPATH. Um arquivo teste.class é criado (Groovy não exige que classes públicas tenham letras maiúsculas). Esse arquivo é uma classe Java normal e pode ser utilizado dentro de outros programas Java (apesar de haver um meio melhor de fazer isso, como veremos). Apesar dos bytecodes gerados pelo compilador não serem tão otimizados quanto os criados utilizando diretamente a linguagem Java, geralmente essa diferença não é tão grave. É também importante notar que os benefícios de otimização da JVM (como compilador JIT) também se aplicam a bytecodes produzidos pelo compilador Groovy.
Todos os exemplos abaixo podem ser rodados como script.
Multi-Paradigma: Orientada a Objetos e Procedural
Groovy suporta o conceito de classe para modelar um objeto (nem todas as linguagens que implementam objetos utilizam classes para isso), porém permite que programas sejam escritos sem o uso destas.
Uma classe em Groovy é definida de uma forma bem parecida como em Java:
class Teste{
@Property meuNome= "Classe de Teste";
String cabecalho="isso é um teste ";
def imprimir(texto){
println (cabecalho+texto)
}
}
def somar(um, outro){
um+outro
}
meuTeste = new Teste();
meuTeste.imprimir("groovy")
println meuTeste.meuNome
println somar (1,2)
Este código já mostra algumas coisas interessantes. Groovy assim como JavaScript e Ruby não exige que sejam declarados os tipos dos parâmetros de métodos, de variáveis locais (definidas dentro de blocos de código)e os tipos de retorno dos métodos (neste caso, a palavra-chave def -também utilizada em Ruby- precede o nome do método), se você não definir o tipo o compilador vai utilizar todos como java.lang.Object.
No corpo do método imprimir, existe uma chamada à função println, que é uma função de conveniência oferecida pela linguagem. Seu uso é o mesmo que chamar java.lang.System.out.println(). Em Groovy, parênteses são opcionais ao chamar métodos e funções com argumentos não-ambíguos (se não existe outro método com o mesmo nome e tipo de parâmetros).
É notável a existência de código fora de uma classe. Em Java isso não é permitido, porém em Groovy você pode ter métodos sem classes. Como C++, Groovy é uma linguagem de multi-paradigma: procedural/orientado a objetos. Essa característica de C++ é alvo de críticas, mas Groovy se propõe a ser uma linguagem simples, não algo para se construir Sistemas Operacionais ou Bancos de Dados, e isso facilita muito a criação de scripts. Com Groovy você define funções utilizando a palavra-chave def como faz com métodos, mas fora de uma classe.
Atributos definidos com a anotação @Property</ode> (não é uma annotation Java 5), são automaticamente dotados de métodos setters e getters. Esses são os GroovyBeans. Para acessar o atributo basta utilizar seu nome, sem necessidade do <code>get/set como se os membros fossem públicos(como em JSTL e alguns frameworks).
Uma outra nota importante é que o operador == em Groovy chama o método equals do objeto (Groovy possui auto-boxing mesmo sem usar Java 5).
Sobrecarga de Operadores
Rode o seguinte exemplo:
class Numero{
def multiply(outro){
print "multiplicando por "+outro
}
}
n = new Numero();
n*12;
Groovy oferece um meio prático de efetuar sobrecarga de operadores (que Java não possui). Todo operador chama um método no objeto, basta que a classe sobrescreve este método. Alguns dos métodos são:
| Operador | Método a Sobrescrever |
a + b
| a.plus(b)
|
a - b
| a.minus(b)
|
a * b
| a.multiply(b)
|
a / b
| a.divide(b)
|
a++ ou ++a
| a.next()
|
<code>a—- ou --a
| a.previous()
|
a[b]
| a.getAt(b)
|
a[b] = c
| a.putAt(b, c)
|
Closures
Closures devem ser a coisa mais exótica em Groovy, já que não são comuns em linguagens de programação de uso geral que deram origem à Java (entre outras, estão presentes em Python, Perl, Ruby, Smalltalk, JavaScript e Lua). Uma closure é algo parecido com um ponteiro de função em C ou os objetos da classe java.lang.reflect.Method exceto pelo fato de qeu um Closure mantêm seu contexto. Também é parecido com o Padrão de Projeto Command (muitas vezes utilizado – especialmente com classes anônimas- em Java pela ausência dos próprios closures). Se você não conhece nenhum dos anteriores para fazer uma analogia, pense em closures como funções-objeto que podem ser armazenadas dentro de variáveis.
Rode esse exemplo:
class ImprimeUmDeles{
def a=2;
def b=4;
def imprimir(algoritmo){
println algoritmo.call (a, b)
}
}
impressora = new ImprimeUmDeles();
def maior = {p, s ->
println 'imprimindo o maior!'
if(p>s) reuturn p; else return s;
};
def menor = {p, s ->
println 'imprimindo o menor!'
if(p<s) return p; else return s;
};
impressora.imprimir(maior)
impressora.imprimir(menor)
Como você pode ver, o algoritmo utilizado é um parâmetro. A estrutura de um closure em groovy é:
def <variável> = { <parâmetros> -> <código> }
E para invoca-lo basta chamar o método call, passando os parâmetros necessários.
Closures são uma adição importante à linguagem, é recomendada a leitura da documentação oficial para saber mais.
Suporte Nativo a Listas e Mapas
Os benefícios dos closures são mais vistos na biblioteca de coleções de Groovy. Para declarar uma lista é simples:
def novaLista = ["tatiana", "phillip"] println novaLista.get(0) println novaLista.get(1) def intervalo=0..10 println intervalo.size() println intervalo.get(5) def mapa = ["chave":"valor", "chave2":"valor2"] mapa["nova"]="novo valor" println mapa["chave"] println mapa["nova"]
Lembrando muito Perl, PHP e Ruby.
novaLista e intervalo são instâncias de java.util.List. Definir o objeto como intervalo preenche a lista criada com os valores desejados (podem ser utilizados caracteres). A definição de um mapa parece com a de uma lista, exceto pelo fato de serem passados dados no formato [<chave>:<valor>].
As coleções em Groovy também possuem métodos de conveniência que são utilizados com closures:
def lista = ["a", "b", "c"]
lista.each{
elemento ->
println elemento;
}
lista.eachWithIndex{
elemento, indice ->
println indice+"o elemento é "+elemento;
}
O método each retorna como parâmetro para a closure todos os membros da coleção, na ordem.
Suporte e Acesso a qualquer API de Java SE ou Java EE
A grande vantagem de utilizar uma linguagem de script para a JVM ao invés de uma clássica como Perl ou Bash é a possibilidade de utilizar os recursos da plataforma Java. Praticamente qualquer framework, biblioteca e classe podem ser usadas num script Groovy sem muito esforço. No exemplo a seguir, utilizaremos uma classe do Swing:
import javax.swing.JOptionPane
def resposta = JOptionPane.showInputDialog (null, "Você gosta de Swing?")
println "Sua resposta foi ${resposta}"
Basta você importar a classe necessária (como faz num código Java normal).
Um detalhe importante neste código simples é a linha abaixo:
println "Sua resposta foi ${resposta}"
Se você já trabalhou com JSTL, Perl, Ruby e outra linguagem de script deve estar acostumado com a sintaxe acima. Em Groovy, isso vai ter o mesmo efeito que:
println "Sua resposta foi" + resposta
Integrável a um Programa Java
Como mencionado acima, um dos grandes benefícios de uma linguagem de script para a JVM é integrar seu programa Java com scripts em Groovy.
Uma das formas de se rodar um script Groovy é utilizar a classe groovy.lang.GroovyShell:
private static void exemplo6() throws CompilationFailedException {
GroovyShell shell = new GroovyShell();
Object resultado = shell.evaluate("println 'teste' ; return 'groovy'");
System.out.println("Resultado é "+resultado);
}
O método evaluate pode receber uma string contendo código em Groovy, um InputStream ou mesmo um objeto da classe File. Ele retorna uma instância de Object contendo o valor de retorno do código executado.
Para interagir com o script utilizamos um <code>groovy.lang.Binding, que é uma classe mensageira, trazendo e levando informações entre Groovy e Java.
private static void exemplo7() throws CompilationFailedException {
Date dataDeDoje = new Date();
Binding binding = new Binding();
binding.setVariable("hoje", dataDeDoje);
GroovyShell shell = new GroovyShell(binding);
shell.evaluate ("def a='variavel definida dentro do script' ; println 'hoje é: '+java.text.SimpleDateFormat.getInstance().format(hoje)");
Object variavelDoScript = binding.getVariable("a");
System.out.println(variavelDoScript);
}
Este exemplo é um pouco mais complexo, vamos dar uma olhada. Primeiro, foi criada uma instância de Java.util.Date contendo a data atual (que é o que o construtor padrão faz). Depois criamos o objeto Binding que vai ser nosso mensageiro e colocamos esta data dentro dele, para que leve ao script.
Vamos analisar o código do script melhor:
def a='variavel definida dentro do script' ; println 'hoje é: '+java.text.SimpleDateFormat.getInstance().format(hoje)
A primeira linha simplesmente declara a variável que iremos resgatar depois em Java. A segunda linha utiliza java.text.SimpleDateFormat (coloquei o nome todo da classe apenas para evitar o import) para transformar nossa data no formato do Locale do computador (dia/mês/ano se for Português/Brasil) e imprimimos seu valor.
Continuando no código Java, após a execução do script nós utilizamos o Binding para recuperar o valor de a (que foi definido dentro do script) e imprimimos na tela. Tanto a variável que é enviada ao script quanto à recebida podem ser de qualquer classe.
Para usar a classe Groovy como uma classe Java normal é necessário um conhecimento mínimo de reflection. Se você não entender os termos utilizados aqui, procure uma bibliografia sobre o tema.
Groovy possui e disponibiliza um ClassLoader próprio. Você precisa utilizar este ClassLoader para obter um objeto que age como um Proxy para o código Groovy chamado GroovyObject. No exemplo, vamos chamar a função imprimeNome deste script:
def imprimeNome(){ println 'meu nome é groovy';}
Para utilizarmos esta classe, vamos criar o seguinte código em Java:
private static void exemplo8() throws Exception{
GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
Class classeProxy = groovyClassLoader.parseClass(new File("exemplo8.groovy"));
GroovyObject proxy = (GroovyObject) classeProxy.newInstance();
proxy.invokeMethod("imprimeNome", null);
}
Se você encontrar problemas de "Arquivo não encontrado", tente criar o objeto File com o caminho completo para o arquivo.
Neste código instanciamos o ClassLoader do Groovy e passamos para ele um objeto java.io.File que aponta para o arquivo do script. Com um processo parecido com o carregamento de um driver JDBC, obtemos um objeto que representa a classe em si e utilizamos o método newInstance deste para criamos um objeto da classe que ele representa.
Este objeto, representante do script, possui um método chamado invokeMethod que recebe o nome do método a ser invocado e uma lista de parâmetros a serem passados para o método. Tudo muito parecido com reflection normal em Java.
Como chamar métodos através de uma string com seu nome é chato e tende a bugs. Você pode fazer sua classe Groovy implementar uma ou mais interfaces do seu sistema. Isso permite que você tenha interfaces que são utilizadas dentro do seu sistema tanto em classes Java normais quanto para classes Groovy. Se você obedecer ao princípio geral de programar para interfaces, não classes, seu programa sequer precisa saber se está manipulando uma classe em Groovy ou Java!
Vamos a um exemplo. Temos a seguinte interface:
public interface ObjetoQualquer {
public void meuMetodo();
}
E essa classe Groovy (não esqueça dos imports):
class ImplementacaoEmGroovy implements ObjetoQualquer{
def void meuMetodo(){
println 'classe groovy chamada'
}
}
E vamos utilizar um código muito parecido com o anterior para carregar uma classe Groovy, exceto que usaremos a interface na hora de fazer o cast:
private static void exemplo9() throws Exception {
GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
Class classeProxy = groovyClassLoader.parseClass(new File("exemplo9.groovy"));
ObjetoQualquer objeto = (ObjetoQualquer) classeProxy.newInstance();
objeto.meuMetodo();
}
Outros Recursos
Groovy possibilita a criação de Servlets (Groovlets) e tem um template parecido com JSP (GSP - Groovy Server Pages), é um os modos mais simples de processar XML em Java (precisa de um script que processe XML?), facilita o uso de componentes Microsoft COM (com o jacob) possui uma estrutura de Expressões Regulares parecida com Perl, possui um Shell próprio (como bash, tcsh, etc.) , possibilita a escrita de testes unitários de modo extremamente fácil, possui mecanismos para gerar saída em Swing, XML e SWT facilita a criação de conexões e consultas a bancos de dados... e muito mais do que poderia descrever aqui.
Recentemente foi iniciado em Groovy um projeto para um framework nos moldes do Ruby on Rails chamado Grails.
Outras Linguagens de Script para Java
A plataforma java tem sido verdadeiramente invadida pelas linguagens dinâmicas de script. Muitas delas são apenas ports de linguagens já existentes para a JVM, mas algumas são completamente novas. A JSR 223 cuida apenas disso. Uma grande novidade é que as próximas versões de Java deverão suportar JavaScript como linguagem de script integrada à JVM.
Algumas das linguagens mais comuns são:
| Jython (http://www.jython.org/) | Port de Python para a JVM |
| JRuby (http://jruby.sourceforge.net/) | Port de Ruby para a JVM |
| BeanShell (http://www.beanshell.org/) | Linguagem padronizada pela JSR 274 |
| Rhino (http://www.mozilla.org/rhino/) | Implementação de JavaScript para JVM da Mozilla, será incorporada ao Java SE 6.0 (Mustang) |
| JudoScript (http://www.judoscript.com/) | Linguagem focada em praticidade cheia de ferramentas como um Shell UNIX. |
Mas existem implementações de Smalltalk, LISP, BASIC, Logo, Prolog, Eiffel, COBOL, ADA e diversas outras clássicas e novas linguagens. Apesar de muitas delas serem projetos Open-Source pequenos, provam a capacidade da JVM em suportar múltiplas linguagens já hoje. Uma lsita pode ser obtida aqui.
Desvantagens
Não espere de um script Groovy uma performance como a de um programa Java. A equipe do projeto está trabalhando em seus compiladores, mas a performance quase sempre vai ser inferior a de uma aplicação normal (como citado acima, a JVM pode melhorar bastante isso com JIT).
A linguagem ainda não suporta as novidades do Java 5, porém pode ser utilizada tranqüilamente num programa Java construído e/ou num JRE/SDK Java 5.
Depurar um programa em Groovy costuma ser meio frustrante. As mensagens de erro retornadas pelo compilador podem não parecer ter sentido algum (algo que realmente deve ser melhorado), mas muito disso se deve á sintaxe relaxada da linguagem (quando se tem tantas opções, é difícil dizer quando algo está errado).
Groovy possui plugins para Eclipse, UltraEdit, Emacs e IntelliJ, mas estes ainda estão na sua fase inicial, apesar de usáveis.
Conclusão
Groovy é uma das várias linguagens de script disponíveis hoje em dia. É uma das mais antigas e usadas, muito bem aceita pela comunidade e esta sendo padronizada.
Já vi em diversos lugares soluções "gambiarras" para requisitos de regra de negócio altamente flexíveis. Geralmente o cliente paga uma taxa ao desenvolvedor que tem que modificar o programa original (fazendo um fork no projeto) para introduzir essas regras dinâmicas, depois volta tudo ao normal.
O uso de scripts pode aliviar muito este processo, além de facilitar a administração de uma aplicação grande. Como você faria hoje para varrer todos os seus objetos em memória caso seu cliente precisasse? Usuários de UNIX estão acostumados com o poder de scripts há muito tempo, aposto que estes já pensaram em um milhão de coisas que podem fazer com Groovy.
Outra área que merece estudo é o uso de scripts com Programação Orientada a Aspectos. O poder dessa combinação pode mudar radicalmente uma aplicação do dia pra noite, literalmente.
Hoje em dia muito tem se falado sobre Ruby e suas influências em Java e no desenvolvimento de aplicações em geral. Groovy é uma linguagem que oferece muitas das características de Ruby com uma sintaxe mais aprecida com o que o programador Java está acostumado.


