14.6 编译及打包

你已经掌握了如何建立项目和声明模块,接下来学习如何使用像Maven这样的构建工具编译你的项目。本节假设你已经对Maven有一定的了解,它是Java生态圈里使用最广泛的构建工具之一。除了Maven之外,另一个同样很流行的构建工具是Gradle,如果你从未听说过,建议你抽时间了解一下。

构建的第一步,你需要为每一个模块创建一个pom.xml文件。实际上,每一个模块都需要能单独编译,这样其自身才能成为一个独立的项目。你还需要为所有模块的上层父项目创建一个pom.xml,用于协调整个项目的构建。这样一来,项目的整体结构就如下所示:

|─ pom.xml

|─ expenses.application

  1. |─ pom.xml
  2. |─ src
  3. |─ main
  4. |─ java
  5. |─ module-info.java
  6. |─ com
  7. |─ example
  8. |─ expenses
  9. |─ application
  10. |─ ExpensesApplication.java

|─ expenses.readers

  1. |─ pom.xml
  2. |─ src
  3. |─ main
  4. |─ java
  5. |─ module-info.java
  6. |─ com
  7. |─ example
  8. |─ expenses
  9. |─ readers
  10. |─ Reader.java
  11. |─ file
  12. |─ FileReader.java
  13. |─ http
  14. |─ HttpReader.java

请注意这三个新创建的pom.xml以及Maven项目的目录结构。项目的模块描述符(module-info.java)应置于src/main/java目录之中。Maven会设置javac,让其匹配对应模块的源码路径。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
  5. http://maven.apache.org/xsd/maven-4.0.0.xsd">
  6. <modelVersion>4.0.0</modelVersion>
  7. <groupId>com.example</groupId>
  8. <artifactId>expenses.readers</artifactId>
  9. <version>1.0</version>
  10. <packaging>jar</packaging>
  11. <parent>
  12. <groupId>com.example</groupId>
  13. <artifactId>expenses</artifactId>
  14. <version>1.0</version>
  15. </parent>
  16. </project>

有一点非常重要,你需要在代码中显式地指定构建过程中使用的父模块。父模块在这个例子中是ID为expenses的构件。正如很快就能看到的例子所示,你需要在pom.xml中显式地定义父模块。

接下来,你需要指定模块expenses.application对应的pom.xml。这个文件与之前的那个pom.xml很类似,不过你还需要为其添加对expenses.readers项目的依赖,因为ExpensesApplication需要使用它提供的类和接口进行编译:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
  5. http://maven.apache.org/xsd/maven-4.0.0.xsd">
  6. <modelVersion>4.0.0</modelVersion>
  7. <groupId>com.example</groupId>
  8. <artifactId>expenses.application</artifactId>
  9. <version>1.0</version>
  10. <packaging>jar</packaging>
  11. <parent>
  12. <groupId>com.example</groupId>
  13. <artifactId>expenses</artifactId>
  14. <version>1.0</version>
  15. </parent>
  16. <dependencies>
  17. <dependency>
  18. <groupId>com.example</groupId>
  19. <artifactId>expenses.readers</artifactId>
  20. <version>1.0</version>
  21. </dependency>
  22. </dependencies>
  23. </project>

至此expenses.applicationexpenses.readers都有了各自的pom.xml,你可以着手建立指导构建流程的全局pom.xml了。Maven通过一个特殊的XML元素支持一个项目包含多个Maven模块的情况,这个定义了对应的子构件ID。下面是完整的定义,它包含了前面提到的两个子模块expenses.applicationexpenses.readers

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
  5. http://maven.apache.org/xsd/maven-4.0.0.xsd">
  6. <modelVersion>4.0.0</modelVersion>
  7. <groupId>com.example</groupId>
  8. <artifactId>expenses</artifactId>
  9. <packaging>pom</packaging>
  10. <version>1.0</version>
  11. <modules>
  12. <module>expenses.application</module>
  13. <module>expenses.readers</module>
  14. </modules>
  15. <build>
  16. <pluginManagement>
  17. <plugins>
  18. <plugin>
  19. <groupId>org.apache.maven.plugins</groupId>
  20. <artifactId>maven-compiler-plugin</artifactId>
  21. <version>3.7.0</version>
  22. <configuration>
  23. <source>9</source>
  24. <target>9</target>
  25. </configuration>
  26. </plugin>
  27. </plugins>
  28. </pluginManagement>
  29. </build>
  30. </project>

恭喜你!现在可以执行mvn clean package为你项目中的模块生成JAR包了。运行这条命令会产生下面的文件:

  1. ./expenses.application/target/expenses.application-1.0.jar
  2. ./expenses.readers/target/expenses.readers-1.0.jar

把这两个JAR文件添加到模块路径中,你就可以运行你的模块应用了,如下所示:

  1. java --module-path \
  2. ./expenses.application/target/expenses.application-1.0.jar:\
  3. ./expenses.readers/target/expenses.readers-1.0.jar \
  4. --module \
  5. expenses.application/com.example.expenses.application.ExpensesApplication

至此,你已经学习并创建了模块,知道如何利用requires引用java.base。然而现实生产环境中,软件依赖更多的往往是外部的模块和库。如果遗留代码库没有使用module-info.java,又该如何处理呢?下一节会通过介绍自动模块(automatic module)来回答这些问题。