Первое приложение (Avalanche — application framework for Java) / Habr

Содержание
  1. Что такое система сборки?
  2. Что вам потребуется
  3. Как сделать описание проекта
  4. Hello world на kotlin tornadofx
  5. Javafx
  6. Javafx, helloworld — продолжение
  7. Scenebuilder
  8. Shebang-файлы: запускаем java как shell-скрипт
  9. Альтернативы electron
  10. Горячая перезагрузка кода jvm
  11. Запускаем .java с помощью java
  12. Зачем собирать код без ide
  13. Использование отдельных таблиц стилей
  14. Итак, приступим к написанию hello world’a.
  15. Как интерпретатор java выполняет программу hellouniverse
  16. Как собрать проект
  17. Как структурировать проект для maven
  18. Как установить maven
  19. Класс application
  20. Метод main
  21. Можно использовать модули?
  22. Можно ли передавать в командной строке аргументы?
  23. Настраиваем spring mvc (webconfig.java)
  24. Настройка stage
  25. Обновление таблиц стилей
  26. Определяем уровень исходного кода с помощью опции –source
  27. Первый пример
  28. Программные и встроенные стили
  29. Работает ли этот подход с несколькими классами?
  30. Реализация адаптера — demoadapter
  31. Реализация класса приложения — demoapplication
  32. Реализация класса функции — demofunction
  33. Создание приложения javafx
  34. Удаленный вызов функции demofunction
  35. Заключение
  36. Узнайте больше о Huawei

Что такое система сборки?

Система сборки это программа, которая собирает другие программы. На вход система сборки получает исходный код, а на выход выдаёт программу, которую уже можно запустить.

Чем она отличается от компилятора? Если коротко, то система сборки вызывает компилятор при своей работе, а компилятор о существовании системы сборки даже не подозревает.

Если более длинно, то сборка, помимо компиляции, включает в себя ещё целый спектр задач, для решения которых компилятор не пригоден от слова совсем.

Например, если программе для работы нужны какие-нибудь картинки, то положить их в директорию с программой — задача системы сборки. Если программе нужны сторонние библиотеки, то положить их в директорию с программой — задача системы сборки. Ну и так далее.

И да, если автоматическая сборка проекта настроена и работает, то нет нужды вручную конфигурировать IDE — современная среда разработки сгенерирует проект самостоятельно, достаточно показать ей где находится конфигурация для системы сборки.

Систем сборки для java по большому счёту 3 — ant, maven и gradle. ant отживает свой век, нынче его используют либо ретрограды, либо реально крутые чуваки, типа Антона Кекса, gradle пока удел хипстеров, а вот maven — стандарт индустрии. Уметь им пользоваться просто необходимо.

Что вам потребуется

Для запуска кода, приведённого в статье, вам понадобится версия Java не ниже 11. На момент написания статьи текущим релизом был Java SE Development Kit 12.0.1 — финальная версия находится

, достаточно принять условия лицензии и кликнуть на ссылку для вашей ОС. Если хотите поэкспериментировать с самыми свежими возможностями, то можете

Как сделать описание проекта

Но просто положить файлы в ожидаемую maven структуру директорий недостаточно. Для сборки проекта необходимо его описание в виде xml документа. Maven будет искать описание в корневой директории проекта в файле pom.xml .

Содержимое минимального файла pom.xml будет примерно следующим. Для прохождения туториала можно таким его и оставить, если ничего не поменять, код нормально соберётся.

Hello world на kotlin tornadofx

Прежде чем двигаться дальше, давайте посмотрим, как такой код выглядит на современном языке вроде Kotlin с его собственной библиотекой для написания приложений JavaFX, которая называется

 class Main : App() {
     override val primaryView = HelloWorld::class
 }

 class HelloWorld : View() {
     override val root = stackpane {
         prefWidth = 300.0
         prefHeight = 120.0
         text("Hello world")
     }
 }

src/main/kotlin/main/app.kt

Многим может показаться привлекательным использовать Kotlin и JavaFX, особенно если вы предпочитаете безопасность типов (в TornadoFX есть приятная функция «типобезопасные таблицы стилей») и если добавить лишние 5 МБ в приложения для вас не проблема.

Даже если подобные накладные расходы кажутся чрезмерными и вы не хотите включать фреймворк в программу (и разбираться со всеми его сложностями и причудами), то Kotlin нормально работает и с чистым JavaFX.

Javafx


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

Что бы вы ни думали о JVM, не существует никакой другой платформы (кроме, может быть, самого Electron!), настолько простой для кросс-платформенной разработки. Как только вы создали свой jar, на любой платформе, вы можете распространять его среди пользователей всех ОС — и он просто будет работать.

При большом разнообразии языков, которые сейчас поддерживаются в JVM, выбор языка тоже не должен стать проблемой: определённо найдётся такой, какой вам понравится (в том числе JavaScript, если вы не способны от него отказаться), и вы можете использовать JavaFX с любым языком JVM без особых проблем. В этой статье, кроме Java, я покажу немного кода на Kotlin.

Сам UI создаётся просто кодом (если у вас есть замечательная поддержка IDE от IntelliJ, Eclipse или NetBeans: это всё отличные бесплатные IDE, которые, наверное, превосходят любых конкурентов и, кстати, представляют собой самые лучшие образцы десктопных приложений на Java) или в визуальном конструкторе UI: SceneBuilder (который умеет интегрироваться в IntelliJ) или NetBeans Visual Debugger.

История JavaFX

JavaFX — не новая технология. Она появилась в декабре 2008 года и сильно отличалась от того, что мы видим сегодня. Идея заключалась в создании современного фреймворка UI для замены устаревшего Swing Framework, который являлся официальным фреймворком JVM с конца 90-х.

Oracle чуть не испортила всё с самого начала, приступив к созданию особого, декларативного языка, который предполагалось использования для создания UI приложений. Это не очень хорошо восприняли Java-разработчики, и та инициатива чуть не погубила JavaFX.

