A mágica por trás das interceptações em Java

Tinha escrito esse no LinkedIn há algum tempo. Só estou postando aqui para manter tudo junto.

Qualquer programador Java deve estar mais habituado a injetar dependências, definir métodos de callback ou, entre outras, fazer lazy load de suas entidades. Mas você já parou para pensar como isso acontece por trás dos panos? Como, por exemplo, o Hibernate permite que você defina métodos para executar antes e depois da sua entidade ser sincronizada com o banco de dados subjascente, como um container EJB/CDI consegue interceptar as chamadas de métodos para iniciar transações, executar rotinas de segurança e encapsular exceções?

Como acontece em qualquer profissão, o profissional trabalha melhor quando conhece bem a ferramenta que utiliza. E não seria diferente com os profissionais de TI. Tomar conhecimento dos mecanismos que permitem com que a mágica por trás das tecnologias aconteçam não só te tornará um profissional melhor, mas te permitirá antecipar situações inconvenientes e utilizar seguramente um componente qualquer.

Neste caso, vamos falar dos proxies.

Proxy é o nome do design pattern designado a permitir fornecer um substituto para um objeto qualquer. Esse substituto deve ser capaz de interceptar as chamadas ao objeto substituído permitindo que operações extras sejam executadas em complemento à função do objeto real.

Suponha que você tenha a classe a seguir:

public class Greetings {
   
    public String sayHello(String to) {         
        return "Olá " + to + ".";     
    } 
} 

E você tenha uma factory para os objetos dela:

public class GreetingsFactory {

    public static Greetings createGreetings() {  
       return new Greetings();
    }
} 

Quando você precisar gerar uma saudação, você pode facilmente invocar:

GreetingsFactory.createGreetings().sayHello("Jaumzera");

Que vai te retornar um belo:

Olá Jaumzera.

Agora suponha que em dado momento seu cliente decida que a saudação deva ser retornada entre tags <h1> e </h1>. É claro que no mundo real simplesmente alteraríamos a classe Greetings para fazer o desejado. Entretanto, aqui estamos para exemplificar o padrão proxy.

public class GreetingsHtmlProxy extends Greetings {
   
    @Override
    public String sayHello(String to) {         
        return "<h1> " + new Greetings().sayHello(to) + ".</h1>";     
    } 
} 

A nova classe GreetingsHtmlProxy implementa a mesma interface do proxy (que neste caso é a própria classe), delega a chamada para um objeto da classe Greetings e altera seu resultado para se adequar ao novo requisito. E então a fábrica é alterada para retornar para retornar o novo objeto saudador:

public class GreetingsFactory {

    public static Greetings createGreetings() {  
       return new GreetingsHtmlProxy();
    }
} 

O fato da classe proxy estender a classe original, fará com que não tenhamos problemas de compatibilidade em quaisquer que sejam os pontos onde a classe Greetings original estava sendo usada.

E onde antes exibia uma suadação simples, passará a exibir:

<h1>Olá Jaumzera.</h1>

Observe que utilizando a classe proxy poderíamos, por exemplo, executar ações antes ou depois do método orignal ser executado, como executar rotinas de log, iniciar e parar transações, criar callbacks para pré e pós execução, entre outras.

Concordamos no fato de que é um exemplo medíocre, mas serve bem para mostrar o papel de um objeto substituto, ou seja, um proxy.

Com a importância que esse tipo de abordagem foi ganhando, foi inserida na plataforma Java, a partir da versão 1.3, a possibilidade de criar-se versões dinâmicas do pattern proxy. Essas versões nada mais são do que implementações de runtime de interfaces Java. Pelo fato de se basearem em interfaces, vamos extrair uma interface do exemplo anterior.

public interface IGreetings {
    
    String sayHello(String to);
    
}

E alterar o objeto Greetings para implementá-la:

public class Greetings implements IGreetings {
    
    public String sayHello(String to) {
        return "Olá " + to;
    }        
}

Para criar um proxy dinâmico, neste caso, vamos implementar a interface InvocationHandler, do pacote java.lang.reflect:

public class GreetingsInvocationHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {

        return "<h1>" + new Greetings().sayHello((String) args[0]) + "</h1>";
    }
}

