Engatinhando em Java para a web - Parte 3

No post anterior falamos sobre a especificação Servlet, bem como sobre o uso de JavaServer Pages e Expression Language. Para finalizar o protótipo proposto, e encerrar essa introdução ao Java, precisamos implementar o método POST, e durante esse percurso abordaremos os conceitos de filters e listeners.

doGet e doPost

Recaptulando o protótipo: Precisamos implementar a funcionalidade de adicionar uma tarefa ao todo list.

Cypher em The Matrix
Se continuar no Python é ficar na Matrix, pode me deixar lá (filmfoodsafari.wordpress.com)

Se inspecionarmos o método service da classe HttpServlet, veremos que ele já se encarrega de determinar qual verbo HTTP está sendo utilizado pela requisição. Portanto, ao invés de sobrescrevê-lo, é mais eficiente implementarmos os métodos nos quais queremos suportar.

Renomeie o método service para doGet, e crie um método vazio para o doPost:

@WebServlet(urlPatterns = "")
public class OlaMundoServlet extends HttpServlet {
    List<Tarefa> tarefas = new ArrayList<>() {
        {
            add(new Tarefa("Tarefa A"));
            add(new Tarefa("Tarefa B"));
            add(new Tarefa("Tarefa C"));
        }
    };

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setAttribute("tarefas", tarefas);

        RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/index.jsp");
        rd.forward(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {

    }
}

Note a alteração em List<Tarefa> tarefas.

Com a seguinte adição de código ao método doPost temos um protótipo que atinge o seu objetivo:

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    final Tarefa tarefa = new Tarefa(req.getParameter("nome"));
    tarefas.add(tarefa);

    req.setAttribute("tarefas", tarefas);
    RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/index.jsp");
    rd.forward(req, resp);
}

Executando o Maven (mvn clean tomcat7:run), e através do endereço http://localhost:8080, somos capazes de ver o resultado:

Exemplo de POST
Exemplo de POST

Ao analisar o código acima, além das possibilidades de melhoria de código (como reaproveitar o req.getRequestDispatcher("/WEB-INF/index.jsp") ao invés de duplicá-lo, ou fazer um redirect), há algo muito importante que precisa ser salientado: Servlets não são "thread safe".

Don't repeat yourself

Segundo o material da Caelum:

De acordo com a especificação de Servlets, por padrão, existe uma única instância de cada Servlet declarada. Ao chegar uma requisição para a Servlet, uma nova Thread é aberta sobre aquela instância que já existe.

Isso significa que, se colocássemos em nossa Servlet uma variável de instância, ela seria compartilhada entre todas as threads que acessam essa Servlet! Em outras palavras, seria compartilhado entre todas as requisições e todos os clientes enxergariam o mesmo valor. Provavelmente não é o que queremos fazer.

Nosso protótipo está pronto, e a brincadeira com o atributo de instância nos permitiu interagir com o método POST.

Se quisermos seguir em frente e tornar a aplicação mais "production ready", outros aspectos terão que ser considerados, como persistência, segurança, validação de dados, modularidade, e não podemos esquecer desses detalhes como a natureza multithreading que envolve o Java.

Trinity em The Matrix
Fazer algo funcionar na plataforma JavaEE parece como a Trinity acertando um agente em Matrix (vulturehound.co.uk)

E se nesse momento você ainda está disposto a fazer isso "all by yourself", reconsidere. Para fins de aprendizado, esse tipo de experimento é ótimo mas nada produtivo, portanto, considere o uso de algum framework.

Para finalizar: Filters & Listeners

Mas calma! Há dois tópicos que valem a pena ser compreendidos antes de pular para o uso de algum framework.

Diretamente de uma thread do StackOverflow:

Servlet Filter is used for monitoring request and response from client to the servlet, or to modify the request and response, or to audit and log.

Servlet Listener is used for listening to events in a web containers, such as when you create a session, or place an attribute in an session or if you passivate and activate in another container, to subscribe to these events you can configure listener in web.xml, for example HttpSessionListener.

Em outras palavras:

  • Filtros: Usados para interagir com a requisição e com a resposta.
  • Listeners: Usados para interagir com eventos que acontecem dentro de um container.

Filters

Assim como a interface Servlet, possuímos uma interface para a escrita de filtros. O propósito do filtro abaixo é "logar" o endereço IP do usuário que está realizando a requisição:

// src/main/java/webapp/LogFilter.java
package webapp;