Заметив проблему, Oracle решила выпустить JavaFX 2 в 2022 году без собственного особого языка, а вместо этого применив FXML в качестве опции для чистого Java-кода (как мы увидим позже).

Около 2022 года JavaFX обрёл некую популярность, а Oracle приложила значительные усилия для улучшения и популяризации этой платформы. С версии 2.2 фреймворк JavaFX стал достаточно цельным фреймворком, но его по-прежему не включали в стандартную среду выполнения Java (хотя он всегда поставлялся вместе с JDK).

Только с версии JavaFX 8 (изменение версии сделано для соответствия Java 8) он стал частью стандартного рантайма Java.

Сегодня фреймворк JavaFX может и не является крупным игроком в мире UI, но на нём сделано немало реальных приложений, у него есть довольно много связанных библиотек и его портировали на мобильную платформу.

Javafx, helloworld — продолжение

HelloWorld из примера, предложенного Oracle в «Getting Started with JavaFX», на ПК с Windows. Развитие простейшего приложения до окна ввода логина и пароля. По-прежнему с использованием командной строки с приоткрытием недоговоренностей туториала, в котором приведен код этого приложения.

В предыдущем посте получилось завести пример из первого раздела Getting Started with JavaFX.

Пример был скомпилирован, запущен, упакован в jar и запущен из jar с помощью командной строки.

Каждая команда для удобства была спрятана в файл .cmd. Забавно, но наглядно. Продолжим использовать эти команды.

Во втором разделе предлагается сделать форму ввода логина и пароля, с блэкдже элементами управления. Попробуем развить имеющийся пример, оставив как есть пакет и класс &mdash helloworld.HelloWorld. jfxpub-get_started предлагает создать новый проект в NetBeans. Однако, пропустим первые три пункта из «Create the Project», а четвертый, замену тела метода «start» кодом из примера 2-1, применим к имеющемуся у нас файлу HelloWorld.java:

    @Override
	public void start(Stage primaryStage) {
	    primaryStage.setTitle("JavaFX Welcome");
        
	    primaryStage.show();
	}

Задача сводится к удалению строк с 18 по 29 и замене заголовка primaryStage. В таком виде файл откомпилируется и запустится, но такая ерунда получится, ничего интересного. Далее туториал предлагает использовать GridPane layout, потому что оно позволяет создавать столбцы и строки для размещения элементов управления, и это вроде как удобно. Послушаемся, и добавим код перед primaryStage.show():

	    GridPane grid = new GridPane();
	    grid.setAlignment(Pos.CENTER);
	    grid.setHgap(10);
	    grid.setVgap(10);
	    grid.setPadding(new Insets(25, 25, 25, 25));
	    Scene scene = new Scene(grid, 300, 275);
	    primaryStage.setScene(scene)

А вот это уже не откомпилируется:

Итого: не найдены классы GridPane, Pos и Insets. А где их искать? Я тоже не знал. Но на сайте Oracle нашел вот такой

«справочник»

. Из него ясно, что

GridPane - Class in javafx.scene.layout
    GridPane lays out its children within a flexible grid of rows and columns.
Pos - Enum in javafx.geometry
    A set of values for describing vertical and horizontal positioning and alignment.
Insets - Class in javafx.geometry
    A set of inside offsets for the 4 side of a rectangular area

Это значит, что надо добавить строчки импорта:

import javafx.scene.layout.GridPane;
import javafx.geometry.*;

Теперь файл откомпилируется и запустится, но все еще ничего интересного. Продолжим наполнять по рекомендациям туториала. Добавим код с элементами управления после строчки, устанавливающей свойство Padding таблицы grid, то есть, перед Scene scene = new Scene(grid, 300, 275).

Добавка к HelloWorld.java
	    Text scenetitle = new Text("Welcome");
	    scenetitle.setFont(Font.font("Tahoma", FontWeight.NORMAL, 20));
	    grid.add(scenetitle, 0, 0, 2, 1);
	    Label userName = new Label("User Name:");
	    grid.add(userName, 0, 1);
	    TextField userTextField = new TextField();
	    grid.add(userTextField, 1, 1);
	    Label pw = new Label("Password:");
	    grid.add(pw, 0, 2);
	    PasswordField pwBox = new PasswordField();
	    grid.add(pwBox, 1, 2);

	    Button btn = new Button("Sign in");
	    HBox hbBtn = new HBox(10);
	    hbBtn.setAlignment(Pos.BOTTOM_RIGHT);
	    hbBtn.getChildren().add(btn);
	    grid.add(hbBtn, 1, 4);

	    final Text actiontarget = new Text();
		    grid.add(actiontarget, 1, 6);

	    btn.setOnAction(new EventHandler<ActionEvent>() {
	     
		@Override
		public void handle(ActionEvent e) {
		    actiontarget.setFill(Color.FIREBRICK);
		    actiontarget.setText("Sign in button pressed");
		}
	    });

Получаем длинную простыню ошибок. Но они уже знакомы и понятны. Мы, оказывается, не в курсе, кто такие Text, Font, Label, Color и еще некоторые их друзья. Пороемся в упомянутом справочнике, посмотрим, в каких пакетах их родина. Добавим импорт:

import javafx.scene.text.*;
import javafx.scene.control.*;
import javafx.scene.paint.*;
import javafx.scene.layout.HBox;

Такое вот у меня здесь произвольное отношение к звездочкам. Для учебных целей сойдет и так.