A interface faz exatamente o mesmo trabalho do proxy que implementamos manualmente, ou seja, envolve o retorno do objeto original em tags <h1> e </h1>.

Para executar o exemplo, criamos uma instância do proxy a partir da classe Proxy, também do pacote `java.lang.reflect`:

IGreetings greet = (IGreetings) Proxy.newProxyInstance(
                Greetings.class.getClassLoader(),
                new Class[]{IGreetings.class},
                new GreetingsInvocationHandler());

System.out.println(greet.sayHello("Jaumzera"));

E, ao executar, podemos observar o resultado desejado.

<h1>Olá Jaumzera.</h1>

O fato é que utilizar implementações de runtime baseadas em interfaces limita um pouco a utilização da estratégia de proxy. Portanto, frameworks como o próprio Hibernate utilizam APIs mais sofisticada que permite a criação de proxies baseadas em classes concretas, como por exemplo o CGLib (https://github.com/cglib/cglib), uma vez que seus objetos de trabalho, ou seja, as entidades, são baseadas em POJOs frequentemente criados sem interface de negócio.

Para implementar o mesmo proxy anterior utilizando a API CGLib, faríamos da seguinte forma.

 Greetings greetCGLib = (Greetings) Enhancer.create(
                Greetings.class, new MethodInterceptor() {

            @Override
            public Object intercept(Object o, Method method,
                    Object[] os, MethodProxy mp) throws Throwable {
                
                return "<h1>" + mp.invokeSuper(o, os) + "</h1>";
            }
        });
        
        System.out.println(greetCGLib.sayHello("Jaumzera"));

E, ao executar, verás que o resultado é o mesmo do exemplo anterior. Também é evidente que a CGLib simplifica muito a criação dos proxies dinâmicos, uma vez que podem ser utilizadas a partir de classes concretas, em detrimento dos proxies Java, que são baseados em interfaces.

Enfim, resta dizer que a CGLib não possui uma documentação muito legal, portanto, você tem que confiar no tato e no JavaDoc da API. Além do fato de que, a menos que você esteja projetando frameworks ou qualquer coisa do tipo, sempre haverá uma solução baseada em modelos de objetos mais elegante do que apelar para introspecção de objetos ou qualquer coisa mais “baixo nível” dessas.

Até o próximo.

Decorators

Continuando os posts sobre padrões de projeto, vamos falar sobre composição, mais especificamente decoradores; e vamos começar pelo código! Imagine uma classe de saudação chamada Greetings:

public class Greetings {

   public static Greetings create() {
       return new Greetings();
   }

   public String sayHello(String to) {
        return "Hello " + to;
   }
}

Agora imagine que os objetos da classe Greetings são usados por todo seu código:

// código ...
Greetings.create().sayHello(loggedUser);
// mais código

Agora digamos que, hipoteticamente, em dado momento, alguém mudou os requisitos para que as classe tenha que retornar a saudação entre tags h1 e /h1 (e é claro que isso é hipotético).

Você poderia simplesmente sair alterando o seu código para concatenar a saudação retornada entre as tags HTML. Mas digamos que existam milhares de pontos onde você precisa alterar e você não quer sair alterando manualmente. De repente, você tem a idéia genial de usar o polimorfismo:

public class Greetings {
    public static Greetings create() {
        return new Greetings() {

            // NOTE: eu não sei por quê Satanás o WordPress quebra
            // a formatação desse código, mas você pode ver ele
            // bonitinho e formatado aqui:
            // @see https://pastebin.com/C2nFykjw
            @Override
            public String sayHello(String to) {

                return "
<h1>" + super.sayHello(to) + "</h1>
";

            }
         };
     }

     public String sayHello(String to) {
         return "Hello " + to;
     }
}

E é isso. Você alterou o método de criação da sua classe para retornar um objeto decorado. É claro que aqui usamos o artifício das classes anônimas do Java, mas a idéia é basicamente essa:

a classe decoradora estende a decorada com objetivo de acrescentar comportamentos

Você consegue identificar um strategy por aqui? Para o cliente (ou seja, quem usa) trata-se ainda de um objeto da classe Greetings, no entanto, nos bastidores, com o comportamento modificado por uma de suas subclasses.

Vamos aos conceitos…

A idéia por trás do padrão Decorator é a utilização do contrato (uma interface). Embora não tenhamos usado explicitamente uma, pelo conceito de herança/polimorfismo, a classe Greetings permite um ponto de extensão: um método público não-final. O cliente espera por um objeto da classe Greetings (ou qualquer uma das suas subclasses), desde que especifique um método sayHello com a assinatura especificada. O padrão decorator (se você clicar no link) também é conhecido como Wrapper e possui outra variação muito conhecida: o pattern Proxy.

Pretendo falar sobre o pattern proxy em outra ocasião. Por hora, dê uma lida sobre os Dynamic Proxies que escrevi no Linkedin.

Até o próximo.

Tratamento de exceções em Java

Todo o desenvolvedor de software já passou pela situação de ter que caçar um bug no código do projeto. Na grande maioria das vezes o problema inicia-se com a queixa de um usuário sobre o insucesso ao tentar utilizar uma funcionalidade qualquer. Geralmente ele liga para o suporte e reporta uma mensagem de erro. Outras vezes você tem um pouco mais de artefatos como screenshots e códigos de erros. Nesse tipo de situação, qualquer evidência pode te poupar de perder tempo (dinheiro) e de dores de cabeça. É exatamente por isso que venho estudando boas práticas no que tange o tratamento de exceções e erros há algum tempo. Um pequeno conjunto de diretrizes neste sentido pode te poupar de horas e horas de transtornos.

Em uma das minhas pesquisas, um dos melhores “guias” – vamos chamar assim – que encontrei foi uma resposta à uma questão do StackOverflow resumindo em 10 tópicos as boas práticas do tratamento de exceções. É a partir dessa resposta que estou escrevendo este post. A partir dela vou descambar para outros materiais que venho utilizando.

Tipos de exceções em Java

A estrutura de exceções em Java está definida da seguinte forma (se você não está familiarizado com as exceções em Java, dê uma lida neste post da Caelum):

Throwable, embora esteja nomeada segundo a orientação de se nomear interfaces (sufixo “able”) é na verdade uma classe concreta e permite que, além dela, suas especializações sejam lançadas (cláusula throw*) e capturadas (bloco try-catch) no seu código. Não vamos falar de Errors aqui, contudo, cabe ressaltar que eles são irreversíveis e quase sempre vão interromper a execução da aplicação.

Exceções verificadas

A classe java.lang.Exception é a raiz das exceções em Java e, por natureza, é uma exceção verificada (checked exception). Exceções verificadas são uma das característica do Java que faz com que o desenvolvedor tome algumas medidas em relação à exceção lançada: tratá-la (bloco try-catch) ou propagá-la (assinar o métodos com a cláusula throws**) para alguma outra rotina tratar. Todas as classes que derivam imediatamente de Exception são exceções verificadas, exceto RuntimeException e suas especializações.

Exceções não-verificadas

A classe java.lang.RuntimeException é uma derivada especial de Exception que tem como característica principal não ser verificada (unchecked exception). Exceções não-verificadas podem ser lançadas a qualquer momento e não necessariamente precisam ser tratadas ou propagadas.

Estrutura das exceções

Uma exceção é uma classe Java comum; com construtores, métodos e atributos. Se você não tem o source code do Java instalado, dê uma olhada no GrepCode. A classe, despida do JavaDoc, não passa de vinte e poucas linhas de código sobrescrevendo o próprio construtor:


package java.lang;

public class Exception extends Throwable {

    static final long serialVersionUID = -3387516993124229948L;

    public Exception() {
        super();
    }

    public Exception(String message) {
        super(message);
    }

    public Exception(String message, Throwable cause) {
        super(message, cause);
    }

    public Exception(Throwable cause) {
        super(cause);
    }
}

E para estendê-la é necessário nada mais do que:


public class MyOwnException extends Exception {}

Sim! Uma linha de código. A maior parte do comportamento de Exception está encapsulado na superclasse dela – Throwable – e serve basicamente para auxiliar o rastreamento da origem da exceção na pilha de chamadas de métodos (dê uma olhada em StrackTraceElement).

A grande maioria das extensões de Exception com as quais me deparei na minha vida de programador não faziam nada a mais do que isso. Um pecado! O simples fato de você adicionar algum comportamento extra em uma especialização da classe Exception, seja ela qual for, já seria uma mão na roda na sua leitura de stacktrace em busca de erros. Mas não vamos falar disso aqui. Deixa isso para outra ocasião.

Tratando exceções

Com base na resposta do Vineet Reynolds no StackOverflow, que divide o tratamento de exceções em 10 tópicos primordiais,  pretendo aqui implementá-lo com todo o resto que encontrei por aí, seja em livros, fóruns, vídeos e podcasts. Eu tomei a liberdade de modificá-los um pouco, às vezes trocando a ordem, outras removendo ou adicionando algum tópico de forma que faça mais sentido pra mim. Você, no entanto, está livre para (e deve) dar uma lida na resposta original.

Lance imediatamente

Assim que o código encontra uma situação adversa, é interessante lançar a exceção. Isso ajuda a rastrear a linha onde o erro foi ocasionado.

Encapsule a exceção original

Em complemento ao tópico anterior, se você vai lançar uma exceção nova, ao invés de propagar a que foi gerada, encapsule a original. Isso faz com que seja possível rastrear a pilha inteira de erros até se chegar à causa:


try {
    // código que gera a exception
} catch(Exception originalException) {
    throw new MyOwnException(“Erro ao tentar fazer qualquer coisa”,
        originalException);
}

log ^ throw

Você loga ou (XOR) você lança. Simples assim. Nunca faça os dois! Logar uma exceção relançá-la vai gerar pilhas imensas de log, vai dificultar a leitura do arquivo de saída e consequentemente a busca por origens de bugs.

try {
    // código que gera a exception
} catch(Exception ex) {
    LOG.warn(“Problema ao tentar…”);
    // qualquer coisa que você for fazer para tratar a exception.
    // menos relançá-la
}

 Ou


try {
    // código que gera a exception
} catch(Exception ex) {
    throw new MyOwnException(
        “Erro ao tentar fazer qualquer coisa”, ex);
}

Só capture exceções que você puder tratar

Parece óbvio, não? Se o código tenta abrir um arquivo e o arquivo não existe a pergunta é: devo tratar o IOException lançado? Sim:


public File openConfigFile() {
    try {
        // código que abre o arquivo
    } catch(IOException ex) {
        LOG.warn(“Não foi possível abrir o arquivo: “
            + ex.getMessage());
        return createNewFile();
    }
}

Alternativamente, você pode decidir que o código que está chamando o método openConfigFile deve tomar alguma medida em relação à exceção:


public File openConfigFile() throws IOException {
    // código que abre o arquivo
}

Ou simplesmente você pode decidir que não há nada a fazer:


public File openConfigFile() {
    try {
        // código que abre o arquivo
    } catch(IOException ex) {
        throw new RuntimeException(“Erro ao abrir arquivo”, ex);
    }
}

Opte por exceções da API sempre que puder

A linguagem Java já especifica uma gama de exceções que são usadas dentro das próprias APIs. É encorajado reutilizá-las ao invés de criar uma série de outras exceções dentro da sua própria aplicação. Por exemplo, um método pode receber uma String representando uma data e, antes de gravá-la no banco de dados, ele pode tentar convertê-la em um objeto java.util.Date. Para isso, foi definido que a data tem que estar no padrão ISO 8601: yyyy-mm-dd. O código poderia, por exemplo, especificar (criar) uma InvalidDateFormatException ou qualquer outra coisa parecida. Mas porque não simplesmente lançar uma:

throw new IllegalArgumentException(“Formato de data inválido”);

do pacote java.lang? IllegalArgumentException é uma exceção não-verificada, já que certamente não há o que fazer neste caso. Nada mais justo do que propagá-la para para as camadas mais acima, nas quais ela possa ser logada e eventualmente ter a mensagem de erro ao usuário final.

Lance exceções não-verificadas para erros programação

No caso anterior, com a data em formato inválido, não há o que ser feito. Uma alternativa seria utilizar a data atual (new Date()) dentro do bloco catch, mas isso vai sempre depender do domínio do seu problema. Entre elas está a própria IllegalArgumentException, mencionada no tópico anterior, e qualquer outra que estenda RuntimeException, como IllegalStateException para objetos em estados inválidos, UnsupportedOperationException para operações não implementadas ou métodos não mais suportados (removidos de uma API, por exemplo). Vale frisar que – seja lá qual for a ocasião – nunca lance um NullPointerException e se você não consegue entender o porquê, dá uma lida nisso aqui. Hehehe. Brincadeira =). NullPointerException é uma condição adversa muito especifica: foi invocada uma operação em uma referência de objeto que estava nula. Você consegue imaginar em que situações isso aconteceria? Pois bem:


