Experimentos com Automatic Resource Management do Java 7

Compartilhar/Favoritos

Fiquei animado quando li a proposta de incluir na linguagem o que chamaram de Automatic Resource Management. Pareceu daquelas pequenas modificações com benefícios bem legais.

Quando trabalhamos com recursos que precisam ser fechados (streams, java.sql.Connection, etc), podemos usar alguns padrões parecidos com:

public void executa() throws IOException {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    try {
        out.write(new byte[1]);
    } finally {
        out.close();
    }
}

Há alguns elementos que considero problemáticos neste exemplo. O mais grave é o comportamento do método quando uma exceção for lançada na linha 4 e, depois, no close. Quando isto ocorre, a primeira, que é a mais importante, é silenciada. Este comportamento causa muitas dificuldades no diagnóstico de problemas em execução.

Uma saída para este comportamento é silenciar a exceção gerada no close:

public void executa() throws IOException {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    try {
        out.write(new byte[1]);
    } finally {
        try {
            out.close();
        } catch (IOEexception e1) {
            logErro("Erro fechando o output stream");
        }
    }
}

Este padrão resolve o problema, mas gera outros. O mais óbvio é o tamanho: o código começa a ficar grande e complexo. É possível criar ou utilizar utilitários que fecham recursos silenciosamente, ignorando as exceções:

public void fechaSilenciosamente(Closeable closeable) {
    try {
        closeable.close();
    } catch (IOException e) {
        logErro("Erro fechando o recurso", e);
    }
}
 
public void executa() throws IOException {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    try {
        out.write(new byte[1]);
    } finally {
        fechaSilenciosamente(out);
    }
}

Movendo o método fechaSilenciosamente para uma classe utilitária, ela pode ser usada em toda a aplicação. De fato, há algo parecido já pronto no commons-io.

A solução “resolve” aquele problema, mas também traz um efeito colateral: se algo sair errado apenas no fechamento, o fluxo de execução segue como se nada tivesse acontecido. Isso pode ser até desejável, mas pode causar problemas mais difíceis de diagnosticar ainda, pois é muito provável que a aplicação dependa de conteúdo fechado corretamente (caso contrário, poderia ler arquivos sem a parte final, por exemplo). Aparentemente, o ideal seria o seguinte:

  • Se ocorrer uma exceção apenas no close, ela deveria ser lançada.
  • Se ocorrer uma exceção apenas no bloco do try, ela deveria ser lançada.
  • Se ocorrer uma exceção no bloco do try e outra no close, apenas a exceção ocorrida no bloco principal deveria ser lançada.

Isto poderia criar códigos bizarros como:

public void experimento() throws IOException {
    IOException erro = null;
    MyOutputStream out = new MyOutputStream();
    try {
        out.write(new byte[1]);
    } catch (IOException e) {
        erro = e;
    } finally {
        try {
            out.close();
        } catch (IOException e) {
            if (erro == null) {
                throw e;
            } else {
                logErro("Erro no fechamento do stream", e);
            }
        }
    }
    if (erro != null) {
        throw erro;
    }
}

Com um pouco de refactoring, é possível melhorar um pouco:

private void fechaNaoTaoSilenciosamente(IOException erro, Closeable out) throws IOException {
    try {
        out.close();
    } catch (IOException e) {
        if (erro == null) {
            throw e;
        } else {
            logErro("Erro no fechamento do stream", e);
        }
    }
}                                                                           
 
public void experimento() throws IOException {
    IOException erro = null;
    MyOutputStream out = new MyOutputStream();
    try {
        out.write(new byte[1]);
    } catch (IOException e) {
        erro = e;
    } finally {
        fechaNaoTaoSilenciosamente(erro, out);
    }
    if (erro != null) {
        throw erro;
    }
}

Mesmo assim, na minha opinião, fica um lixo. E isso que abrimos apenas um stream, e sabemos que é muito comum a necessidade de abertura de dois ou mais destes bichinhos aninhados.

