SeamFramework.orgCommunity Documentation
Até agora, vimos alguns exemplos de anotações de tipo de escopo. O escopo de um bean determina o ciclo de vida das instâncias do bean. O escopo também determina que clientes se referem a quais instâncias do bean. De acordo com a especificação CDI, um escopo determina:
Quando uma nova instância de qualquer bean com esse escopo é criada
Quando uma instância existente de qualquer bean com esse escopo é destruída
Quais referências injetadas referem-se a qualquer instância de um bean com esse escopo
Por exemplo, se temos um bean com escopo de sessão CurrentUser
, todos os beans que são chamados no contexto do mesmo HttpSession
verão a mesma instância de CurrentUser
. Essa instância será criada automaticamente na primeira vez que um CurrentUser
for necessário nessa sessão, e será automaticamente destruída quando a sessão terminar.
Entidades JPA não se encaixam muito bem nesse modelo. Entidades possuem seu próprio ciclo de vida e modelo de identidade que não pode ser mapeado adequadamente para o modelo utilizado em CDI. Portanto, não recomendamos o tratamento de entidades como beans CDI. Você certamente vai ter problemas se tentar dar a uma entidade um escopo diferente do escopo padrão @Dependent
. O proxy cliente irá atrapalhar se você tentar passar uma instância injetada para o EntityManager
do JPA.
CDI possui um modelo extensível de contexto. É possível definir novos escopos, criando uma nova anotação de tipo de escopo:
@ScopeType
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface ClusterScoped {}
Evidentemente, essa é a parte mais fácil do trabalho. Para esse tipo de escopo ser útil, nós também precisamos definir um objeto Context
que implementa o escopo! Implementar um Context
é geralmente uma tarefa muito técnica, destinada apenas ao desenvolvimento do framework. Você pode esperar uma implementação do escopo de negócio, por exemplo, em uma versão futura do Seam.
Podemos aplicar uma anotação de tipo de escopo a uma classe de implementação de um bean para especificar o escopo do bean:
@ClusterScoped
public class SecondLevelCache { ... }
Normalmente, você usará um dos escopos pré-definidos do CDI.
CDI possui quatro escopos pré-definidos:
@RequestScoped
@SessionScoped
@ApplicationScoped
@ConversationScoped
Para uma aplicação web que utiliza CDI:
qualquer requisição servlet tem acesso aos escopos de solicitação, sessão e aplicação ativos, e, adicionalmente
qualquer requisição JSF tem acesso ao escopo de conversação ativo.
Uma extensão do CDI pode implementar o suporte para o escopo de conversação em outros frameworks web.
Os escopos de solicitação e aplicação também estão disponíveis:
durante invocações de métodos remotos de EJB,
durante invocações de métodos assíncronos de EJB,
durante timeouts de EJB,
durante a entrega de mensagem a um message-driven bean,
durante a entrega de mensagem a um MessageListener
, e
durante a invocação de um web service
Se a aplicação tentar invocar um bean com um escopo que não possui um contexto ativo, uma ContextNotActiveException
é lançada pelo contêiner em tempo de execução.
Os managed beans com escopo @SessionScoped
ou @ConversationScoped
devem ser serializáveis, uma vez que o contêiner mantém a sessão HTTP resguardada ao longo do tempo.
Três dos quatro escopos pré-definidos devem ser extremamente familiares a todos os desenvolvedores Java EE, então não vamos perder tempo discutindo-os aqui. No entanto, um dos escopos é novo.
O escopo de conversação é um pouco parecido com o tradicional escopo de sessão na medida em que mantém estado associado a um usuário do sistema, e o expande durante várias solicitações ao servidor. No entanto, ao contrário do escopo de sessão, o escopo de conversação:
é demarcado explicitamente pela aplicação, e
mantém estado associado a uma aba específica do navegador web em uma aplicação JSF (navegadores tendem a compartilhar os cookies do domínio, que também inclui o cookie de sessão, entre as abas, de modo que este não é o caso para o escopo de sessão).
Uma conversação representa uma tarefa—uma unidade de trabalho do ponto-de-vista do usuário. O contexto de conversação mantém o estado associado com o que o usuário estiver trabalhando no momento. Se o usuário estiver fazendo várias coisas ao mesmo tempo, existirão várias conversações.
O contexto de conversação fica ativo durante qualquer solicitação JSF. A maioria das conversações é destruída no final da solicitação. Se uma conversação deve manter estado através de múltiplas solicitações, ela deve ser explicitamente promovida a uma conversação de longa duração.
CDI oferece um bean pré-definido para o controle do ciclo de vida das conversações em uma aplicação JSF. Esse bean pode ser obtido por injeção:
@Inject Conversation conversation;
Para promover a conversação associada com a solicitação atual em uma conversação de longa duração, chame o método begin()
no código da aplicação. Para agendar a destruição do atual contexto de conversão de longa duração no final da solicitação atual, chame end()
.
No exemplo a seguir, um bean com escopo de conversação controla a conversação na qual estiver associado:
@ConversationScoped @Stateful
public class OrderBuilder {
private Order order;
private @Inject Conversation conversation;
private @PersistenceContext(type = EXTENDED) EntityManager em;
@Produces public Order getOrder() {
return order;
}
public Order createOrder() {
order = new Order();
conversation.begin();
return order;
}
public void addLineItem(Product product, int quantity) {
order.add(new LineItem(product, quantity));
}
public void saveOrder(Order order) {
em.persist(order);
conversation.end();
}
@Remove
public void destroy() {}
}
Este bean é capaz de controlar seu próprio ciclo de vida através do uso da API Conversation
. Mas alguns outros beans possuem um cliclo vida que depende totalmente de um outro objeto.
O contexto de conversação propaga-se automaticamente em qualquer solicitação JSF (formulário de submissão JSF) ou redirecionamento. E não se propaga automaticamente em requisições não JSF, por exemplo, navegação através de um link.
Nós podemos forçar a propagação da conversação em uma solicitação não JSF incluindo o identificador único da conversação como um parâmetro da solicitação. A especificação CDI reserva o parâmetro denominado cid
para essa utilização. O identificador único da conversação pode ser obtido a partir do objeto Conversation
, que possui o nome de bean conversation
em EL.
Portanto, o seguinte link propaga a conversação:
<a href="/addProduct.jsp?cid=#{conversation.id}"
>Add Product</a
>
É provavelmente melhor usar um dos componentes de link em JSF 2:
<h:link outcome="/addProduct.xhtml" value="Add Product">
<f:param name="cid" value="#{javax.enterprise.context.conversation.id}"/>
</h:link
>
O contexto da conversação se propaga sobre redirecionamentos, tornando muito fácil implementar o padrão POST-then-redirect, sem ter de recorrer a construções frágeis, como um objeto "flash". O contêiner adiciona automaticamente o identificador da conversação na URL redirecionada como um parâmetro de solicitação.
O contêiner pode destruir uma conversação e todo estado mantido em seu contexto, a qualquer momento, a fim de preservar recursos. A implementação do CDI irá fazer isso normalmente, com base em algum tipo de tempo limite (timeout)—embora isso não seja exigido pela especificação. O tempo limite é o período de inatividade antes que a conversação seja destruída (em contraste com a quantidade de tempo que a conversação está ativa).
O objeto Conversation
fornece um método para definir o tempo limite (timeout). Essa é uma sugestão para o contêiner, que está livre para ignorar essa configuração.
conversation.setTimeout(timeoutInMillis);
Além dos quatro escopos pré-definidos, CDI também suporta dois pseudo-escopos. O primeiro é o pseudo-escopo singleton, que especificamos usando a anotação @Singleton
.
Ao contrário dos outros escopos, os quais pertencem ao pacote javax.enterprise.context
, a anotação @Singleton
é definida no pacote javax.inject
.
Você pode adivinhar o que "singleton" significa aqui. Ele significa que um bean é instanciado apenas uma vez. Infelizmente, existe um pequeno problema com este pseudo-escopo. Os beans com escopo @Singleton
não possuem um objeto de proxy. Os clientes mantêm uma referência direta para a instância singleton. Portanto, precisamos considerar o caso de um cliente que pode ser serializado, por exemplo, qualquer bean com escopo @SessionScoped
ou @ConversationScoped
, qualquer objeto dependente de um bean com escopo @SessionScoped
ou @ConversationScoped
, ou qualquer bean de sessão com estado.
Agora, se a instância singleton é simples objeto imutável e serializável como uma string, um número ou uma data, provavelmente não importaremos muito se ele ficar duplicado na serialização. No entanto, isso faz com que ele deixe de ser um verdadeiro singleton, e podemos muito bem apenas o declarar com o escopo padrão.
Existem várias maneiras para garantir que o bean singleton permaneça como singleton quando seu cliente se torna serializável.
mandar o bean singleton implementar writeResolve()
e readReplace()
(como definido pela especificação de serialização Java),
certificar-se que o cliente mantém apenas uma referência transiente para o bean singleton, ou
dar ao cliente uma referência do tipo Instance<X>
, onde X
é o tipo do bean singleton.
Uma quarta e melhor solução é usar @ApplicationScoped
, permitindo que o contêiner faça um proxy do bean, e resolva automaticamente os problemas de serialização.
Finalmente, CDI possui o assim chamado pseudo-escopo dependente. Esse é o escopo padrão para um bean que não declare explicitamente um tipo de escopo.
Por exemplo, esse bean possui o tipo de escopo @Dependent
:
public class Calculator { ... }
Uma instância de um bean dependente nunca é compartilhada entre clientes diferentes ou pontos de injeção diferentes. Ele é estritamente um objeto dependente de algum outro objeto. Ele é instanciado quando o objeto a que ele pertence é criado, e destruído quando o objeto a que ele pertence é destruído.
Se uma expressão Unified EL se refere a um bean dependente pelo seu nome EL, uma instância do bean é instaciada toda vez que a expressão é avaliada. A instância não é reutilizada durante qualquer outra avaliação de expressão.
Se você precisa acessar um bean diretamente pelo nome EL em uma página JSF, você provavelmente precisa dar a ele um escopo diferente de @Dependent
. Caso contrário, qualquer valor que for definido no bean por uma entrada JSF será perdido imediatamente. É por isso que CDI possui o estereótipo @Model
; ele permite que você dê um nome ao bean e define seu escopo para @RequestScoped
de uma só vez. Se você precisa acessar um bean que realmente necessita possuir o escopo @Dependent
a partir de uma página JSF, injete-o dentro de um bean diferente e exponha-o em EL por meio de um método getter.
Os beans com escopo @Dependent
não precisam de um objeto de proxy. O cliente mantém uma referência direta para sua instância.
CDI torna fácil a obtenção de uma instância dependente de um bean, mesmo se o bean já tiver declarado como um bean com algum outro tipo de escopo.
O qualificador pré-definido @New
nos permite obter um objeto dependente de uma classe especificada.
@Inject @New Calculator calculator;
A classe deve ser um managed bean ou session bean válido, mas não precisa ser um bean habilitado.
Isso funciona mesmo se Calculator
já estiver declarado com um tipo de escopo diferente, por exemplo:
@ConversationScoped
public class Calculator { ... }
Portanto, os seguintes atributos injetados obtêm uma instância diferente de Calculator
:
public class PaymentCalc {
@Inject Calculator calculator;
@Inject @New Calculator newCalculator;
}
O campo calculator
tem uma instância de Calculator
em escopo de conversação injetada. O campo newCalculator
tem uma nova instância do Calculator
injetada, com ciclo de vida que é vinculado à PaymentCalc
.
This feature is particularly useful with producer methods, as we'll see in Capítulo 8, Métodos produtores.