if(obj == null) {
    throw new NullPointerException(“Objeto obj null”);
} else {
    obj.doSomething();
}

Se você não conseguir entender o que está errado no código acima, sugiro que você então dê uma olhada neste link. E desta vez não é brincadeira.

Lance exceções verificadas quando a exceção deve ser tratada por quem invocou

Eu sei que você questionou o exemplo anterior da data, por isso vou reiterá-lo aqui. Se, por qualquer razão, você espera que o cliente do seu método (o método que o está invocando) trate a situação adversa da data não estar no formato esperado, aí sim: cria uma exception verificada do tipo InvalidDateFormatException, como foi mencionado naquela seção e a lance. Neste caso, o cliente estará ciente de que se a data não for informada no formato esperado, ele terá que tomar alguma providência.

Uma exceção por módulo é mais que suficiente

Não que você não possa especificar suas exceções, mas faça isso com carinho. Se você tem um módulo de controle de clientes, por exemplo, crie uma CustomerModuleException. Fica mais fácil trafegar-la entre as várias camadas ou serviços da sua aplicação. Nada te impedirá de especializá-la internamente, criando subclasses como CustomerNotFoundException, InativeCustomerException ou qualquer outra coisa que possa representar uma condição intrínseca ao seu módulo. Mas dele pra fora, a CustomerModuleException é mais do que suficiente. Dê uma olhada na estrutura de EJBException e de PersistenceException.