Para facilitar a vida das pessoas que trabalham com isso diariamente, há uma pequena modificação na linguagem Java a partir da versão 7. Ela é chamada de Automatic Resource Management e faz parte do projeto Coin. Esta modificação incrementa a sintaxe do bloco try. A implementação em Java 7 do primeiro exemplo ficaria:

public void executa() throws IOException {
    try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
        out.write(new byte[1]);
    }
}

Note que após a palavra try, é possível colocar entre parênteses a inicialização do recurso. O compilador então irá gerar o bytecode para tratar o finally. O tratamento das exceções ficou bem interessante:

  • Se ocorrer uma exceção apenas no close, ela será lançada.
  • Se ocorrer uma exceção apenas no bloco do try, ela será lançada.
  • Se ocorrer uma exceção no bloco do try e outra no close, apenas a exceção ocorrida no bloco principal é lançada, mas ela contém uma lista de exceções silenciadas que são apresentadas também no stack trace em um formato diferente.

Exemplo de stack trace:

java.io.IOException: Erro ao gravar dados
        at org.cristianok.CoinAutoResourceManagement$MyByteArrayOutputStream.write(CoinAutoResourceManagement.java:20)
        at org.cristianok.CoinAutoResourceManagement.autoResourceManagementNovo1(CoinAutoResourceManagement.java:70)
        at org.cristianok.CoinAutoResourceManagement.executa(CoinAutoResourceManagement.java:114)
        at org.cristianok.CoinAutoResourceManagement.main(CoinAutoResourceManagement.java:127)
        Suppressed: java.io.IOException: Erro no fechamento do stream
                at org.cristianok.CoinAutoResourceManagement$MyByteArrayOutputStream.close(CoinAutoResourceManagement.java:30)
                at org.cristianok.CoinAutoResourceManagement.autoResourceManagementNovo1(CoinAutoResourceManagement.java:71)
                ... 2 more

Ainda é possível abrir vários recursos em um mesmo bloco:

public void executa() throws IOException {
    try (MyByteArrayOutputStream out1 = new MyByteArrayOutputStream();
            MyByteArrayOutputStream out2 = new MyByteArrayOutputStream()) {
        // ...
    }
}

Caso ocorra exceções no bloco do try e ainda nos dois close, sairá algo assim:

java.io.IOException: Erro ao gravar dados
        at org.cristianok.CoinAutoResourceManagement$MyByteArrayOutputStream.write(CoinAutoResourceManagement.java:20)
        at org.cristianok.CoinAutoResourceManagement.autoResourceManagementNovo1(CoinAutoResourceManagement.java:73)
        at org.cristianok.CoinAutoResourceManagement.executa(CoinAutoResourceManagement.java:118)
        at org.cristianok.CoinAutoResourceManagement.main(CoinAutoResourceManagement.java:131)
        Suppressed: java.io.IOException: Erro no fechamento do stream
                at org.cristianok.CoinAutoResourceManagement$MyByteArrayOutputStream.close(CoinAutoResourceManagement.java:30)
                at org.cristianok.CoinAutoResourceManagement.autoResourceManagementNovo1(CoinAutoResourceManagement.java:75)
                ... 2 more
        Suppressed: java.io.IOException: Erro no fechamento do stream
                at org.cristianok.CoinAutoResourceManagement$MyByteArrayOutputStream.close(CoinAutoResourceManagement.java:30)
                at org.cristianok.CoinAutoResourceManagement.autoResourceManagementNovo1(CoinAutoResourceManagement.java:75)
                ... 2 more

Depois de fazer uns testes, achei uma ótima adição para a linguagem.

Ela já está implementada nos builds recentes do JDK 7. Se desejar brincar um pouco, pegue a versão mais recente do JDK na página de download do site do projeto.

You may also like...

Deixe uma resposta

O seu endereço de email não será publicado Campos obrigatórios são marcados *

Você pode usar estas tags e atributos de HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Translate »