import java.io.IOException;
import java.util.Date;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

@WebFilter("/*")
public class LogFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // vazio
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException {
        String ipAddress = request.getRemoteAddr();
        String now = new Date().toString();

        System.out.println(String.format("IP: %s; Time: %s", ipAddress, now));

        chain.doFilter(request, response);
    }
}

Com o uso da annotation WebFilter, dizemos que queremos aplicar o filtro para todas as requisições (/*). A annotation também é responsável por não precisarmos tocar no arquivo web.xml, e temos o filtro funcionando a partir do momento que reiniciamos o Tomcat.

Com Filter agora temos a capacidade de aplicar de uma maneira mais modular conceitos como cache, compactação da resposta, controle de sessão, segurança, entre outros.

Leia mais sobre filtros na apostila de desenvolvimento web em Java, da Caelum.

Listeners

A necessidade de filtros fica bem evidente, principalmente quando estamos acostumados com estruturas como a do Django. Possivelmente a necessidade dos listeners exija um pouco mais de criatividade de nossa parte. O JavaTPoint pode ajudar:

If you want to perform some action at the time of deploying the web application such as creating database connection, creating all the tables of the project etc, you need to implement ServletContextListener interface and provide the implementation of its methods.

Perfeito! Para ilustrar como interagir com os (context) listeners, temos o código abaixo:

// src/main/java/webapp/MyListener.java
package webapp;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("Imagine que estamos setando um pool de conexões com o banco de dados");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("Imagine que estamos fechando o pool de conexões com o banco de dados");
    }
}

Ao iniciar a aplicação, temos:

$ mvn tomcat7:run
[INFO] Scanning for projects...
(...)
INFO: Starting Servlet Engine: Apache Tomcat/7.0.47
Imagine que estamos setando um pool de conexões com o banco de dados

E quando finalizamos a aplicação:

Imagine que estamos fechando o pool de conexões com o banco de dados

E além de fechar o pool de conexões, fechamos também a parte de Servlets dessa série. Fica uma clara impressão de que grande parte do que precisamos para desenvolver uma plataforma web já é oferecida pela versão atual do Java Enterprise Edition.

Leia mais sobre context listener na apostilia de desenvolvimento web em Java, da Caelum.

Antes de ir: O lado "agente Smith" da força

E aqui chegamos ao fim dessa nossa saga cobrindo aspectos do Java EE. Ainda que faltem tópicos considerados relevantes, como o EJB, JSF ou JPA, cobrimos outros tantos que fazem parte da especificação.

Agente Smith em The Matrix
Seja a resistência! Lute contra os engravatados (antagonist.wikia.com)

Especificação essa que, além de ter como proposta proporcionar tecnologias para um ambiente mais enterprise, possui outras vantagens, como por exemplo, a premissa de evitar o vendor lock-in.

Mas ao contrário do que você possa pensar, e do que eu acreditava até pouco tempo atrás, não seguir essas especificações é uma possibilidade. De fato, algumas comunidades vem desafiando essas "regras" e apresentando soluções elegantes e inovadoras dentro do ecossistema Java desde muito tempo. Talvez a mais relevante delas sendo a do Spring:

Spring came into being in 2003 as a response to the complexity of the early J2EE specifications. While some consider Java EE and Spring to be in competition, Spring is, in fact, complementary to Java EE. The Spring programming model does not embrace the Java EE platform specification; rather, it integrates with carefully selected individual specifications from the EE umbrella.

Inclusive, em um mundo cada vez mais "microservices-driven", seguir completamente essas especificações torna-se questionável, como ilustra Rod Johnson em sua palestra Eighteen Years of Spring.

E é com o Spring que no próximo artigo experimentaremos esse lado "rebelde" e, sem dúvida, mais divertido, do desenvolvimento web com Java. Mas se você quiser continuar em um ambiente mais enterprise: Glassfish, WildFly e Geronimo; podem ser os próximos passos.

Considerações finais

E mais uma vez, embora possa ser tentador cair na discussão "Spring x Java EE", Siva Prasad deixa essa pérola no artigo "A developer's perspective on Spring vs. Java EE":

As an enthusiastic Java developer I read the Spring vs JavaEE discussions hoping there might be few things which I don't know such as "in which areas one is better than the other". But I find 70% of discussions goes on lame arguments which is not very interesting to me.

Ter uma noção de como funciona a plataforma EE do Java é benéfico, embora eu admita que não seja lá muito divertido.

Até a próxima.

Referências