Статьи

Универсальный Maven для обучения Java-приложений для настольных компьютеров


колледже Доусон в Монреале, Канада. Он также является программным консультантом и по совместительству преподавателем в Школе расширенного обучения при Институте вычислительной техники Университета Конкордия . Он ведет блог на omniprogrammer.com и пишет в Твиттере @omniprof . Его регулярные колонки о NetBeans в образовании перечислены здесь .

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

  • Должен быть независимым от какой-либо конкретной IDE

  • Используется для любого типа настольного приложения в Java 8, будь то Console, Swing или JavaFX

  • Представляет лучшую практику для Maven в среде преподавания / обучения

Последний момент важно иметь в виду. Есть много способов добиться подобных результатов в Maven. Мое определение лучшей практики состоит в том, что студент должен иметь возможность легко понять, что делает пом. Это, вероятно, не всегда лучший подход в коммерческой производственной среде. Если студент может понять мою лучшую практику, он должен понимать более сложные системы, с которыми он может столкнуться в отрасли.

Давайте начнем с того, что эти первые строки совпадают с тем, что создал архетип.

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.kenfogel</groupId>
    <artifactId>FXMavenArticle</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>FXMavenArticle</name>

Информация о проекте

    <description>Example of super pom for Java application development</description>

    <developers>
        <developer>
            <id>9999999</id>
            <name>Student Name</name>
            <email>[email protected]</email>
        </developer>
    </developers>
	
    <organization>
        <name>Dawson College</name>
    </organization>

Здесь мы видим три раздела. Первый, <description>, позволяет вам написать краткое описание программы, которая использует этот pom.

Второй раздел, <developers>, определяет программистов, вовлеченных в проект. В документации по Maven обсуждается различие между тем, кто вносит код, и тем, кто отвечает за фиксацию кода в хранилище, и для этой цели есть тег <role>. В школьной ситуации ученик выполняет обе роли. <Id> — это тег, используемый для создания уникального идентификатора для раздела, и в школьной среде ученик использует свой идентификационный номер ученика. Далее идет <имя>, а затем <электронная почта>.

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

свойства

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- class that has the main method -->
        <mainClass>com.kenfogel.fxmavenarticle.MainApp</mainClass>
        <!-- name appended to standard jar name to indicate shaded/executable version -->
        <shadedClassifierName>executable</shadedClassifierName>
    </properties>

В разделе <properties> есть одно новое свойство. Мы настраиваем Maven для создания исполняемого файла JAR, который вы можете дважды щелкнуть для запуска, если на компьютере установлена ​​JRE. Плагин, который делает это, хочет добавить текст ‘-shaded’ к имени файла JAR. Тег <shadedClassifierName> позволяет нам выбирать наш собственный текст.

зависимости

    <dependencies>
        <!-- Everyone needs a logger, this is my preference -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.7</version>
        </dependency>

        <!-- Unit testing is mandatory -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <!-- only to be used during test phase -->
            <!-- will not be included in executable jar -->
            <scope>test</scope>
        </dependency>
    </dependencies>

До того, как я принял Maven в классе, мои ученики часто сталкивались с проблемой приобретения необходимых библиотек для своих проектов и правильного размещения их в пути сборки. Еще больше раздражало то, что я загружал студенческий проект на мой компьютер, чтобы оценить его, но обнаружил, что он указывает на библиотеки, хранящиеся на персональном компьютере студента, обычно в другом проекте, к которому у меня не было доступа. Трудно оставаться объективным, когда ученики не следуют инструкциям, а затем заставляют меня тратить время на перенастройку своего проекта.

Maven зависимости устраняет все это. Библиотеки, перечисленные в разделе <dependencies>, будут автоматически добавлены в сборку проекта. Я знаю, что они могут отправлять только проекты, которые работают в любой системе. Документация для большинства библиотек и фреймворков включает в себя раздел зависимостей, который вам нужно будет добавить в ваш pom. Вы также можете найти нужный вам XML-файл зависимостей на maven.org.

У моего помпа есть две зависимости, которые я считаю необходимыми в своем классе. Первый — для JUnit 4 для модульного тестирования. Исходный файл pom использовал плагин maven-dependency-plugin для исключения тестовых библиотек из окончательного файла JAR. Вы можете достичь того же результата, объявив <scope> библиотеки как test.

