构建多模块的Quarkus项目
按步骤写完的最后的模版项目在这: MaidSG/my-quarkus-app (github.com)
构建步骤
1 创建父模块(根模块)
在安装了Quarkus CLI后通过Quarkus CLI的quarkus create app创建项目
quarkus create app org.acme:my-quarkus-app --no-code
使用--no-code生成一个空模版文件,详细的配置可以使用quarkus create app -help查看创建项目的详细配置,如引用什么依赖,包管理工具,详细配置;


创建好后,用idea打开项目,修改pom文件,如添加packaging标签为pom、调整打包运用的插件 补充jandex-maven-plugin插件等;
调整完后的pom文件内容:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.acme</groupId>
<artifactId>my-quarkus-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<compiler-plugin.version>3.13.0</compiler-plugin.version>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.13.2</quarkus.platform.version>
<skipITs>true</skipITs>
<surefire-plugin.version>3.2.5</surefire-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<!-- <goal>generate-code-tests</goal>-->
<goal>native-image-agent</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.smallrye</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>make-index</id>
<goals>
<goal>jandex</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<properties>
<skipITs>false</skipITs>
<quarkus.native.enabled>true</quarkus.native.enabled>
</properties>
</profile>
</profiles>
</project>
官方文档指出Quarkus默认情况下不会对其他模块进行CDI Bean依赖项注入,对于多模块项目来说,使模块之间能够正常实现Spring的依赖注入、上下文管理和生命周期管理的最佳方法是添加jandex-maven-plugin,父模块配置上插件后,模块间就可以自由进行索引。

2 创建子模块
在父模块的目录中,使用 Quarkus CLI 创建子模块。可以根据需求创建多个子模块,每个子模块都将作为父模块的子项目。
如果使用的是idea的话,可以直接使用idea自带的创建quarkus模块

或者使用Quarkus CLI 命令: -x 参数为指定模块携带的依赖。 创建一个用于业务逻辑的 core 子模块:
cd my-quarkus-app
quarkus create app org.acme:my-quarkus-app-core -x resteasy-reactive
创建另一个用于数据访问的 data 子模块:
quarkus create app org.acme:my-quarkus-app-data -x hibernate-orm-panache
子模块中添加父pom坐标、父模块的pom中添加modules

调整完后的子模块中pom文件内容:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.acme</groupId>
<artifactId>my-quarkus-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<groupId>org.acme</groupId>
<artifactId>my-quarkus-app-core</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables> <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path> <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<properties>
<skipITs>false</skipITs>
<quarkus.native.enabled>true</quarkus.native.enabled>
</properties>
</profile>
</profiles>
</project>
3 打包构建
传统spring启动类方式打包:
默认的 Maven package 命令./mvnw clean package生成的 JAR 文件可能不包含所有运行时依赖项。特别是对于 Quarkus 多模块项目,因为Quarkus默认是不带启动类的。
所以想用传统java类打包的方式的话,首先得给模块加启动类:
加完以后再执行打包命令:
./mvnw clean package
这个时候target下生成的jar包就是可以正常用于部署的了。不过不推荐使用这种方法,显然官方的脚手架里生成的项目都没有启动类,这是因为Quarkus 的默认打包类型 fast-jar
fast-jar 打包方式会将应用程序的多个组件和依赖项分别打包到 quarkus-app 目录下。如果在部署或传输过程中遗漏了某些文件,整个应用程序可能无法正常启动或运行。因此,务必要确保 quarkus-app 目录完整无误。
可以使用以下命令来运行生成的应用程序:
java -jar target/quarkus-app/quarkus-run.jar
优点是使用 fast-jar 打包会生成一个启动速度稍快且内存占用稍低的构件,相比于传统的 Quarkus jar 文件有更好的性能。而且空模版生成的quarkus-app文件夹总共才16.4M。
缺点也显而易见必须保证 quarkus-app 目录的完整性,否则应用程序无法正常运行。
打包为 Fat JAR
另外一种办法就是将一个模块所有涉及的按maven依赖pom文件将所有依赖项都打包到一个单独的 JAR 文件中,这种做法也可以获得可执行的jar包
./mvnw clean package -Dquarkus.package.type=uber-jar
这个命令会生成一个包含所有依赖项的 “fat JAR” 文件,该文件也会被放置在各模块的 target 目录下。