Теперь компилируется и работает. Надо коротенько разобрать наделанное:

  • javafx.application.Application — главный класс приложения JavaFX.
  • Его метод start() является точкой входа.
  • Класс Stage — контейнер верхнего уровня для создания пользовательского интерфейса (окно).
  • Класс Scene — контейнер следующего уровня и содержит все элементы.
  • Наша сцена основана на GridPane, которая является как бы таблицей, в ячейках которой можно расположить элементы интерфейса. HGap и VGap определяют зазор между столбцами и строками соответственно. Padding задает зазор между краем таблицы и краем окна, который таблица постарается по возможности выдерживат. Строчка «grid.setGridLinesVisible(true);» покажет таблицу в окне — сделает ее линии видимыми.
  • Интересно, что количество строк и столбцов таблицы не задается ее свойствами. Вместо этого мы располагаем некоторые элементы в определенных ячейках (задавая столбец и строку), а таблица «сама» разрастается до нужного количества. Метод grid.add добавляет элемент (первый параметр) в указанную ячейку (второй и третий параметр — столбец, строка). Можно добавить четвертый и пятый параметры (как при добавлении scenetitle) — сколько столбцов и строк использовать под элемент.
  • Метод main() не является необходимым для приложений JavaFX, если JAR-файл создан с помощью javafxpackager (он внедряет JavaFX Launcher в JAR-файл). Однако, полезно оставить этот метод, чтобы иметь возможность запускать JAR-файл, созданный без JavaFX Launcher, например, в IDE с неполной интеграцией с JavaFX. Опять же, Swing-приложения, включающие код JavaFX, требуют метод main().
  • HBox — это такая специальная панелька, которая позволяет задать выравнивание кнопки, засунутой в эту панельку, отличающимся от остальных элементов — с правого края.
  • Остальное достаточно очевидно из кода.

Осталось раскрасить это все с помощью CSS, нарисовать с помощью FXML и объединить эти буквы вместе в одном приложении.

И еще один интересный момент, которым хотелось бы поделиться напоследок. В прошлом посте я упомянул, что если в коде приложения пакет обозвать package HelloWorld, то в папке ./out создастся папка HelloWorld, программа откомпилируется, но не запустится — не будет найден класс. Поправим обратно регистр символов в названии пакета — все снова заработает, хотя регистр символов папки останется «неправильным». ОС Windows на регистр имени папки совершенно плевать, и пересоздавать или переименовывать ее система не будет. На запуск программы из файла HelloWorld.class это не повлияет. А вот если теперь упаковать в .JAR и попытаться его выполнить — полезут ошибки. В архиве папка будет с неправильным (уже без кавычек) регистром двух символов, и этого достаточно, чтобы не найти главный класс приложения. Придется стереть папку ./out/HelloWorld, заново откомпилировать и создать .jar.

Scenebuilder

Первое из них — это

, визуальный конструктор UI, который позволяет создавать FXML, просто перетаскивая компоненты UI.

Его можно интегрировать в любые Java IDE, что упрощает создание новых видов (экранов).

Мне раньше приходилось использовать SceneBuilder для создания форм и тому подобных сложных видов, но я обычно просто набрасывал там что-то по-быстрому, а затем редактировал код вручную для приведения его к конечному виду.

Если вы так сделаете, а потом откроете вид в SceneBuilder, он по-прежнему будет нормально работать, так что можно поочерёдно редактировать код вручную или в SceneBuilder — и просматривать результат.

Shebang-файлы: запускаем java как shell-скрипт

Итак, в Java SE 11 появилась поддержка скриптов, включая традиционные shebang-файлы из мира *nix. Для их поддержки не потребовалось спецификации языка.

В shebang-файле первые два байта должны быть 0x23 и 0x21. Это ASCII-кодировка символов #!.. Все последующие байты в файле читаются на основе действующей по умолчанию на данной платформе системы кодировки.

Таким образом, чтобы файл исполнился с помощью встроенного в ОС shebang-механизма, необходимо соблюсти лишь одно требование: чтобы первая строка начиналась с #!.. Это означает, что нам не нужна какая-то особенная первая строка, когда модуль запуска Java явно используется для запуска кода из исходного файла, как в случае с HelloUniverse.java.

