Quarkus 框架初始化

考虑到现有 spring-boot 框架内部冗余越来越多, 所以一直想找比较轻量级的网络框架.

在多次从易用性和轻量化考虑之后发现了 Quarkus 号称 极致优化启动速度、内存占用 的框架.

官方网站: Quarkus

主要选择有以下优点:

  • 大厂背书: RedHat 主导开发, 有大厂做代码质量把控

  • GraalVM: 同时支持 JVM 模式和 Native Image 模式, 可以将应用打包成原生二进制

  • 功能完备: 大部分 spring-boot 相关都有对应替代, 按照文档将需求切换过去问题不大

这里先生成基础的 POM 依赖配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
<?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>io.meteorcat.game</groupId>
<artifactId>pico</artifactId>
<version>1.0-SNAPSHOT</version>

<!-- 全局属性 -->
<properties>
<!-- 编译配置 -->
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.release>21</maven.compiler.release>
<compiler-plugin.version>3.14.0</compiler-plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<!-- quarkus 依赖, 我这里采用 3.27 版本, 其他最新版本可以参照官方选择 -->
<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.27.0</quarkus.platform.version>
<skipITs>true</skipITs>
<surefire-plugin.version>3.5.3</surefire-plugin.version>
</properties>

<!-- 全局版本管理 -->
<dependencyManagement>
<dependencies>

<!-- quarkus 依赖 -->
<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>
<!-- Quarkus 核心依赖 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core</artifactId>
</dependency>

<!-- Quarkus 容器依赖 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>

<!-- Quarkus 测试依赖 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>

</dependencies>


<!-- 公共编译配置 -->
<build>

<plugins>
<!-- quarkus-maven 插件配置 -->
<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>

<!-- maven 编译配置 -->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<parameters>true</parameters>
<source>${maven.compiler.release}</source>
<target>${maven.compiler.release}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</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>

这就是基础的 POM 格式文件, 之后就是生成启动的唯一入口:

1
2
3
4
5
6
7
8
# 假设目前已经在项目目录之中, 则需要创建以下文件作为入口启动文件
# 这里以 Linux 为开发平台, touch 其实就是创建文件
touch src/main/java/io/meteorcat/game/PinoApplication.java
# 启动文件取名一般是 '{artifactId}Application' 格式( artifactId 必须为大驼峰格式)
# 最终启动文件路径为 'src/main/java/{groupId}/{}{artifactId}Application.java'
# 我这里 groupId: io.meteorcat.game, artifactId: pion 所以最后路径如下
vim src/main/java/io/meteorcat/game/PionApplication.java
# 注意目录内部如果还有其他带有 `main` 方法的文件就要删除掉

PinoApplication.java 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package io.meteorcat.game;

import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.annotations.QuarkusMain;

/**
* 项目启动入口
* <p>
* 参考文档: <a href="https://cn.quarkus.io/guides/lifecycle">应用程序的初始化和终止</a>
*/
@QuarkusMain
public class PinoApplication {

/**
* 程序启动入口
*
* @param args 参数列表
*/
public static void main(String[] args) {
Quarkus.run(args);
}
}

之后之后就会输出以下最基础的信息, 该项目目前仅仅只有基础功能和容器功能(cdi):

1
2
3
2025-12-08 03:43:07,657 INFO  [io.quarkus] (Quarkus Main Thread) pico 1.0-SNAPSHOT on JVM (powered by Quarkus 3.26.1) started in 0.453s. 
2025-12-08 03:43:07,658 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2025-12-08 03:43:07,659 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi]

Web

spring-boot 主要是 Web 生态的开发简易性, 而 Quarkus 内部也能可以轻松实现, 这里先引入组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 第三方包依赖配置 -->
<dependencies>
<!-- 其他略 -->

<!-- Quarkus Rest 功能 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>

<!-- Quarkus Rest 测试单元依赖 -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
1
2
# 创建并编辑对应外部访问控制器文件放置于 controller 目录之中
vim src/main/java/io/meteorcat/game/controller/HelloController.java

HelloController.java 文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package io.meteorcat.game.controller;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* 声明访问路径 {服务器地址}/hello
*/
@Path("/hello")
public class HelloController {

/**
* 日志句柄
*/
final Logger logger = LoggerFactory.getLogger(HelloController.class);

/**
* 声明默认访问的方法 - GET
*
* @return 显示内容
*/
@GET
@Produces(MediaType.TEXT_PLAIN)
public String index() {
logger.info("Hello World!");
return "Hello World!";
}
}

启动之后访问默认地址显示如下:

1
2
3
4
5
6
7
8
# 默认 Java 的 Web 服务都是 localhost:8080
curl localhost:8080/hello