Converta exceções verificadas em não-verificadas somente quando necessário

Em um dos exemplos da leitura de arquivos, nas seções anteriores, foi encapsulada uma IOException em uma RuntimeException para que ela pudesse “vazar” do método. Tal coportamento não deve ser deliberado. Considere esse tipo de “conversão” (de checked para unchecked) em situações em que a thread em execução deva ser abortada.

Não use Throwable.printStackTrace()

Algumas IDE’s costumam inserir um ex.printStackTrace() quando você opta por englobar um código em um bloco try-catch. Embora seja útil quando se trata de pequenas codificações para testes, é completamente desencorajado em códigos que vão para ambientes de produção. O método printStackTrace é definido na classe Throwable e herdado por todas as subclasses de Exception. Ele faz uso da API System.err que às vezes compartilha os recursos com o System.out (se estiverem sendo redirecionadas para o mesmo dispositivo/arquivo). Só isso já é uma boa razão para evitá-la. Considere o seguinte: uma vez que elas não fornecem qualquer alternativa thread-safe de acesso, o log gerado em uma situação concorrente seria completamente ilegível.

Use enums para mensagens

Eu gosto de exibir mensagens de erros significativas para o usuário. Exibir as mensagens de exceções é melhor ainda, desde que elas sejam compreensíveis. Utilizar enums nesse contexto pode tornar sua vida mais fácil; já que você está definindo uma mensagem source-level como enum e na ponta (front) você pode usar qualquer mecanismo de internacionalização para traduzí-la para o usuário final.