Вторая зависимость для регистратора. Мой фаворит недели — это slf4j, который действительно является фасадом для log4j. Я напишу статью об этом. Достаточно сказать, что System.out.println — это не способ отображения информации во время работы программы.

Сложение

Теперь мы переходим к разделу <build>, который отвечает за компиляцию, сборку и выполнение проекта.

<build>
        <!-- Goals may be set in the IDE or the pom -->
        <!-- IDE goals override the defaultGoal -->
        <defaultGoal>clean compile package exec:java</defaultGoal>

Тег <defaultGoal> используется для определения целей Maven. Цели — это действия, которые Maven должен выполнить. Большинство IDE позволяют вам объявлять цели в своей команде Run. Если вы сделаете это, то это переопределит то, что вы видите здесь. Я использую этот тег, чтобы показать студентам, каковы общие действия Maven.

Очистка удаляет все файлы классов и файлы JAR.

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

Под пакетом подразумевается создание файла JAR. Обычно библиотеки, перечисленные в разделе зависимостей, не включены в пакет. Плагин, который мы будем использовать для создания исполняемого JAR-файла, добавит зависимости в пакет.

Finally comes exec that means to execute the JAR. There are two flavours of exec, one is exec:java and the other is exec:exec. You must use one of these. The exec:exec runs the package as if it were being run from the command line. This means that it will run in its own instance of the JVM. The exec:java runs the package within the same JVM as the IDE. I prefer exec:java because logging and System.out appear in the IDE’s console while if you use exec:exec you do not see this information until the execution ends.

The next plugin deals with the compiler.

        <plugins>
            <plugin>
                <!-- Select the version of the compiler and binary file -->
                <!-- format of the classfiles -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <!-- sometimes the IDE does not reveal all the important warning -->
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>

In the original pom, the compiler version was 1.7. In Java 1.7 the JavaFX library was not in the classpath and so it had to appear in the compiler plugin. Java 8 has JavaFX in the classpath. The three tabs, <compilerArgument>, <showWarnings> and <showDeprecation>, are used to have the compiler display any warnings it detects. NetBeans already shows us warnings right in the editor however this is configurable and not all the warning I like are turn on. The Xlint is a message to the compiler to display any issues it finds. Errors will always be displayed but we have to explicitly ask for warnings.

If you ever try to use the Date API, you will discover that much of it is deprecated. I describe this as an API that was extensively used before someone noticed that it sucked. Being deprecated means that it may disappear from a newer version of Java so stop using it. So far, in my experience, nothing that is deprecated has been dropped but since it sucks you should use the replacement. See the JavaDocs for the API to learn about the replacement. Sometimes the IDE will be able to tell you what you should be using.

Shade

The next plugin creates an executable jar.

            <plugin>
                <!-- shade creates an executable jar with the main class -->
                <!-- showing in the manifest file -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <shadedArtifactAttached>true</shadedArtifactAttached>
                            <shadedClassifierName>
                                ${shadedClassifierName}
                            </shadedClassifierName> 
                            <createDependencyReducedPom>
                                false
                            </createDependencyReducedPom>
                            <transformers>
                                <transformer implementation=
               "org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>${mainClass}</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

В Maven мы называем исполняемый файл JAR затененным. Иногда также называется Uber-Jar. Этот плагин собирает все необходимые файлы в один JAR. JAR-файлы содержат текстовый файл с именем MANIFEST.MF. Плагин Shade добавляет имя <mainClass> в этот файл. <ShadedClassifierName> — это то, что вы хотите использовать вместо ‘-shaded’. Значение <createDependencyReducedPom>, установленное в false, не позволяет плагину создавать нечто, называемое уменьшенным pom. Я должен признать, что я не уверен, какова его цель, поэтому я не позволяю этому быть созданным, и мне не нужно говорить своим ученикам, что я не знаю, что это такое (хотя после прочтения этого они будут знать, что я не понимаю не знаю) <Transformer> — это объект в Maven, который создает затененный JAR.

Посмотрите на следующее изображение экрана, которое показывает разницу между обычным JAR и затененным JAR.

В оригинальном pom создание исполняемого JAR-файла осуществлялось внешней программой javafxpackager. Эта программа не требуется в Java 8 для создания исполняемой программы JavaFx. У него есть интересная новая возможность в Java 8, которую я еще не пробовал. Он способен создавать исполняемые файлы, содержащие JRE. Эти файлы могут иметь расширение .exe для систем Windows. Он также может создавать установочные пакеты, MSI для Windows и DMG для Mac. Это может возродить интерес к созданию и распространению настольных приложений Java.

