Escopos do CDI nos detalhes

O CDI é a especificação mais impactante dos últimos tempos no mundo JAVA EE. Através dela a plataforma começou a trilhar o caminho para ter um nível de integração bem próximo ao que existe quando trabalhamos em projetos que são baseados nos módulos do Spring. Tem bastante coisa que podemos debater sobre ela, mas no post de hoje quero falar de um tópico até mais básico, que é a parte dos escopos prevista pela especificação.

Vamos gastar só uns minutos para revisar quais são os principais escopos suportados.

  1. ApplicationScoped -> Anotamos uma classe com esse escopo quando queremos usar apenas uma instância durante todo o tempo de vida da aplicação

  2. SessionScoped -> Utilizado quando queremos manter o  mesmo objeto por várias requisições do mesmo usuário. O caso de uso típico é manter as informações de um usuário logado
  3. RequestScoped -> Utilizado quando queremos criar um objeto para ser utilizado durante a execução de um request da aplicação web. Esse escopo é muito utilizado para os managed beans do JSF, por exemplo. O VRaptor também utiliza esse escopo quando uma classe é anotada com @Controller.

  4. Dependent -> Caso esteja usando o beans.xml como o discovery-mode all, esse é o escopo padrão. Basicamente o escopo de um objeto anotado com essa annotation, ou sem nenhuma anotação, depende do ponto de injeção. Caso você injete ele em um objeto com o escopo de aplicação, este vai ser o tempo de vida dele.

Existe também o @ConversationScoped, que não é tão comum e vou deixar de fora do nosso debate.  Com os escopos bem definidos, vamos dar uma olhada no código abaixo.

	@WebServlet("/mail")
	public class MailServlet extends HttpServlet{
		
		@Inject
		private MailSender sender;
		@Inject
		private LoggedUser user;

		@Override
		protected void service(HttpServletRequest req, HttpServletResponse resp)
				throws ServletException, IOException {
			resp.getWriter().print("enviando email em outra thread...");		
			sender.send(user);

		}
	}

O Servlet vive no escopo de aplicação, assim como o objeto do tipo MailSender, mas a classe LoggedUser está anotada com @SessionScoped. Vamos refletir sobre essa injeção por alguns instantes. Como que um objeto que é criado apenas uma vez, como é o caso da Servlet, pode receber injetado um objeto que é criado uma vez por usuário, que é justamente a situação da injeção do objeto do tipo LoggedUser.

Pensando de maneira convencional, as únicas injeções possíveis seriam as de objetos de escopos maiores em escopos menores. Vamos analisar o código abaixo, pensando em uma aplicação que utiliza VRaptor.

	@Controller
	public class MailController {
		
		@Inject
		private MailSender sender;
		@Inject
		private LoggedUser user;

		@Override
		protected void service(HttpServletRequest req, HttpServletResponse resp)
				throws ServletException, IOException {			
		        sender.send(user);

		}
	}

Como todo @Controller é criado no escopo de request, fica muito mais fácil fazer a injeção, da até para a gente imaginar o código.

	LoggedUser user = (LoggedUser)request.getSession()
			.getAttribute("loggedUser");
	new MailController(mailSenderJaCriado,user)	

Como já comentamos, o contrário que fica complicado. O Servlet já está criado e o tempo todo é necessário trocar o objeto relacionado ao usuário, que está ligado ao request. Para fazer essa mágica, a implementação do CDI tem que apelar para uma técnica conhecida como proxy dinâmico.  Para deixar mais claro que é outro objeto mesmo, basta que a gente imprima o nome da classe do objeto que foi injetado.

  LoggedUser$Proxy$_$$_WeldClientProxy

Além disso, eu coloquei um breakpoint no código, para deixar ainda mais claro que objeto que estamos lidando é realmente um proxy. Abaixo segue a imagem dos atributos do objeto.

proxy-dinamico

Perceba que um dos atributos é do tipo ThreadLocal. Essa classe guarda, basicamente, um mapa entre uma Thread e algum objeto. Agora, quando invocamos qualquer método no objeto do tipo LoggedUser ele passa antes pelo proxy, que vai usar o ThreadLocal para buscar o objeto associado ao request. Sem essa classe, seria impossível injetar um escopo menor em um maior. Em geral, ela sempre aparece quando algum contorno(gambiarra) é necessário no código.