Запустим следующий пример в терминале, работающем под macOS Mojave 10.14.5. Но сначала определим важные правила, которым нужно следовать при создании shebang-файла:

  • Не смешивать Java-код с кодом скриптового языка оболочки вашей ОС.
  • Если вам нужно добавить опции виртуальной машины, необходимо после имени исполняемого файла в shebang-файле первой опцией задать --source. К опциям виртуальной машины относятся: --class-path, --module-path, --add-exports, --add-modules, --limit-modules, --patch-module, --upgrade-module-path, а также любые их вариации. Также в этот список могут включить новую опцию --enable-preview, описанную в JEP 12.
  • Вы должны задать версию Java, которая используется в исходном файле.
  • Первая строка файла должна начинаться с shebang-символов (#!). Например:
    #!/path/to/java --source <vеrsion>
  • Применительно к исходным Java-файлам НЕЛЬЗЯ использовать shebang-механизм для исполнения файлов, которые соответствуют стандартному соглашению о наименованиях (заканчиваются на .java)
  • Вы должны пометить файл как исполняемый с помощью команды:
    chmod x <Filеname>.<Extеnsion>.

Создадим shebang-файл (скриптовую программу), который выведет список содержимое директории, чьё имя будет передано в качестве параметра. Если никаких параметров не передаётся, по умолчанию будет взята текущая директория.

#!/usr/bin/java --source 11
import java.nio.file.*;
import static java.lang.System.*;

public class DirectoryLister {
      public static void main(String[] args) throws Exception {
            vardirName = ".";

            if ( args == null || args.length< 1 ){
err.println("Will list the current directory");
            } else {
                  dirName = args[0];
            }

            Files
            .walk(Paths.get(dirName))
            .forEach(out::println);       
      }
}

Сохраним код в файл с именем dirlist без расширения, а затем пометим его как исполняемый:

mohamed_taman:code$ chmod x dirlist

Запустим файл:

Альтернативы electron

Ели вы не возражаете, что несколько групп разработки будут создавать разные версии приложения под разные ОС, то варианты выглядят примерно так:

для MacOS,

для Windows (я не специалист по разработке под конкретные платформы, так что дайте знать, пожалуйста, какие варианты в наши дни более популярны).

Однако реальные конкуренты Electron — это мультиплатформенные фреймворки. Думаю, среди них самыми популярными сегодня являются GTK , Qt и JavaFX.

Горячая перезагрузка кода jvm

Если хотите изменить код приложения, который напрямую не связан с UI, то длоя этого подходит отладчик Java с горячей заменой кода во время работы приложения. Базовая поддержка перезагрузки кода имеется в Oracle JVM и HotSpot. Думаю, что она есть и в OpenJDK JVM.

Однако базовая поддержка этой функции очень ограничена: вам позволено менять только реализацию уже существующих методов.

Зато есть расширение HotSpot VM под названием DCEVM (Dynamic Code Evolution VM) с гораздо большей функциональностью: добавление/удаление методов и полей, добавление/удаление классов, изменение значения итоговых переменных и прочее. В другой статье я уже писал о нём и о других способах перезагрузки кода в работающей JVM.

Я использовал это расширение при разработке LogFX — и оно отлично себя проявило. Если не закрыть и заново не открыть окно, то вид не меняется автоматически при перезагрузке кода, но это не такая большая проблема, если менять что-то в Stage… к тому же, если вы хотите изменить только компонент UI, то можно использовать ScenicView или просто вернуться в ScenicBuilder и как угодно поменять дизайн.

Для запуска DCEVM нужно только установить его и сверить номера версий расширения и JVM. После этого приложение запускается с отладчиком — и каждый раз после перекомпиляции в IDE новый код автоматически подгрузится в работающую программу.

В IntelliJ после изменения класса и перекомпиляции вы увидите нечто подобное (Cmd F9 на «маке»):

Запускаем .java с помощью java


Функция

(запуск однофайловых программ с исходным кодом) появилась в JDK 11. Она позволяет напрямую исполнять исходные файлы с исходным Java-кодом, без использования интерпретатора. Исходный код компилируется в памяти, а затем исполняется интерпретатором без создания на диске .class-файла.

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

Чтобы обойти это ограничение, все классы нужно определять в одном файле. Никаких ограничений на их количество нет. Кроме того, пока они находятся в одном файле, не имеет значения, являются ли они публичными или частными.

Первый класс, определённый в файле, будет считаться основным, и в него нужно поместить метод main. То есть важна очерёдность.

Зачем собирать код без ide

Первый вопрос, который возникает у начинающего разработчика — как в реальной жизни может пригодиться умение собирать код без среды разработки. Она ведь установлена и настроена у всех — это первое, что делает программист, когда приходит на проект.

Ответ простой — для того, чтобы нормально организовать рабочий процесс — код нужно регулярно собирать, проверять что он вообще компилируется, а потом, для того чтобы убедиться в работоспособности кода, прогонять тесты.

Можно, конечно, выделить специального человека, который будет раз в 15 минут запускать IDE и проводить описанные выше процедуры, но это безумие, такие вещи следует делать автоматически.

Уметь собирать код без среды разработки — суровая необходимость. Настолько суровая, что для решения этой задачи существует особый класс программного обеспечения, называемого системами сборки.

Использование отдельных таблиц стилей

Если вы не хотите удаляться от мира веба и предпочитаете задавать стили в отдельных таблицах стилей, JavaFX и это умеет! Именно такой подход я выбрал, потому что он позволяет стилизовать всё в одном месте и даже даёт пользователям возможность выбирать стили на свой вкус.

Для этого сначала создаём таблицу стилей:

 .root {
     -fx-base: darkslategray;
 }

 Text {
     -fx-stroke: white;
 }

src/main/resources/css/hello.css

Теперь добавляем её в Scene:

 primaryStage.getScene().getStylesheets().add("css/hello.css");

И всё.

Заметьте, что таблицы стилей устанавливают не только фоновый цвет StackPane как darkslategray, но и меняют основной цвет темы.

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

Например, в нашем случае более подходящим цветом текста станет не белый, а «противоположный» цвет относительно основного цвета темы, чтобы текст всегда оставался читаемым:

 -fx-stroke: ladder(-fx-base, white 49%, black 50%);

Таблицы стилей JavaFX довольно умные, для дополнительной информации см.

Вот пример простого приложения, где мы поставили кнопку вместо текста. Слева показаны стили по умолчанию, а справа используется таблица стилей, которую мы только что создали:

Первое приложение (Avalanche — application framework for Java) / Habr
Слева: стили по умолчанию JavaFX. Справа: кастомные стили, созданные выше

В своём приложении я хотел поставить по умолчанию тёмную тему, но при этом оставить пользователям возможность загружать собственные стили, чтобы они могли использовать любую тему, какая им нравится.

Вот как LogFX выглядит в итоге с темой по умолчанию:

Обратите внимание, что для кнопок я использовал иконки FontAwesome. Было довольно просто стилизовать кнопки в CSS. Просто убедитесь в том, чтобы шрифт устанавливался как можно раньше с помощью такой инструкции:

 Font.loadFont( LogFX.class.getResource( "/fonts/fontawesome-webfont.ttf" ).toExternalForm(), 12 );

С кастомными таблицами стилей можно кардинально изменить внешний вид приложения. Например, вот очень зелёная тема в Linux Mint:

Хотя хороший вкус автора этой зелёной темы под вопросом, она показывает мощные возможности стилизации в JavaFX. Здесь вы можете реализовать практически всё, на что способно ваше воображение.

В завершение хотел бы упомянуть классные эффекты, которые есть в JavaFX… Я хотел сделать начальный экран, который бы хорошо выглядел просто с форматированным текстом.

В JavaFX это делается просто. Вот что у меня получилось (я сделал экран на основе образца GroovyFX):

И вот какая таблица стилей соответствует этому стартовому экрану:

 Text {
     -fx-fill: white;
 }

 #logfx-text-log {
     -fx-font-family: sans-serif;
     -fx-font-weight: 700;
     -fx-font-size: 70;
     -fx-fill: linear-gradient(to top, cyan, dodgerblue);
 }

 #logfx-text-fx {
     -fx-font-family: sans-serif;
     -fx-font-weight: 700;
     -fx-font-size: 86;
     -fx-fill: linear-gradient(to top, cyan, dodgerblue);
     -fx-effect: dropshadow(gaussian, dodgerblue, 15, 0.25, 5, 5);
 }


