「Quarkus基础系列」Quarkus 多模块项目实践:从构建到部署的全流程指南

1,100 阅读6分钟

构建多模块的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查看创建项目的详细配置,如引用什么依赖,包管理工具,详细配置; image.png

image.png

创建好后,用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,父模块配置上插件后,模块间就可以自由进行索引。 image.png

2 创建子模块

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

或者使用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 

image.png 子模块中添加父pom坐标、父模块的pom中添加modules image.png

调整完后的子模块中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类打包的方式的话,首先得给模块加启动类: image.png 加完以后再执行打包命令:

./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。 image.png 缺点也显而易见必须保证 quarkus-app 目录的完整性,否则应用程序无法正常运行。

打包为 Fat JAR
另外一种办法就是将一个模块所有涉及的按maven依赖pom文件将所有依赖项都打包到一个单独的 JAR 文件中,这种做法也可以获得可执行的jar包

./mvnw clean package -Dquarkus.package.type=uber-jar

这个命令会生成一个包含所有依赖项的 “fat JAR” 文件,该文件也会被放置在各模块的 target 目录下。 image.png

打包为原生镜像
Quarkus 的一个显著特点是支持将应用程序打包为原生镜像,使用 GraalVM 进行 AOT(Ahead-of-Time)编译,从而生成启动速度极快的可执行文件。 我参照的是quarkus官网提供的文档:Building a Native Executable - Quarkus 打包原生镜像必备条件为GraalVM for JDK 21 我的是mac环境,安装文档中下载的jdk: image.png 按照官网和有关GraalVM配置的参数后,执行maven install开始构建原生镜像:

./mvnw install -Dnative

GraalVM构建原生镜像的速度取决于cpu核心数和内存我的小pro下构建简单的空模版大概花了一分钟。 image.png image.png 构建完毕后,就可以得到原生的可执行文件了: image.png 原生镜像的启动速度效果拔群! image.png

将模块打包为原生镜像并部署到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分配的资源。 image.png 使用docker run启动镜像: image.png相比直接在系统中执行原生镜像,放到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应用程序的基本经验,在实际操作中,我发现确保每个模块的正确配置和打包顺利是成功的关键。

编程是一场没有终点的探险,每个小发现都可能带来无限可能。如果你也是和我一样的探索者,祝你在未来的探索中,代码如诗,应用如歌! image.png