SeamFramework.orgCommunity Documentation
Uma das características mais significativas do CDI—certamente a mais reconhecida—é injeção de dependência; desculpe-me, injeção de dependência com typesafe.
A anotação @Inject
nos permite definir um ponto de injeção que é injetado durante a instanciação do bean. A injeção pode ocorrer por meio de três diferentes mecanismos.
Injeção por parâmetro no construtor do bean:
public class Checkout {
private final ShoppingCart cart;
@Inject
public Checkout(ShoppingCart cart) {
this.cart = cart;
}
}
Um bean pode possuir somente um construtor injetável.
Injeção por parâmetro em método inicializador:
public class Checkout {
private ShoppingCart cart;
@Inject
void setShoppingCart(ShoppingCart cart) {
this.cart = cart;
}
}
Um bean pode possuir múltiplos métodos inicializadores. Se o bean é um session bean, o método inicializador não é necessário ser um método de negócio do session bean.
E injeção direta de campos:
public class Checkout {
private @Inject ShoppingCart cart;
}
Métodos getter e setter não são necessários para injeção em campo funcionar (exceto com managed beans do JSF).
A injeção de dependências sempre ocorre quando a instância do bean é instanciada pela primeira vez no contêiner. Simplificando um pouco, as coisas acontecem nesta ordem:
Em primeiro lugar, o contêiner chama o construtor do bean (o construtor padrão ou um anotado com @Inject
) para obter uma instância do bean.
Em seguida, o contêiner inicializa os valores de todos os campos injetados do bean.
Em seguida, o contêiner chama todos os métodos inicializadores do bean (a ordem de chamada não é portável, não confie nela).
Finalmente, o método @PostConstruct
, se for o caso, é chamado.
(A única complicação é que o contêiner pode chamar métodos inicializadores declarados por uma superclasse antes de inicializar campos injetados declarados por uma subclasse.)
Uma grande vantagem de injeção em construtores é que isto nos permite que o bean seja imutável.
CDI também suporta injeção de parâmetro para alguns outros métodos que são invocados pelo contêiner. Por exemplo, a injeção de parâmetro é suportada em métodos produtores:
@Produces Checkout createCheckout(ShoppingCart cart) {
return new Checkout(cart);
}
This is a case where the @Inject
annotation is not required at the injection point. The same is true for observer methods (which we'll meet in Capítulo 11, Eventos) and disposer methods.
A especificação CDI define um procedimento, chamado de resolução segura de tipos, que o contêiner segue ao indentificar o bean a ser injetado em um ponto de injeção. Este algoritmo parece complexo no início, mas uma vez quq você o entende, é realmente muito intuitivo. A resolução segura de tipos é realizada durante a inicialização do sistema, o que significa que o contêiner informará ao desenvolvedor imediatamente se alguma das dependências de um bean não puder ser satisfeita.
O objetivo deste algoritmo é permitir que múltiplos beans implementem o mesmo tipo de bean e também:
permitir que o cliente escolha qual implementação ele necessita utilizando um qualificador ou
permitir ao implantador (deployer) da aplicação escolher qual implentação é adequada para uma determinada implantação, sem alterações para o cliente, ao ativar ou desativar um alternativo, ou
permitir que os beans sejam isolados dentro de módulos separados.
Obviamente, se você possui exatamente um bean de um dado tipo, e um ponto de injeção com este mesmo tipo, então o bean A irá para onde pedir um A. Este é o cenário mais simples possível. Quando você começar sua aplicação, você terá provavelmente vários desses.
Mas então, as coisas começam a ficar complicadas. Vamos explorar como o contêiner determina qual bean injetar em casos mais avançados. Nós iniciaremos dando um olhar mais atento em qualificadores.
Se temos mais do que um bean que implementa um tipo de bean específico, o ponto de injeção pode especificar exatamente qual bean deve ser injetado usando uma anotação de qualificador. Por exemplo, pode haver duas implementações de PaymentProcessor
:
@Synchronous
public class SynchronousPaymentProcessor implements PaymentProcessor {
public void process(Payment payment) { ... }
}
@Asynchronous
public class AsynchronousPaymentProcessor implements PaymentProcessor {
public void process(Payment payment) { ... }
}
Onde @Synchronous
e @Asynchronous
são anotações de qualificadores:
@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface Synchronous {}
@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface Asynchronous {}
Um desenvolvedor de um bean cliente utiliza a anotação de qualificador para especificar exatamente qual bean deve ser injetado.
Utilizando injeção por campos (field injection):
@Inject @Synchronous PaymentProcessor syncPaymentProcessor;
@Inject @Asynchronous PaymentProcessor asyncPaymentProcessor;
Utilizando injeção de método de inicialização:
@Inject
public void setPaymentProcessors(@Synchronous PaymentProcessor syncPaymentProcessor,
@Asynchronous PaymentProcessor asyncPaymentProcessor) {
this.syncPaymentProcessor = syncPaymentProcessor;
this.asyncPaymentProcessor = asyncPaymentProcessor;
}
Usando injeção no construtor:
@Inject
public Checkout(@Synchronous PaymentProcessor syncPaymentProcessor,
@Asynchronous PaymentProcessor asyncPaymentProcessor) {
this.syncPaymentProcessor = syncPaymentProcessor;
this.asyncPaymentProcessor = asyncPaymentProcessor;
}
Anotações de qualificadores podem também qualificar argumentos de métodos produtores, destruidores ou observadores. Combinar argumentos qualificados com métodos produtores é uma boa forma para ter uma implementação de um tipo de bean selecionado em tempo de execução com base no estado do sistema:
@Produces
PaymentProcessor getPaymentProcessor(@Synchronous PaymentProcessor syncPaymentProcessor,
@Asynchronous PaymentProcessor asyncPaymentProcessor) {
return isSynchronous() ? syncPaymentProcessor : asyncPaymentProcessor;
}
Se um campo injetado ou um parâmetro de um construtor de bean ou método inicializador não é explicitamente anotado com um qualificador, o qualificador padrão, @Default
, é assumido.
Agora, você pode estar pensando, "Qual a diferença entre usar um qualificador e apenas especificar a exata classe de implementação que você deseja?" É importante entender que um qualificador é como uma extensão da interface. Ele não cria uma dependência direta para qualquer implementação em particular. Podem existir várias implementações alternativas de @Asynchronous PaymentProcessor
!
Sempre que um bean ou ponto de injeção não declara explicitamente um qualificador, o contêiner assume o qualificador @Default
. Em algum momento, você precisará declarar um ponto de injeção sem especificar um qualificador. Existe um qualificador para isso também. Todos os beans possuem o qualificador @Any
. Portanto, ao especificar explicitamente @Any
em um ponto de injeção, você suprime o qualificador padrão, sem restringir os beans que são elegíveis para injeção.
Isto é especialmente útil se você quiser iterar sobre todos os beans com um certo tipo de bean. Por exemplo:
@Inject
void initServices(@Any Instance<Service
> services) {
for (Service service: services) {
service.init();
}
}
As anotações Java podem possuir membros. Podemos usar membros de anotação para discriminar melhor um qualificador. Isso impede uma potencial explosão de novas anotações. Por exemplo, em vez de criar vários qualificadores representando diferentes métodos de pagamento, podemos agregá-los em uma única anotação com um membro:
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface PayBy {
PaymentMethod value();
}
Então selecionamos um dos possíveis valores do membro ao aplicar o qualificador:
private @Inject @PayBy(CHECK) PaymentProcessor checkPayment;
Podemos forçar o contêiner a ignorar um membro de um tipo de qualificador ao anotar o membro com @Nonbinding
.
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface PayBy {
PaymentMethod value();
@Nonbinding String comment() default "";
}
Um ponto de injeção pode especificar múltiplos qualificadores:
@Inject @Synchronous @Reliable PaymentProcessor syncPaymentProcessor;
Neste caso, somente um bean que possua ambas anotações de qualificador seriam elegíveis para injeção.
@Synchronous @Reliable
public class SynchronousReliablePaymentProcessor implements PaymentProcessor {
public void process(Payment payment) { ... }
}
Os alternativos são beans cuja implementação é específica para um módulo cliente ou cenário de implantação específico. Este alternativo define uma implementação simulada de @Synchronous PaymentProcessor
e @Asynchronous PaymentProcessor
, tudo em um:
@Alternative @Synchronous @Asynchronous
public class MockPaymentProcessor implements PaymentProcessor {
public void process(Payment payment) { ... }
}
Por padrão, os beans com @Alternative
estão desabilitados. Precisamos habilitar um alternativo no descritor beans.xml
de um arquivo de bean para torná-lo disponível para instanciação e injeção. Esta ativação somente se aplica aos beans neste arquivo.
<beans
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<alternatives>
<class
>org.mycompany.mock.MockPaymentProcessor</class>
</alternatives>
</beans
>
Quando uma dependência ambígua houver em um ponto de injeção, o contêiner tenta resolver a ambiguidade procurando por um bean alternativo habilitado entre os beans que podem ser injetados. Se existir exatamente um alternativo habilitado, este será o bean a ser injetado.
O algoritmo de resolução segura de tipos falha quando, após considerar as anotações de qualificadores em todos os beans que implementam o tipo de bean de um ponto de injeção e filtrar os beans desabilitados (beans com @Alternative
que não estão explicitamente habilitados), o contêiner não é capaz de identificar exatamente um bean para injetar. O contêiner abortará a implantação, nos informando sobre as dependências não satisfeitas ou ambíguas.
Durante o progresso de seu desenvolvimento, você vai encontrar essa situação. Vamos aprender como resolvê-la.
Para corrigir uma dependência não satisfeita:
crie um bean que implemente o tipo de bean e possua todos os tipos de qualificador do ponto de injeção,
certifique-se que o bean que você já possui esteja no classpath do módulo com o ponto de injeção, ou
habilite explicitamente um bean @Alternative
que implemente o tipo de bean e possua os tipos de qualificador apropriados, usando beans.xml
.
Para corrigir uma dependência ambígua:
introduza um qualificador para distinguir entre as duas implementações do tipo de bean,
desabilite um dos beans anotando-o com @Alternative
,
mova uma das implementações para um módulo que não está no classpath do módulo com o ponto de injeção, ou
desabilite um dos beans @Alternative
que estão tentando ocupar o mesmo espaço, usando beans.xml
.
Veja este FAQ para instruções passo-a-passo de como resolver uma exceção de resolução ambígua entre um tipo de bean e um método produtor que retorna o mesmo tipo de bean.
Apenas lembre-se: "Só pode haver um."
On the other hand, if you really do have an optional or multivalued injection point, you should change the type of your injection point to Instance
, as we'll see in Seção 4.10, “Obtendo uma instância contextual através de pesquisa programática”.
Agora há mais uma questão que você precisa estar ciente quando usar o serviço de injeção de dependência.
Os clientes de um bean injetado não costumam manter uma referência direta para uma instância do bean, a menos que o bean seja um objeto dependente (com escopo @Dependent
).
Imagine que um bean vinculado ao escopo da aplicação mantenha uma referência direta para um bean vinculado ao escopo da solicitação. O bean com escopo de aplicação é compatilhado entre várias solicitações diferentes. No entanto, cada solicitação deverá ver uma instância diferente do bean com escopo de solicitação—a atual!
Agora imagine que um bean vinculado ao escopo da sessão mantenha uma referência direta para um bean vinculado ao escopo da aplicação. Em algum momento, o contexto da sessão é serializado para o disco, a fim de usar a memória de forma mais eficiente. No entanto, a instância do bean com escopo de aplicação não deve ser serializado junto com o bean de escopo de sessão! Ele pode obter esta referência a qualquer momento. Não há necessidade de armazená-lo!
Portanto, a menos que um bean possua o escopo padrão @Dependent
, o contêiner deve injetar indiretamente todas as referências para o bean através de um objeto proxy. Este proxy cliente é responsável por assegurar que a instância do bean que recebe uma invocação de método seja a instância que está associada ao contexto atual. O proxy cliente também permite que beans vinculados a contextos, como o contexto de sessão, sejam serializados para o disco sem serializar recursivamente outros beans injetados.
Infelizmente, devido às limitações da linguagem Java, alguns tipos Java não podem ser feitos proxies pelo contêiner. Se um ponto de injeção declarado com um destes tipos referencia um bean com qualquer escopo diferente de @Dependent
, o contêiner abortará a implantação, nos informando sobre o problema.
Os seguintes tipos Java não podem ser "proxied" pelo contêiner:
classes que não possuem um construtor não privado sem parâmetros, e
classes que são declaradas final
ou que tenham um método final
,
arrays e tipos primitivos.
Geralmente é muito fácil de corrigir um problema de dependência com proxies. Se um ponto de injeção do tipo X
resulta em uma dependência que não pode ser feito um proxy, simplesmente:
adicione um construtor sem parâmetros em X
,
modifique o tipo do ponto de injeção para Instance<X>
,
introduza uma interface Y
, implementada pelo bean injetado, e mude o tipo do ponto de injeção para Y
, ou
se tudo isso falhar, mude o escopo do bean a injetar para @Dependent
.
Uma versão futura do Weld provavelmente suportará uma solução não padrão para esta limitação, usando APIs não portáveis da JVM:
Sun, IcedTea, Mac: Unsafe.allocateInstance()
(A mais eficiente)
IBM, JRockit: ReflectionFactory.newConstructorForSerialization()
Mas não somos obrigados a implementar isto ainda.
Em certas situações, injeção não é o meio mais conveniente de obter uma referência contextual. Por exemplo, não pode ser usada quando:
o tipo do bean ou qualificadores variam dinamicamente em tempo de execução, ou
dependendo da implantação, pode haver nenhum bean que satisfaça o tipo e qualificadores, ou
gostaríamos de realizar uma iteração sobre todos os beans de um certo tipo.
Nestas situações, a aplicação pode obter uma instância da interface Instance
, parametrizada para o tipo do bean, por injeção:
@Inject Instance<PaymentProcessor
> paymentProcessorSource;
O método get()
de Instance
produz uma instância contextual do bean.
PaymentProcessor p = paymentProcessorSource.get();
Qualificadores podem ser especificados em uma de duas maneiras:
anotando o ponto de injeção Instance
, ou
passando qualificadores para o método select()
de Event
.
Especificar os qualificadores no ponto de injeção é muito, muito mais fácil:
@Inject @Asynchronous Instance<PaymentProcessor
> paymentProcessorSource;
Agora, o PaymentProcessor
retornado por get()
terá o qualificador @Asynchronous
.
Alternativamente, podemos especificar o qualificador dinamicamente. Primeiro, adicionamos o qualificador @Any
no ponto de injeção, para suprimir o qualificador padrão. (Todos os beans possuem o qualificador @Any
.)
@Inject @Any Instance<PaymentProcessor
> paymentProcessorSource;
Em seguida, precisamos obter uma instância de nosso tipo de qualificador. Uma vez que anotações são interfaces, não podemos apenas escrever new Asynchronous()
. Também é bastante tedioso criar uma implementação concreta de um tipo de anotação a partir do zero. Em vez disso, o CDI nos permite obter uma instância do qualificador criando uma subclasse da classe auxiliar AnnotationLiteral
.
abstract class AsynchronousQualifier
extends AnnotationLiteral<Asynchronous
> implements Asynchronous {}
E alguns casos, podemos utilizar uma classe anônima:
PaymentProcessor p = paymentProcessorSource
.select(new AnnotationLiteral<Asynchronous
>() {});
No entanto, não podemos utilizar uma classe anônima para implementar um tipo de qualificador com membros.
Agora, finalmente, podemos passar o qualificador para o método select()
de Instance
.
Annotation qualifier = synchronously ? new SynchronousQualifier() : new AsynchronousQualifier(); PaymentProcessor p = anyPaymentProcessor.select(qualifier).get().process(payment);
Existem certos tipos de objetos dependentes (beans com escopo @Dependent
) que precisam saber alguma coisa sobre o objeto ou ponto de injeção no qual eles são injetados para serem capazes de fazer o que fazem. Por exemplo:
A categoria de log para um Logger
depende da classe do objeto que a possui.
A injeção do valor de um parâmetro ou cabeçalho HTTP depende de qual nome de parâmetro ou cabeçalho foi especificado no ponto de injeção.
Injeção do resultado da avaliação de uma expressão EL depende da expressão que foi especificada no ponto de injeção.
Um bean com escopo @Dependent
pode injetar uma instância de InjectionPoint
e acessar metadados relacionados com o ponto de injeção ao qual ele pertence.
Vejamos um exemplo. O seguinte código é prolixo e vulnerável a problemas de refatoração:
Logger log = Logger.getLogger(MyClass.class.getName());
Este método produtor pouco inteligente lhe permite injetar um Logger
da JDK sem especificar explicitamente a categoria de log:
class LogFactory {
@Produces Logger createLogger(InjectionPoint injectionPoint) {
return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
}
}
Podemos agora escrever:
@Inject Logger log;
Não está convencido? Então aqui está um segundo exemplo. Para injetar parâmetros HTTP, precisamos definir um tipo de qualificador:
@BindingType
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface HttpParam {
@Nonbinding public String value();
}
Gostaríamos de usar este tipo de qualificador em pontos de injeção do seguinte modo:
@HttpParam("username") String username;
@HttpParam("password") String password;
O seguinte método produtor faz o trabalho:
class HttpParams
@Produces @HttpParam("")
String getParamValue(InjectionPoint ip) {
ServletRequest request = (ServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
return request.getParameter(ip.getAnnotated().getAnnotation(HttpParam.class).value());
}
}
Observe que a aquisição da solicitação neste exemplo é centrada em JSF. Para uma solução mais genérica você pode escrever seu próprio produtor para a solicitação e a injetar como um parâmetro do método.
Observe que o membro value()
da anotação HttpParam
é ignorado pelo contêiner uma vez que ele está anotado com @Nonbinding.
O contêiner fornece um bean embutido que implementa a interface InjectionPoint
:
public interface InjectionPoint {
public Type getType();
public Set<Annotation
> getQualifiers();
public Bean<?> getBean();
public Member getMember();
public Annotated getAnnotated();
public boolean isDelegate();
public boolean isTransient();
}