Здесь возможно создание очень неплохих эффектов. Для дополнительной информации см.

В следующих разделах обсудим, как менять виды (экраны), перезагружать код на лету (hot reload) и обновлять таблицы стилей во время работы программы.

Итак, приступим к написанию hello world’a.

Что нам понадобится:

Я не буду заострять Ваше внимание на установке и настройке необходимого ПО, так как, я думаю, Вы и сами вполне справитесь.


Для начала надо сказать, что по синтаксису Java ME ничем не отличается от обычной джавы, так что если вы знакомы с Java SE, то проблем у Вас возникнуть не должно.

Как интерпретатор java выполняет программу hellouniverse

В JDK 10 модуль запуска Java может работать в трёх режимах:

  1. Исполнение class-файла.
  2. Исполнение основного класса из JAR-файла.
  3. Исполнение основного класса модуля.

А в Java 11 появился четвёртый режим:

  1. Исполнение класса, объявленного в исходном файле.

В этом режиме исходный файл компилируется в памяти, а затем выполняется первый класс из этого файла.

Система определяет ваше намерение ввести исходный файл по двум признакам:

  1. Первый элемент в командной строке не является ни опцией, ни частью опции.
  2. В строке может присутствовать опция --source <vеrsion>.

В первом случае Java сначала выяснит, является ли первый элемент команды опцией или её частью. Если это имя файла, заканчивающееся .java, тогда система будет считать его исходным кодом, который нужно скомпилировать и запустить. Вы также можете добавлять в Java-команду опции перед именем исходного файла.

Например, если нужно задать путь класса, когда исходный файл использует внешние зависимости.

Во втором случае выбирается режим работы с исходным файлом, и первый элемент в командной строке, который не является опцией, считается исходным файлом, который нужно скомпилировать и запустить.

Если файл не имеет расширения .java, то нужно использовать опцию –source, чтобы принудительно перейти в режим работы с исходным файлом.

Это важно в случаях, когда исходный файл представляет из себя «скрипт», который нужно выполнить, а имя файла не соответствует обычным соглашениям о наименованиях исходных файлов с Java-кодом.

С помощью опции –source можно определять версию языка исходника. Об этом мы поговорим ниже.

Как собрать проект

Теперь, когда структура файлов проекта соответствует ожидаемой, а его описание присутствует в файле pom.xml, для того, чтобы собрать проект, осталось только открыть консоль, сменить текущую директорию на директорию проекта и написать в консоли:

mvn compile

После того, как maven закончит свою работу, в директории проекта появится директория target, в которой будет находиться скомпилированный код.

Скомпилированный java код выглядит так же, как исходный, только вместо файлов с расширением java в директориях, соответствующих названиями пакетов, находятся файлы с расширением class, в которых будет уже не исходный код, а код, предназначенный для интерпретации джава-машиной.

То есть теперь этот код можно запустить.

Как структурировать проект для maven

В терминологии maven совокупность файлов с исходным кодом программы, файлов настроек и всего такого называется проектом. Директория, в которой располагаются эти файлы, называется корневой директорией проекта. В дальшейшем я буду обозначать эту директорию как <project>

Как известно, язык программирования java навязывает программисту структуру директорий, которая диктует расположение файлов с классами. Напимер класс с полным именем com.app.HelloWorld должен находиться в файле com/app/HelloWorld.java и никак иначе.

Maven добавляет к этому ограничению ещё одно — исходный код должен находиться в директории <project>/src/main/java. То есть класс com.app.HelloWorld maven будет искать в <project>/src/main/java/com/app/HelloWorld.java

Вот как будет выглядеть этот самый HelloWorld

package com.app;

public class HelloWorld {
    public static void main(String[] argv) {
        System.out.println("Hello world");
    }
}

Как установить maven

Maven устанавливается просто копированием в нужную директорию — никакого инсталлера нет. Как и в случае с большинством консольных утилит для использования достаточно добавить директорию maven/bin в переменную окружения PATH.

То есть, если maven находится в d:/soft/maven, то в PATH надо добавить d:/soft/maven/bin

Ещё для работы maven потребует переменную JAVA_HOME, которая указывает на JDK. Если JDK находится в C:/Program Files/Java/jdk1.8.0_05, то именно такое значение нужно поместить в JAVA_HOME. Добавлять bin в конец не нужно.

После этого можно попробовать написать в консоли

mvn --version

Если получится, значит maven установлен.

Класс application

Время начать программировать. Если вы следовали

, у вас уже готовы все необходимые зависимости.

Каждое приложение JavaFX должно иметь класс main, который расширяет класс:

javafx.application.Application

Кроме того, необходимо переопределить абстрактный метод из класса Application:

public void start(Stage primaryStage) throws Exception

Касс main выглядит примерно так:

import javafx.application.Application;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        // TODO implement me!
    }
}

Метод main

Для запуска JavaFx приложения не обязательно нужен метод main(). Вы можете упаковать исполняемый файл jar, используя

. Однако гораздо удобнее иметь метод main.

При этом приложение не только легче запустить, но в него можно как обычно передавать параметры командной строки.

Внутри метода main() приложение можно запустить используя метод:

Application.launch()

Легко заметить, что это статический метод в классе Application. Мы не указали основной класс, но JavaFX может определить это автоматически в зависимости от класса, который вызывает этот метод.

Можно использовать модули?

Да, никаких ограничений. Скомпилированный в памяти код запускается как часть безымянного модуля с опцией

--add-modules=ALL-DEFAULT

, которая даёт доступ ко всем модулям, поставляемым с JDK.

То есть код может использовать разные модули без необходимости явно определять зависимости с помощью module-info.java.

Можно ли передавать в командной строке аргументы?

Давайте расширим нашу программу Hello Universe, чтобы она выводила персональное приветствие любому пользователю, зашедшему на InfoQ Universe:

public class HelloUniverse2{
    public static void main(String[] args){
        if ( args == null || args.length< 1 ){
System.err.println("Name required");
System.exit(1);
        }
  var name = args[0];
  System.out.printf("Hello, %s to InfoQ Universe!! %n", name);
    }
}

