Engatinhando em Java para a web - Parte 2

Logotipo da linguagem Java

No post anterior demos uma breve pincelada sobre JDK, JRE, JSE e JEE. Uma vez que temos a infrastrutura funcionando para os fins desse artigo, através do uso de Maven e Tomcat, vamos partir para uma abordagem mais prática, e finalmente falar sobre os famosos Servlets.

Mas antes de continuar, um breve disclaimer.

Disclaimer

Recebi alguns feedbacks sobre o artigo anterior, questionando sobre a abordagem em relação a Servlets, JavaServer Pages e até mesmo ao Tomcat e Maven. Existem escolhas mais "sexy" (principalmente aos três últimos citados), e que em relação ao Servlets a escolha mais natural seria a adoção de algum framework que abstraia todo esse "javanês" (como SpringBoot ou Play).

Continuamos a trair a movimento assim como o Saruman fez em LOTR (sott.net)

A narrativa dessa série de artigos tem justamente a intenção de ilustrar que em um primeiro momento, Java para web pode parecer obsoleto, mas que com o progredir dos posts novas tecnologias serão apresentadas e que por fim, é possível sim ter um ambiente deveras moderno e prático.

Estamos "engatinhando", afinal...

Obrigado pelos feedbacks :)

Servlets

Servlet pode ser comparado ao Common Gateway Interface (CGI), onde há um "acordo" o servidor web ou de aplicação, e a sua aplicação. As definições podem vir das mais variadas formas:

  • É uma tecnologia usada para a criação de aplicações web em Java.
  • É uma API que disponibiliza classes, interfaces e documentação necessárias para tal operação.
  • É uma classe que estende as capacidades de servidores e reage à requisições (de qualquer tipo).
  • É um componente web que cria páginas dinâmicas para a web.

Mas talvez a melhor definição venha (novamente) da apostila de Java para web da Caelum:

Uma primeira ideia da servlet seria que cada uma delas é responsável por uma página, sendo que ela lê dados da requisição do cliente e responde com outros dados (uma página HTML, uma imagem GIF etc). Como no Java tentamos sempre que possível trabalhar orientado a objetos, nada mais natural que uma servlet seja representada como um objeto a partir de uma classe Java.

Neha Vaidya descreve de forma simples e didática o ciclo de vida de um servlet em uma thread do Quora, no qual vou traduzir abaixo:

  • Quando o servidor web (ex.: Apache Tomcat) inicia, o servlet container "deploia" e carrega todos os Servlets;
  • O servlet é inicializado chamando o método init(). O método Servlet.init() é chamado pelo servlet container para indicar que essa servlet instance foi iniciada com sucesso e está pronta para responder;
  • O servlet então chama o método service() para processar uma requisição do cliente;
  • O servlet é terminado ao chamar o método destroy();
  • Então o destroy(), executado no fim do ciclo de vida do servlet sinaliza o fim da servlet instance.

Diagrama ilustrando o ciclo de vida de um servlet (javatpoint.com)

init e destroy são chamados apenas uma vez. Por fim, o servlet é coletado pelo gargabe collector da JVM.

Um pouco de prática

Dentro do contexto da linguagem Java, a Servlet também é uma Interface. No nosso escopo, falando de desenvolvimento web, usaremos uma implementação mais especializada para trabalhar com o protocolo HTTP, chamada HttpServlet.

Voltando ao projeto ola-mundo, criado no artigo anterior, vamos adicionar a servlet API como dependência do projeto. Antes do nó <build>, acrescente o seguinte:

<properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
</properties>

<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

Altere o maven.compiler.source para a versão do Java que você estiver utilizando no momento.

Na sequência, crie o pacote onde escreveremos a classe:

$ mkdir -p src/main/java/webapp

Uma vez na pasta, crie o arquivo OlaMundoServlet.java com o seguinte conteúdo:

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

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = "/servlet")
public class OlaMundoServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter out = resp.getWriter();

        out.println("<html>");
        out.println("<body>");
        out.println("<h2>Hello World! (from servlet)</h2>");
        out.println("</body>");
        out.println("</html>");
    }
}

Execute novamente o comando mvn clean tomcat7:run, e acesse o endereço http://localhost:8080/servlet. Bingo!

Exemplo utilizando Servlet

Mais razão, menos magia

A partir da especificação Servlet 3, é possível utilizar annotations para configurar o deployment do servlet. Com essa forma mais "programática" não é necessário alterar o arquivo WEB-INF/web.xml, e o resultado final fica (na minha opinião) mais prático de ser gerenciado.

Vemos aqui Gandalf claramente desfrutando do seu vício em Java (greenide.com)

Com a anotação @WebServlet mapeamos um nome e rota específicos (/servlet) ao servlet em questão. O servlet container irá reconhecê-lo e fará a ligação entre rota e classe Java.

Há mais vantagens (e annotations) disponíveis com essa versão da especificação. Para saber mais, o "An Overview of Servlet 3.0", do site DZone, traz um breve resumo.

