terça-feira, 6 de outubro de 2009

Struts 2 com Annotations

Apesar de ser um assunto não muito atual, com bastante documentação, eu tive muito trabalho para escrever minha primeira aplicação com Struts 2. O site do projeto tem muito conteúdo, mas nada mais prático para começar um projeto do zero. Em minha busca atrás de tutoriais, algo que me ajudasse, achei uma quantidade razoável de artigos/tutoriais, porém, nenhum que funcionasse efetivamente, principalmente utilizando annotations. Fiz o download de vários exemplos, uns funcionavam, outros não, mas não consegui fazer minha aplicação funcionar lendo apenas um tutorial. Dada essa dificuldade, pretendo colocar em prática o que eu aprendi, e, quem sabe, se algum desesperado precisar aprender uma aplicação simples utilizando Struts 2, esse tutorial funcione :-)

Para começar, precisamos montar nosso ambiente, vamos precisar do Eclipse, e do TomCat plugin.

Nosso projeto ficará mais ou menos assim:



Agora, criamos um projeto Java, e inserimos as bibliotecas do Struts 2 (essas são os jars necessários para o nosso exemplo simples, precisando de mais funcionalidades, vamos precisar de mais jars - a versão que estou utilizando é a 2.1.6)
  • commons-fileupload-1.1.1.jar
  • commons-logging-1.0.4.jar
  • freemaker-2.3.13.jar
  • ognl-2.6.11.jar
  • struts2-convention-plugin-2.1.6.jar (Esse jar é necessário somente se for utilizar annotations em sua aplicação, para as configurações em XML, esse jar não é necessário)
  • struts2-core-2.1.6.jar
  • xwork-2.1.2.jar

Criamos uma pasta WEB-INF no nosso projeto, que vai conter nosso querido amigo web.xml (arquivo padrão para configuração do struts). Nele, precisamos obrigatoriamente configurar um FilterDispatcher, o default do struts é o org.apache.struts2.dispatcher.FilterDispatcher, mas nada impede o mapeamento de outros filtros.








struts2
org.apache.struts2.dispatcher.FilterDispatcher



struts2
/*



index.jsp





Por que preciso de um FilterDispatcher?! Bom, ele é o responsável por filtrar requisições vindas do browser! Junto com as Actions e os Interceptors, é a camada de Controller do MVC. MV O QUE?!

MVC - Model-View-Controller, é o padrão implementado pelo Struts.

Actions - Unidade mais básica do struts. É basicamente uma ação a ser executada pelo usuário.

Interceptors - Uma das grandes novidades do Struts 2 com relação ao Struts 1, é o controle sobre o fluxo de execução de uma requisição, por meio de Interceptors. Com eles podemos executar métodos antes ou depois de uma determinada Action ser executada.

Chega de teoria! Tendo o web.xml configurado, é hora de cuidar da View! O Model não é nosso foco agora, porque ele seria o Domínio da nossa aplicação. Mas ... não temos domínio, é só um exemplo simples! Então vamos criar os jsp's necessários para o nosso exemplo.

index.jsp (repare que esse é o jsp configurado no welcome-file do web.xml)


<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>







resposta.jsp


<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>




Ok, temos a View pronta. E agora? Agora é hora de criar as Actions! No Struts 2, não é necessário estender nenhuma classe, a Action pode ser um POJO simples, desde que devidamente anotada. Repare que o valor(value) da @Action definida no método a executar, é o mesmo que colocamos na action do formulário no nosso jsp. Quando subirmos nossa aplicação, o Struts vai recuperar as classes anotadas, verificar quais são as actions (essa verificação é feita com o Struts procurando classes anotadas dentro do pacote action), e é assim que o jsp vai saber que ação executar.

Exemplo de Action (repare que é um POJO comum, anotado):

package br.com.maps.action;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Result;

/**
* Action de exemplo para o tutorial de Struts 2 com annotation.
*
* @author finx
* @created Oct 19, 2009
*/
public class HelloWorldAction {

private String requisicao;

private String resposta;

/**
* Executa a Action.
*
* @return resultado da execução da Action.
*/
@Override
@Action(value = "/helloworld", results = { @Result(name = "success", location = "/resposta.jsp"), @Result(name = "input", location = "/index.jsp") })
public String execute() {
this.resposta = "Olá! Eu estou executando uma ação do usuário, com a seguinte requisição: ";
return "success";
}

/**
* Retorna a mensagem da requisição.
*
* @return a mensagem da requisição.
*/
public String getRequisicao() {
return requisicao;
}

/**
* Define a mensagem da requisição passada como parâmetro.
*
* @param requisicao a mensagem da requisição a ser definida.
*/
public void setRequisicao(String requisicao) {
this.requisicao = requisicao;
}

/**
* Retorna a resposta da requisição, chamada pelo componente s:property do jsp.
*
* @return a resposta da requisição, chamada pelo componente s:property do jsp.
*/
public String getResposta() {
return this.resposta + " [" + this.getRequisicao() + "].";
}

}


Subindo nossa aplicação, ela deve ficar com essa cara:



Hmmm bem legal ... mas quando eu aperto o botão e não escrevo nada, a minha resposta é vazia! Há, aí é que entram os interceptadores. Estendendo nossa action de ActionSupport, ganhamos um fluxo de execução com interceptadores configurado no arquivo struts-default.xml, e aí é só configurar!

Exemplo de requisição vazia:



Um exemplo de como a Action ficaria com validação:

package br.com.maps.action;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Result;

import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.validator.annotations.RequiredStringValidator;
import com.opensymphony.xwork2.validator.annotations.Validations;