Сохраним код в файле Greater.java. Обратите внимание, что имя файла не соответствует имени публичного класса. Это нарушает правила спецификации Java.

Запустим код:

mohamed_taman$ java Greater.java "Mo. Taman"
Hello, Mo. Taman to InfoQ universe!!

Как видите, совершенно не важно, что имена класса и файла не совпадают. Внимательный читатель мог также заметить, что мы передали в код аргументы после обработки имени файла. Это означает, что любой аргумент в командной строке, идущий после имени файла, передаётся стандартному основному методу.

Настраиваем spring mvc (webconfig.java)

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

@Configuration

(в будущем мы будем называть такие классы конфигурационными).

WebConfig.java

package com.elennaro.sshwa.config.application;

//Import section ommited...

@Configuration
@EnableWebMvc
@ComponentScan({ "com.elennaro.sshwa.config", "com.elennaro.sshwa.controllers" })
//@Import({ AppSecurityConfig.class })
public class WebConfig {
 
@Bean
 public InternalResourceViewResolver viewResolver() {
 InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
 viewResolver.setViewClass(JstlView.class);
 viewResolver.setPrefix("/WEB-INF/views/");
 viewResolver.setSuffix(".jsp");
 return viewResolver;
 }
}

Коротко рассмотрим код приведенный выше:

Далее рассмотрим непосредственно настройки безопасности Spring Security.

Настройка stage

Теперь мы знаем как запустить наше приложение, используя метод main(). Тем не менее, ничего не произойдет, если мы сделаем это. Нужно окно, которое мы хотим показать. Окно называется stage, помните? На самом деле, у нас уже есть первичная stage, переданная в метод start в качестве входного параметра:

public void start (Stage primaryStage)

Мы можем использовать эту компоненту stage. Единственная проблема в том, что она скрыта по умолчанию. К счастью, мы можем легко показать ее, используя метод primaryStage.show():

@Override
public void start(Stage primaryStage) throws Exception {
    primaryStage.show();
}

Теперь, когда вы запустите приложение, вы должны увидеть следующее окно:

Не очень впечатляет, правда? Во-первых, давайте добавим хорошую подпись к нашему окну.

primaryStage.setTitle("Hello world Application");


Чтобы окно выглядело еще лучше, давайте добавим красивую иконку в верхнюю панель окна:

InputStream iconStream = getClass().getResourceAsStream("/icon.png");
Image image = new Image(iconStream);
primaryStage.getIcons().add(image);

Вы можете добавить несколько иконок, представляющих приложение. Точнее одну и ту же иконку разных размеров. Это позволит использовать иконку походящего размера в зависимости от контекста приложения.

Теперь можно настроить свойства и поведение объекта Stage, например:


Теперь у нас есть окно с таким фантастическим названием, но оно все еще пустое. Вы уже знаете, что нельзя добавлять компоненты непосредственно в Stage (окно). Вам нужна сцена (scene).

Однако для запуска конструктора сцены требуется указать дочерний узел. Давайте сначала создадим простую метку (label). Затем мы создаем сцену (scene) этой меткой (label) в качестве дочернего узла.

Label helloWorldLabel = new Label("Hello world!");
Scene primaryScene = new Scene(helloWorldLabel);

Вы заметили, что Scene допускает только один дочерний компонент. Что если нам нужно больше? Необходимо использовать layout (макет), который является компонентом, который может включать несколько дочерних элементов и размещать их на экране в зависимости от используемого layout (макета). Мы рассмотрим макеты позже в этой серии статей.

Чтобы сделать приложение немного более визуально привлекательным, давайте разместим метку в центре экрана.

helloWorldLabel.setAlignment(Pos.CENTER);


Наконец, нам нужно установить Scene для Stage, которая у нас уже есть:

@Override
public void start(Stage primaryStage) throws Exception {
    primaryStage.setTitle("Hello world Application");
    primaryStage.setWidth(300);
    primaryStage.setHeight(200);

    InputStream iconStream = getClass().getResourceAsStream("/icon.png");
    Image image = new Image(iconStream);
    primaryStage.getIcons().add(image);

    Label helloWorldLabel = new Label("Hello world!");
    helloWorldLabel.setAlignment(Pos.CENTER);
    Scene primaryScene = new Scene(helloWorldLabel);
    primaryStage.setScene(primaryScene);

    primaryStage.show();
}

Теперь наше окно содержит сцену с компонентом метка (label):

Обновление таблиц стилей

JavaFX не обновляет автоматически таблицы стилей. Но для LogFX я хотел сделать такую возможность, чтобы можно было изменять стили — и немедленно наблюдать эффект в приложении.

Поскольку LogFX — программа для просмотра логов, у неё довольно продвинутый FileChangeWatcher, который подходит для просмотра стилей и их перезагрузки.

Но он работает только если стили поставляются из отдельного файла, а не из самого приложения (из jar).

Поскольку я уже разрешил пользователям устанавливать произвольный файл с таблицами стилей, то это не стало проблемой.

Я использовал эту функцию в процессе разработки, и она очень впечатляет. Если вам нужна такая же фича, можно написать собственный диспетчер файлов или скопировать мой (в конце концов, он с открытыми исходниками).

