SeamFramework.orgCommunity Documentation
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:
expõe uma entidade JPA como um bean,
expõe qualquer classe do JDK como um bean,
definir vários beans, com diferentes escopos ou inicialização, para a mesma classe de implementação, ou
varia a implementação de um tipo de bean em tempo de execução.
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.
Um método produtor não herda o escopo do bean que declara o método. Existem dois beans diferentes aqui: o método produtor, e o bean que o declara. O escopo do método produtor determina como o método será invocado, e o ciclo de vida dos objetos retornados pelo método. O ecopo do bean que declara o método produtor determina o ciclo de vida do objeto no qual o método produtor é invocado.
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.