打包为原生镜像
Quarkus 的一个显著特点是支持将应用程序打包为原生镜像,使用 GraalVM 进行 AOT(Ahead-of-Time)编译,从而生成启动速度极快的可执行文件。
我参照的是quarkus官网提供的文档:Building a Native Executable - Quarkus
打包原生镜像必备条件为GraalVM for JDK 21
我的是mac环境,安装文档中下载的jdk:
按照官网和有关GraalVM配置的参数后,执行maven install开始构建原生镜像:
./mvnw install -Dnative
GraalVM构建原生镜像的速度取决于cpu核心数和内存我的小pro下构建简单的空模版大概花了一分钟。
构建完毕后,就可以得到原生的可执行文件了:
原生镜像的启动速度效果拔群!

将模块打包为原生镜像并部署到docker容器中
在使用原生镜像打包的基础上,Quarkus提供了容器镜像扩展,为本地可执行文件打包容器镜像基本上只需执行一个简单的命令:
./mvnw package -Dnative -Dquarkus.native.container-build=true -Dquarkus.container-image.build=true
- 参数解释: quarkus.native.container-build=true:允许在没有安装GraalVM的情况下创建Linux可执行文件(仅在本地未安装GraalVM或本地操作系统不是Linux时才需要)。 quarkus.container-image.build=true:指示Quarkus使用最终的应用程序工件(即本例中的本地可执行文件)来创建容器镜像。
打包好后,执行docker build构建镜像:
docker run -i --rm -p 8080:8080 quarkus-quickstart/getting-started
如果你和我一样在idea命令行中启动的话,构建原生镜像使用的cpu、内存资源为系统给idea分配的资源。
使用docker run启动镜像:
相比直接在系统中执行原生镜像,放到docker容器中的原生quarkus空模版容器的启动时间是:22ms!
概念解释
CDI Bean 定义:CDI(Contexts and Dependency Injection)Bean 是由 CDI 框架管理的 Java 对象,它支持依赖注入和上下文管理。在 Quarkus 和 Jakarta EE 中,CDI Bean 被广泛用于将应用的不同组件(如服务、DAO 等)进行解耦。在默认情况下,Quarkus 不会自动发现和管理位于不同模块中的 CDI Beans
• 作用:通过 CDI,可以将依赖项(如服务类)自动注入到其他类中,而无需手动创建实例,从而提高代码的可维护性和测试性。
jandex-maven-plugin 定义:这是一个 Maven 插件,用于在构建过程中为 Java 项目生成 Jandex 索引文件。Jandex 索引包含了项目中所有类和注解的元数据信息。
• 作用:当 Quarkus 需要在多个模块之间共享 CDI Bean 时,jandex-maven-plugin 可以帮助生成必要的索引文件,以便 Quarkus 能够发现并使用这些 Bean。
索引 (Indexing) 定义:索引是一种预处理操作,通过扫描项目中的类和注解信息,并生成一个用于快速查找和访问的结构化数据文件(通常是 .idx 文件)。确保使用 jandex-maven-plugin 插件生成每个模块的 Jandex 索引文件,以便 Quarkus 能够发现 CDI Beans。
• 作用:索引可以显著提高 Quarkus 在运行时查找 CDI Bean 和注解的速度,从而缩短应用启动时间和提高性能。
从理论到实践,我希望能够慢慢从0到1铺就了一条从无到有的Quarkus微服务架构道路,这篇文章中我分享了我在探索多模块项目中构建Quarkus应用程序的基本经验,在实际操作中,我发现确保每个模块的正确配置和打包顺利是成功的关键。
编程是一场没有终点的探险,每个小发现都可能带来无限可能。如果你也是和我一样的探索者,祝你在未来的探索中,代码如诗,应用如歌!
