O Ciclo de Vida de um Servlet
Em aplicações web Java, os Servlets são componentes fundamentais para o processamento de requisições. A maneira como um Servlet é gerenciado – desde sua criação até sua destruição – é definida pelo seu ciclo de vida, que é orquestrado pelo contêiner de Servlets (como o Apache Tomcat).
O ciclo de vida de um Servlet Jakarta (antigo Java EE Servlet) descreve as fases pelas quais uma instância de Servlet passa no contêiner. Entender essas fases é crucial para desenvolver aplicações robustas e eficientes, especialmente no que diz respeito ao gerenciamento de estado e concorrência.
Principais Fases do Ciclo de Vida
Cada Servlet passa por fases distintas, e cada fase corresponde à invocação de métodos específicos pelo contêiner. A tabela a seguir resume as principais etapas:
| Fase do Ciclo de Vida | Método Correspondente | Momento de Execução | Frequência de Execução |
|---|---|---|---|
| Instanciação | Construtor do Servlet | Primeira requisição ou inicialização do contêiner (se configurado) | Uma vez |
| Inicialização | init(ServletConfig config) |
Após a construção do objeto, antes de processar qualquer requisição | Uma vez |
| Processamento de Requisições | service(HttpServletRequest req, HttpServletResponse resp) |
Para cada requisição HTTP recebida | Múltiplas vezes |
| Destruição | destroy() |
Quando o contêiner de Servlets é desligado ou o aplicativo é removido | Uma vez |
Detalhes das Fases:
- Instanciação: O contêiner de Servlets cria uma única instância da classe do Servlet, geralmente quando a primeira requisição chega para esse Servlet, ou durante a inicialização do contêiner se o Servlet for configurado com
<load-on-startup>. - Inicialização (
init()): Logo após a instanciação, o contêiner invoca o métodoinit(). Este método é ideal para inicializar recursos que serão usados pelo Servlet ao longo de sua vida, como conexões de banco de dados ou configurações. - Processamento de Requisições (
service()): Para cada requisição HTTP recebida pelo contêiner e mapeada para este Servlet, o métodoservice()é invocado. EmHttpServlet, este método delega para métodos específicos comodoGet(),doPost(), etc., com base no tipo da requisição HTTP. É aqui que a lógica de negócios para responder à requisição é implementada. - Destruição (
destroy()): Antes que o Servlet seja removido da memória pelo contêiner (geralmente no desligamento do contêiner ou desinstalação da aplicação), o métododestroy()é invocado. Este é o local para liberar recursos alocados, como fechar conexões de banco de dados ou arquivos.
Exemplo de Implementação de Ciclo de Vida
Para ilustrar o ciclo de vida, considere o seguinte Servlet que exibe mensagens no console em cada fase:
Código do Servlet:
package br.com.example.servlets;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class ExemploCicloVidaServlet extends HttpServlet {
private int contadorRequisicoes = 0; // Variável para demonstrar chamadas ao método service
public ExemploCicloVidaServlet() {
super();
System.out.println("ExemploCicloVidaServlet: Construtor invocado. Instância criada.");
}
@Override
public void init() throws ServletException {
super.init();
System.out.println("ExemploCicloVidaServlet: Método init() invocado. Servlet inicializado.");
}
@Override
protected void service(HttpServletRequest requisicao, HttpServletResponse resposta) throws ServletException, IOException {
contadorRequisicoes++;
System.out.println("ExemploCicloVidaServlet: Método service() invocado. Requisição #" + contadorRequisicoes + " sendo processada.");
resposta.setContentType("text/html");
PrintWriter out = resposta.getWriter();
out.println("");
out.println("<html><head><title>Ciclo de Vida Servlet</title></head><body>");
out.println("<h1>Requisição processada pelo Servlet!</h1>");
out.println("<p>Esta é a requisição número: " + contadorRequisicoes + "</p>");
out.println("</body></html>");
}
@Override
public void destroy() {
super.destroy();
System.out.println("ExemploCicloVidaServlet: Método destroy() invocado. Servlet sendo destruído.");
}
}
Configuração do Servlet (web.xml):
Para que o contêiner reconheça e gerencie o Servlet, ele precisa ser configurado no web.xml. Usamos a tag <load-on-startup> para indicar que o Servlet deve ser inicializado quando o contêiner inicia, não apenas na primeira requisição.
<servlet>
<servlet-name>cicloVidaServlet</servlet-name>
<servlet-class>br.com.example.servlets.ExemploCicloVidaServlet</servlet-class>
<!--
A tag <load-on-startup> com um valor positivo instrui o contêiner
a instanciar e inicializar este Servlet na inicialização da aplicação web.
O número indica a ordem de inicialização (menores primeiro).
-->
<load-on-startup>10</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>cicloVidaServlet</servlet-name>
<url-pattern>/ciclo-vida-exemplo</url-pattern>
</servlet-mapping>
Ao iniciar o contêiner (ex: Tomcat), você verá as mensagens do construtor e do método init() no console. Cada vez que você acessar http://localhost:8080/suaAplicacao/ciclo-vida-exemplo no navegador, a mensagem do método service() será exibida. Ao desligar o contêiner, a mensagem do método destroy() aparecerá.
Considerações Importantes sobre o Ciclo de Vida
- Instância Única (Singleton): Por padrão, o contêiner de Servlets cria apenas uma instância de cada classe de Servlet. Isso significa que todos os usuários que acessam o mesmo Servlet compartilharão a mesma instância.
- Gerenciamento de Concorrência: Como a mesma instância de Servlet é usada para processar múltiplas requisições (que podem ocorrer simultaneamente em diferentes threads), é crucial evitar o uso de variáveis de instância que armazenem estado mutável e específico de uma requisição. Variáveis de instância devem ser tratadas como imutáveis ou sincronizadas para evitar problemas de concorrência.
load-on-startup: O valor numérico em<load-on-startup>define a ordem de inicialização dos Servlets. Valores menores têm prioridade. É uma prática comum atribuir valores maiores (como 10, 20, etc.) aos seus próprios Servlets, pois os contêineres podem reservar os primeiros números (como 1 a 5) para seus Servlets internos.