# 如果要修改默认系统配置可以创建对应的配置文件
# 官方支持 yaml 和 properties 两种配置方式, 但是我个人更喜欢 properties 配置
echo '# 修改访问地址和端口
quarkus.http.host=127.0.0.1
quarkus.http.port=8889' > src/main/resources/application.properties

这里没问题应该会 curl 访问到我们输出的内容, 接下来就是我们日常用的比较多的 JSON 来做交换数据格式, 这里还需要组件支持:

1
2
3
4
5
6
7
8
9
10
<!-- 第三方包依赖配置 -->
<dependencies>
<!-- 其他略 -->

<!-- Quarkus RestJSON 依赖, 引入底层 jackson 处理 JSON -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
</dependencies>

之后我们需要创建两个组件用于格式化我们的统一JSON和拦截全局错误转化为我们自定义的格式:

  • 全局错误拦截器(ApiResponse): src/main/java/io/meteorcat/game/utils/ApiResponse.java

  • 全局格式化返回(ClientErrorExceptionMapper): src/main/java/io/meteorcat/game/config/ClientErrorExceptionMapper.java

ApiResponse 文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package io.meteorcat.game.utils;

import jakarta.ws.rs.core.Response;

import java.util.Collections;
import java.util.Objects;

/**
* API统一响应
* 这里采用 Java11 之后新特性 record 节约编写 getter/setter
*
* @param code 错误码, 默认响应 HTTP 错误码
* @param message 错误消息, 默认响应 HTTP 对应错误消息
* @param data 返回数据, 默认返回空 Mao, data 数据最好是采用映射表 {list:{},pos:0,total:0}, 方便内部嵌套多层数据列表
*/
public record ApiResponse<T>(
int code,
String message,
T data
) {

/**
* 直接包装成 Quarks 组件内部依赖的响应对象
*/
public static <T> Response response(int status, int code, String message, T data) {
return Response
.status(status)
.entity(new ApiResponse<>(code, message, Objects.isNull(data) ? Collections.EMPTY_MAP : data))
.build();
}

/**
* 直接包装成 Quarks 组件内部依赖的响应对象
*/
public static <T> Response response(int code, String message, T data) {
return response(code, code, message, data);
}

/**
* 成功响应
*/
public static <T> Response success(T data) {
final Response.Status status = Response.Status.OK;
return response(status.getStatusCode(), status.getReasonPhrase(), data);
}

/**
* 异常响应, 采用默认 400 错误
*/
public static Response fail(int code, String message) {
return response(Response.Status.BAD_REQUEST.getStatusCode(), code, message, null);
}

/**
* 自定义响应码的返回
*/
public static Response fail(int status, int code, String message) {
return response(status, code, message, null);
}

/**
* 自定义响应码的返回 - 直接沿用所有 400 错误
*/
public static Response fail(String message) {
int status = Response.Status.BAD_REQUEST.getStatusCode();
return response(status, status, message, null);
}
}

ClientErrorExceptionMapper 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package io.meteorcat.game.config;

import io.meteorcat.game.utils.ApiResponse;
import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;

/**
* 客户端通用 HTTP 异常拦截
* '@Provider' 注解代表启动的时候自动加载到系统内部
*/
@Provider
public class ClientErrorExceptionMapper implements ExceptionMapper<ClientErrorException> {

/**
* 拦截处理回调
*/
@Override
public Response toResponse(ClientErrorException e) {
Response response = e.getResponse(); // 获取默认错误响应重新构建成我们的自定义JSON
return ApiResponse.fail(
response.getStatus(),
response.getStatusInfo().getReasonPhrase()
);
}
}

自定义的测试控制器文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package io.meteorcat.game.controller;

import io.meteorcat.game.utils.ApiResponse;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import java.util.Map;

/**
* 获取服务器时间戳
*/
@Path("/timestamp")
@Produces(MediaType.APPLICATION_JSON) // 默认返回 JSON 响应
@Consumes(MediaType.APPLICATION_JSON) // 默认接受 JSON 提交
public class TimestampController {

/**
* 默认访问方法
*/
@GET
public Response index() {
return ApiResponse.success(Map.of(
"timestamp", System.currentTimeMillis()
));
}
}

启动之后访问对应的请求地址就能查看到具体的 JSON 返回:

1
2
3
4
5
6
7
# 访问默认时间戳接口 - 返回 200 和 数据内容
curl http://127.0.0.1:8889/timestamp
# {"code":200,"message":"OK","data":{"timestamp":1765168872610}}

# 访问不存在的接口 - 返回 404
curl http://127.0.0.1:8889/not-found
# {"code":404,"message":"Not Found","data":{}}

注意: 错误拦截最好追加个全局通用的运行时崩溃拦截, 在崩溃的时候写入日志并且格式化成自定义 JSON 返回