SeamFramework.orgCommunity Documentation

Capítulo 8. Métodos produtores

8.1. Escopo de um método produtor
8.2. Injeção em métodos produtores
8.3. Uso do @New em métodos produtores
8.4. Métodos destruidores

Métodos produtores permitem superarmos certas limitações que surgem quando um contêiner, em vez da aplicação, é responsável por instanciar objetos. Eles são também a forma mais fácil de integrar os objetos que não são beans ao ambiente CDI.

De acordo com a especificação:

Um método produtor funciona como uma fonte de objetos a serem injetados, onde:

  • os objetos a serem injetados não necessitam ser instâncias de beans,

  • o tipo concreto dos objetos a serem injetados pode variar em tempo de execução ou

  • os objetos requerem alguma inicialização personalizada que não é realizada pelo construtor do bean

Por exemplo, métodos produtores permitem:

Em particular, métodos produtores permitem-nos utilizar polimorfismo em tempo de execução com CDI. Como vimos, os beans alternativos são uma solução para o problema do polimorfismo em tempo de implantação. Mas, uma vez que o sistema está implantado, a implementação do CDI é imutável. Um método produtor não tem essa limitação:

@SessionScoped

public class Preferences implements Serializable {
   private PaymentStrategyType paymentStrategy;
   ...
   @Produces @Preferred 
   public PaymentStrategy getPaymentStrategy() {
       switch (paymentStrategy) {
           case CREDIT_CARD: return new CreditCardPaymentStrategy();
           case CHECK: return new CheckPaymentStrategy();
           case PAYPAL: return new PayPalPaymentStrategy();
           default: return null;
       } 
   }
}

Considere o ponto de injeção:

@Inject @Preferred PaymentStrategy paymentStrategy;

Este ponto de injeção tem o mesmo tipo e anotações de qualificadoras que o método produtor. Assim, ele resolve para o método produtor utilizando as regras de injeção do CDI. O método produtor será invocado pelo contêiner para obter uma instância para servir esse ponto de injeção.

O escopo padrão dos métodos produtores é @Dependent, e, por isso, serão chamados toda vez que o contêiner injetar esse atributo ou qualquer outro atributo que resolve para o mesmo método produtor. Assim, pode haver várias instâncias do objeto PaymentStrategy para cada sessão do usuário.

Para mudar esse comportamento, nós podemos adicionar a anotação @SessionScoped ao método.

@Produces @Preferred @SessionScoped

public PaymentStrategy getPaymentStrategy() {
   ...
}

Agora, quando o método produtor é chamado, o PaymentStrategy retornado será associado ao contexto de sessão. O método produtor não será invocado novamente na mesma sessão.

Existe um problema potencial com o código acima. As implementações de CreditCardPaymentStrategy são instanciadas utilizando o operador new de Java. Objetos instanciados diretamente pela aplicação não usufruem da injeção de dependência e não possuem interceptadores.

Se não é isso o que queremos, podemos utilizar a injeção de dependência no método produtor para obter instâncias do bean:

@Produces @Preferred @SessionScoped

public PaymentStrategy getPaymentStrategy(CreditCardPaymentStrategy ccps,
                                          CheckPaymentStrategy cps,
                                          PayPalPaymentStrategy ppps) {
   switch (paymentStrategy) {
      case CREDIT_CARD: return ccps;
      case CHEQUE: return cps;
      case PAYPAL: return ppps;
      default: return null;
   } 
}

Espere, o que ocorre se CreditCardPaymentStrategy for um bean com escopo de requisição? Assim o método produtor tem o efeito de "promover" a instância atual com escopo de requisição para escopo de sessão. Isso certamente é um erro! O objeto com escopo de requisição será destruído pelo contêiner antes de terminar a sessão, mas a referência ao objeto será deixada "presa" ao escopo de sessão. Esse erro não será detectado pelo contêiner. Por isso, tome cuidado adicional ao retornar instâncias de bean em métodos produtores!

Existem pelo menos três maneiras de corrigirmos esse problema. Podemos alterar o escopo da implementação de CreditCardPaymentStrategy, mas isso poderia afetar outros clientes desse bean. A mehor opção seria alterar o escopo do método produtor para @Dependent ou @RequestScoped.

Mas uma solução mais comum é utilizar a anotação qualificadora especial @New.

Considere o seguinte método produtor:

@Produces @Preferred @SessionScoped

public PaymentStrategy getPaymentStrategy(@New CreditCardPaymentStrategy ccps,
                                          @New CheckPaymentStrategy cps,
                                          @New PayPalPaymentStrategy ppps) {
   switch (paymentStrategy) {
      case CREDIT_CARD: return ccps;
      case CHEQUE: return cps;
      case PAYPAL: return ppps;
      default: return null;
   } 
}

Assim a nova instância dependente de CreditCardPaymentStrategy será criada, passada para o método produtor, retornada pelo método produtor e, finalmente, associada ao contexto de sessão. O objeto dependente não será destruído até que o objeto Preferences seja destruído, ao término da sessão.

Alguns métodos produtores retornam objetos que requerem destruição explícita. Por exemplo, alguém precisa fechar esta conexão JDBC:

@Produces @RequestScoped Connection connect(User user) {

   return createConnection(user.getId(), user.getPassword());
}

A destruição pode ser realizada por um método destruidor apropriado, definido pela mesma classe do método produtor:

void close(@Disposes Connection connection) {

   connection.close();
}

O método destruidor deve ter ao menos um parâmetro, anotado com @Disposes, com o mesmo tipo e qualificadores do método produtor. O método destruidor é chamado automaticamente quando o contexto termina (neste caso, ao final da requisição), e este parâmetro recebe o objeto produzido pelo método produtor. Se o método produtor possui parâmetros adicionais, o contêiner procurará por um bean que satisfaça o tipo e qualificadores de cada parâmetro e o passará para o método automaticamente.