Voltando ao exemplo do CustomerModuleException, poderíamos especificar algo do tipo:

throw new CustomerModuleException(
    CustomerModuleExceptionMessages.EMAIL_ALREADY_TAKEN);

Ainda neste cenário, considere configurar atributos dinâmicos à sua exceção. Lembre-se, exceptions são classes Java serializáveis comuns e você pode facilmente permitir o comportamento abaixo com o emprego de um Map<String, Serializable>:

throw new CustomerModuleException(
    CustomerModuleExceptionMessages.EMAIL_ALREADY_TAKEN)
        .put("email", customer.getEmail())
        .put("username", customer.getUsername());

 

Use o método de logging adequado

Evite usar log.error() ou seus variantes para tudo. Existem situações em que o comportamento excepcional é esperado ou não vai causar qualquer prejuízo para o usuário final. Nestes casos, você pode considerar usar log.warning() ou até mesmo log.info(). Use log.error() para situações irrecuperáveis.

Considerações

Bem, é isso. Gostaria de convidar você à compartilhar suas considerações sobre exception handling aqui nos comentários e me ajude a melhorar o post.

“No mais” [sic], muito obrigado e até o próximo.

 

Strategy

Estou iniciando uma série de posts sobre padrões de projeto. Embora tal assunto se tratar de um assunto repleto de material, pretendo aqui dar uma abordagem mais rápida e prática sobre cada padrão e sobre suas aplicações.