Para ficar ainda mais claro a necessidade do request, para que este proxy funcione, vamos pegar a situação onde queremos executar alguma lógica em outra Thread.

	@WebServlet("/mail")
	public class MailServlet extends HttpServlet{
		
		@Inject
		private MailSender sender;
		@Inject
		private LoggedUser user;

		@Override
		protected void service(HttpServletRequest req, HttpServletResponse resp)
				throws ServletException, IOException {
			resp.getWriter().print("enviando email em outra thread...");
			
			new Thread(() -> {
				sender.send(user);
			}).start();
		}
	}

Quando esse código é executado, supondo que estamos usando o Weld como implementação do CDI,  a seguinte exception é lançada:

	org.jboss.weld.context.ContextNotActiveException: WELD-001303: No active contexts for scope type javax.enterprise.context.SessionScoped
	at org.jboss.weld.manager.BeanManagerImpl.getContext(BeanManagerImpl.java:689)
	at org.jboss.weld.bean.proxy.ContextBeanInstance.getInstance(ContextBeanInstance.java:79)
	at org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke(ProxyMethodHandler.java:78)
	at teste.LoggedUser$Proxy$_$$_WeldClientProxy.getName(Unknown Source)

ContextNotActiveException indica que o proxy precisa do request para ser executado, mas como estamos rodando em outra Thread não foi possível encontrá-lo. Pensando em Orientação a Objetos, o encapsulamento acabou de ser completamente quebrado. O programador(a) tem que saber desse detalhe de implementação para conseguir implementar a funcionalidade corretamente.

Por hoje é isso pessoal. Como é de praxe, estamos sempre tentando deixar mais claro detalhes internos das bibliotecas que escolhemos para fazermos nossos sistemas.  Fique atento aos escopos e não se surpreenda com exceptions relacionadas a eles. Para quem usa o Spring, as mesmas regras são aplicadas! A diferença é que você precisa deixar explicito que os objetos de tal classe devem ser criados já usando o proxy :).

 

Advertisements

Melhorias no SetupMyProject

Nos últimos tempos trabalhamos para atender algumas solicitações dos usuários do nosso serviço :). As melhorias passam por projetos baseados em Spring, VRaptor e JSF. Abaixo seguem algumas delas.

A primeira tem a ver com o tipo de servidor que você quer usar no seu projeto. Antigamente o SetupMyProject gerava todas as configurações supondo que o programador estava trabalhando com um Servlet Container, estilo Tomcat. A partir de agora você pode decidir se quer realmente usar um Servlet Container ou Application Server. Por enquanto os projetos suportados para isso são o VRaptor e o JSF, que já fazem uso da stack do JAVA EE. Quando a opção Application Server for escolhida, as dependências do pom.xml já virão marcadas como provided, o que é o ideal quando vamos usar as bibliotecas que já estão no servidor.

Uma segunda novidade interessante, para qualquer projeto baseado no CDI, é a possibilidade de adicionar o DeltaSpike. Nesse momento fizemos o suporte apenas para os projetos baseados no JSF, mas não vai demorar para projetos baseados no VRaptor também passarem a ter esse benefício.  Só não adicionamos para o VRaptor porque ele já possui um plugin para integração com a JPA e, por enquanto, esse foi o único módulo que adicionamos do DeltaSpike.

Um outro ponto que chateava os usuários é que nosso projeto gerava uma tag chamada dependencyManagement que, na maioria das vezes, era inútil para o projeto em questão. Acaba que o programador adicionava a dependência lá e a mesma não era refletida no projeto!

Para fechar, adicionamos mais possibilidades para a geração de CRUD’s. Uma muito legal é que adicionamos um componente de paginação para as listagens de projetos baseados em VRaptor ou Spring. Paginação está presente em quase todo cadastro que somos obrigados a fazer e, querendo ou não, perdemos um certo tempo nas lógicas. Para os amantes do JSF, ainda vamos suportar a paginação baseada no componente do PrimeFaces.

Ainda no campo do CRUD, agora também é possível gerar novos cadastros para projetos que já estejam em andamento. Logo na primeira tela do fluxo de geração, tem uma opção para você seguir baseado em algum projeto que já esteja em andamento. No fim é gerado um zip que você pode importar como um archive file no seu Eclipse.

Estamos comprometidos em ser uma ferramenta de geração de código que não deixa o programador alienado. Hoje já geramos um pequeno readme explicando o que ele precisa fazer quando escolhe cada uma das opções. Nossa ideia é deixar esse readme mais completo, mostrando para o programador o motivo da geração de cada parte do código.

Por enquanto é isso pessoal, o projeto vai andando bem e com downloads diários. O objetivo é suportar a geração automática de código para o máximo de trabalho braçal que for possível. Afinal de contas queremos gastar tempo pensando em features que realmente exijam da gente :).