Статьи

GWT с весенней загрузкой

вступление

Недавно я изучал варианты написания кода пользовательского интерфейса на Java. В моем предыдущем посте я исследовал Eclipse RAP и обнаружил, что его можно интегрировать с Spring Boot в один исполняемый файл. На этот раз я хотел сделать тот же трюк с GWT.

Всем нравится Spring Boot. Это делает многие вещи намного чище и проще. Но исторически у каркасов для создания пользовательского интерфейса в веб-браузере были свои методы, позволяющие делать некоторые вещи из Spring Boot. К сожалению, во многих случаях их методы выглядят старыми и устаревшими. Таким образом, цель состояла в том, чтобы максимально использовать Spring Boot и использовать GWT только для пользовательского интерфейса.

Я должен предупредить читателей, что этот пост на самом деле является классическим примером TL; DR :-)).

GWT подход

GWT использует специальный компилятор для генерации кода Javascript из кода Java. Метод состоит в том, чтобы создать файл описания модуля .gwt.xml, использовать его для импорта некоторых других модулей и написать код на Java с виджетами GWT. Затем их компилятор сгенерирует много кода javascript, который необходимо включить на html-страницу. У них есть учебник на www.gwtproject.org, который объясняет основы.

Они используют GWT RPC для вызова методов на сервере. Этот подход требует интерфейса, который является общим для клиента и сервера. Клиент использует интерфейс для вызова метода RPC. Реализация метода на стороне сервера зарегистрирована в web.xml как сервлет с соответствующим шаблоном URL.

На мой взгляд, одной из основных проблем является отладка. GWT в последних версиях принял радикальный подход исходных карт. Это означает, что отладка кода Java происходит в браузере с включенными исходными картами, а не в Eclipse (или, возможно, я не смог заставить его работать в Eclipse). Я попробовал это в Chrome, и это на самом деле работает, но это выглядит как бред. GWT даже не генерирует исходные карты по умолчанию. Чтобы использовать их, необходимо запустить сервер кода и загрузить другой javascript на html-странице с этого сервера кода. Большинство людей в этом случае добавляют опцию к компилятору.

Я действительно имею в виду не обижать команду GWT и сторонников этой технологии, но в целом она выглядит немного устаревшей. Они не тратят слишком много времени на разработку новых функций. Даже сборочные плагины поддерживаются энтузиастами.

цели

Вот что я хотел достичь в своем исследовании:

  1. Используйте GWT только для генерации кода Javascript, который должен быть заархивирован вместе со всем остальным в исполняемый файл jar.
  2. Используйте Spring Boot для конечных точек REST и полностью избегайте GWT RPC
  3. Используйте исполняемый файл Jar Spring Boot, чтобы запустить приложение и обслуживать HTML-файлы GWT с помощью встроенного Tomcat. Это также означает, что могут быть использованы все другие замечательные функции Spring Boot.

Инструмент для сборки

Для достижения цели № 1 нам нужен хороший инструмент для сборки. Я создал пример проекта из учебника с плагином Maven. Вот полная конфигурация, которая работала для меня:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<plugin>
<groupId>net.ltgt.gwt.maven</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<version>1.0-rc-6</version>
<executions>
<execution>
<goals>
<goal>import-sources</goal>
<goal>compile</goal>
<goal>import-test-sources</goal>
<goal>test</goal>
</goals>
</execution>
</executions>
<configuration>
<moduleName>org.example.gwt.StockWatcher</moduleName>
<moduleShortName>StockWatcher</moduleShortName>
<failOnError>true</failOnError>
<!-- GWT compiler 2.8 requires 1.8, hence define sourceLevel here if you use
a different source language for java compilation -->
<sourceLevel>1.8</sourceLevel>
<!-- Compiler configuration -->
<compilerArgs>
<!-- Ask GWT to create the Story of Your Compile (SOYC) (gwt:compile) -->
<arg>-compileReport</arg>
<arg>-XcompilerMetrics</arg>
</compilerArgs>
<!-- DevMode configuration -->
<warDir>${project.build.directory}/${project.build.finalName}</warDir>
<classpathScope>compile+runtime</classpathScope>
<!-- URL(s) that should be opened by DevMode (gwt:devmode). -->
<startupUrls>
<startupUrl>StockWatcher.html</startupUrl>
</startupUrls>
</configuration>
</plugin>

С помощью плагина GWT Eclipse я заставил его работать, и даже отладка работала в Chrome, потому что плагин GWT для Eclipse автоматически запускает сервер кода и каким-то образом обновляет html-файл для загрузки javascript с сервера кода.

Суть в том, что плагин GWT Maven работает :-)). Но интеграция Spring Boot и GWT будет сложной задачей. Сначала мне нужно запустить компиляцию GWT, а затем добавить полученный javascript в исполняемый файл Jar. Возможно, это возможно сделать с Maven, но для этой задачи я решил использовать Gradle .

Gradle — быстро развивающийся инструмент для сборки. DSL и API еще не стабильны, но обеспечивают значительную гибкость. В то время как Maven имеет довольно прямую линию фаз сборки, Gradle может выполнять задачи в любом порядке. Эта гибкость — то, что мне нужно.

После некоторых копаний я нашел один работающий плагин Gradle для GWT: de.esoco.gwt . Это вилка плагина Putnami. Документация достаточно хороша, чтобы этот плагин работал. Я не заметил никаких серьезных проблем. Конфигурация в build.gradle находится внутри блока gwt:

1
2
3
4
5
6
gwt {
 gwtVersion = gwtVersion
 
 module("org.example.gwt.StockWatcher2", "de.richsource.gradle.plugins.gwt.example.Example")
        // other configuration options
}