/**
* Action de exemplo para o tutorial de Struts 2 com annotation.
*
* @author finx
* @created Oct 19, 2009
*/
public class HelloWorldAction extends ActionSupport {

private String requisicao;

private String resposta;

/**
* Executa a Action.
*
* @return resultado da execução da Action.
*/
@Override
@Action(value = "/helloworld", results = { @Result(name = "success", location = "/resposta.jsp"), @Result(name = "input", location = "/index.jsp") })
@Validations(requiredStrings = @RequiredStringValidator(fieldName = "requisicao", message = "Campo obrigatório"))
public String execute() {
this.resposta = "Olá! Eu estou executando uma ação do usuário, com a seguinte requisição: ";
return "success";
}

/**
* Retorna a mensagem da requisição.
*
* @return a mensagem da requisição.
*/
public String getRequisicao() {
return requisicao;
}

/**
* Define a mensagem da requisição passada como parâmetro.
*
* @param requisicao a mensagem da requisição a ser definida.
*/
public void setRequisicao(String requisicao) {
this.requisicao = requisicao;
}

/**
* Retorna a resposta da requisição, chamada pelo componente s:property do jsp.
*
* @return a resposta da requisição, chamada pelo componente s:property do jsp.
*/
public String getResposta() {
return this.resposta + " [" + this.getRequisicao() + "].";
}

}


E um exemplo de como ficaria a nossa requisição vazia, com validação:



Finalmente, com o fluxo completo:



E, disponibilizando o exemplo utilizado nesse tutorial na página de projetos do blog.

terça-feira, 1 de setembro de 2009

Just Java 2009

Daqui a duas semanas (dias 15, 16 e 17 de setembro) ocorrerá a oitava edição do Just Java. O evento contará com a apresentação do trabalho "Performance com Hibernate em Cluster" por membros de nossa equipe técnica. Estudaremos um "case" de processamento distribuído de dados e os seguintes tópicos serão abordados:
  • Distribuição de tarefas por JMS
  • Hibernate
    • Cuidados com FlushMode e gerenciamento do PersistenceContext
    • Lock otimista com uso de @Version
    • Uso de cache secundário
Esperamos vê-los no evento.

terça-feira, 21 de julho de 2009

Java Generics - Parte I: Definições

Embora tenham sido introduzidos há quase cinco anos, com o lançamento do java 1.5 (codinome Tiger), os generics em java são fonte de confusão para muitas pessoas - mesmo programadores mais experientes. Esta série de posts tem como objetivo explicar como funcionam os generics em java, explicar seus diversos casos de uso e seus principais pontos de confusão. Este primeiro artigo é um pouco teórico mas artigos subsequentes se concentrarão na prática dos conceitos aqui explicados.
O que são generics
Generics é/são um recurso de linguagem que permite às classes especificarem uma parametrização "abstrata" de um tipo. Em outras palavras, os generics permitem que se escreva códigos que dependam de classes que serão definidas pelos clientes da sua classe. O exemplo óbvio são as coleções: quem escreveu a classe List, por exemplo, não poderia escrever uma variante para cada tipo de elemento que seria guardado dentro da List. Para poder definir de maneira uniforme a interface List (e sem que códigos clientes precisem fazer casts ou extender as classes), é possível escrever uma classe que tenha um parâmetro genérico, descrito na declaração da mesma. Em java, o parâmetro genérico é declarado dentro dos angle brackets '<' e '>':
public interface List<E> {

public E get(int index);

//resto da classe

}

Permitindo escrever código cliente deste modo:
ArrayList<String> listaDeStrings = new ArrayList<String>();

listaDeStrings.add("umaString");

String umaStrings = listaDeStrings.get(0);

Este recurso está presente em várias linguagens de programação, tal como C++, C#, Haskel, scala, etc, embora em algumas dessas linguagens seu nome seja diferente. Em teoria de tipos, este recurso também é conhecido como polimorfismo paramétrico. Para as definições a seguir, onde está escrito "A instância de B" deve ser lida como "A é um substituto válido para B" em relação à tipagem.

Covariância, contravariância e invariância
Um pré-requisito para se entender as confusões mais comuns causadas por generics, é preciso entender um conceito conhecido como variância de tipos genéricos:

Covariância
Por definição, diz-se que um tipo T com um parâmetro genérico B é covariante em B se e somente se toda instância de T[A0] é uma instância de T[A1] para toda classe concreta A0 que extende de uma outra classe concreta A1. Por exemplo em java (e em C#) arrays são covariantes, o que nos permite escrever:

Object[] arrayDeObjects = new String[10];
Considerando que o tipo do array é seu "parâmetro genérico", embora não use o generics explicitamente.

Por outro lado, os generics "normais" em java não são covariantes, o que torna o código abaixo inválido:

//erro de compilação
List<Object> listDeObjects = new ArrayList<String>();
Um tipo genérico covariante não permite que se "escreva" nele - em outras palavras, um tipo genérico covariante é read-only, ou seja, o tipo genérico só pode aparecer em posições covariantes (apenas como parte de retorno de métodos). Para verificar porquê, imagine que o compilador permitisse o código abaixo:
ArrayList<String> listDeStrings = new ArrayList<String>();

//a linha abaixo é reportada como erro pelo compilador
ArrayList<Object> listDeObjects = listDeStrings;

//em um array list de objects, podemos inserir um Object
listDeObjects.add(new Object());

A seguinte linha, porém, faria com que fosse lançada uma ClassCastException mesmo que ela não tenha nenhum cast:
String a = listDeStrings.get(0);
Os arrays em java resolvem (ou melhor, contornam) este problema lançando a exceção ArrayStoreException:
// array de strings
String[] arrayDeStrings = new String[10];

//sem exceção, pois arrays são covariantes
Object[] arrayDeObjects = arrayDeStrings;

// a linha abaixo, porém, lança a exceção ArrayStoreException
arrayDeObjects[0] = new Object();



Contravariância
Por outro lado, um tipo genérico T é considerado contravariante em B se e somente se para qualquer classe concreta A0 super classe de A1, toda instância de T[A0] é uma instância de T[A1]. Embora isso pareça estranho a princípio, considere a interface Comparator:
public interface Comparator<E>  {

public int compare(E arg0, E arg1);

}

Sempre que um método precisar receber um Comparator para, por exemplo, String, poderia receber na verdade um Comparator para qualquer coisa que saiba comparar Strings - por exemplo, um Comparator de Objects.

Desse modo, podemos ver que um tipo genérico contravariante é write-only, ou seja, o tipo paramétrico só pode aparecer em posições contravariantes (apenas como parte dos argumentos dos seus métodos).

Em JAVA
Como foi visto anteriormente, arrays em java são covariantes. Os tipos genéricos, porém, são invariantes nos tipos paramétricos nele definidos - ou seja, não são nem covariantes nem contravariantes. Isso permite que tipos genéricos sejam usados tanto em retorno de métodos quanto em seus argumentos. Na declaração de variáveis, porém, é possível especificar a variância de um tipo genérico através das palavras-chave extends (para covariância) e super (para contravariância).

Assim sendo, uma List covariante em um tipo T tem o tipo:

List<? extends T> listDeT = ....;


A mesma sintaxe se extende para parâmetros de métodos, como neste método presente na interface Collection:

public void addAll(Collection<? extends E> collection)


Declarando um parâmetro dessa maneira, a collection é covariante em E, o que significa que no corpo do método, não podemos chamar métodos nessa lista que declarem E como parte do parâmetro.


Por outro lado, um Comparator contravariante em K tem o tipo:

Comparator<? super K> comparatorDeK = ....;


As construções acima funcionam também para declaração de parâmetros de métodos e construtores, bem como o tipo de retorno de um método.

Note a presença do wildcard '?'. O wildcard serve para indicar que o tipo exato da List e do Comparator é desconhecido, mas deve obedecer às restrições de variância impostas (respectivamente, covariante em T e contravariante em K). Como dito anteriormente, uma List covariante é "read-only"[1] (apenas é possível ler coisas da List) e, comparativamente, o Comparator contravariante é write-only. Este tipo de variância é chamado de use-site variance, contrastando com definition-site variance (presentes em, por exemplo, C# 4.0 e scala), onde a variância é indicada na própria declaração do tipo genérico.


Conclusão

Vimos aqui a definição de generics, bem como definições sobre variância e sobre como funciona a variância de arrays e demais tipos genéricos em Java. No próximo post, serão mostrados alguns usos de tipos variantes e os problemas mais comuns que aparecem com o uso de generics.



[1] - não são read-only no sentido de que a List não pode ser modificada, mas sim no sentido que não se pode chamar métodos que usam o tipo genérico como parte do parâmetro de algum método.

sexta-feira, 10 de julho de 2009

Scala parte I - sintaxe

Durante a escrita de um outro artigo (ainda a ser publicado), percebi que preferia escrever os exemplos em uma linguagem de programação chamada Scala ao invés de Java. Por isso, ao invés de explicar todas as linhas do código, resolvi escrever um pouco sobre esta linguagem que, na minha humilde opinião, tem potencial para ser a próxima grande linguagem de programação.

O que é Scala?
Scala é uma linguagem de programação orientada a objetos, com tipagem estática e compilada, que é executada na JVM e na máquina virtual do .Net[1]. Além disso, Scala tem muitas funcionalidades típicas de linguagens de programação funcional, como closures, otimização de chamadas recursivas, etc. Por último, por ser primariamente executada na JVM, apresenta ótima compatibilidade com Java, podendo se aproveitar das muitas bibliotecas existentes. Foi inventada por Martin Odersky e seu nome se refere à escalabilidade da linguagem, na medida em que pode ser usada para escrever pequenos scripts ou grandes sistemas completos, sem que haja uma alteração na maneira de escrever.

Como usar
Todos os exemplos abaixo podem ser testados no console de Scala, invocado por scala ou scala.bat dependendo do seu sistema operacional.

Um pouco sobre a sintaxe
Na sua estrutura geral, Scala se parece muito com Java, embora os detalhes da sintaxe sejam bastante diferentes. Para começar, a declaração de variável é precedida de val ou var e o tipo da variável é pós-escrito e separado do nome da variável por ':', desse modo:

var umaString: String = "aaa";
val umNumero: Int = 1;

uma variável declarada como var pode ser reassignada, enquanto que uma variável declarada com val é imutável, ou seja, para todos os efeitos, equivalente a declarar uma variável 'final' em Java. O compilador de Scala permite, porém, que se omita o tipo de uma variável (que deve ser sempre inicializada durante a declaração):

var outraString = "aaa"
val umDouble = 0.0

E o ';' pode ser omitido se for desnecessário (ou seja, na última expressão de uma linha).
Métodos em Scala são definidos pela palavra chave 'def':

def metodoSemParametros():String = { "a" }

O ':String' significa que o método devolve String. O retorno do método, se omitido, é o resultado da última expressão do método. Um método com parâmetros:

def metodoComUmParametroInteiro(parametro: Int) = parametro + 1

Este método declara um parâmetro do tipo Int (que é o mesmo que 'int'). Neste caso, o compilador também infere e insere o tipo de retorno do método e os delimitadores também são opcionais para métodos compostos por apenas uma expressão. Por falar em expressão, Scala é composto quase que exclusivamente por expressões, o que significa que você pode atribuir quase qualquer coisa a uma variável (embora haja expressões do tipo Unit, o equivalente a void em C/C++/Java).


val maior: String = { if (1 > 2) { "1 maior que 2"} else { "1 menor que 2"} }

Mesmo para expressões desse tipo, o compilador infere o tipo da expressão e o insere se este for omitido. As chaves também podem ser omitidas para expressões únicas:

val maior = if (1 > 2) "1 maior que 2" else "1 menor que 2"

Escrever classes em Scala é bem simples:

class MinhaClasse {
var propriedadeMutavel: String = "a"
val valorImutavel = 10
}


Var e Val dentro do corpo da classe criam propriedades mutáveis e imutáveis respectivamente (o bytecode gerado é o equivalente a um campo e getters e setters).
Isso faz com que classes cliente não saibam se determinado valor vindo de outra classe é um campo ou uma invocação de método, uma propriedade conhecida como princípio de acesso a dados uniforme.

Em Scala, há o conceito de construtor primário que deve ser chamado pelos outros construtores e sua assinatura está junto com a declaracao da classe:

class Ponto(x0: Int, y0: Int) {
val x = x0
val y = y0
}

Construtores auxiliares são declarados por métodos chamados 'this'

class Ponto(x0: Int, y0:Int) {
val x = x0
val y = y0

def this() = this(0, 0) //construtor vazio
}

Em especial, o construtor primário pode já ter a declaração de propriedades de classe:
class Ponto(val x:Int, val y:Int) {
def this() = this(0, 0)
}

Métodos em Scala podem ter praticamente qualquer nome:
class Ponto(val x:Int, val y:Int) {
def this() = this(0, 0)
def +(ponto: Ponto) = new Ponto(x + ponto.x, y + ponto.y)
}

Por último, generics em scala funcionam de maneira um pouco diferente e usam [ ] ao invés de < >, mas os detalhes serão dados em um post futuro. Além disso, posts posteriores vão mostrar construções avançadas, bem como algumas das propriedades que estão tornando scala uma linguagem tão popular

[1] - Embora o suporte ao .Net esteja bem defasado e incompleto.

terça-feira, 23 de junho de 2009

JavaFX - conceitos básicos

Um pouco de background

O cenário para RIA's está bastante aquecido e JavaFX é a tecnologia desenvolvida pela Sun para disputar este mercado com Flash/Flex e Silverlight. Estas três tecnologias tem em comum o fato de serem disponibilizadas aos usuários através do browser porém executadas numa máquina virtual local. Desta forma é possível oferecer ao usuário interfaces muito mais elaboradas do que é possível com a combinação HTML+JavaScript (o cenário no qual trabalha JSF por exemplo).

A linguagem

Um dos principais diferenciais em relação ao Java é o estilo declarativo de codificação. Por exemplo, compare a forma Java de se instanciar um objeto do tipo SomeObject e sua dependência SomeObjectChild:
SomeObjectChild child = new SomeObjectChild();
child.setValue3(123);
SomeObject ref = new SomeObject();
ref.setValue1(123);
ref.setValue2("123");
ref.setChild(child);
ref.setSomeEventListener(new SomeEventListener() {
void someEvent() {
//do something
}
});
e a forma JavaFX de se instanciar um objeto equivalente:
def ref : SomeObject = SomeObject {
value1: 123
value2: "123"
child: SomeObjectChild {
value3: 123
}
someEvent: function(): Void {
//do something
}
}
O estilo declarativo é visivelmente mais claro e conciso.

Outra recurso interessante é chamado de binding. Através deste recurso é possível amarrar um atributo a outro. Desta forma toda vez que algum atributo é modificado todos os que estiverem amarrados são notificados. Na verdade é mais do que uma notificação, a expressão com a qual foi feita a amarração é executada novamente atualizando assim o valor do atributo amarrado. Vejamos alguns exemplos:
var stage : Stage;
var lbl: Label;

lbl = Label {
font : Font {
size : 20
}
width: 100

translateX: bind (stage.scene.width - lbl.boundsInLocal.width) / 2
translateY: bind (stage.scene.height - lbl.boundsInLocal.height) / 2
text: bind
if (stage.scene.width > 300) then
"LARGE!"
else
"thin..."

textFill: bind
if (stage.scene.width > 300) then
Color.RED
else
Color.BLACK
};

stage = Stage {
title: "Simple Binding"
width: 250
height: 80
scene: Scene {
content: [lbl]
}
}
A variável stage representa uma janela que será exibida para o usuário. Esta janela irá conter apenas um texto representado pela variável lbl. Repare que os atributos do texto estão definidos usando a palavra-chave bind. Isto irá fazer com que toda vez que as propriedades stage.scene.width e stage.scene.height forem modificadas os atributos text, textFill, translateX e translateY sejam calculados novamente. Na prática isto fará com que o texto seja movido para o centro da janela sempre que ocorrer um redimensionamento da mesma e também irá mudar o texto sendo exibido e sua cor de acordo com a largura da janela.

O recurso de binding é bastante poderoso e permite manter o estado dos componentes da janela sincronizados em torno de atributos chaves sem que para isso seja necessário definir listeners de eventos como seria feito numa interface Swing tradicional.

O exemplo acima não reflete isso mas este recurso facilita a construção da interface no padrão MVC. Fica bem simples sincronizar os componentes da View que exibem dados aos atributos do Model que armazenam estes dados. Desta forma ações (Controller) que impactam o Model irão automaticamente atualizar a View sem que isto fico explícito no código das ações. Portanto é possível alcançar um menor acoplamento entre estas camadas ficando de fato possível alterar componentes da View sem impactar as camadas de Model e Controller.

Mixins

Até a versão 1.1 do JavaFX existia suporte a herança múltipla. Este recurso foi eliminado na versão 1.2 em favor de "mixins". Este recurso permite que interfaces tenham uma implementação padrão de um ou mais de seus métodos. Desta maneira, uma classe qualquer que deve implementar uma interface referencia o mixin da interface ao invés da própria interface. Vejamos um exemplo simples composto de duas interfaces, dois mixins, uma classe abstrata e uma concreta:

Interfaces (Java):

public interface Persistable {
void save();
void delete();
}

public interface Identifiable {
String getIdentity();
}

Mixins (JavaFX):

public mixin class IdentifiableMixin extends Identifiable {
public override function getIdentity() : String {
return toString();
}
}

public mixin class PersistableMixin extends Persistable {
public override function save() : Void {
Persister.save(this);
}

public override function delete() : Void {
Persister.delete(this);
}
}

Classe abstrata (Java):

public abstract class Entity {
private Long id;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}
}

Classe concreta (JavaFX):

public class SomePersistableIdentifiableEntity extends Entity, PersistableMixin, IdentifiableMixin {
public-init var field: String;

public override function toString() {
return field;
}
}

Desta maneira a classe SomePersistableIdentifiableEntity implementa as interfaces Persistable e Identifiable sem no entanto ter sido obrigado a implementar os métodos da interface pois foram utilizados as definições providas pelos "mixins". Caso necessário é possível sobreescrever os métodos para modificar algum comportamento. Graças a este recurso não foi preciso "sujar" a árvore de herança da classe com implementações dos métodos das interfaces. De uma maneira geral "mixins" promovem reuso de código e consistência nas chamadas a métodos de interfaces. Permitem que diversas classes que querem implementar determinada interface já ganhem de brinde uma implementação padrão de um ou mais de seus métodos.

Num próximo post sobre JavaFX iremos falar sobre novos modificadores de acesso a variáveis, operadores especiais para trabalhar com listas e mais.

domingo, 14 de junho de 2009

A JVM e seu compilador JIT

Até mesmo entre alguns programadores Java, há certas dúvidas sobre como alguns aspectos da JVM funcionam. Em particular, embora muitos saibam que existe um compilador durante a execução do programa, poucos sabem exatamente quais os benefícios. Mais do que isso, não é raro encontrar desenvolvedores procurando um compilador ahead of time (AOT) para java para gerar programas nativos. Este post pretende mostrar algumas vantagens do compilador just in time (JIT), bem como demonstrar um pouco da sua capacidade. As informações contidas neste artigo se referem ao Hotspot, o compilador jit da Sun. Porém, a maior parte da informações contida é aplicável a todas as JVM's.

Hotspot client e server
Antes de falar especificamente sobre o Hotspot, é preciso saber que existem na verdade dois Hotspot's, chamados de client e server.
O client compiler é o compilador presente na JRE de 32 bits. Ele tem como objetivos gerar código nativo rapidamente e, preferencialmente, sem afetar a responsividade do código sendo executado. Seu nome vem do fato de que é preferivelmente utilizado em aplicações clientes que normalmente interagem com usuários. Já o server compiler está presente apenas no JDK (e nas JRE de 64bits) e exige mais tempo para gerar código nativo. Contudo, várias otimizações só estão presentes no server compiler. Seu principal uso é para aplicações que tipicamente executam em servidores, com um tempo de vida maior. Para ativar o server, basta invocar o executável java com a opção -server.

E como funciona o Hotspot?
O Hotspot (nome do compilador jit da máquina virtual da Sun) observa a execução dos programas e, a partir do perfil de execução e da plataforma em que se encontra, gera código nativo para o programa sendo executado. Isso significa que ele pode (e o faz) gerar código específico para o processador em que o programa está sendo executado, se aproveitando de registradores específicos e instruções especiais.

Exemplo prático
Para ilustrar o comportamento do JIT, vamos mostrar um pequeno exemplo. O código abaixo (que também se encontra no repositório de códigos deste blog) é bem simples e mostra bem o que o Hotspot é capaz de fazer:

long bits = 0L;
if(args.length > 0) {
bits = Long.parseLong(args[0]);
}
long start = System.nanoTime();
int n = 2000000001;
boolean shift = bits > 0;
while (n > 0) {
if(shift) {
bits ^= 1 << 5;
}
n--;
}
System.out.println("bits: "+ bits);
long end = System.nanoTime();
System.out.println(TimeUnit.NANOSECONDS.toMillis(end-start) + "ms");

Executando o código acima nos três modos (puramente interpretado, compilado com o client compiler e com o server compiler) passando o número 1 como argumento, temos os seguintes resultados (em um Solaris usando java6u14):

Interpretado (invocado com -Xint): infinito!
Client compiler: 2903ms
Server compiler: 11ms

O resultado do server compiler é impressionante. Para comparar, o código C equivalente (compilado no GCC 3.4.6), no mesmo computador mas com o tempo medido com o utilitário 'time' do UNIX:

gcc: 7422ms
gcc -O1: 2541ms
gcc -O5: 1447ms

A versão server é inclusive muito mais rápida que a versão nativa compilada com -O5 (o 5o nível de otimização do GCC - mais do que isso não trouxe melhorias para este exemplo). Mas mesmo a versão client tem desempenho parecido com o GCC -O1.

Java mais rápido que código nativo?
Para entender o que ocorreu com o programa anterior, serão explicadas algumas das técnicas de otimização feitas pelo Hotspot. Para começar, o Hotspot detecta que é necessário otimizar o corpo do loop. Para isso, ele aplica uma técnica conhecida como loop unrolling com fator de 2. O loop resultante fica parecido com:

if(n % 2 == 1) {
if(shift) bits ^= 1 << 5;
n--;
}
while (n > 0) {
if(shift) {
bits ^= 1 << 5;
}
n--;
if(shift) {
bits ^= 1 << 5;
}
n--;

}

Porém, dentro do loop, o valor de shift não é alterado e o 'n--' não tem efeito sobre a computação sendo feita. O compilador, dessa vez, reordena as instruções para ficar parecido com:

while (n > 0) {
if(shift) {
bits ^= 1 << 5;
bits ^= 1 << 5;
}
n--;
n--;
}

Para que a variável 'bits' não seja carregada mais de uma vez e porque a operação XOR (^) é associativa, o compilador resolve colapsar as duas instruções em uma só:



while (n > 0) {
if(shift) {
bits ^= (1 << 5) ^ (1 << 5);
}
n -= 2;
}


Nesse momento, o Hotspot percebe que a expressão:
(1 << 5) ^ (1 << 5)
na verdade é a constante 0! Assim, a parte interna do loop pode ser eliminada, bastando adicionar as instruções do seu efeito colateral (decrementar o n até 0). Dessa forma, o código final executado é parecido com:

if(n % 2 == 1) {
if(shift) bits ^= 1 << 5;
n--;
}
n = 0;

É claro que esta é uma simplificação do que acontece realmente (o loop na verdade é dividido em três seções, mas estas explicações detalhadas ficam para outro post). A versão client compiler pára antes de fazer todas as otimizações para que o código nativo possa ser executado mais rapidamente. O código gerado, porém, não é o mais otimizado possível - que é um quase noop.

Conclusão
Não se preocupe em escrever o código java mais otimizado possível. É preferível escrever um código mais legível e deixar as otimizações mais estranhas para o Hotspot.
Embora não se possa afirmar que a JVM consiga gerar código tão eficiente quanto os compiladores nativos, é seguro afirmar, pelo menos, que eles tem desempenhos equivalentes.

PS: O exemplo aqui presente e muitas outras discussões bastante interessantes podem ser acompanhadas no google groups de linguagens da jvm.

update: muitos erros de português...

update2: Os tempos da versão java medidos com o time foram omitidos por serem virtualmente identicos ao tempo acima. Em todo caso, o user time é, em média, 100ms superior ao tempo medido de dentro da aplicação.

sábado, 30 de maio de 2009

Java6u14: EA e LE

Sem fazer nenhum alarde (e uma semana antes do JavaOne 2009) a Sun resolveu lançar mais uma atualização para o JRE6 - o Update 14. Devido às indefinições sobre o Java7 (que ainda não possui uma JSR), a Sun tem feito, em suas atualizações do Java6, grandes modificações, entre as quais o "novo plugin" no update 10, capacidade de atualização "in place" para windows no update 11, o plugin de 64 bits no update 12 e - o principal assunto deste artigo - análise de escape e o novo garbage collector (G1) no update 14.


Escape Analysis
A análise de escape permite à JVM detectar quando um objeto é "não escapante" (ou seja, seu ciclo de vida está confinado a um escopo local). Quando um objeto desse tipo é detectado, o JIT vai (supostamente) alocá-lo na pilha (ao invés do heap) e tratar seus campos como variáveis locais e, no melhor caso, trabalhar apenas com os registradores do seu CPU. Além disso, é garantidamente seguro ignorar os monitores destes objetos - uma otimização conhecida como "lock elision".

Um pouco de história:
Na época do desenvolvimento do Java6, a análise de escape estava prometida como um dos responsáveis por um aumento de performance sobre o Java5. Algumas JVMs da época já tinham essa feature (ou pelo menos em teoria, de acordo com este artigo), mas devido ao tempo escasso, a JVM6 era capaz de fazer a análise, mas não implementava nenhuma otimização com essas informações.

Na prática...
Vamos ver como a EA (escape analysis) e LE (lock elision) se saem em alguns microbenchmarks simples.
O primeiro teste consiste em alocar um objeto simples, com dois campos, dentro de um for, alterar o valor dos seus campos, e somá-los. Estas operações são feitas através de métodos ao inves de acessar os campos diretamente, com a análise de escape desligada primeiro, depois ligada.
Antes de mais nada, a EA só pode ser ativada na jvm server (ou seja, está presente apenas na JDK6U14 e não na JRE6U14) e com uma opção não padrão (daquelas que começam com -XX)
-XX:+DoEscapeAnalysis

A parte principal do código:


public void execute() {
for (int i = 0; i < max; i++) {
Foo foo = new Foo();
foo.setNumA(i);
foo.setNumB(i * i);
total += foo.sum();
}
}



Os resultados foram:

Tempo de execução para 1000000000 iterações
client vm: 11922 ms
server vm: 8452 ms
server vm + EA: 1611ms

Ou, em iterações por millisegundo:
client vm: 83872
server vm: 118306
server vm + EA: 620578

Como pode ser visto, usando a EA, o pequeno teste ficou mais de 6x mais rápido. Claro que estes números não devem ser lidos literalmente, pois este teste foi especificamente designado para tirar vantagem da análise - em outras palavras não espere esta melhoria no eclipse do dia-a-dia ou no jboss em produção.

Lock elision
Lock elision é uma das técnicas que poderia ser uma gigantesca vantagem das linguagens dinamicamente compiladas sobre as estaticamente compiladas. E o update 14 especificamente menciona que com a análise de escape ativada, os locks também seriam eliminados. Executando o mesmo teste, agora com todos os métodos marcados como sincronizados, temos:

Alterando o código anterior para:

public void execute() {
for (int i = 0; i < max; i++) {
SFoo foo = new SFoo();
foo.setNumA(i);
foo.setNumB(i * i);
total += foo.sum();
}
}

Onde SFoo é idêntico a Foo, porém com seus métodos marcados como synchronized.

Tempo de execução para 100000000 (perceba que eu tive que usar 10x menos operações para a versão sincronizada):
client vm: 6337 ms
server vm: 4184 ms
server vm + EA: 276 ms (!!!)

Ou, em iterações por millisegundo:
client vm: 15778
server vm: 23897
server vm + EA: 361271

Ou seja, o speedup aqui foi de 21x - bem melhor do que a versão não sincronizada. Este resultado sugere que o lock elision é, de alguma forma feito. Porém, se compararmos somente os resultados da EA, a versão sincronizada é quase 2x mais lenta, o que sugere que a JIT não elimina todo o overhead da entrada e saída do monitor - quem sabe para continuar com algumas garantias de visibilidade.

Já se alterarmos o código inicial para:

public void execute() {
for (int i = 0; i < max; i++) {
Foo foo = new Foo();
synchronized (foo) {
foo.setNumA(i);
foo.setNumB(i * i);
total += foo.sum();
}
}
}

Seria esperado que os resultados fossem parecidos com o primeiro caso, já que o monitor do objeto foo só é adquirido uma vez por iteração. Vamos aos resultados (dessa vez apenas com o EA ativado), desta vez combinado com outras opções presentes antes do update 14 que poderiam afetar este teste:

Tempo em millisegundos para 1000000000 iterações:
server + EA: 9606ms
server + EA + Eliminate locks: 9543ms
server + EA + Biased locking: 9599ms
server + todas acima: 9515ms

As opções acima (Eliminate Locks e Biased Locking) são ativadas respectivamente por: -XX:+EliminateLocks e -XX:+UseBiasedLocking e, supostamente, são ativadas por padrão.
Aparentemente, um bloco sincronizado maior limita as otimizações que podem ser feitas pela análise de escape - os resultados obtidos estão aproximadamente 3x piores do que o teste anterior (com os métodos marcados com 'synchronized').

G1 e "Compressed OOPTS"
Além da EA, o update 14 trouxe também um novo garbage collector chamado G1 (garbage first), apresentado no JavaOne 2008 e ainda em estágio experimental. Trata-se de um GC paralelo e que tenta garantir uma pausa máxima (que pode ser definida pela linha de comando) e pode ser ativada pela combinação de opções:
-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC
Por último, os "compressed object pointers" também foram integrados. Quem usa uma jvm de 64bits já deve ter notado que o consumo de memória é bem maior do que na jvm de 32 bits. Isso ocorre porque as referências para objetos ocupam 64 bits, mesmo quando todo o espaço de endereçamento não é necessário. Com esta opção nas JVM de 64 bits, as referências para objetos continuam com 32 bits a menos que seja necessário. Esta opção é ativada por:

-XX:+UseCompressedOops


e já existia nas JVM de "performance".


Mais informações sobre o G1 podem ser encontradas em:

e uma pequena demonstração dos ponteiros comprimidos pode ser vista em:
(ainda na jvm6 de performance)

Conclusão
Estas últimas atualizações podem trazer ganhos de performance muito bons para alguns cenários, embora haja alguma limitação no que a JVM pode fazer na presença de locks - o que pode muito bem ser corrigido em updates futuros ou quem sabe no jdk7. Embora eu espere que o JDK7 não demore a sair, acho muito interessante que a Sun não esteja se contendo em aperfeiçoar a sua JVM mesmo nas atualizações menores.







public static void main(String[] args) {
//yey
/**
* woot
*/
}

terça-feira, 26 de maio de 2009

Falando em Java

Olá,

sejam bem vindos ao Blog da Maps. Este primeiro post irá falar do evento "Falando em Java" organizado pela Caelum neste último domingo 24 de maio de 2009.

Este ano os palestrantes internacionais convidados foram Jim Webber da Thoughtworks e Bill Burke da Red Hat. Este último no entanto não conseguiu ir ao evento por problemas com seu visto para o Brasil. Sua palestra foi substituida por uma palestra adicional de Jim Webber. Seguem minhas impressões:

"Keynote: Guerrilha SOA" por Jim Webber

Boa apresentação. O palestrante demonstrou um ótimo preparo e muito conhecimento sobre o tema discutido. Jim contou rapidamente como a computação vem encarando o problema da integração entre softwares para então criticar o modelo atual. Segundo ele, este modelo não escala muito bem ou não se adequa muito bem por depender de middlewares que realizem a comunicação. O modelo que ele propõe faz uso da própria Web como middleware através de operações REST. REST propõe que funcionalidades da aplicação sejam abstraídas em "recursos" únicos (por exemplo uma URI) e que o acesso a estes recursos seja feito por meio de uma interface padronizada (por exemplo HTTP) e que desta forma sejam transportados representações destes recursos (html, json, xml, etc). Jim defende que a própria Web atende todos estes requisitos além de prover "methods" (GET, POST, PUT, DELETE) que ajudam a identificar as operações (ou serviços) que queremos executar. Neste modelo não é necessário nenhum tipo de middleware para realizar a comunicação entre aplicações, apenas a capacidade de disparar um request com determinado método para determinada URI e a capacidade de entender a resposta. Para quem quiser saber mais sobre isso segue o link da wiki: http://en.wikipedia.org/wiki/Representational_State_Transfer .

"O profissional Java efetivo" por Paulo Silveira e Rafael Cosentino

Apresentação inconsistente. O tema parecia muito interessante porém a palestra foi mal conduzida. Os palestrantes não demonstraram preparo. Adotaram um modelo de apresentação baseado em piadas (internas, sem a menor graça) e perderam muito tempo com isso. Abstraindo a questão das piadas, os palestrantes se concentraram um demonstrar diversos problemas de integração entre softwares e qual era a maneira mais adequada de resolver cada um dados os requisitos da comunicação a ser estabelecida. Por exemplo, para uma integração semanal entre assistências técnicas e uma matriz sugeriram CSV pela simplicidade. Para um médico controlar remotamente um robô que está realizando uma operação em tempo real sugeriram um protocolo binário pela performance. Para "mashup's" (http://en.wikipedia.org/wiki/Mashup_(web_application_hybrid)) foram sugeridos web services. Por terem se concentrado nas piadas acabaram não aprofundando nenhum dos exemplos apenas listando os prós e contras de cada solução adotada. Foi apenas no final da palestra que o Paulo conseguiu fazer alguns comentários alinhados com o título da palestra. Ele sugeriu (com razão) que profissionais Java devem continuar estudando sobre programação em geral, se envolver com a comunidade, ler blogs técnicos, etc. Concordo plenamente, inclusive a recomendação é válida para qualquer tipo de profissional. Hoje em dia as coisas mudam de figura muito rápido, o que hoje está em alta rapidamente pode estar em baixa. Para não ficarmos para trás temos que nos manter atualizados.

"JBoss Seam e WebBeans" por Alessandro Lazarotti e Ricardo Nakamura

Apresentação regular. Começaram com uma metáfora para explicar inversão de controle / injeção de dependências. Embora a metáfora tenha sido boa (um contraste entre ir até a padaria comprar o pão e receber o pão em casa) acabaram perdendo tempo com demasiadas tentativas de humor (uma ou outra boa). Na sequência, mostraram como resolver o problema usando anotações do WebBeans. Explicaram rapidamente que WebBeans é a implementação de referência da JSR-299 que se propõe a padronizar a forma como injeção de dependências deve ser feita em Java. Em seguida mostraram uma demo que funcionou corretamente. Acabaram pouco de Seam, apenas disseram que o core do Seam em sua próxima versão será o WebBeans e mostraram algumas features adicionais. No geral a apresentação foi melhor que a anterior embora tenham mal aproveitado o tempo.

"VRaptor 3: Guerrilha Web" por Felipe Sabella e Guilherme Silveira

Infelizmente voltamos do almoço apenas a tempo de pegar o final da palestra. A apresentação mostrou o VRaptor que está estreiando sua versão 3.0. O VRaptor é um framework MVC desenvolvido pelo pessoal da Caelum com foco em Ajax, rich interfaces, REST e integração com outros frameworks.

Arquitetura para aplicações Java de médio porte por Guilherma Moreira e Segio Lopes

Apresentação ruim. Novamente ficou clara a falta de preparo. Perderam tempo com encenações e as demos não funcionaram corretamente. Deram algumas dicas de Hibernate para resolução de problemas comuns:
- commit + clear para limpar o transaction log e o persistence context evitando um demasiado uso de memória e consequente diminuição de performance por conta de "garbage collection"
- uso de stateless session para inserts em massa

Para onde vai a plataforma Java? Linguagens dinâmicas, JavaTV, JavaFX e além! por Anderson Leite e Fabio Kung

Apresentação regular. Explicaram como são feitas chamadas a métodos no bytecode em diversos casos (invokestatic, invokespecial, invokevirtual e o novo invokedynamic que será introduzido no Java7). Depois disso abordaram funcionalidades encontradas em linguagens dinâmicas como Closures. Mostraram as propostas de inclusão de Closures em Java (BGGA, CICE e FCM) porém com péssimos exemplos que não demonstraram o real poder que este recurso permite. Do jeito que foi colocado pareceu que closures em Java seriam apenas um jeito mais bizarro de se escrever as mesmas coisas. Isto é péssimo pois quem não tinha tido contato com este tipo de construção antes ficou com uma impressão completamente equivocada. No fim, por terem perdido tempo nos tópicos anteriores não falaram de JavaTV e nem de JavaFX apenas citando rapidamente o Ginga-NCL e o GingaJ (http://www.ginga.org.br/).

GET /Connected por Jim Webber

Ótima apresentação. Após um dia inteiro de palestras regulares ou inconsistentes foi refrescante ter encerrado com mais uma boa palestra de Jim Webber. Novamente o palestrante demonstrou excelente preparo inclusive com improvisações muito bem encaixadas. Jim continuou falando de "Web Enabled Services" com REST. Seguiu defendendo a Web como o middleware perfeito para este cenário sem necessidade de outros frameworks realizarem a comunicação. Não chegou a dar um exemplo prático mas explicou com maiores detalhes como funcionariam por exemplo pedidos de café numa cafeteria qualquer usando REST, basicamente um case CRUD. Mostrou como seriam feitos a inserção, alteração e remoção do pedido e o que aconteceria em casos inválidos (por exemplo a tentativa de alteração de um pedido já preparado).

Para encerrar este longo primeiro post posso dizer que a ida ao evento valeu a pena, principalmente por conta das palestras do Jim Webber.