SeamFramework.orgCommunity Documentation

Capítulo 4. Injeção e pesquisa programática de dependências

4.1. Pontos de injeção
4.2. Como as injeções são obtidas
4.3. Anotações de qualificadores
4.4. Os qualificadores embutidos @Default e @Any
4.5. Qualificadores com membros
4.6. Múltiplos qualificadores
4.7. Alternativos
4.8. Corrigindo dependências não satisfeitas e ambíguas
4.9. Proxies clientes
4.10. Obtendo uma instância contextual através de pesquisa programática
4.11. O objeto InjectionPoint

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;
   }
    
}

E injeção direta de campos:

public class Checkout {


   private @Inject ShoppingCart cart;
    
}

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:

(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.)

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:

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:

Para corrigir uma dependência ambígua:

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:

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:

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:

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:

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:

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();
}