"Hello, World!" em GWT usando arquétipo do TBroyer
Como tudo bom na programação, vamos começar com um “hello, world!”?
No caso, quero codar um componente em GWT, e para não precisar codar com o
app do trabalho, melhor resumir ao extritamente necessário. Como no mundo Maven
existem os “arquétipos” (archetypes
, no inglês), eles já trazem meio caminho montado
para ser feliz. Então, que tal usar um desses archetypes
?
Eu já conhecia o net.ltgt.gwt.archetypes
do @tbroyer, mas ao pesquisar no Google a primeira opção que apareceu foi o
branflake2267/Archetypes
. Aqui vou testar
focando no arquétipo do TBroyer, deixando o branflake2267 para um outro momento.
Cavando mais apareceram outros arquétipos, mas aparentemente não tão significativos.
tbroyer ltgt archetype, base para começar
Existem 3 arquétipos distintos:
modular-webapp
modular-requestfactory
dagger-guice-rf-activities
Eu quero a coisa mais simples para começar. Não necessito de requestfactory
nem nada fancy
assim. Isso entra em conflito com os dois últimos arquétipos, só restando o primeiro. De todo jeito,
o primeiro ainda fornece um esquema modular no Maven, não exatamente o que eu queria mas funciona bem…
./mvnw archetype:generate \
-DarchetypeGroupId=net.ltgt.gwt.archetypes \
-DarchetypeVersion=LATEST \
-DarchetypeArtifactId=modular-webapp
Estou usando o Maven Wrapper por conveniência, Maven 3.6.3 e Wrapper 0.5.6
E voilà, sou apresentado com o prompt me fazendo algumas perguntas:
- qual o
groupId
/artifactId
/version
? - qual o valor para
package
?- deixar essa propriedade em branco fez ele assumir o
groupId
- deixar essa propriedade em branco fez ele assumir o
- simplesmente me informou que estava usando
module=App
- qual o
module-short-name
?
E então pediu para confirmar os dados.
Mandei fazer um package
a partir da raiz. Deu incompatibilidade de versão do plugin de war
com o Java 16. A versão era 2.2, e eu sei que a versão mais atual (no caso de hoje seria a 3.3.2)
funciona para versões mais novas e está listado como compatível com o Java 7. Simplesmente declaro que
desejo usar o maven-war-plugin
na versão adequado dentro do pluginManagement
e fica tudo bem: build
sucesso.
<project>
...
<build>
...
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
...
Bem, não estava acostumado mais com isso mas o war
gerado não era auto-executável (spring-boot me viciou).
Então, vamos lá, instalar um Tomcat novo. Peço para o sdkman
cuidar disso para mim:
$ sdk list tomcat # para verificar as versões disponíveis
$ sdk install tomcat 9.0.40 # escolhi essa versão porque ela está "fechada"
Encontrei esse blog post que pode trazer mais informações úteis https://franciscochaves.com.br/blog/instale-o-apache-tomcat-com-sdkman, mas não segui as dicas dele para isso.
Peguei o war
gerado e joguei na pasta do Tomcat (no meu caso, ~/.sdkman/candidates/tomcat/current
), na subpasta onde deveriam morar
as aplicações webapp/
. Daí startup.sh
no Tomcat!
No primeiro momento não carregou corretamente o script do GWT. Ao inspecionar o index.html
gerado, a tag
que deveria carregar o .nocache.js
estava assim:
<script src="${module.toLowerCase()}/${module.toLowerCase()}.nocache.js"></script>
alterei para apontar para o JS correto:
<script src="br.com.softsite.gsos.hw.App/br.com.softsite.gsos.hw.App.nocache.js"></script>
Eu creio que foi algum <ENTER>
que apertei com a opção de usar a opção padrão durante o uso do arquétipo.
Após alterar para pegar do lugar certo (eu coloquei o valor dentro do war
expandido pelo próprio Tomcat),
a página carregou corretamente. Porém… chamadas GWT-RPC não foram bem sucedidas como eu queria, deu 404.
Vou atrás de alguns suspeitos, nada para mim é muito claro quanto a esse erro… então, após procurar
um tanto, encontro o seguinte mapeamento no web.xml
:
<servlet-mapping>
<servlet-name>greetServlet</servlet-name>
<url-pattern>/${module.toLowerCase()}/greet</url-pattern>
</servlet-mapping>
Outra red-flag de que eu fiz alguma bobeira na construção do arquétipo! Corrigindo:
<servlet-mapping>
<servlet-name>greetServlet</servlet-name>
<url-pattern>/br.com.softsite.gsos.hw.App/greet</url-pattern>
</servlet-mapping>
Tudo funcionou como uma maravilha!!
Estrutura do projeto
O projeto consiste de um reator-pai com as quebras de linha um tanto quanto bagunçadas:
<modelVersion>4.0.0</modelVersion>
<groupId>br.com.softsite.gsos.hw</groupId>
<artifactId>ltgt</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
Mas, tirando isso, não tem nada demais. Precisei fazer o ajuste do plugin do war para funcionar e só.
Existem 3 módulos:
ltgt-client
ltgt-shared
ltgt-server
O shared
é dependência comum do client
e do server
. A única dependência que possui
é com.google.gwt:gwt-servlet
como provided
para lidar com GWT-RPC. Tem o plugin
maven-source-plugin
habilitado justamente para que, quando seja publicado, seus fontes
também sejam publicados e seja possível o GWT serializar corretamente as suas classes.
Mandar instalar esse módulo instala os seguintes artefatos:
ltgt-shared-1.0-SNAPSHOT.jar
ltgt-shared-1.0-SNAPSHOT-sources.jar
Esse comportamento é exibido porque o maven-source-plugin
foi configurado na raiz
para gerar esse artefato de fonte, com o goal jar-no-fork
atrelado à fase package
.
Não foi gerado nenhum gwt.xml
para esse artefato. Para um futuro, não devo confiar na coincidência
de que tudo do shared
esteja exatamente no mesmo pacote das coisas do client
>
O client
por sua vez tem como dependência o shared
normal E também com
<classifier>sources</classifier>
:
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>ltgt-shared</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>ltgt-shared</artifactId>
<version>${project.version}</version>
<classifier>sources</classifier>
</dependency>
Ele é empacotado como gwt-app
. Nada mais esquisito que eu dê conta
exatamente agora, apenas uma menção a como ele configura o plugin
net.ltgt.gwt.maven.gwt-maven-plugin
.
O gwt.xml
(situado em br.com.softsite.gsos.hw.App.gwt.xml
) gerado é bem simples,
apenas com menção ao entry-point
e ao source
além de um punhado trivial de inherits
e uma definição arbitrária:
<source path=""/>
<entry-point class="br.com.softsite.gsos.hw.App"/>
Note que como todos os fontes estão exatamente no mesmo pacote, não há necessidade
de importar do shared
nem de especificar um diretório de onde estarão os fontes.
Jà o server
é mais interessante. Por padrão ele depende do shared
e pronto,
sem puxar diretamente o sources
. Porém ele tem dependência ativa por perfil Maven.
Se ele estiver com o perfil env-prod
ligado (ligado por padrão, para caso eu esquela
algo na linha de comando ele já ativa esse perfil), ele tem a dependência de runtime
do client
, como war
:
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>ltgt-client</artifactId>
<version>${project.version}</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
A dependência é colocada como war
justamente para deixar o próprio Maven lidar com a
sobreposição de seus elementos. O gwt-app
do client
vai produzir um war
consistindo
apenas da parte client-side do aplicativo. Isso inclui eventual CSS e JS que o programador
suba no projeto, assets diversos como imagens como também o resultado do próprio compilador
GWT, o código transformado de Java para JS. Dizer que é uma dependência war
vai fazer a magia
da sobreposição correta no aplicativo gerado.
Agora, caso seja especificado que se deve rodar com o perfil env-dev
(e, portanto, não
ativar o perfil env-prod
), temos que não há dependência extra. Em compensação, temos os seguintes
plugins configurados:
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<configuration>
<webApp>
<resourceBases>
<resourceBase>${basedir}/src/main/webapp</resourceBase>
<resourceBase>${basedir}/../target/gwt/launcherDir/</resourceBase>
</resourceBases>
</webApp>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<contextFile>${basedir}/src/main/tomcatconf/context.xml</contextFile>
</configuration>
</plugin>
</plugins>
Notou o jetty
ali? Ele está pegando de ../target/gwt/lancherDir/
como um resourceBase
. Ou seja,
um target
na posição do reator!
Onde mais vi isso? Na configuração do plugin net.ltgt.gwt.maven.gwt-maven-plugin
dentro do
pom
do reator (prettified):
<plugin>
<groupId>net.ltgt.gwt.maven</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<inherited>false</inherited>
<configuration>
<launcherDir>${project.build.directory}/gwt/launcherDir</launcherDir>
</configuration>
</plugin>
Outra coisa bacana a se perceber é a configuração padrão do plugin do jetty
, que a priori
estará simplesmente ligada (e será usada pelo gwt-maven-plugin
):
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<configuration>
<scanIntervalSeconds>1</scanIntervalSeconds>
<webApp>
<extraClasspath>${basedir}/../ltgt-shared/target/classes/</extraClasspath>
</webApp>
<contextXml>${basedir}/src/main/jettyconf/context.xml</contextXml>
</configuration>
</plugin>
aqui foi definido que o shared
é parte do classpath extra, algo que precisa ser cuidado
pelo Jetty.