Для выбора таблицы стилей как файла (в отличие от ресурса jar), к сожалению, придётся использовать разный синтаксис под Unix/Mac и Windows. Вот такой метод я применил, чтобы решить проблему:

 private static String toAbsoluteFileUri( File file ) {
     String absolutePath = file.getAbsolutePath();
     if ( File.separatorChar == '\' ) {
         // windows stuff
         return "file:///"   absolutePath.replace( "\", "/" );
     } else {
         return "file:"   absolutePath;
     }
 }

Это работает на Mac, Windows и Linux Mint. Но это только первая из двух проблем, которые возникают на разных ОС (вторая — то, что не отображается иконка в системном трее на Mac, хотя есть

этой проблемы). В остальном JavaFX всё абстрагирует довольно хорошо по большей части.

Наконец, когда диспетчер определил изменение в файле с таблицами стилей, вы можете обновить стиль, просто удалив его и немедленно добавив обратно:

Определяем уровень исходного кода с помощью опции –source


Есть два сценария использования опции

--source

  1. Определение уровня исходного кода.
  2. Принудительный перевод runtime-среды Java в режим работы с исходным файлом.

В первом случае, если вы не указали уровень исходного кода, за него принимается текущая версия JDK. А во втором случае файлы с расширениями, отличными от .java, можно передавать для компилирования и выполнения на лету.

Давайте сначала рассмотрим второй сценарий. Переименуем Greater.java просто в greater без расширения и попробуем выполнить:

mohamed_taman$ java greater "Mo. Taman"
Error: Could not find or load main class greater
Caused by: java.lang.ClassNotFoundException: greater


При отсутствии расширения .java интерпретатор команд ищет скомпилированный класс по имени, переданному в виде аргумента — это первый режим работы модуля запуска Java. Чтобы это не происходило, воспользуемся опцией

--source

для принудительного переключения в режим работы с исходным файлом:

mohamed_taman$ java --source 11 greater "Mo. Taman"
Hello, Mo. Taman to InfoQ universe!!

Теперь перейдём к первому сценарию. Класс Greater.java совместим с JDK 10, поскольку содержит ключевое слово

var

, но не совместим с JDK 9. Изменим

source10

mohamed_taman$ java --source 10 Greater.java "Mo. Taman"
Hello Mo. Taman to InfoQ universe!!


Снова запустим предыдущую команду, но в этот раз передадим

--source 9

вместо

10

mohamed_taman$ java --source 9 Greater.java "Mo. Taman"
Greater.java:8: warning: as of release 10, 'var' is a restricted local variable type and cannot be used for type declarations or as the element type of an array
var name = args[0];
            ^
Greater.java:8: error: cannot find symbol
var name = args[0];
        ^
  symbol:   class var
  location: class HelloWorld
1 error
1 warning
error: compilation failed

Обратите внимание: компилятор предупреждает о том, что

var

стала в JDK 10 ограниченным именем типа. Но поскольку у нас язык уровня 10, компиляция продолжается. Однако возникает сбой, потому что в исходном файле нет типа с именем

var

Всё просто. Теперь рассмотрим использование нескольких классов.

Первый пример

Начнём с классического простейшего примера — Hello Universe!

Мы продемонстрируем описываемую возможность на разных примерах, чтобы вы получили представление, как можно её использовать в повседневном программировании.

Создайте файл HelloUniverse.java с кодом из начала статьи, скомпилируйте и запустите получившийся class-файл. Затем удалите его, сейчас поймёте зачем:

mohamed_taman$ rm HelloUniverse.class


Если теперь с помощью Java-интерпретатора вы запустите class-файл без компиляции:

mohamed_taman$ java HelloUniverse.java
Hello InfoQ Universe

то увидите тот же результат: файл будет исполнен.

Это означает, что теперь можно просто выполнить java HelloUniverse.java. Мы передаём сам исходный код, а не class-файл: система внутри себя компилирует его, запускает и показывает в консоли сообщение.

То есть под капотом всё же выполняется компиляция. И в случае её ошибки мы получим уведомление об этом. Можете проверить структуру директорий и убедиться, что class-файл не генерируется, компиляция выполняется в памяти.

Теперь давайте разберёмся, как это всё устроено.

Программные и встроенные стили

Один из вариантов (мучительный, но зато с безопасными типами) — сделать это программным способом:

 root.setBackground(new Background(new BackgroundFill(
         Color.DARKSLATEGRAY, CornerRadii.EMPTY, Insets.EMPTY)));

 helloWorld.setStroke(Color.WHITE);

Более простой программный способ — установить стили в CSS:

 root.setStyle("-fx-background-color: darkslategray");
 helloWorld.setStyle("-fx-stroke: white");


Обратите внимание, что здесь IntelliJ опять обеспечивает автодополнение для значений строк.

Если вы используете FXML:

То же самое…

Работает ли этот подход с несколькими классами?

Да, работает.

Рассмотрим пример с двумя классами. Код проверяет, является ли заданное строковое значение палиндромом.

Вот код, сохранённый в файле PalindromeChecker.java:

import static java.lang.System.*;
public class PalindromeChecker {
      public static void main(String[] args) {
            
            if ( args == null || args.length< 1 ){
                err.println("String is required!!");
                exit(1);
            }
            out.printf("The string {%s} is a Palindrome!! %b %n",
                  args[0],
                  StringUtils
                        .isPalindrome(args[0]));            
      }
}
public class StringUtils {
      public static Boolean isPalindrome(String word) {
      return (new StringBuilder(word))
            .reverse()
            .toString()
            .equalsIgnoreCase(word);
      }
}

Запустим файл:

mohamed_taman:code$ java PalindromeChecker.java RediVidEr
The string {RediVidEr} is a Palindrome!! True

Запустим снова, подставив «RaceCar» вместо «MadAm»:

mohamed_taman:code$ java PalindromeChecker.java RaceCar
The string {RaceCar} is a Palindrome!! True


Теперь подставим «Mohamed» вместо «RaceCar»:

mohamed_taman:code$ java PalindromeChecker.java Taman
The string {Taman} is a Palindrome!! false

Как видите, можно добавлять в один исходный файл сколько угодно публичных классов. Следите только за тем, чтобы основной метод был определён первым. Интерпретатор будет использовать первый класс в качестве стартовой точки для запуска программы после компилирования кода в памяти.

Реализация адаптера — demoadapter

С точки зрения языка программирования Java, адаптер — это интерфейс, в которомдекларируются методы класса функции, которые планируются вызывать с помощью этогоадаптера. Для класса DemoFunction требуется написать следующий код интерфейса

Код интерфейса DemoAdapter.java

package ru.funsys.demo.avalanche;

import java.util.Hashtable;

import ru.funsys.avalanche.AvalancheRemote;

public interface DemoAdapter {

    public Hashtable<String, String> getInfo() throws AvalancheRemote;

}

Следуют отметить, что все методы созданного интерфейса DemoAdapter должныобязательно содержать декларирование исключения AvalancheRemote. Даннаяреализация чем то напоминает реализацию RMI в языке программирования Java, но есть иотличия:

Реализация адаптера позволяет вызывать методы функции, где бы она не была опубликована.

Реализация класса приложения — demoapplication

Класс DemoApplication — выполняет вызов метода getInfo() класса DemoFunctionпри помощи адаптера DemoAdapter. Пусть класс DemoApplication преобразуетполученный результат вызова getInfo() в формат TXT или HTML.

Код интерфейса DemoApplication.java

package ru.funsys.demo.avalanche;

import java.util.Enumeration;
import java.util.Hashtable;

import ru.funsys.avalanche.Application;
import ru.funsys.avalanche.AvalancheRemote;

public class DemoApplication extends Application {

    /**
     * Определение поля для хранения экземпляра адаптера
     */
    private DemoAdapter info;

    /**
     * Метод вызова метода адаптера и форматирования полученного результата
     * в текстовый формат или формат HTML в зависимости от параметра метода.
     * 
     * @param html true, если необходим формат HTML, иначе false
     * 
     * @return текс или таблицу HTML с параметрами ОС
     */
    public String getInfo(boolean html) {
        StringBuilder builder = new StringBuilder();
        if (html) {
            builder.append("<table border="1">");
            builder.append("<tr><th>key</th><th>value</th></tr>");
        }
        try {
            // Вызов метода адаптера
            Hashtable<String, String> result = info.getInfo();

            for (Enumeration<String> enumeration = result.keys(); enumeration.hasMoreElements(); ) {
                String key = enumeration.nextElement();
                String value = result.get(key);
                if (html) {
                    builder.append("<tr><td>").append(key).append("</td><td>").append(value).append("</td></tr>");
                } else {
                    builder.append(key).append(": ").append(value).append("rn");
                }
            }
        } catch (AvalancheRemote e) {
            if (html) {
                builder.append("<tr><td>").append("error").append("</td><td>").append(e.getLocalizedMessage()).append("</td></tr>");
            } else {
                builder.append("error").append(": ").append(e.getLocalizedMessage()).append("rn");
            }
        }
        if (html) builder.append("</table>");
        return builder.toString();
    }

}

К классу приложения предъявляются следующие требование: он обязательно должен наследоваться от класса ru.funsys.avalanche.Application.

Поле info определять не обязательно, ссылку на адаптер можно получить по имени, вызвав наследуемый метод getAdapter(String name) класса ru.funsys.avalanche.Application. Имя адаптера задается в конфигурационном файле приложения. Определение поля позволяет сократить объем кодирования.

Реализация класса функции — demofunction

В классе реализации функции нет ничего особенного, это обычный класс Java. В качествекласса функции может быть использован любой класс. Пусть демонстрационный класс функциивозвращает следующую информацию из операционной системы: PID процесса и имя сервера;версия операционной системы; название операционной системы.

Код класса DemoFunction.java

package ru.funsys.demo.avalanche;

import java.lang.management.ManagementFactory;
import java.util.Hashtable;

public class DemoFunction {

    /**
     * Получить информацию из операционной системе.<br>
     * <br>
     *  Из системных свойств выбираются значения<br>
     * <b>os.name</b> - имя ОС,<br>
     * <b>os.version</b> - версия ОС<br>
     * и <b>PID@name</b> - идентификатор процесса и имя сервера 
     * 
     * @return именованный список с параметрами ОС
     */
    public Hashtable<String, String> getInfo() {
        Hashtable<String, String> result = new Hashtable<String, String>();
        result.put("os.name", System.getProperty("os.name"));
        result.put("os.version", System.getProperty("os.version"));
        result.put("PID@name", ManagementFactory.getRuntimeMXBean().getName());
        return result;
    }

}

Создание приложения javafx


В своём приложении для просмотра логов

, я решил просто использовать Java (потому что там в основном довольно низкоуровневый код, а я хотел сконцентрироваться на скорости и малом размере пакета) и IntelliJ в качестве IDE. Я почти решился писать на

, но поддержка Java в IntelliJ оказалась настолько хорошей, так что писать на Java (точнее, позволить IntelliJ делать это за меня — это ближе к истине) стало не такой большой проблемой, чтобы оправдать лишние 0,9 МБ в дистрибутиве.

Я решил не использовать FXML (язык описания GUI для JavaFX на основе XML) или визуальный конструктор UI, потому что интерфейс у программы очень простой.

Итак, посмотрим на какой-нибудь код!

Удаленный вызов функции demofunction

Вызов удаленной функции обычно требует дополнительной специализированной реализации серверной и клиентской части, предварительно выбрав какое то решения из множества существующих технологий, например REST, SOAP или какую то другую.

“Avalanche — application framework for Java” позволяет избежать дополнительного кодирования для вызова методов удаленной функции DemoFunction. Для этого достаточно изменить конфигурацию приложения и опубликовать копию приложения на удаленном сервере.

Необходимо добавить в конфигурационный файл avalanche-config.xml секции:

Заключение

Создание приложения на JavaFX стало довольно приятным опытом. У меня имелась некоторая практика написания JavaFX-приложений для работы несколько лет назад (когда JavaFX находился на ранней стадии развития, что теперь уже осталось в прошлом), так что у меня определённо была некая фора… но я также работал как веб-разработчик и теперь не могу поверить, что кто-то предпочтёт использовать веб-стек вместо такой вменяемой среды как JVM.

Созданное приложение LogFX, на мой взгляд, работает очень хорошо, и оно достигло поставленных целей по скорости работы и быстрому отклику, и в то же время оно хорошо выглядит на всех операционных системах без внесения изменений. Пожалуйста, посмотрите сами и выскажите свой мнение:

Оцените статью
Huawei Devices
Добавить комментарий