单元1 · Quarkus 快速入门
GraalVM 原生镜像、快速启动、Hello World
- Quarkus:云原生 Java 框架,原生镜像启动毫秒级、低内存。
- 创建:quarkus-maven-plugin create + quarkus:dev 热更新。
- REST:JAX-RS 注解 @Path/@GET 定义接口。
实训1.1 Quarkus 特性
说明 Quarkus 的核心特性(原生镜像、快速启动、低内存)。
Quarkus 为云原生设计:编译期处理、原生镜像启动毫秒级、内存占用低。
// Quarkus 核心优势
// 1. 原生镜像:GraalVM 编译为机器码,启动毫秒级
// 2. 低内存:JVM 模式内存占用也低于传统框架
// 3. 开发体验:live reload 热更新
// 4. 云原生:容器与 K8s 友好,内置健康检查
public class QuarkusIntro {
// 以上为要点
}
实训1.2 创建 Quarkus 项目
使用 Maven 命令创建 Quarkus 项目并运行 dev 模式。
quarkus-maven-plugin 的 create 生成项目骨架,dev 模式支持热更新。
# 创建项目
mvn io.quarkus.platform:quarkus-maven-plugin:3.6.0:create -DprojectGroupId=com.helpme -DprojectArtifactId=quarkus-demo -DclassName=com.helpme.GreetingResource -Dpath=/hello
# 运行开发模式(支持热更新)
cd quarkus-demo
mvn quarkus:dev
实训1.3 第一个 REST 接口
编写 GreetingResource 返回 JSON 数据。
JAX-RS 注解 @Path/@GET 定义接口,Quarkus 自动注册。
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
public class GreetingResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public String hello() {
return "{"message":"Hello Quarkus"}";
}
}
单元2 · 依赖注入 (CDI)
@Inject、@ApplicationScoped、构造器注入
- CDI:@ApplicationScoped Bean + @Inject 注入。
- 作用域:ApplicationScoped 单例、RequestScoped 每请求、Dependent 跟随。
实训2.1 CDI Bean
定义 GreetingService 并注入到资源类。
@ApplicationScoped 单例 Bean;@Inject 字段注入或构造器注入。
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class GreetingService {
public String greet(String name) {
return "Hello, " + name;
}
}
实训2.2 构造器注入
使用构造器注入 GreetingService 到 REST 资源。
构造器注入利于测试,Quarkus 支持 CDI 标准注入。
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
@Path("/greet")
public class GreetResource {
private final GreetingService greetingService;
@Inject
public GreetResource(GreetingService greetingService) {
this.greetingService = greetingService;
}
@GET
@Path("/{name}")
public String greet(@PathParam("name") String name) {
return greetingService.greet(name);
}
}
实训2.3 作用域与生命周期
对比 @ApplicationScoped、@RequestScoped、@Dependent 的作用域。
ApplicationScoped 应用级单例;RequestScoped 每请求;Dependent 跟随注入者。
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.RequestScoped;
import jakarta.enterprise.context.Dependent;
@ApplicationScoped
public class AppCounter { }
@RequestScoped
public class RequestContext { }
@Dependent
public class Helper { }
单元3 · REST 与路由
JAX-RS、@PathParam、@QueryParam、@POST
- 参数:@PathParam/@QueryParam/@DefaultValue。
- JSON:@Consumes/@Produces + 实体参数绑定。
- 状态码:Response.status().entity().build()。
实训3.1 路径与查询参数
编写接收路径参数与查询参数的接口。
@PathParam 路径参数、@QueryParam 查询参数、@DefaultValue 默认值。
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
@Path("/api/users")
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {
@GET
@Path("/{id}")
public String getUser(@PathParam("id") Long id,
@QueryParam("verbose") @DefaultValue("false") boolean verbose) {
return "用户 " + id + ", verbose=" + verbose;
}
}
实训3.2 POST 与 JSON 绑定
接收 JSON 请求体创建用户。
@POST + @Consumes(JSON),@RequestBody 对应 JAX-RS 的实体参数。
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
@Path("/api/users")
public class UserResource {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public User create(User user) {
user.setId(1L);
return user;
}
}
实训3.3 响应状态码
使用 Response 构建带状态码的响应。
Response.status(201).entity(obj) 返回自定义状态码与体。
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/api/users")
public class UserResource {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response create(User user) {
user.setId(100L);
return Response.status(Response.Status.CREATED).entity(user).build();
}
}
单元4 · 配置管理
application.properties、@ConfigProperty、Profile
- 配置:application.properties + @ConfigProperty。
- Profile:application-dev/prod.properties 环境隔离。
- 映射:@ConfigMapping 类型安全配置接口。
实训4.1 读取配置
在 application.properties 配置自定义属性并注入使用。
@ConfigProperty 注入配置值,支持默认值。
# application.properties
app.name=Helpme
app.version=1.0.0
import org.eclipse.microprofile.config.inject.ConfigProperty;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/config")
public class ConfigResource {
@ConfigProperty(name = "app.name")
String appName;
@ConfigProperty(name = "app.version", defaultValue = "0.0.1")
String version;
@GET
public String info() {
return appName + ":" + version;
}
}
实训4.2 Profile 环境配置
创建 dev/prod 不同环境配置文件。
application-dev.properties 与 application-prod.properties,quarkus.profile 切换。
# application.properties
quarkus.profile=dev
# application-dev.properties
app.message=开发环境
# application-prod.properties
app.message=生产环境
实训4.3 配置映射类
使用 @ConfigMapping 将配置映射为类型安全接口。
@ConfigMapping 将一组配置映射为接口,编译期校验。
import io.smallrye.config.ConfigMapping;
@ConfigMapping(prefix = "app")
public interface AppConfig {
String name();
String version();
Server server();
interface Server {
int port();
boolean ssl();
}
}
单元5 · 数据访问 (Panache)
PanacheEntity、Repository、Active Record
- Panache:Active Record 静态方法或 Repository 模式。
- 分页:Page.of(page, size) 分页查询。
实训5.1 Panache 实体
定义 User 实体继承 PanacheEntity 并使用内置 CRUD。
PanacheEntity 提供静态方法 find/list/persist/delete,Active Record 模式。
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;
import java.util.List;
@Entity
public class User extends PanacheEntity {
public String name;
public int age;
// 静态方法直接使用
public static User findByName(String name) {
return find("name", name).firstResult();
}
public static List<User> findOlderThan(int age) {
return list("age > ?1", age);
}
}
实训5.2 Panache Repository
使用 Repository 模式封装数据访问。
PanacheRepository 接口 + @ApplicationScoped 实现,方法与实体类同。
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;
@ApplicationScoped
public class UserRepository implements PanacheRepository<User> {
public List<User> findByName(String name) {
return list("name", name);
}
public long countAdults() {
return count("age >= 18");
}
}
实训5.3 分页查询
实现用户分页查询。
Page 对象封装页码与大小,page 方法返回分页结果。
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
import io.quarkus.panache.common.Page;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;
@ApplicationScoped
public class UserRepository implements PanacheRepository<User> {
public List<User> pageUsers(int page, int size) {
return findAll().page(Page.of(page, size)).list();
}
public long totalPages(int size) {
return findAll().page(Page.ofSize(size)).pageCount();
}
}
单元6 · 响应式编程
Mutiny、Uni、Multi
- Mutiny:Uni 单值、Multi 多值响应式流。
- 组合:combine().all() 并行、flatMap 串行。
实训6.1 Uni 单值异步
使用 Mutiny 的 Uni 返回异步单值。
Uni
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/async")
public class AsyncResource {
@GET
public Uni<String> helloAsync() {
return Uni.createFrom().item(() -> {
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
return "异步 Hello";
});
}
}
实训6.2 Multi 多值流
使用 Multi 返回响应式流数据。
Multi
import io.smallrye.mutiny.Multi;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/stream")
public class StreamResource {
@GET
@Produces(MediaType.SERVER_SENT_EVENTS)
public Multi<String> stream() {
return Multi.createFrom().items("A", "B", "C");
}
}
实训6.3 响应式组合
组合多个异步操作。
combine().all().asTuple 组合并行 Uni;flatMap 串行依赖。
import io.smallrye.mutiny.Uni;
public Uni<String> composed() {
Uni<Integer> a = Uni.createFrom().item(10);
Uni<Integer> b = Uni.createFrom().item(20);
return Uni.combine().all().asTuple(a, b)
.onItem().transform(t -> "sum=" + (t.getItem1() + t.getItem2()));
}
单元7 · 原生镜像
GraalVM、native 构建
- 原生镜像:package -Pnative,毫秒启动低内存。
- 兼容:@RegisterForReflection 注册反射。
实训7.1 构建原生镜像
使用 Maven 构建 Quarkus 原生可执行文件。
package -Pnative 触发 GraalVM 编译,产物无 JVM 依赖。
# 安装 GraalVM 后执行
mvn package -Pnative
# 产物:target/quarkus-demo-1.0.0-runner
# 直接运行(无需 JVM)
./target/quarkus-demo-1.0.0-runner
# 容器镜像构建
mvn package -Pnative -Dquarkus.native.container-build=true
实训7.2 原生兼容注意事项
说明反射、资源文件在原生镜像中的处理。
原生镜像需要注册反射与资源;Quarkus 扩展自动处理大部分场景。
# 反射注册
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
public class User {
public String name;
}
# 资源文件
# src/main/resources 中的资源自动包含
# 动态加载的类需显式注册
实训7.3 性能对比
对比 JVM 模式与原生模式启动时间与内存。
原生模式启动毫秒级、内存降低 5-10 倍;JVM 模式兼容性更好。
// 性能对比
// | 指标 | JVM 模式 | 原生模式 |
// | 启动时间 | 1-3 秒 | < 100 毫秒 |
// | 内存占用 | 200MB+ | 30-60MB |
// | 编译时间 | 秒级 | 分钟级 |
// 选择:需要极致冷启动与低内存用原生;追求构建速度用 JVM
public class PerfNote {
// 以上为对比要点
}
单元8 · 消息与事件
Quarkus Messaging、Kafka、事件总线
- 消息:@Outgoing/@Incoming 连接 Kafka 等消息源。
- 事件:CDI Event + @Observes 进程内事件。
实训8.1 消息生产者
使用 @Outgoing 注解发布消息到 Kafka topic。
@Outgoing("topic") 方法返回值作为消息流发出。
import io.smallrye.mutiny.Multi;
import org.eclipse.microprofile.reactive.messaging.Outgoing;
import jakarta.enterprise.context.ApplicationScoped;
import java.time.Duration;
@ApplicationScoped
public class MessageProducer {
@Outgoing("orders")
public Multi<String> produce() {
return Multi.createFrom().ticks().every(Duration.ofSeconds(1))
.map(t -> "order-" + t);
}
}
实训8.2 消息消费者
使用 @Incoming 注解消费消息。
@Incoming("topic") 方法接收消息流逐条处理。
import org.eclipse.microprofile.reactive.messaging.Incoming;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class MessageConsumer {
@Incoming("orders")
public void consume(String order) {
System.out.println("收到订单:" + order);
}
}
实训8.3 事件总线
使用 CDI Event 实现进程内事件发布订阅。
@Inject Event
import jakarta.enterprise.event.Event;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class OrderService {
@Inject
Event<OrderEvent> orderEvents;
public void createOrder(String no) {
System.out.println("创建订单 " + no);
orderEvents.fire(new OrderEvent(no));
}
}
@ApplicationScoped
public class OrderListener {
public void onOrder(@Observes OrderEvent event) {
System.out.println("收到事件:" + event.orderNo);
}
}
单元9 · 定时任务与调度
@Scheduled、Quartz
- 调度:@Scheduled every/cron 定时执行。
- 并发:concurrentExecution 控制并发策略。
实训9.1 @Scheduled 定时任务
使用 @Scheduled 创建每 10 秒执行的定时任务。
@Scheduled(every = "10s") 固定周期执行;方法需无参。
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped;
import java.time.LocalTime;
@ApplicationScoped
public class SchedulerBean {
@Scheduled(every = "10s")
public void everyTenSeconds() {
System.out.println("定时任务:" + LocalTime.now());
}
}
实训9.2 Cron 表达式
使用 cron 表达式每天 8 点执行任务。
@Scheduled(cron = "0 0 8 * * ?") 按 cron 触发。
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class DailyJob {
@Scheduled(cron = "0 0 8 * * ?")
public void dailyReport() {
System.out.println("生成每日报告");
}
}
实训9.3 并发与隔离
配置定时任务并发策略。
concurrentExecution 控制是否允许并发执行,默认为跳过。
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class SlowJob {
@Scheduled(every = "5s", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
public void slowTask() throws InterruptedException {
Thread.sleep(8000);
System.out.println("慢任务完成");
}
}
单元10 · 安全认证
Security、JWT、RBAC
- JWT:quarkus-smallrye-jwt 认证,publickey 验证。
- 权限:@RolesAllowed 角色控制。
- 生成:Jwt.issuer().subject().sign() 签发令牌。
实训10.1 开启安全与 JWT
引入 quarkus-smallrye-jwt 依赖配置 JWT 认证。
JWT 依赖 + 配置 issuer/audience,@Authenticated 保护接口。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt</artifactId>
</dependency>
# application.properties
mp.jwt.verify.publickey.location=publicKey.pem
mp.jwt.verify.issuer=https://helpme.com
quarkus.smallrye-jwt.enabled=true
实训10.2 角色权限控制
使用 @RolesAllowed 限制接口访问角色。
@RolesAllowed("admin") 指定角色,JWT roles 声明提供权限。
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/admin")
public class AdminResource {
@GET
@RolesAllowed("admin")
public String adminOnly() {
return "管理员可见";
}
}
实训10.3 生成 JWT
使用小型 JWT API 在服务端生成令牌。
Jwt.issuer().subject().groups().expiresIn().sign() 生成签名令牌。
import io.smallrye.jwt.build.Jwt;
import java.util.Set;
public class TokenService {
public String generateToken(String username, Set<String> roles) {
return Jwt.issuer("https://helpme.com")
.subject(username)
.groups(roles)
.expiresIn(3600)
.sign();
}
}
单元11 · 测试
QuarkusTest、REST 测试
- 测试:@QuarkusTest + RestAssured。
- Mock:@InjectMock 替换 Bean 隔离测试。
实训11.1 QuarkusTest 基础
编写 @QuarkusTest 测试 REST 接口。
@QuarkusTest 启动应用上下文;RestAssured 发起 HTTP 断言。
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;
@QuarkusTest
public class GreetingResourceTest {
@Test
public void testHello() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body("message", is("Hello Quarkus"));
}
}
实训11.2 测试注入
在测试中注入服务 Bean 进行单元验证。
@Inject 注入被测 Bean,@QuarkusTest 支持完整 CDI 上下文。
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@QuarkusTest
public class GreetingServiceTest {
@Inject
GreetingService greetingService;
@Test
public void testGreet() {
assertEquals("Hello, Tom", greetingService.greet("Tom"));
}
}
实训11.3 Mock 测试
使用 @InjectMock 模拟依赖进行隔离测试。
@InjectMock 替换 CDI Bean,when/thenReturn 控制行为。
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectMock;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;
@QuarkusTest
public class GreetResourceMockTest {
@InjectMock
GreetingService greetingService;
@Test
public void testMockedGreet() {
Mockito.when(greetingService.greet("Tom")).thenReturn("Mocked Tom");
given()
.when().get("/greet/Tom")
.then().statusCode(200)
.body(is("Mocked Tom"));
}
}
单元12 · 综合项目实训
综合运用所学知识完成项目
- 综合应用:实体 + 仓储 + REST + 异步组合。
实训12.1 待办事项 API - 实体与仓储
定义 Todo 实体与 Panache Repository。
综合运用 Panache 实体映射与仓储模式。
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.Entity;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import java.util.List;
@Entity
public class Todo extends PanacheEntity {
public String title;
public boolean completed;
}
@ApplicationScoped
public class TodoRepository implements PanacheRepository<Todo> {
public List<Todo> listByStatus(boolean completed) {
return list("completed", completed);
}
}
实训12.2 待办事项 API - REST 接口
实现 Todo 增删改查 REST 接口。
综合运用 JAX-RS 注解与 Panache 静态方法。
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
@Path("/api/todos")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class TodoResource {
@GET
public List<Todo> list() {
return Todo.listAll();
}
@POST
public Response create(Todo todo) {
todo.persist();
return Response.status(Response.Status.CREATED).entity(todo).build();
}
@DELETE
@Path("/{id}")
public Response delete(@PathParam("id") Long id) {
Todo.deleteById(id);
return Response.noContent().build();
}
}
实训12.3 待办事项 API - 异步与消息
添加异步统计接口:返回待办总数与已完成数。
综合运用 Uni 异步与 Panache count 方法。
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/api/todos/stats")
public class TodoStatsResource {
@GET
public Uni<String> stats() {
return Uni.combine().all().asTuple(
Uni.createFrom().item(Todo.count()),
Uni.createFrom().item(Todo.count("completed", true)))
.onItem().transform(t ->
"{"total":" + t.getItem1() + ","completed":" + t.getItem2() + "}");
}
}