казнить

Следующий плагин позволяет Maven выполнять программу.

            <plugin>
                <!-- executes the program -->
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.3.1</version>
                <executions>
                    <execution>
                        <id>default-cli</id>
                        <goals>
                            <!-- java runs the code from the main class -->
                            <goal>java</goal> 
                        </goals>
                        <configuration>
                            <!-- used by java goal -->
                            <mainClass>${mainClass}</mainClass>
                            <!-- used by exec goal -->
                            <executable>${java.home}/bin/java</executable>
                            <commandlineArgs>
                                -jar ${project.build.directory}/${project.build.finalName}-
                                ${shadedClassifierName}.jar
                            </commandlineArgs>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

<Цель> может быть либо exec, либо java, я предпочитаю java. В конфигурации есть теги для обоих возможных методов выполнения. Это необходимо, потому что если вы используете команду NetBeans Run в проекте, а не выполняете Maven, то NetBeans будет хотеть теги, связанные с целью exec: exc.

Тег <commandlineArgs> содержит текст, который используется для exec: exec. У определенной IDE, которая почти рифмуется с «миксом торта», есть плохая привычка разбивать текст на пробел после «–jar» при форматировании xml (NetBeans этого не делает). Это вызывает ошибку при использовании цели exec: exec.

безошибочный

Наконец, приходит верный плагин.

            <plugin>
                <!-- Executes JUnit tests and writes the results as an xml and txt file -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.17</version>
            </plugin>

Результаты тестов JUnit записываются в консоль и в файл. Это упрощенная версия по сравнению с оригинальным pom, потому что JavaFX находится в classpath Java 8.

Моя последняя и намного более короткая статья в этой серии расскажет о том, как вы можете использовать проекты Maven в NetBeans.

Вот полный pom.xml. Пожалуйста, дайте мне знать, если вы видите, что я сделал что-то не так. Живя в моей башне из слоновой кости (на самом деле это просто прогулка), я иногда делаю ошибочные предположения.

Pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.kenfogel</groupId>
    <artifactId>FXMavenArticle</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>FXMavenArticle</name>

    <description>Example of super pom for Java application development</description>

    <developers>
        <developer>
            <id>9999999</id>
            <name>Student Name</name>
            <email>[email protected]</email>
        </developer>
    </developers>
	
    <organization>
        <name>Dawson College</name>
    </organization>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- class that has the main method -->
        <mainClass>com.kenfogel.fxmavenarticle.MainApp</mainClass>
        <!-- name appended to standard jar name to indicate shaded/executable version -->
        <shadedClassifierName>executable</shadedClassifierName>
    </properties>

    <dependencies>
        <!-- Everyone needs a logger, this is my preference -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.7</version>
        </dependency>

        <!-- Unit testing is mandatory -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <!-- only to be used during test phase -->
            <!-- will not be included in executable jar -->
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <!-- Goals may be set in the IDE or the pom -->
        <!-- IDE goals override the defaultGoal -->
        <defaultGoal>clean compile package exec:java</defaultGoal> 
        <plugins>
            <plugin>
                <!-- Select the version of the compiler and binary file -->
                <!-- format of the classfiles -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <!-- sometimes the IDE does not reveal all the important warning -->
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>

            <plugin>
                <!-- shade creates an executable jar with the main class -->
                <!-- showing in the manifest file -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <shadedArtifactAttached>true</shadedArtifactAttached>
                            <shadedClassifierName>${shadedClassifierName}</shadedClassifierName> 
                            <createDependencyReducedPom>false</createDependencyReducedPom>
                            <transformers>
                                <transformer
                                    implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>${mainClass}</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <!-- executes the program -->
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.3.1</version>
                <executions>
                    <execution>
                        <id>default-cli</id>
                        <goals>
                            <!-- java runs the code from the main class -->
                            <goal>java</goal> 
                        </goals>
                        <configuration>
                            <!-- used by java goal -->
                            <mainClass>${mainClass}</mainClass>
                            <!-- used by exec goal -->
                            <executable>${java.home}/bin/java</executable>
                            <commandlineArgs>-jar ${project.build.directory}/${project.build.finalName}-${shadedClassifierName}.jar</commandlineArgs>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <!-- Executes JUnit tests and writes the results as an xml and txt file -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.17</version>
            </plugin>

        </plugins>
    </build>
</project>