JavaServer Pages

Você deve estar se perguntando: De onde veio aquele "Hello World!", do endereço http://localhost:8080/?

Ele veio do arquivo index.jsp:

$ cat src/main/webapp/index.jsp

<html>
<body>
<h2>Hello World!</h2>
</body>
</html>

Segundo o Wikipedia:

JavaServer Pages (JSP) is a collection of technologies that helps software developers create dynamically generated web pages based on HTML, XML, SOAP, or other document types. (...) JSP is similar to PHP and ASP, but it uses the Java programming language.

To deploy and run JavaServer Pages, a compatible web server with a servlet container, such as Apache Tomcat or Jetty, is required.

Na prática, JSP é uma abstração de servlet. Os arquivos JSP são traduzidos em servlets durante o runtime, resultando em algo similar com o que fizemos ao escrever o nosso próprio:

$ cat target/tomcat/work/Tomcat/localhost/_/org/apache/jsp/index_jsp.java

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;

public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent {

    (...)

    public void _jspInit() {
        (...)
    }

    public void _jspDestroy() {
    }

    public void _jspService(final javax.servlet.http.HttpServletRequest request,
                            final javax.servlet.http.HttpServletResponse response)
            throws java.io.IOException, javax.servlet.ServletException {

        (...)

        out.write("<html>\n");
        out.write("<body>\n");
        out.write("<h2>Hello World!</h2>\n");
        out.write("</body>\n");
        out.write("</html>\n");

        (...)
    }
}

Estamos em 2019, e já estamos cansados de saber que misturar HTML com sua regra de negócios não é uma boa ideia. Uma alternativa seria alterar o servlet que escrevemos e utilizar o JSP como uma espécie de "template engine". Paramos de imprimir HTML e deixamos a linguagem lidar com esse tipo de trabalho (~mais ou menos o que se faz usando JSX~):

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

import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = "/servlet")
public class OlaMundoServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        RequestDispatcher rd = req.getRequestDispatcher("/index.jsp");
        rd.forward(req, resp);
    }
}

É isso mesmo o que você está lendo: Redirecionamos a requisição do "nosso servlet" para o JSP (que também é um servlet). Respeitamos a especificação e conseguimos dividir as responsabilidades. Não aparenta ser bonito, mas pelo menos por hora já conseguimos separar o que é o nosso controlador do que é a nossa visualização.

Traçando nova rota

O index.jsp ainda está acessível através do endereço /. Para ocultá-lo do acesso público, mova-o para a pasta WEB-INF:

$ mv src/main/webapp/index.jsp src/main/webapp/WEB-INF

Ao executar o Tomcat novamente, ambos endereços (/ e /servlet) devem apresentar uma resposta 404 agora (já que o recurso público index.jsp não existe mais). Na annotation @WebServlet, altere o path para fazê-lo virar a index da aplicação:

@WebServlet(urlPatterns = "")

O próximo passo agora é dizer para o RequestDispatcher a nova localização do index.jsp:

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

Reinicie o Tomcat (mvn clean tomcat7:run) e acesse o http://localhost:8080.

Funciona!

Deixando um pouquinho mais dinâmico

Com o que temos aqui já é possível prototipar uma aplicação simples. Vamos imaginar uma lista de tarefas (such a cliché!), onde eu tenho um campo para adicionar uma tarefa e uma lista com as mesmas logo abaixo. Para deixar tudo simples, vamos continuar com o projeto ola-mundo.

Altere o index.jsp para o seguinte código HTML:

<html>
<body>
<h2>TODO list</h2>

<form action="" method="post">
    <label>
        Tarefa:
        <input type="text" name="nome" required />
    </label>

    <input type="submit" value="Salvar" />
</form>
<ul>
    <li>Tarefa</li>
</ul>
</body>
</html>

Fica óbvio que precisaremos de um elemento que represente uma tarefa. Se formos por uma abordagem MVC, faria sentido categorizar esse elemento como um modelo:

$ mkdir -p src/main/java/webapp/modelo
$ touch src/main/java/webapp/modelo/Tarefa.java

A classe será muito simples:

// src/main/java/webapp/modelo/Tarefa.java
package webapp.model;

public class Tarefa {
    protected String nome;

