SeamFramework.orgCommunity Documentation

Capítulo 5. Escopos e contextos

5.1. Tipos de escopo
5.2. Escopos pré-definidos
5.3. O escopo de conversação
5.3.1. Demarcação de contexto
5.3.2. Propagação de conversação
5.3.3. Tempo limite de conversação
5.4. O pseudo-escopo singleton
5.5. O pseudo-escopo dependente
5.6. O qualificador @New

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.

Nota

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:

Para uma aplicação web que utiliza CDI:

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:

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:

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.

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.

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.

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.

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