Os design patterns foram um conceito adaptado da construção cívil, onde problemas comuns eram resolvidos sempre de uma forma padrão, conhecida entre os envolvidos da área. Você vai encontrar muito material sobre as origens dos design patterns com uma busca rápida no Google.

O livro Design Patterns: Elements of Reusable Object-Oriented Software (GoF, 1995), que popularizou o assunto entre a comunidade dos desenvolvedores de software, propõe três categorias de padrões, sendo eles: os estruturais, os comportamentais e os de criação. Embora o objetivo dessa empreitada seja apresentar cada padrão dentro do seu próprio domínio de desenvolvimento, vamos por hora ignorar essas classficações e começar pelo padrão que julgo (IMHO) ser a base para os demais: o pattern Strategy.

Segundo os autores do livro acima mencionado, numa tradução livre:

Cada padrão descreve um problema que ocorre recorrentemente em nosso ambiente, e então descreve a essência da solução para ele, de forma que você possa usar tal solução milhares de vezes sem, no entanto, fazer isso do mesmo jeito duas (ou mais) vezes.

Ou seja, um padrão deve descrever a forma com que você resolve um problema. Para isso, ele precisa estar sustentado sob quatro aspectos:

  • o nome do pattern: um nome descritivo, intuitivo e que remeta ao problema assim que mencionado. O objetivo aqui é nivelar e contextualizar os desenvolvedores à respeito de uma solução assim que o pattern for referenciado pelo nome. Em outras palavras, em uma reunião, por exemplo, ao sugerir a adoção (implementação) de um pattern para atacar um problema, todos os envolvidos devem estar cientes das medidas que serão adotadas para saná-lo;
  • o problema: qual o problema e qual o contexto em que o padrão deve ser aplicado;
  • a solução: descreve a metodologia de aplicação. O algorítimo.
  • as consequências: os benefícios e efeitos colaterais relacionados à adoção do padrão. Isso deve levar em conta aspectos que tangem desde à tecnologia de implementação à decisões de design.

Dito isso, seguimos para a definição do primeiro padrão de projeto: o Strategy.

Strategy

Estamos começando pelo padrão Strategy por uma razão muito simples: é o mais simples e ele vai te servir de base para conceber os demais. O Strategy parte de um conceito primordial do paradígma da orientação à objetos: encapsular o que varia. Basicamente, serve para que você possa definir famílias de algorítimos sob um contrato comum, mas que no entanto, permita comportamentos diferentes para cada um deles.

Vamos ao exemplo. Uma interface denominada Animal que defina um método action.

public interface Animal {
    void action();
}

Pode ter uma realização do tipo:

public class Dog implements Animal {
    @Override
    public void action() {
       System.out.println("Bark");
    }
}

Uma outra:

public class Cat implements Animal {
    @Override
    public void action() {
        System.out.println("Meow");
    }
}

E outra:

public class Cow implements Animal {
    @Override
    public void action() {
        System.out.println("Moo");
    }
}

Enquanto que um código cliente pode facilmente mixá-los em uma coleção de animais:

public static void main(String[] args) {
    List<Animal> animals = new ArrayList<>();
    animal.add(new Dog());
    animal.add(new Cat());
    animal.add(new Cow());

    for(Animal item : animals) {
        item.action();
    }
}

Se você observar, a própria definição da interface List é baseada no pattern Strategy: o contrato é definido pela interface enquanto que as variações comportamentais (implementações de facto) são definidas pelas classe concretas ArrayList, LinkedList, Stack, Vector, etc. Toda a API de coleções Java foi concebida sob esse conceito.