    public Tarefa(String nome) {
        this.nome = nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public String getNome() {
        return nome;
    }
}

E o servlet deverá ficar parecido com o seguinte:

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

(...)

import webapp.model.Tarefa;

@WebServlet(urlPatterns = "")
public class OlaMundoServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        List<Tarefa> tarefas = List.of(
                new Tarefa("Tarefa A"),
                new Tarefa("Tarefa B"),
                new Tarefa("Tarefa C")
        );
        req.setAttribute("tarefas", tarefas);

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

Note que estamos passando uma lista (estática) de tarefas como atributo da requisição (req.setAttribute) que acionará o index.jsp. O próximo passo é imprimir tais tarefas no JSP.

(Unified) Expression Language

O JSP possui um recurso chamado de Scriptlets, que permite que você escreva de forma explícita código Java em arquivos JSP através de scripts (mais ou menos o que podemos ver em linguagens como PHP e ASP). Se você parar para considerar que JSP é no fundo código Java imprimindo HTML, essa ideia não parece ser tão louca assim.

Oras... estamos em 2019, e já estamos cansados de saber que escrever HTML misturado com lógica de negócios não é uma boa ideia.

Renda-se a Sauron! Digo, Java! (inews.co.uk)

A partir da versão 2.0 da especificação do JSP temos uma alternativa chamada "Expression Language" (ou Unified Expression Language, a partir da versão 2.1).

Segundo o Wikipedia:

The Java Unified Expression Language is a special purpose programming language mostly used in Java web applications for embedding expressions into web pages. The Java specification writers and expert groups of the Java web-tier technologies have worked on a unified expression language which was first included in the JSP 2.1 specification (JSR-245), and later specified by itself in JSR-341, part of Java EE 7.

Com ela, ao invés de escrevermos isso (quem teve contato com PHP achará muito familiar):

<li><%= tarefa.getNome() %></li>

Escrevemos isso (similar a qualquer engine de templates):

<li>${tarefa.nome}<li>

A EL não é capaz de realizar operações como um for, por exemplo. Para isso, se quisermos "fugir" do uso de scripts, temos um outro recurso à disposição.

Taglibs

Voltando a citar o material da Caelum:

A Sun percebeu que os programadores estavam abusando do código Java no JSP e tentou criar algo mais "natural" (um ponto um tanto quanto questionável da maneira que foi apresentada no início), sugerindo o uso de tags para substituir trechos de código.

O resultado final é um conjunto de tags (uma tag library, ou taglib) padrão, que possui, entre outras tags, a funcionalidade de instanciar objetos através do construtor sem argumentos.

Com esse recurso, ao invés de escrevermos isso:

<ul>
<% for (Tarefa tarefa : tarefas ) { %>
    <li><%= tarefa.getNome() %>
<% } %>
</ul>

Escreveremos isso:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

(...)

<ul>
    <c:forEach var="tarefa" items="${tarefas}">
        <li>${tarefa.nome}<li>
    </c:forEach>
</ul>

E embora possa ser tentador cair na armadilha de discutir qual opção é melhor, eu diria que nenhuma delas. Considere uma terceira alternativa, como o Thymeleaf, e não invista muito esforço nesse debate. Estamos em 2019, e discutir sobre JSP x Taglibs é "tão anos 2000".

Por hora, vamos "aceitar" a combinação EL + JSTL.

Aplicando as alterações

Primeiro, precisamos alterar os atributos do nó web-app, do arquivo web.xml:

<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app
        xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">

<display-name>Archetype Created Web Application</display-name>
</web-app>

Na sequência, precisamos instalar a interface de JSTL, bem como sua implementação. Altere o arquivo pom.xml para ficar semelhante ao seguinte:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.kplaube</groupId>
    <artifactId>ola-mundo</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>ola-mundo Maven Webapp</name>
    <url>http://maven.apache.org</url>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- Servlet -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>

        <!-- JSTL -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.1.2</version>
        </dependency>
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>c</artifactId>
            <version>1.1.2</version>
            <type>tld</type>
        </dependency>
    </dependencies>

    <build>
        <finalName>ola-mundo</finalName>
        <plugins>
            <!-- Tomcat plugin -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <path>/</path>
                    <contextReloadable>true</contextReloadable>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Já o HTML, no arquivo index.jsp, deve ficar parecido com o seguinte:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<body>
<h2>TODO list</h2>

    <form action="" method="post">
        <label>
            Tarefa:
            <input type="text" name="nome" required="required" />
        </label>

        <input type="submit" value="Salvar" />
    </form>
    <ul>
        <c:forEach var="tarefa" items="${tarefas}">
            <li>${tarefa.nome}</li>
        </c:forEach>
    </ul>
</body>
</html>

Reinicie o Tomcat, e ao acessar o endereço http://localhost:8080, você deve obter o seguinte resultado:

Exemplo de visualização com aplicação de Taglibs

Considerações finais

Embora haja uma forma programática de formularmos o deployment descriptor da aplicação, ainda assim precisamos lidar com escrita de XML que em muitos casos parecem ser alterações exotéricas. Mesmo com a Servlet 3.0, não escapamos dessa espécie de "karma" que é lidar com a linguagem Java.

No entanto, frameworks como o Spring tendem a eliminar essa necessidade, tornando tudo um pouco mais interessante, principalmente para desenvolvedores acostumados com ambientes mais "dinâmicos" como Python, Ruby ou Node.js. Mas infelizmente, não atacaremos essa transição no próximo post...

No próximo artigo, finalizaremos a prototipação com a parte da adição da tarefa. Abordaremos brevemente filtros e listeners, e fecharemos essa parte da trinca Servlet + Tomcat + Maven.

Até lá.

Referências