A injeção de dependência e contextos (CDI), especificada por JSR-299, é uma parte integral do Java EE 6 e fornece uma arquitetura que permite que os componentes Java EE, como os servlets, o enterprise beans e o JavaBeans, existam dentro do ciclo de vida de um aplicativo com escopos bem definidos. Além disso, os serviços CDI permitem aos componentes do Java EE, como beans de sessão EJB e beans gerenciados do JavaServer Faces (JSF), serem injetados e interagir de maneira acoplada flexível iniciando e observando eventos.
Este tutorial tem base no post do blog de Andy Gibson, intitulado Primeiros passos com o CDI parte 2: injeção. Ele demonstra como é possível utilizar a injeção de CDI para injetar classes ou interfaces em outras classes. Também mostra como aplicar qualificadores CDI ao código, de modo que é possível especificar qual tipo de classe deverá ser injetado em um determinado ponto de injeção.
O NetBeans IDE fornece suporte embutido para a Injeção de dependência e contextos, incluindo a opção de geração do arquivo de configuração CDI beans.xml no momento da criação do projeto, suporte ao editor e à navegação para anotações, assim como vários assistentes para a criação de artefatos CDI comumente usados.
Para concluir este tutorial, os seguintes recursos e softwares são necessários.
O pacote NetBeans IDE Java também inclui o servidor de aplicativos GlassFish 3.x edição de código-fonte aberto, que é um recipiente compatível com o Java EE 6.
O suporte para CDI também está disponível para o NetBeans 6.8 através do Patch 1.
O projeto de amostra de solução para esse tutorial pode ser baixado: cdiDemo.zip
Injeção: o "I" do CDI
O CDI é uma API para injeção de dependências e contextos. Em Seam e Spring, a maioria das dependências funcionam nomeando beans e os vinculando aos seus pontos de injeção pelos nomes. Se estiver seguindo este tutorial depois de ter concluído Primeiros passos com Injeção de dependência e contextos e JSF 2.0, você somente terá feito referência a um bean gerenciado por nome, da página do JSF, quando definimos o nome para o bean utilizando a anotação @Named. A principal função da anotação @Named é definir o bean a fim de resolver instruções EL dentro do aplicativo, normalmente por meio dos resolvedores JSF EL. A injeção pode ser executada através da utilização de nomes, mas isso não era como a injeção no CDI deveria funcionar, já que o CDI nos proporciona uma maneira muito mais rica de expressar pontos de injeção e os beans a serem injetados neles.
No exemplo a seguir, você cria um ItemProcessor que pega uma lista de itens de uma classe que implementa a interface do ItemDao. Obtenha benefícios da anotação @Inject do CDI para demonstrar como é possível injetar um bean em outra classe. O diagrama a seguir retrata o cenário que foi construído nesse exercício.
DAO significa data access object.
Comece extraindo o projeto de início da amostra do arquivo cdiDemo.zip (Consulte a tabela que lista os recursos necessários acima.) Abra o projeto no IDE escolhendo Arquivo > Abrir projeto (Ctrl-Shift-O; ⌘-Shift-O no Mac) e, em seguida, selecione o projeto no seu local no computador.
Crie uma nova classe Item e armazene-a em um novo pacote nomeado exercise2. Clique em Novo arquivo ( ) ou pressione Crtl-N (⌘-N no Mac) para abrir o assistente Arquivo.
Selecione a categoria Java e, em seguida, a classe Java. Clique em Próximo.
Insira Item como o nome da classe e, em seguida, exercise2 como o pacote. (O novo pacote é criado depois da conclusão do assistente.)
Clique em Terminar. A nova classe e o novo pacote são gerados e a classe item é aberta no editor.
Crie as propriedades valor e limite para o POJO do Item e implemente o método toString(). Adicione o conteúdo a seguir à classe.
public class Item {
private int value;
private int limit;
@Override
public String toString() {
return super.toString() + String.format(" [Value=%d, Limit=%d]", value,limit);
}
}
Adicione os métodos getter e setter à classe. Para fazer isso, garanta que o cursor seja colocado entre a definição de classe (ou seja, entre as chaves da classe) e, em seguida, clique com o botão direito do mouse no editor e escolha Inserir código (Alt-Insert; Ctrl-I no Mac). Escolha Getter e setter.
Na janela pop-up
Marque a caixa de seleção Item (fazer isso seleciona todas as propriedades contidas na classe).
Clique em Gerar. Os métodos getter e setter são gerados para a classe.
public class Item {
private int value;
private int limit;
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
@Override
public String toString() {
return super.toString() + String.format(" [Value=%d, Limit=%d]", value, limit);
}
}
Crie um construtor que utilize os argumentos valor e limite. Novamente, o IDE pode ajudar com isso. Pressione Ctrl-Espaço na definição da classe e escolha a opção "Item(int value, int limit) - generate".
O construtor a seguir é adicionado à classe.
public class Item {
public Item(int value, int limit) {
this.value = value;
this.limit = limit;
}
private int value;
private int limit;
...
Crie uma interface ItemDao para definir como obtemos a lista de objetos Item. Nesse aplicativo de teste, antecipamos o uso de várias implementações, portanto, codificaremos para interfaces.
Clique em Novo arquivo ( ) ou pressione Crtl-N (⌘-N no Mac) para abrir o assistente Arquivo.
Selecione a categoria Java e, em seguida, selecione Interface Java. Clique em Próximo.
Digite ItemDao como o nome da classe e, em seguida, insira exercise2 como o pacote.
Clique em Terminar. A nova interface é gerada e aberta no editor.
Adicione um método chamado fetchItems() que retorna uma Lista de objetos Item.
public interface ItemDao {
List<Item> fetchItems();
}
(Utilize a dica do editor para adicionar a instrução de importação para java.util.List.)
Crie uma classe ItemProcessor. Essa é classe principal em que injetaremos nossos beans e da qual executaremos o processo. Por enquanto, iniciaremos com a DAO e examinaremos como a será feita a sua injeção no nosso bean processador.
Clique em Novo arquivo ( ) ou pressione Crtl-N (⌘-N no Mac) para abrir o assistente Arquivo.
Selecione a categoria Java e, em seguida, a classe Java. Clique em Próximo.
Digite ItemProcessor como o nome da classe e, em seguida, insira exercise2 como o pacote. Clique em Terminar.
A nova classe é gerada e aberta no editor.
Modifique a classe da seguinte maneira:
@Named
@RequestScoped
public class ItemProcessor {
private ItemDao itemDao;
public void execute() {
List<Item> items = itemDao.fetchItems();
for (Item item : items) {
System.out.println("Found item " + item);
}
}
}
Corrigir importações. Clique com o botão direito do mouse no editor e selecione Corrigir importações ou pressione Ctrl-Shift-I (⌘-Shift-I no Mac).
Clique em OK. Instruções de importação para as seguintes classes são necessárias:
java.util.List
javax.inject.Named
javax.enterprise.context.RequestScoped
Comece com uma DAO simples que apenas cria uma lista de itens e retorna uma lista fixa de itens.
Na janela Projetos, clique com o botão direito do mouse no nó de pacote exercise2 e escolha Novo > Classe Java. No assistente de Classe Java, nomeie a classe DefaultItemDao. Clique em Terminar.
No editor, faça com que DefaultItemDao implemente a interface ItemDao e forneça uma implementação de fetchItems().
public class DefaultItemDao implements ItemDao {
@Override
public List<Item> fetchItems() {
List<Item> results = new ArrayList<Item>();
results.add(new Item(34, 7));
results.add(new Item(4, 37));
results.add(new Item(24, 19));
results.add(new Item(89, 32));
return results;
}
}
(Pressione Ctrl-Shift-I (⌘-Shift-I no Mac) para adicionar instruções de importação para java.util.List e java.util.ArrayList.)
Mude para a classe ItemProcessor (pressione Ctrl-Tab). Para injetar o DefaultItemDao no ItemProcessor, adicionamos a anotação javax.inject.Inject ao campo ItemDao para indicar que esse campo é um ponto de injeção.
import javax.inject.Inject;
...
@Named
@RequestScoped
public class ItemProcessor {
@Inject
private ItemDao itemDao;
...
}
Utilize o suporte autocompletar de código do editor para adicionar a anotação @Inject e a instrução de importação à classe. Por exemplo, digite "@Inj" e, em seguida, pressione Ctrl-Espaço.
Finalmente, precisamos de alguma maneira para chamar o método execute() no ItemProcessor. Podemos executá-lo em um ambiente SE, mas, no momento, o manteremos em uma página do JSF. Crie uma nova página chamada process.xhtml que contém um botão para chamar o método execute().
Clique em Novo arquivo ( ) ou pressione Crtl-N (⌘-N no Mac) para abrir o assistente Arquivo.
Selecione a categoria JavaServer Faces e selecione a Página JSF. Clique em Próximo.
Digite processo como o nome do arquivo e clique em Terminar.
No novo arquivo process.xhtml, adicione um botão que esteja conectado ao método ItemProcessor.execute(). Utilizando EL, o nome padrão para o bean gerenciado é o mesmo que o nome da classe, mas com a primeira letra minúscula (por exemplo., itemProcessor).
Antes de executar o projeto, defina o arquivo process.xhtml como a nova página de boas-vindas no descritor de implementação Web do projeto.
Utilize a caixa de diálogo Ir para arquivo do IDE para abrir rapidamente o arquivo web.xml. Escolha Navegar > Ir para arquivo no menu principal do IDE (Alt-Shift-O; Ctrl-Shift-O no Mac) e, em seguida, digite "web".
Clique em OK. Na visualização XML do arquivo web.xml, faça a alteração a seguir.
Clique no botão Executar projeto () na barra de ferramentas principal do IDE. O projeto é compilado e implantado no GlassFish e o arquivo process.xhtml é aberto no navegador.
Clique no botão "Executar" que é exibido na página. Alterne de volta para o IDE e examine o registro do servidor GlassFish. O registro do servidor é exibido na janela Saída (Ctrl-4; ⌘-4 no Mac) na aba GlassFish Server. Quando o botão é clicado, o registro lista os itens da implementação de DAO padrão.
Clique com o botão direito do mouse na janela Saída e escolha Limpar (Ctrl-L; ⌘-L no Mac) para limpar o registro. Na imagem acima, o registro foi limpo pouco antes de clicar no botão "Executar".
Criamos uma classe que implementa a interface ItemDao e quando o aplicativo foi implantado, nossos beans gerenciados no módulo foram processados pela implementação CDI (por causa do arquivo beans.xml no módulo). Nossa anotação @Inject especifica que queremos injetar um bean gerenciado nesse campo e a única coisa que sabemos sobre o bean injetável é que ele deve implementar ItemDao ou algum subtipo dessa interface. Nesse caso, a classe DefaultItemDao se adapta perfeitamente.
O que aconteceria se houvesse várias implementações de ItemDao que pudessem ter sido injetadas? O CDI não saberia qual implementação escolher e sinalizaria um erro de tempo de implantação. Para superar isso, seria necessário utilizar um qualificador CDI. Qualificadores são explorados na próxima seção.
Trabalhando com qualificadores
Um qualificador CDI é uma anotação que pode ser aplicada no nível da classe para indicar o tipo de bean que a classe é e, no nível do campo (entre outros lugares), para indicar que tipo de bean precisa ser injetado nesse ponto.
Para demonstrar a necessidade de um qualificador no aplicativo que estamos construindo, vamos adicionar outra classe DAO ao nosso aplicativo, que também implementa a interface ItemDao. O diagrama a seguir retrata o cenário construído neste exercício. O CDI deve conseguir determinar qual implementação de bean deverá ser utilizada em um ponto de injeção. Como há duas implementações de ItemDao, podemos resolver isso criando um nome qualificador Demo. Em seguida, "marcamos" o bean que queremos utilizar e o ponto de injeção em ItemProcessor com uma anotação @Demo.
Realize as seguintes etapas.
Na janela Projetos, clique com o botão direito do mouse no pacote exercise2 e escolha Novo > Classe Java.
No assistente Nova classe Java, nomeie a nova classe AnotherItemDao e clique em Terminar. A nova classe é gerada e aberta no editor.
Modifique a classe como se segue, de modo que ela implemente a interface ItemDao e defina o método fetchItems() da interface.
public class AnotherItemDao implements ItemDao {
@Override
public List<Item> fetchItems() {
List<Item> results = new ArrayList<Item>();
results.add(new Item(99, 9));
return results;
}
}
Certifique-se de adicionar instruções de importação para java.util.List e java.util.ArrayList. Para isso, clique com o botão direito do mouse no editor e escolha Corrigir importações ou pressione Ctrl-Shift-I (⌘-Shift-I no Mac).
Agora que há duas classes que implementam o ItemDao, a escolha não está tão clara com relação a em qual bean queremos injetar.
Clique no botão Executar projeto ( ) para executar o projeto. Observe que o projeto agora falha na implementação.
Provavelmente, você só precisa salvar o arquivo porque o IDE implementará automaticamente, visto que a opção Implementar ao salvar está ativada por padrão.
Examine o registro do servidor na janela Saída (Ctrl-4; ⌘-4 no Mac). Será exibida uma mensagem de erro semelhante ao seguinte.
Provocado por: org.jboss.weld.DeploymentException: O ponto de injeção tem dependências ambíguas.
Ponto de injeção: field exercise2.ItemProcessor.itemDao;
Qualificadores: [@javax.enterprise.inject.Default()];
Possíveis dependências: [exercise2.DefaultItemDao, exercise2.AnotherItemDao]
Para quebrar a linha em várias linhas na janela Saída, clique com o botão direito do mouse e escolha Quebra de linha. Isso elimina a necessidade de rolar horizontalmente.
Weld, a implementação para CDI, nos proporciona um erro de dependência ambígua, o que significa que não pode determinar qual bean utilizar para o ponto de injeção determinado. A maioria dos erros, se não todos, que podem ocorrer com relação à injeção CDI em Weld são informados no momento da implantação, mesmo o erro dos beans habilitados para passividade estarem sem uma implementação Serializable.
Poderíamos tornar nosso campo itemDao no ItemProcessor um tipo específico que corresponde a um dos tipos de implementação (AnotherItemDao ou DefaultItemDao), já que corresponderia, em seguida, a um e apenas um tipo de classe. No entanto, perderíamos os benefícios da codificação para uma interface e seria mais difícil alterar as implementações sem alterar o tipo de campo. Uma solução melhor seria, portanto, examinarmos os qualificadores CDI.
Quando o CDI inspeciona um ponto de injeção para encontrar um bean adequado para injetar, não leva em conta apenas o tipo de classe, mas também todos os qualificadores. Sem saber disso, já utilizamos um qualificador que é o qualificador padrão chamado @Any. Vamos criar um qualificador @Demo que podemos aplicar à nossa implementação de DefaultItemDao e também ao ponto de injeção em ItemProcessor.
O IDE fornece um assistente que permite gerar qualificadores CDI.
Clique em Novo arquivo ( ) ou pressione Crtl-N (⌘-N no Mac) para abrir o assistente Arquivo.
Selecione a categoria Injeção de dependência e contexto e, em seguida, selecione Tipo do qualificador. Clique em Próximo.
Digite Demo como o nome da classe e, em seguida, exercise2 como o pacote.
Clique em Terminar. O novo qualificador Demo é aberto no editor.
A seguir, você adicionará este qualificador à implementação DAO padrão no nível da classe.
Altere para DefaultItemDao no editor (pressione Ctrl-Tab) e, em seguida, digite "@Demo" sobre a definição de classe.
@Demo
public class DefaultItemDao implements ItemDao {
@Override
public List<Item> fetchItems() {
List<Item> results = new ArrayList<Item>();
results.add(new Item(34, 7));
results.add(new Item(4, 37));
results.add(new Item(24, 19));
results.add(new Item(89, 32));
return results;
}
}
Depois de digitar "@", pressione Ctrl-Espaço para invocar sugestões do autocompletar de código. O editor reconhece o qualificador Demo e lista @Demo como uma opção para o autocompletar de código.
Clique no botão Executar projeto ( ) para executar o projeto. O projeto é compilado e implementado sem erros.
Observação. Para esta modificação, você pode precisar executar explicitamente o projeto para reimplementar o aplicativo em vez de implementar de forma incremental as modificações.
No navegador, clique no botão "Executar", retorne para o IDE e examine o registro do servidor na janela Saída. Você verá a seguinte saída.
INFO: Found item [Value=99, Limit=9]
A saída lista o item da classe AnotherItemDao. Lembre-se de que anotamos a implementação DefaultItemDao, mas não o ponto de injeção em ItemProcessor. Adicionando o qualificador @Demo à implementação DAO padrão, tornamos a outra implementação uma correspondência mais adequada para o ponto de injeção, pois houve coincidência no tipo e no qualificador. O DefaultItemDao tem, no momento, o qualificador Demo, que não está no ponto de injeção, o que o torna, portanto, menos adequado.
A seguir, você adicionará a anotação @Demo ao ponto de injeção em ItemProcessor.
Altere para ItemProcessor no editor (pressione Ctrl-Tab) e, em seguida, faça a seguinte alteração.
@Named
@RequestScoped
public class ItemProcessor {
@Inject @Demo
private ItemDao itemDao;
public void execute() {
List<Item> items = itemDao.fetchItems();
for (Item item : items) {
System.out.println("Found item " + item);
}
}
}
No navegador, clique no botão "Executar", retorne para o IDE e examine o registro do servidor na janela Saída. Você novamente verá a saída da implementação padrão (DefaultItemDao).
INFO: Found item [Value=34, Limit=7]
INFO: Found item [Value=4, Limit=37]
INFO: Found item [Value=24, Limit=19]
INFO: Found item [Value=89, Limit=32]
Isso ocorre porque agora estamos fazendo a correspondência com base em qualificadores do tipo and, e DefaultItemDao é o único bean com o tipo correto e a anotação @Demo.
Métodos de injeção alternativos
Há várias maneiras de definir um ponto de injeção na classe injetada. Até agora, anotamos os campos que fazem referência a objetos injetados. Não é necessário fornecer getters e setters para a injeção de campo. Se quisermos criar beans gerenciados imutáveis com campos finais, poderemos utilizar a injeção no construtor anotando-o com a anotação @Inject. Em seguida, poderemos aplicar quaisquer anotações aos parâmetros do construtor para qualificar os beans para injeção. (Claro, cada parâmetro tem um tipo que pode ajudar na qualificação de beans para injeção). Um bean pode ter apenas um construtor com pontos de injeção definidos, mas pode implementar mais de um construtor.
@Named
@RequestScoped
public class ItemProcessor {
private final ItemDao itemDao;
@Inject
public ItemProcessor(@Demo ItemDao itemDao) {
this.itemDao = itemDao;
}
}
Também podemos chamar um método de inicialização que pode ser passado a um bean a ser injetado.
@Named
@RequestScoped
public class ItemProcessor {
private ItemDao itemDao;
@Inject
public void setItemDao(@Demo ItemDao itemDao) {
this.itemDao = itemDao;
}
}
Embora no caso acima tenhamos utilizado o método setter para a inicialização, podemos criar qualquer método e utilizá-lo para a inicialização com quantos beans quisermos na chamada do método. Também podemos ter vários métodos de inicialização em um bean.
As mesmas regras se aplicam à correspondência de beans, independente de como o ponto de injeção é definido. O CDI tentará encontrar a melhor correspondência com base no tipo e nos qualificadores, e haverá falha na implantação se houver vários beans correspondentes ou nenhum bean correspondente para um ponto de injeção.