Por que eu devo ler este artigo:Injeção de dependências é um pilar do desenvolvimento moderno; com CDI 2.0, esta ferramenta será disponível para desenvolvedores Java SE. A nova versão de CDI também traz avanços para programação assíncrona, que é um tópico muito relevante para a performance de uma aplicação. Além disso, o desenvolvimento com CDI se tornou ainda mais simples ao adotar pequenas facilidades, como literais de anotação predefinidos, ordenação de eventos e uso de lambdas e streams.

Desde o seu lançamento em 2009, Context and Dependency Injection (CDI) vem se tornando uma das tecnologias mais importantes da plataforma Java EE. Após mais de dois anos de trabalho desde a versão 1.2, a especificação final de CDI 2.0 foi publicada em abril de 2017. Ao mesmo tempo, sua implementação de referência, Weld 3.0, também foi disponibilizada para uso.

Nessa versão, provavelmente a funcionalidade mais notável é o recém-adicionado suporte a Java SE. Sempre houve uma grande demanda para utilizar injeção de dependências em outros ambientes, sejam frameworks web alternativos ou aplicações desktop. Buscando atender a esta necessidade, essa especificação traz novas APIs que permitem inicializar um container CDI manualmente.

Para melhor incorporar as funcionalidades de Java SE, o próprio documento foi reestruturado. A nova versão da especificação está dividida em três partes. A primeira especifica o núcleo de CDI: injeção de dependências, contextos, eventos, decoradores, interceptadores etc. Este conteúdo é aplicável tanto a Java SE quanto a Java EE. É a primeira parte que contém a maior parte do documento, inclusive o conteúdo mais antigo. Ainda assim, ela traz algumas novidades interessantes, como novas SPIs (especialmente interessantes para desenvolvedores de frameworks), ordenação de observadores de eventos, eventos assíncronos, configurators para métodos observadores e algumas facilidades de Java 8.

Por sua vez, a segunda parte da especificação descreve a API para utilizar CDI em Java SE. Aqui, o conteúdo mais relevante é a explicação de como iniciar um container CDI em uma aplicação standalone. Veremos como isto é feito mais à frente.

Por fim, referências a outras especificações Java EE ou a comportamentos específicos de servidores foram extraídas para a terceira parte. Nela, especifica-se como CDI interage com tecnologias como EJBs e Expression Language (EL). No geral, há poucas novidades neste aspecto: o conteúdo desta parte já pode ser encontrado em sua quase totalidade nas versões anteriores de CDI.

Neste artigo, exploraremos as novidades de aplicação mais generalizada. Veremos primeiro como ordenar observadores de eventos, uma feature muito útil para evitar race conditions e outros problemas similares. Posteriormente conheceremos os poderosos eventos assíncronos. Programação assíncrona é uma tendência, dado o seu poder e performance; com eventos assíncronos, podemos utilizar este estilo de desenvolvimento de maneira bastante intuitiva. Naturalmente, o uso de CDI em Java SE não passará em branco: há uma seção inteira dedicada a entender como utilizar essa excitante novidade. O novo CDI traz também alguns literais de anotações predefinidos que merecem uma breve análise. Por fim, há exemplos de como as novas capacidades de Java 8 (lambdas, streams) foram utilizadas na especificação.

Interessado? Comecemos então por uma funcionalidade há muito solicitada pela comunidade: previsibilidade na ordem de execução de observadores de eventos.

Ordenação de observadores de eventos

Desde CDI 1.1, decoradores e interceptadores podem ser ordenados. Basicamente, podemos definir que um interceptador (ou decorador) será chamado antes de outro através da anotação @Priority. Desde então, a comunidade vinha pedindo uma maneira de definir também a ordem de execução dos observadores de um evento. A nova especificação finalmente nos permite fazer isso.

Ordenando eventos

Idealmente, observadores de eventos devem ser os mais independentes possível: um observer não deveria afetar o resultado de outro executado posteriormente. Isto mantém o código desacoplado e fácil de compreender.

Isto não é sempre possível, porém, e até CDI 1.2, nada poderia ser feito a esse respeito. CDI 2.0 finalmente traz uma solução: podemos utilizar a anotação @javax.annotation.Priority para ordenar observers em relação a outros. Esta anotação deve ser adicionada ao argumento com @Observes e exige um parâmetro, do tipo int, que é a prioridade de execução.

Na Listagem 1 temos um exemplo de método observer com prioridade. Note que o argumento objeto do método possui duas anotações. @Observes declara este método como observador, enquanto @Priority(1) define a prioridade de execução.

Listagem 1. Declarando um observador com prioridade.


  public class ExemploObserver {
   
   public void metodo(@Observes @Priority(1) Object objeto) {
    System.err.println("Exemplo de observador com prioridade.");
   }
  }

Observadores com prioridade menor são executados antes de outros com prioridades maiores. Assim, por exemplo, o método da Listagem 2 será necessariamente executado depois do observador da Listagem 1.

Listagem 2. Um observador menos prioritário.


  public class ExemploObserverMenosPrioritario {
   
   public void outroMetodo(@Observes @Priority(2) Object objeto) {
    System.err.println("Este observer será executado posteriormente.");
   }
  }

Como se vê, o processo para definir a prioridade do observador é bem simples. Ao ordenar observadores, o maior desafio está em decidir que valores de prioridades utilizar.

Definindo prioridades

Embora @Priority aceite qualquer inteiro não-negativo, é importante escolher bem os valores das prioridades.

Considere, por exemplo, prioridades pequenas, como 0 ou 1. Estas prioridades devem ser raramente utilizadas, pois nos impedem de executar observadores antes dos já definidos. De modo similar, se utilizarmos valores muito próximos (digamos, 1000 e 1001), não será possível executar um observador entre os dois. De qualquer forma, se utilizarmos prioridades arbitrárias, podem ocorrer conflitos com observadores definidos pela plataforma, ou por bibliotecas.

Felizmente, a plataforma provê uma maneira mais portável de definir os valores de prioridades. A classe interna javax.interceptor.Interceptor.Priority define algumas constantes que nos ajudam a escolher estes valores de maneira mais consistente. Os valores destas constantes são definidos de acordo com os seguintes princípios:

  • A JVM e os servidores de aplicação – enfim, a plataforma – devem ser capazes de executar observadores antes que qualquer outro código (para garantir um estado consistente) e depois que todos os outros foram executados (para liberarem recursos);
  • Bibliotecas precisam executar observadores antes da aplicação (de modo a ficarem em estado consistente para serem usadas), assim como depois da aplicação (para desalocarem quaisquer recursos que consumam);
  • Os observadores definidos pela aplicação, portanto, devem ...

    Quer ler esse conteúdo completo? Tenha acesso completo