Этот плагин добавляет некоторые задачи в сборку Gradle. Наиболее важным из них является gwtCompile . Эта задача на самом деле генерирует код JavaScript и помещает его в ${buildDir}/gwt/out . Эти значения (как gwt, так и out) жестко заданы в плагине Gradle GWT.

Важно помнить, что код, скомпилированный в javascript, указан в файле модуля GWT следующим образом:

1
2
<source path='client'/>
<source path='shared'/>

Отдых и отдых

Следующая цель — использовать конечные точки REST Spring Boot. Я нашел RestyGWT, который помог мне сделать это. У них есть простые инструкции на первой странице.

Я добавил необходимые зависимости в build.gradle:

1
2
3
implementation("javax.ws.rs:javax.ws.rs-api:2.0.1")
compileOnly group: "org.fusesource.restygwt", name: "restygwt", version: "2.2.0"
implementation group: "com.fasterxml.jackson.jaxrs", name: "jackson-jaxrs-json-provider", version: "2.8.9"

Зависимости JAX-RS необходимы, потому что RestyGWT использует аннотацию от JAX-RS для объявления конечных точек. Насколько я понял, Джексону также необходимо разобрать JSON.

Я также добавил зависимость в модуль GWT:

1
<inherits name="org.fusesource.restygwt.RestyGWT"/>

Вот сервис, который я создал с помощью RestyGWT:

1
2
3
4
5
6
public interface TestService extends RestService {
 
    @GET
    @Path("test") void test1(@QueryParam("input") String inp,   
        MethodCallback<TestResult> callback);
}

Я называю эту услугу в ClickHandler (я в основном использовал код из исходного учебника GWT):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private final TestService testSrv = GWT.create(TestService.class);
btnCallServer.addClickHandler(clkEvent -> {
    testSrv.test1("TestString", new MethodCallback<TestResult>() {
 
        @Override
        public void onSuccess(Method method, TestResult response) {
            testLabel.setText("Srv success " + response.getStr1());
        }
 
        @Override
        public void onFailure(Method method, Throwable exception) {
            testLabel.setText("Srv failure " + exception.getMessage());
        }
    });
});

Этот сервис вызывает этот простой метод в контроллере Spring Boot:

1
2
3
4
@GetMapping("/test")
public TestResult test1(@RequestParam(name="input", required=false) String inp) {
return new TestResult(inp + " qqq");
}

Хорошо, что весь этот код является частью одного исполняемого файла jar.

Исполняемый Jar

Третья цель — собрать все это в одну исполняемую флягу. В этом разделе я наконец могу использовать гибкость Gradle.

Сначала я помещаю html-файлы в /src/main/resources/static .

Я создал задачу для копирования сгенерированного javascript в статическую папку в $ {buildDir} во время сборки:

1
2
3
4
task copyGWTCode(dependsOn: ["gwtCompile"], type: Copy) {
    from file("${buildDir}/gwt/out")
    into file("${buildDir}/resources/main/static")
}

Затем я сделал задачу bootJar зависимой от этой задачи и скопировал jar в более традиционный целевой каталог:

1
2
3
4
5
6
7
8
bootJar {
    dependsOn copyGWTCode
    doLast {
        mkdir "${buildDir}/target"
        setDestinationDir(file("${buildDir}/target"))
        copy()
    }
}

Отладка в GWT

Одна дополнительная глава об отладке GWT.

Я нашел довольно простой способ отладки GWT UI в Chrome (Chrome справляется с этим лучше, чем Firefox). Вот шаги, чтобы заставить это работать. Я использовал проект из учебника GWT, но переименовал его в «stockwatcher2».

1. Добавьте новый html-файл для отладки в src/main/resources/static . Если исходный файл был, например, StockWatcher2.html, новый файл должен быть StockWatcher2debug.html. В этом новом файле замените строку

1
<script type="text/javascript" src="stockwatcher2/stockwatcher2.nocache.js"></script>

с этой строкой (JavaScript с сервера кода):

2. Выполните задачу bootJar и запустите ее.
3. Запустите сервер программного кода из папки проектов с помощью «gradle gwtCodeServer».
4. Откройте http://<host>:<port>/<somepath>/StockWatcher2debug.html в Chrome.
5. Теперь вы можете найти исходные карты в Developer Tools -> Sources под 127.0.0.1:9876. Точка останова может быть установлена ​​и нажата в Chrome напрямую.

Идея с отдельным файлом состоит в том, чтобы исключить его из производственных сборок, но сохранить его в сборках разработчика. Это легко с Gradle. Есть только одна проблема с этим подходом, и это то, что конечные точки REST, которые вызываются из источников отладки, отличаются от конечных точек, которые вызываются из «обычных» источников. Добавление еще одного сопоставления решает проблему.

Вывод

Поздравляю героических личностей, которые пришли к такому выводу! Вы настоящие программисты, а те, кто сдался, несчастные трусы!

Но суть в том, что работать с GWT довольно сложно. Инструменты сборки довольно громоздки и лишены важных функций. Практически нет интеграции (например, с Spring Boot). Отладка не нужна, сложная.

Если бы кто-то выбирал между GWT и Eclipse RAP, я бы порекомендовал Eclipse RAP.

Нет счастливого конца :-(.

Опубликовано на Java Code Geeks с разрешения Вадима Коркина, партнера нашей программы JCG . Смотреть оригинальную статью здесь: GWT с Spring Boot

Мнения, высказанные участниками Java Code Geeks, являются их собственными.