1 介绍
1.1 定义
01.技术概述
a.基本定义
Micronaut是一个现代化的、基于JVM的全栈框架,专为构建模块化、易测试的微服务和无服务器应用而设计。它由OCI团队开发,于2018年首次发布,核心特点是采用编译时依赖注入和AOP代理,避免运行时反射带来的性能开销。
b.技术定位
a.云原生框架
Micronaut从设计之初就以云原生为目标,优化了启动时间、内存占用和吞吐量,特别适合容器化部署和函数计算场景。
b.编译时处理
通过注解处理器在编译期生成依赖注入和AOP代理代码,消除了运行时反射的性能损耗,实现了快速启动和低内存占用。
c.多语言支持
原生支持Java、Kotlin和Groovy三种JVM语言,开发者可以根据项目需求选择合适的编程语言。
02.核心理念
a.编译优于反射
a.设计思想
Micronaut将传统运行时框架在启动时执行的工作前移至编译期,通过注解处理器生成必要的元数据和代理类,避免运行时的类扫描、反射调用和动态代理生成。
b.性能优势
---
// 传统Spring框架启动过程(运行时反射)
// 1. 类路径扫描
// 2. 反射读取注解
// 3. 动态代理生成
// 4. Bean实例化
// 启动时间: 3-5秒,内存占用: 200MB+
// Micronaut启动过程(编译时处理)
// 1. 编译期生成依赖注入代码
// 2. 编译期生成AOP代理类
// 3. 运行时直接加载生成的类
// 启动时间: <1秒,内存占用: 20MB+
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String[] args) {
// Micronaut应用启动,无需类扫描和反射
Micronaut.run(Application.class, args);
}
}
---
b.响应式优先
a.响应式编程模型
Micronaut内置支持响应式编程,HTTP客户端和服务器默认基于Netty实现,支持Reactive Streams、RxJava、Reactor等响应式库。
b.非阻塞IO
---
// 响应式HTTP客户端示例
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.annotation.Get;
import io.reactivex.Single;
@Client("https://api.example.com")
public interface ExternalApiClient {
// 返回响应式类型,支持非阻塞调用
@Get("/users/{id}")
Single<User> getUser(Long id);
}
---
c.低内存占用
a.优化策略
通过编译时处理、避免反射、减少运行时元数据,Micronaut应用的内存占用远低于传统框架,特别适合容器化和函数计算场景。
b.内存对比
---
// 内存占用对比(Hello World应用)
// Spring Boot 2.x: 堆内存 150-200MB
// Micronaut 3.x: 堆内存 20-30MB
// Micronaut + GraalVM: RSS内存 15-20MB
// 容器资源配置示例
// Docker限制:Micronaut应用可以在64MB内存限制下运行
docker run -m 64m micronaut-app
---
03.技术背景
a.诞生原因
a.Spring框架的局限
Spring框架虽然功能强大,但在微服务和云原生场景下暴露出启动慢、内存占用高、不适合函数计算等问题,这些问题根源于运行时反射机制。
b.云原生需求
容器编排、函数计算、边缘计算等新兴场景对框架提出了快速启动、低内存占用、小体积镜像的严格要求。
b.技术演进
a.版本历史
Micronaut 1.0于2018年发布,随后快速迭代,3.x版本(2021年)引入了重大改进,包括更好的GraalVM支持、增强的数据访问能力和完善的云原生集成。
b.社区生态
---
// Micronaut生态组件
// - Micronaut Data: 编译时数据访问框架
// - Micronaut Security: 安全认证框架
// - Micronaut Kafka: Kafka集成
// - Micronaut gRPC: gRPC支持
// - Micronaut AWS: AWS Lambda和服务集成
// Maven依赖示例
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-runtime</artifactId>
<version>3.8.0</version>
</dependency>
---
1.2 核心概念
01.依赖注入
a.编译时注入原理
a.注解处理器机制
Micronaut使用Java注解处理器(Annotation Processor)在编译期分析代码中的依赖注入注解,生成BeanDefinition类和依赖关系元数据,运行时直接加载这些预生成的类,无需反射扫描。
b.代码生成过程
---
// 源代码:定义一个服务类
import jakarta.inject.Singleton;
@Singleton
public class UserService {
private final UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
}
// 编译时生成的BeanDefinition类(简化版)
// $UserService$Definition.java
public class $UserService$Definition
extends AbstractInitializableBeanDefinition<UserService> {
@Override
protected UserService instantiate(BeanContext context) {
// 直接调用构造器,无需反射
UserRepository repo = context.getBean(UserRepository.class);
return new UserService(repo);
}
}
---
b.JSR-330标准支持
a.标准注解
Micronaut完全支持JSR-330依赖注入标准,使用jakarta.inject包下的标准注解,如@Inject、@Singleton、@Named等,保证代码的可移植性。
b.注解对比
---
// JSR-330标准注解
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import jakarta.inject.Named;
@Singleton
public class OrderService {
@Inject
@Named("primary")
private DataSource dataSource;
}
// Micronaut扩展注解
import io.micronaut.context.annotation.Primary;
import io.micronaut.context.annotation.Requires;
@Singleton
@Primary // Micronaut注解,标记为首选Bean
@Requires(property = "datasource.enabled", value = "true")
public class PrimaryDataSource implements DataSource {
// 实现细节
}
---
02.AOP面向切面编程
a.编译时AOP实现
a.代理生成机制
Micronaut在编译期生成AOP代理类,避免了运行时动态代理的性能开销。支持方法拦截器(MethodInterceptor)和引入通知(Introduction Advice)。
b.拦截器示例
---
// 定义一个方法拦截器
import io.micronaut.aop.MethodInterceptor;
import io.micronaut.aop.MethodInvocationContext;
import jakarta.inject.Singleton;
@Singleton
public class LoggingInterceptor implements MethodInterceptor<Object, Object> {
@Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
String methodName = context.getMethodName();
System.out.println("Before: " + methodName);
Object result = context.proceed();
System.out.println("After: " + methodName);
return result;
}
}
// 使用注解标记需要拦截的方法
import io.micronaut.aop.Around;
import java.lang.annotation.*;
@Around // 声明为环绕通知
@Type(LoggingInterceptor.class) // 指定拦截器类
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Logged {
}
// 应用到业务类
@Singleton
public class ProductService {
@Logged // 该方法会被LoggingInterceptor拦截
public Product findById(Long id) {
return productRepository.findById(id);
}
}
---
b.内置AOP支持
a.事务管理
Micronaut提供@Transactional注解实现声明式事务管理,通过AOP拦截器自动处理事务的开启、提交和回滚。
b.缓存抽象
---
// 缓存注解示例
import io.micronaut.cache.annotation.Cacheable;
import io.micronaut.cache.annotation.CacheInvalidate;
@Singleton
public class UserService {
// 方法结果会被缓存,缓存键为userId
@Cacheable("users")
public User findById(Long userId) {
return userRepository.findById(userId);
}
// 更新用户时使缓存失效
@CacheInvalidate("users")
public void updateUser(User user) {
userRepository.update(user);
}
}
---
03.HTTP服务器与客户端
a.基于Netty的HTTP服务器
a.非阻塞架构
Micronaut内置的HTTP服务器基于Netty实现,采用事件驱动的非阻塞IO模型,可以高效处理大量并发连接。
b.服务器配置
---
// application.yml配置
// micronaut:
// server:
// port: 8080
// netty:
// max-header-size: 16KB
// worker:
// threads: 8 # 工作线程数
// 定义HTTP控制器
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
@Controller("/api")
public class ApiController {
@Get("/hello")
public String hello() {
return "Hello Micronaut";
}
}
---
b.声明式HTTP客户端
a.客户端定义
Micronaut提供声明式HTTP客户端,通过接口和注解定义,编译期生成客户端实现代码,支持同步、异步和响应式调用。
b.客户端示例
---
// 定义HTTP客户端接口
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.annotation.*;
import io.reactivex.Single;
@Client("https://api.github.com")
public interface GithubClient {
// 同步调用
@Get("/users/{username}")
User getUser(String username);
// 异步调用(返回CompletableFuture)
@Get("/repos/{owner}/{repo}")
CompletableFuture<Repository> getRepositoryAsync(
String owner, String repo);
// 响应式调用(返回Single)
@Get("/users/{username}/repos")
Single<List<Repository>> getUserRepos(String username);
// POST请求
@Post("/repos/{owner}/{repo}/issues")
Issue createIssue(String owner, String repo, @Body IssueRequest issue);
}
// 注入并使用客户端
@Singleton
public class GithubService {
private final GithubClient githubClient;
public GithubService(GithubClient githubClient) {
this.githubClient = githubClient;
}
public User getUserInfo(String username) {
return githubClient.getUser(username);
}
}
---
04.配置管理
a.多源配置支持
a.配置来源
Micronaut支持从多种来源加载配置,包括application.yml、环境变量、系统属性、配置中心等,并按优先级合并配置。
b.配置示例
---
// application.yml
// datasource:
// url: jdbc:mysql://localhost:3306/mydb
// username: root
// password: secret
// pool:
// max-size: 10
// 通过@ConfigurationProperties绑定配置
import io.micronaut.context.annotation.ConfigurationProperties;
@ConfigurationProperties("datasource")
public class DataSourceConfig {
private String url;
private String username;
private String password;
private PoolConfig pool;
// getter和setter省略
@ConfigurationProperties("pool")
public static class PoolConfig {
private int maxSize;
// getter和setter省略
}
}
// 注入配置类
@Singleton
public class DatabaseService {
private final DataSourceConfig config;
public DatabaseService(DataSourceConfig config) {
this.config = config;
System.out.println("DB URL: " + config.getUrl());
System.out.println("Pool Size: " + config.getPool().getMaxSize());
}
}
---
b.环境配置
a.多环境支持
Micronaut支持通过application-{env}.yml定义不同环境的配置,启动时通过micronaut.environments指定激活的环境。
b.环境切换
---
// application.yml(默认配置)
// app:
// name: my-service
// version: 1.0
// application-dev.yml(开发环境)
// datasource:
// url: jdbc:h2:mem:devdb
// application-prod.yml(生产环境)
// datasource:
// url: jdbc:mysql://prod-server:3306/proddb
// 启动时指定环境
// java -Dmicronaut.environments=dev -jar app.jar
// java -Dmicronaut.environments=prod -jar app.jar
// 代码中获取当前环境
import io.micronaut.context.env.Environment;
@Singleton
public class EnvironmentService {
private final Environment environment;
public EnvironmentService(Environment environment) {
this.environment = environment;
}
public void printActiveEnvironments() {
Set<String> activeNames = environment.getActiveNames();
System.out.println("Active environments: " + activeNames);
}
}
---
1.3 优缺点
01.优势
a.启动速度快
a.编译时优化
通过编译时依赖注入和AOP代理生成,Micronaut消除了启动时的类扫描、反射分析和动态代理生成,启动速度比Spring Boot快5-10倍。
b.性能测试
---
// 启动时间对比测试(Hello World应用)
// 测试环境:MacBook Pro M1, 16GB RAM, JDK 17
// Spring Boot 2.7
// 启动时间:2.5-3.5秒
// 加载Bean数:150+
// Micronaut 3.8
// 启动时间:0.5-0.8秒
// 加载Bean数:20+
// 性能测试代码
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String[] args) {
long start = System.currentTimeMillis();
Micronaut.run(Application.class, args);
long end = System.currentTimeMillis();
System.out.println("启动耗时: " + (end - start) + "ms");
}
}
---
b.内存占用低
a.运行时内存优化
Micronaut应用的堆内存占用通常只有Spring Boot应用的20-30%,这得益于编译时处理、避免反射和减少运行时元数据。
b.内存监控
---
// 内存占用监控代码
import io.micronaut.scheduling.annotation.Scheduled;
import jakarta.inject.Singleton;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
@Singleton
public class MemoryMonitor {
private final MemoryMXBean memoryBean =
ManagementFactory.getMemoryMXBean();
@Scheduled(fixedRate = "30s")
public void printMemoryUsage() {
long heapUsed = memoryBean.getHeapMemoryUsage().getUsed() / 1024 / 1024;
long heapMax = memoryBean.getHeapMemoryUsage().getMax() / 1024 / 1024;
System.out.printf("堆内存使用: %dMB / %dMB%n", heapUsed, heapMax);
}
}
// 典型内存占用(运行1小时后)
// Spring Boot应用:堆内存 150-200MB
// Micronaut应用:堆内存 30-50MB
// 节省内存:70-80%
---
c.GraalVM原生镜像支持
a.原生编译优势
Micronaut对GraalVM原生镜像提供一流支持,编译时处理的架构使得生成原生镜像更加容易,原生镜像启动时间可达毫秒级,内存占用进一步降低。
b.原生镜像构建
---
// Maven配置(pom.xml片段)
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.9.20</version>
<configuration>
<imageName>micronaut-app</imageName>
<mainClass>com.example.Application</mainClass>
<buildArgs>
<arg>--no-fallback</arg>
<arg>--enable-http</arg>
<arg>--enable-https</arg>
</buildArgs>
</configuration>
</plugin>
// 构建原生镜像
// mvn clean package -Dpackaging=native-image
// 原生镜像性能
// 启动时间:20-50毫秒
// RSS内存:15-25MB
// 可执行文件大小:30-50MB
// 适用场景:函数计算、容器化部署、边缘计算
// Docker镜像大小对比
// JVM应用 + OpenJDK:200-300MB
// 原生镜像 + Alpine:50-80MB
---
d.云原生设计
a.服务发现与配置
Micronaut内置支持Consul、Eureka等服务发现,以及多种配置中心,无需额外依赖即可实现微服务治理。
b.分布式追踪
---
// 集成Zipkin分布式追踪
// application.yml
// tracing:
// zipkin:
// enabled: true
// http:
// url: http://localhost:9411
// 自动生成追踪数据,无需修改业务代码
import io.micronaut.http.annotation.*;
import jakarta.inject.Singleton;
@Controller("/orders")
public class OrderController {
private final OrderService orderService;
private final PaymentClient paymentClient;
public OrderController(OrderService orderService,
PaymentClient paymentClient) {
this.orderService = orderService;
this.paymentClient = paymentClient;
}
@Post
public Order createOrder(@Body OrderRequest request) {
// 自动记录追踪信息,包括跨服务调用
Order order = orderService.create(request);
paymentClient.process(order.getId());
return order;
}
}
---
02.缺点
a.生态不如Spring成熟
a.社区规模
Micronaut相对年轻(2018年发布),社区规模和第三方库数量远少于Spring生态,某些特定场景可能缺少现成的解决方案。
b.学习资源
官方文档完善,但中文资料、书籍和培训课程较少,遇到问题时可参考的案例有限。
b.编译时间增加
a.注解处理开销
编译时依赖注入需要运行注解处理器,对于大型项目,首次编译和全量编译时间会明显增加。
b.编译优化
---
// Maven编译配置优化
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>17</source>
<target>17</target>
<annotationProcessorPaths>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject-java</artifactId>
<version>3.8.0</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amicronaut.processing.incremental=true</arg>
<arg>-Amicronaut.processing.annotations=com.example.*</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
// 增量编译可减少重复编译时间
// 限制注解处理范围可加快编译速度
---
c.动态特性受限
a.反射限制
由于编译时处理的特性,Micronaut对动态反射、动态类加载的支持有限,某些依赖反射的框架可能无法集成。
b.GraalVM限制
原生镜像编译对反射、动态代理、JNI有严格限制,需要提前配置reflection-config.json等元数据文件。
d.学习曲线
a.概念差异
对于熟悉Spring的开发者,需要理解Micronaut编译时处理的思想,转变运行时反射的开发习惯。
b.调试复杂度
编译时生成的代码不直观,调试时需要查看生成的BeanDefinition类,增加了问题排查的难度。
1.4 使用场景
01.微服务架构
a.服务间通信
a.HTTP微服务
Micronaut的快速启动和低内存占用特别适合微服务架构,每个服务可以独立部署、快速扩缩容,降低容器编排的资源开销。
b.服务实现示例
---
// 用户服务
import io.micronaut.http.annotation.*;
import jakarta.inject.Singleton;
@Controller("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@Get("/{id}")
public User getUser(Long id) {
return userService.findById(id);
}
@Post
public User createUser(@Body UserRequest request) {
return userService.create(request);
}
}
// 订单服务调用用户服务
import io.micronaut.http.client.annotation.Client;
@Client("user-service") // 服务名称,通过服务发现解析
public interface UserClient {
@Get("/users/{id}")
User getUser(Long id);
}
@Singleton
public class OrderService {
private final UserClient userClient;
public OrderService(UserClient userClient) {
this.userClient = userClient;
}
public Order createOrder(Long userId, OrderRequest request) {
// 跨服务调用
User user = userClient.getUser(userId);
// 创建订单逻辑
return new Order(user, request);
}
}
---
b.服务注册与发现
a.Consul集成
Micronaut原生支持Consul服务发现,服务启动时自动注册,其他服务通过服务名称进行调用。
b.配置示例
---
// application.yml
// consul:
// client:
// registration:
// enabled: true
// defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
// 服务自动注册到Consul
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String[] args) {
// 启动时自动注册到Consul
Micronaut.run(Application.class, args);
}
}
// 服务健康检查端点
import io.micronaut.management.endpoint.health.HealthEndpoint;
import io.micronaut.management.health.indicator.HealthResult;
@Singleton
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Publisher<HealthResult> getResult() {
// 自定义健康检查逻辑
return Publishers.just(
HealthResult.builder("database").up().build()
);
}
}
---
02.无服务器Serverless应用
a.AWS Lambda函数
a.冷启动优化
Micronaut的快速启动特性使其非常适合AWS Lambda等函数计算场景,可以将冷启动时间从数秒降低到毫秒级。
b.Lambda函数实现
---
// Maven依赖
// <dependency>
// <groupId>io.micronaut.aws</groupId>
// <artifactId>micronaut-function-aws-api-proxy</artifactId>
// </dependency>
// Lambda处理器
import io.micronaut.function.aws.proxy.MicronautLambdaHandler;
public class LambdaHandler extends MicronautLambdaHandler {
// Micronaut自动处理Lambda请求和响应
}
// HTTP控制器(与普通应用相同)
@Controller("/api")
public class ApiController {
@Get("/hello")
public String hello(@QueryValue String name) {
return "Hello " + name + " from Lambda!";
}
}
// 性能指标
// JVM模式冷启动:1-2秒
// GraalVM原生镜像冷启动:50-200毫秒
// 内存占用:128MB即可运行
---
b.Azure Functions集成
a.Azure Functions支持
Micronaut提供Azure Functions适配器,可以将Micronaut应用部署为Azure Functions。
b.实现示例
---
// Maven依赖
// <dependency>
// <groupId>io.micronaut.azure</groupId>
// <artifactId>micronaut-azure-function-http</artifactId>
// </dependency>
// Azure Functions入口
import com.microsoft.azure.functions.*;
import io.micronaut.azure.function.http.AzureHttpFunction;
public class Function extends AzureHttpFunction {
@FunctionName("hello")
public HttpResponseMessage hello(
@HttpTrigger(name = "req", methods = {HttpMethod.GET},
authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage<Optional<String>> request,
ExecutionContext context) {
return super.route(request, context);
}
}
---
03.容器化与Kubernetes部署
a.Docker容器化
a.镜像优化
Micronaut应用可以构建极小的Docker镜像,使用GraalVM原生镜像可以将镜像大小降低到50-80MB。
b.Dockerfile示例
---
// 多阶段构建Dockerfile
FROM ghcr.io/graalvm/graalvm-ce:ol8-java17-22.3.0 AS builder
WORKDIR /app
COPY . .
# 构建原生镜像
RUN ./mvnw package -Dpackaging=native-image
# 最终镜像使用Alpine Linux
FROM alpine:3.17
# 安装运行时依赖
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /app/target/micronaut-app ./app
# 暴露端口
EXPOSE 8080
# 启动应用
ENTRYPOINT ["./app"]
# 镜像大小对比
# JVM应用 + OpenJDK:250-350MB
# 原生镜像 + Alpine:50-80MB
---
b.Kubernetes部署
a.部署配置
Micronaut应用的低资源占用使其可以在Kubernetes集群中高密度���署,降低基础设施成本。
b.Kubernetes配置
---
// k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: micronaut-app
spec:
replicas: 3
selector:
matchLabels:
app: micronaut-app
template:
metadata:
labels:
app: micronaut-app
spec:
containers:
- name: app
image: myregistry/micronaut-app:1.0
ports:
- containerPort: 8080
resources:
requests:
memory: "64Mi" # 最小64MB即可运行
cpu: "100m"
limits:
memory: "128Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/readiness
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: micronaut-service
spec:
selector:
app: micronaut-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
---
04.CLI工具与批处理
a.命令行应用
a.Picocli集成
Micronaut集成Picocli框架,可以快速构建功能强大的命令行工具,启动速度快,适合频繁调用的脚本场景。
b.CLI实现
---
// Maven依赖
// <dependency>
// <groupId>info.picocli</groupId>
// <artifactId>picocli</artifactId>
// </dependency>
// <dependency>
// <groupId>io.micronaut.picocli</groupId>
// <artifactId>micronaut-picocli</artifactId>
// </dependency>
import picocli.CommandLine.*;
import io.micronaut.configuration.picocli.PicocliRunner;
@Command(name = "data-processor",
description = "处理数据文件",
mixinStandardHelpOptions = true)
public class DataProcessorCommand implements Runnable {
@Option(names = {"-i", "--input"},
description = "输入文件路径",
required = true)
private String inputFile;
@Option(names = {"-o", "--output"},
description = "输出文件路径",
required = true)
private String outputFile;
@Option(names = {"-f", "--format"},
description = "输出格式: JSON, XML, CSV",
defaultValue = "JSON")
private String format;
public static void main(String[] args) {
PicocliRunner.run(DataProcessorCommand.class, args);
}
@Override
public void run() {
System.out.println("处理文件: " + inputFile);
System.out.println("输出格式: " + format);
// 实际处理逻辑
}
}
// 使用示例
// java -jar app.jar -i data.csv -o result.json -f JSON
// 启动时间: 200-500ms(JVM模式)
// 启动时间: 20-50ms(GraalVM原生镜像)
---
b.定时任务
a.调度支持
Micronaut内置定时任务调度支持,适合批处理、数据同步等场景。
b.定时任务示例
---
import io.micronaut.scheduling.annotation.Scheduled;
import jakarta.inject.Singleton;
@Singleton
public class DataSyncJob {
private final DataService dataService;
public DataSyncJob(DataService dataService) {
this.dataService = dataService;
}
// 每天凌晨2点执行
@Scheduled(cron = "0 0 2 * * ?")
public void syncData() {
System.out.println("开始数据同步...");
dataService.syncFromRemote();
System.out.println("数据同步完成");
}
// 每30秒执行一次
@Scheduled(fixedRate = "30s")
public void healthCheck() {
dataService.checkHealth();
}
// 初始延迟10秒,之后每1分钟执行
@Scheduled(initialDelay = "10s", fixedDelay = "1m")
public void cleanupCache() {
dataService.clearExpiredCache();
}
}
---
1.5 架构原理
01.编译时处理架构
a.注解处理器流程
a.编译期扫描
Micronaut使用Java标准的注解处理器API(javax.annotation.processing),在编译期扫描源代码中的注解,生成元数据和辅助类。
b.处理流程图
---
// 编译时处理流程
// 1. javac编译器启动
// 2. 加载Micronaut注解处理器
// - BeanDefinitionInjectProcessor(处理依赖注入)
// - TypeElementVisitorProcessor(处理AOP)
// - ConfigurationMetadataProcessor(处理配置)
// 3. 遍历源代码中的注解
// 4. 生成BeanDefinition类
// 5. 生成AOP代理类
// 6. 生成配置元数据
// 7. 输出到target/classes目录
// 注解处理器配置(Maven)
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject-java</artifactId>
</path>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-validation</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
---
b.BeanDefinition生成
a.Bean元数据
编译时为每个Bean生成一个BeanDefinition类,包含Bean的类型、作用域、依赖关系、生命周期回调等信息。
b.生成代码示例
---
// 源代码
import jakarta.inject.Singleton;
@Singleton
public class EmailService {
private final EmailConfig config;
private final SmtpClient smtpClient;
public EmailService(EmailConfig config, SmtpClient smtpClient) {
this.config = config;
this.smtpClient = smtpClient;
}
public void sendEmail(String to, String subject, String body) {
// 发送邮件逻辑
}
}
// 编译后生成的BeanDefinition类(简化版)
// $EmailService$Definition.java
@Generated
public class $EmailService$Definition
extends AbstractInitializableBeanDefinition<EmailService>
implements BeanFactory<EmailService> {
// Bean的类型
@Override
public Class<EmailService> getBeanType() {
return EmailService.class;
}
// Bean的作用域
@Override
public Optional<Class<? extends Annotation>> getScope() {
return Optional.of(Singleton.class);
}
// 实例化Bean
@Override
public EmailService build(BeanResolutionContext context,
BeanContext beanContext,
BeanDefinition<EmailService> definition) {
// 解析依赖
EmailConfig config = (EmailConfig) beanContext
.getBean(EmailConfig.class);
SmtpClient smtpClient = (SmtpClient) beanContext
.getBean(SmtpClient.class);
// 直接调用构造器,无需反射
return new EmailService(config, smtpClient);
}
}
---
02.应用上下文初始化
a.BeanContext加载
a.上下文启动流程
应用启动时,BeanContext加载所有编译期生成的BeanDefinition类,构建依赖关系图,按拓扑顺序实例化Bean。
b.启动代码
---
// Micronaut应用启动
import io.micronaut.context.ApplicationContext;
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String[] args) {
// 启动应用上下文
ApplicationContext context = Micronaut.run(Application.class, args);
// 获取Bean实例
EmailService emailService = context.getBean(EmailService.class);
emailService.sendEmail("[email protected]", "Test", "Hello");
// 关闭上下文
context.close();
}
}
// 内部执行流程
// 1. 加载配置文件(application.yml)
// 2. 扫描并加载所有BeanDefinition类
// 3. 解析Bean依赖关系
// 4. 按依赖顺序实例化Bean
// 5. 执行Bean的@PostConstruct方法
// 6. 发布ApplicationStartedEvent事件
---
b.Bean生命周期
a.生命周期回调
Micronaut支持JSR-250标准的@PostConstruct和@PreDestroy注解,以及Micronaut特有的生命周期接口。
b.生命周期示例
---
import jakarta.inject.Singleton;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import io.micronaut.context.event.ApplicationEventListener;
import io.micronaut.context.event.StartupEvent;
@Singleton
public class DatabaseService implements
ApplicationEventListener<StartupEvent> {
private Connection connection;
// 构造器注入
public DatabaseService(DataSourceConfig config) {
System.out.println("1. 构造器执行");
}
// Bean初始化后执行
@PostConstruct
public void init() {
System.out.println("2. @PostConstruct执行");
connection = createConnection();
}
// 应用启动完成后执行
@Override
public void onApplicationEvent(StartupEvent event) {
System.out.println("3. StartupEvent触发");
// 执行启动后的初始化任务
}
// Bean销毁前执行
@PreDestroy
public void cleanup() {
System.out.println("4. @PreDestroy执行");
if (connection != null) {
connection.close();
}
}
private Connection createConnection() {
// 创建数据库连接
return null;
}
}
---
03.AOP代理实现
a.编译时代理生成
a.子类代理机制
Micronaut在编译期为需要AOP的类生成子类代理,代理类重写被拦截的方法,插入拦截器调用逻辑。
b.代理生成示例
---
// 源代码:定义拦截器
import io.micronaut.aop.MethodInterceptor;
import io.micronaut.aop.MethodInvocationContext;
@Singleton
public class TimingInterceptor implements MethodInterceptor<Object, Object> {
@Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
long start = System.nanoTime();
Object result = context.proceed();
long duration = System.nanoTime() - start;
System.out.println(context.getMethodName() + " 耗时: " +
duration / 1_000_000 + "ms");
return result;
}
}
// 定义拦截注解
import io.micronaut.aop.Around;
import java.lang.annotation.*;
@Around
@Type(TimingInterceptor.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timed {
}
// 应用到服务类
@Singleton
public class ProductService {
@Timed
public Product findById(Long id) {
// 实际业务逻辑
return new Product(id, "Product " + id);
}
}
// 编译后生成的代理类(简化版)
// $ProductService$Intercepted.java
@Generated
public class $ProductService$Intercepted extends ProductService {
private final ExecutableMethod<ProductService, Product> findByIdMethod;
private final Interceptor<Product> timingInterceptor;
@Override
public Product findById(Long id) {
// 构建拦截器链
MethodInvocationContext<ProductService, Product> context =
new MethodInvocationContext<>(this, findByIdMethod,
new Object[]{id});
// 执行拦截器
return timingInterceptor.intercept(context);
}
}
---
b.拦截器链执行
a.拦截器顺序
多个拦截器可以应用到同一个方法,Micronaut按照定义顺序构建拦截器链,依次执行。
b.拦截器链示例
---
// 定义多个拦截器
@Around
@Type(LoggingInterceptor.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface Logged {
}
@Around
@Type(CachingInterceptor.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cached {
}
@Around
@Type(TransactionalInterceptor.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
}
// 应用多个拦截器
@Singleton
public class OrderService {
@Logged // 拦截器1:日志记录
@Cached // 拦截器2:缓存
@Transactional // 拦截器3:事务
public Order findById(Long id) {
return orderRepository.findById(id);
}
}
// 执行顺序
// 1. LoggingInterceptor.before()
// 2. CachingInterceptor.before()
// 3. TransactionalInterceptor.before()
// 4. 实际方法执行
// 5. TransactionalInterceptor.after()
// 6. CachingInterceptor.after()
// 7. LoggingInterceptor.after()
---
04.模块化设计
a.模块化架构
a.核心模块
Micronaut采用模块化设计,核心功能拆分为独立模块,应用可以按需引入依赖,减少不必要的代码。
b.模块列表
---
// 核心模块
// micronaut-core: 核心依赖注入和AOP
// micronaut-runtime: 应用运行时支持
// micronaut-http: HTTP服务器和客户端
// micronaut-inject: 编译时依赖注入
// micronaut-aop: AOP支持
// 数据访问模块
// micronaut-data-jdbc: JDBC支持
// micronaut-data-jpa: JPA支持
// micronaut-data-r2dbc: R2DBC响应式数据访问
// 消息模块
// micronaut-kafka: Kafka集成
// micronaut-rabbitmq: RabbitMQ集成
// micronaut-nats: NATS集成
// 云原生模块
// micronaut-discovery-client: 服务发现客户端
// micronaut-tracing: 分布式追踪
// micronaut-metrics: 指标监控
// Maven依赖示例(最小化应用)
<dependencies>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-runtime</artifactId>
</dependency>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-server-netty</artifactId>
</dependency>
</dependencies>
---
b.扩展机制
a.SPI机制
Micronaut使用Java SPI(Service Provider Interface)机制实现插件式扩展,第三方库可以通过SPI注册自定义组件。
b.自定义扩展
---
// 定义扩展接口
package io.micronaut.inject;
public interface BeanDefinitionRegistry {
void registerBeanDefinition(BeanDefinition<?> definition);
}
// 实现扩展
package com.example.extension;
public class CustomBeanRegistry implements BeanDefinitionRegistry {
@Override
public void registerBeanDefinition(BeanDefinition<?> definition) {
// 自定义Bean注册逻辑
}
}
// SPI配置文件
// src/main/resources/META-INF/services/io.micronaut.inject.BeanDefinitionRegistry
// com.example.extension.CustomBeanRegistry
// Micronaut启动时自动加载扩展
---
1.6 依赖注入特性
01.编译时依赖注入
a.零反射实现
a.编译期代码生成
Micronaut在编译期生成所有依赖注入相关代码,运行时不需要通过反射扫描类路径、读取注解或动态创建代理对象,实现了真正的零反射依赖注入。
b.性能对比
---
// Spring Framework依赖注入(运行时反射)
// 1. 启动时扫描类路径
// 2. 使用反射读取@Component、@Service等注解
// 3. 使用反射调用构造器创建Bean
// 4. 使用反射设置字段值
// 启动开销:2-5秒,内存开销:150-300MB
// Micronaut依赖注入(编译时生成)
// 1. 编译期生成BeanDefinition类
// 2. 运行时直接加载BeanDefinition
// 3. 直接调用构造器(无反射)
// 4. 直接设置字段值(无反射)
// 启动开销:0.5-1秒,内存开销:20-50MB
// 示例:编译时生成的代码
@Generated
public class $UserService$Definition
extends AbstractBeanDefinition<UserService> {
@Override
public UserService build(BeanContext context) {
// 直接调用构造器,无需反射
UserRepository repo = context.getBean(UserRepository.class);
return new UserService(repo);
}
}
---
b.类型安全
a.编译期类型检查
由于依赖关系在编译期确定,编译器可以提前发现类型不匹配、循环依赖等问题,避免运行时错误。
b.类型安全示例
---
// 定义服务接口和实现
public interface NotificationService {
void send(String message);
}
@Singleton
public class EmailNotificationService implements NotificationService {
@Override
public void send(String message) {
System.out.println("Email: " + message);
}
}
@Singleton
public class SmsNotificationService implements NotificationService {
@Override
public void send(String message) {
System.out.println("SMS: " + message);
}
}
// 注入时指定类型
@Singleton
public class OrderService {
private final NotificationService emailService;
private final NotificationService smsService;
public OrderService(
@Named("emailNotificationService") NotificationService emailService,
@Named("smsNotificationService") NotificationService smsService) {
this.emailService = emailService;
this.smsService = smsService;
}
public void placeOrder(Order order) {
emailService.send("订单已创建: " + order.getId());
smsService.send("订单已创建: " + order.getId());
}
}
// 编译期会检查:
// 1. NotificationService类型是否存在
// 2. emailNotificationService和smsNotificationService是否存在
// 3. 类型是否匹配
// 如有错误,编译失败,避免运行时问题
---
02.注入方式
a.构造器注入
a.推荐方式
构造器注入是Micronaut推荐的注入方式,保证了Bean的不可变性,所有依赖在对象创建时就已确定。
b.构造器注入示例
---
import jakarta.inject.Singleton;
@Singleton
public class PaymentService {
private final PaymentGateway gateway;
private final PaymentValidator validator;
private final AuditLogger logger;
// 构造器注入(@Inject可选,单构造器时自动注入)
public PaymentService(PaymentGateway gateway,
PaymentValidator validator,
AuditLogger logger) {
this.gateway = gateway;
this.validator = validator;
this.logger = logger;
}
public PaymentResult processPayment(PaymentRequest request) {
logger.log("开始支付: " + request.getAmount());
if (!validator.validate(request)) {
return PaymentResult.invalid();
}
return gateway.charge(request);
}
}
// 优点:
// 1. 依赖明确,一目了然
// 2. 字段可以声明为final,保证不可变
// 3. 便于单元测试,直接new对象传入mock依赖
// 4. 编译器可以检查依赖完整性
---
b.字段注入
a.字段注入语法
Micronaut支持通过@Inject注解直接注入字段,但不推荐使用,因为会破坏封装性,且不利于测试。
b.字段注入示例
---
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@Singleton
public class ReportService {
// 字段注入
@Inject
private ReportGenerator generator;
@Inject
private DataSource dataSource;
public Report generate(String type) {
return generator.generate(dataSource, type);
}
}
// 缺点:
// 1. 字段不能声明为final
// 2. 依赖不明确,需要查看字段定义
// 3. 单元测试困难,无法直接new对象
// 4. 可能出现NullPointerException
---
c.方法注入
a.Setter注入
通过@Inject标记setter方法实现注入,适合可选依赖或需要重新配置的场景。
b.方法注入示例
---
import jakarta.inject.Singleton;
import jakarta.inject.Inject;
@Singleton
public class CacheService {
private final CacheStore primaryStore;
private CacheStore secondaryStore; // 可选依赖
public CacheService(CacheStore primaryStore) {
this.primaryStore = primaryStore;
}
// 方法注入(可选依赖)
@Inject
public void setSecondaryStore(
@jakarta.annotation.Nullable CacheStore secondaryStore) {
this.secondaryStore = secondaryStore;
}
public void put(String key, Object value) {
primaryStore.put(key, value);
if (secondaryStore != null) {
secondaryStore.put(key, value); // 双写
}
}
}
---
03.作用域管理
a.Singleton单例
a.单例作用域
@Singleton注解标记的Bean在整个应用生命周期内只创建一次,所有注入点共享同一个实例。
b.单例示例
---
import jakarta.inject.Singleton;
import java.util.concurrent.atomic.AtomicLong;
@Singleton
public class RequestCounter {
private final AtomicLong counter = new AtomicLong(0);
public long increment() {
return counter.incrementAndGet();
}
public long getCount() {
return counter.get();
}
}
// 任何地方注入的都是同一个实例
@Controller("/api")
public class ApiController {
private final RequestCounter counter;
public ApiController(RequestCounter counter) {
this.counter = counter;
}
@Get("/count")
public long getRequestCount() {
return counter.getCount();
}
}
@Controller("/admin")
public class AdminController {
private final RequestCounter counter; // 同一个实例
public AdminController(RequestCounter counter) {
this.counter = counter;
}
}
---
b.Prototype原型
a.原型作用域
@Prototype注解标记的Bean每次注入时都会创建新实例,适合有状态的对象。
b.原型示例
---
import io.micronaut.context.annotation.Prototype;
@Prototype
public class OrderProcessor {
private Order currentOrder;
private ProcessingState state;
public void process(Order order) {
this.currentOrder = order;
this.state = ProcessingState.PROCESSING;
// 处理订单逻辑
}
public ProcessingState getState() {
return state;
}
}
// 每次注入都创建新实例
@Singleton
public class OrderService {
private final BeanContext beanContext;
public OrderService(BeanContext beanContext) {
this.beanContext = beanContext;
}
public void handleOrder(Order order) {
// 每次获取新实例
OrderProcessor processor =
beanContext.getBean(OrderProcessor.class);
processor.process(order);
}
}
---
c.RequestScope请求作用域
a.请求作用域
@RequestScope注解标记的Bean在每个HTTP请求范围内是单例,请求结束后销毁。
b.请求作用域示例
---
import io.micronaut.runtime.http.scope.RequestScope;
@RequestScope
public class RequestContext {
private String requestId;
private String userId;
private long startTime;
public RequestContext() {
this.requestId = UUID.randomUUID().toString();
this.startTime = System.currentTimeMillis();
}
public String getRequestId() {
return requestId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public long getElapsedTime() {
return System.currentTimeMillis() - startTime;
}
}
// 在同一请求中共享RequestContext
@Controller("/api")
public class UserController {
private final RequestContext context;
public UserController(RequestContext context) {
this.context = context;
}
@Get("/user")
public User getUser() {
System.out.println("Request ID: " + context.getRequestId());
return userService.findById(context.getUserId());
}
}
---
04.条件化Bean
a.条件注入
a.@Requires注解
Micronaut提供@Requires注解实现条件化Bean注册,根据配置、环境、类路径等条件决定是否创建Bean。
b.条件注入示例
---
import io.micronaut.context.annotation.Requires;
import jakarta.inject.Singleton;
// 仅当配置属性存在且为true时创建
@Singleton
@Requires(property = "redis.enabled", value = "true")
public class RedisCacheService implements CacheService {
@Override
public void put(String key, Object value) {
// Redis缓存实现
}
}
// 仅当Redis不启用时创建
@Singleton
@Requires(property = "redis.enabled", value = "false", defaultValue = "false")
public class InMemoryCacheService implements CacheService {
@Override
public void put(String key, Object value) {
// 内存缓存实现
}
}
// 仅当类路径存在指定类时创建
@Singleton
@Requires(classes = com.mysql.cj.jdbc.Driver.class)
public class MySQLDataSource implements DataSource {
// MySQL数据源实现
}
// 仅在特定环境下创建
@Singleton
@Requires(env = "dev")
public class MockPaymentGateway implements PaymentGateway {
@Override
public PaymentResult charge(PaymentRequest request) {
return PaymentResult.success(); // Mock实现
}
}
---
b.Primary与Replaces
a.Primary首选Bean
@Primary注解标记首选Bean,当有多个同类型Bean时优先注入标记为Primary的。
b.Primary示例
---
import io.micronaut.context.annotation.Primary;
public interface MessageSender {
void send(String message);
}
@Singleton
@Primary // 标记为首选
public class EmailSender implements MessageSender {
@Override
public void send(String message) {
System.out.println("Email: " + message);
}
}
@Singleton
public class SmsSender implements MessageSender {
@Override
public void send(String message) {
System.out.println("SMS: " + message);
}
}
// 不指定名称时注入Primary Bean
@Singleton
public class NotificationService {
private final MessageSender sender; // 注入EmailSender
public NotificationService(MessageSender sender) {
this.sender = sender;
}
}
// Replaces注解替换Bean定义
@Singleton
@Replaces(EmailSender.class) // 替换EmailSender
public class MockEmailSender implements MessageSender {
@Override
public void send(String message) {
System.out.println("Mock Email: " + message);
}
}
---
1.7 编译时优化
01.性能优化策略
a.消除反射开销
a.反射性能损耗
Java反射机制虽然灵活,但存在显著的性能开销,包括类型检查、安全检查、参数装箱拆箱等,Micronaut通过编译时代码生成完全避免了这些开销。
b.性能对比测试
---
// 性能测试代码
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 10, time = 1)
@Fork(1)
@State(Scope.Benchmark)
public class InjectionBenchmark {
// 反射方式创建对象
@Benchmark
public Object reflectionInjection() throws Exception {
Class<?> clazz = UserService.class;
Constructor<?> constructor = clazz.getConstructor(UserRepository.class);
UserRepository repo = new UserRepositoryImpl();
return constructor.newInstance(repo);
}
// 直接调用构造器
@Benchmark
public Object directInjection() {
UserRepository repo = new UserRepositoryImpl();
return new UserService(repo);
}
// Micronaut方式(编译时生成)
@Benchmark
public Object micronautInjection() {
BeanContext context = BeanContext.run();
return context.getBean(UserService.class);
}
}
// 测试结果(纳秒级)
// reflectionInjection: 5000-8000 ns/op
// directInjection: 10-20 ns/op
// micronautInjection: 15-30 ns/op
// Micronaut性能接近直接调用,比反射快200-400倍
---
b.启动时间优化
a.启动流程简化
Micronaut启动时无需扫描类路径、解析注解、构建依赖图,所有元数据在编译期已生成,启动流程大幅简化。
b.启动时间分析
---
// 启动时间监控
import io.micronaut.context.event.ApplicationEventListener;
import io.micronaut.context.event.StartupEvent;
import io.micronaut.runtime.event.ApplicationStartupEvent;
import jakarta.inject.Singleton;
@Singleton
public class StartupTimer implements
ApplicationEventListener<ApplicationStartupEvent> {
private long startTime;
public StartupTimer() {
this.startTime = System.currentTimeMillis();
}
@Override
public void onApplicationEvent(ApplicationStartupEvent event) {
long elapsed = System.currentTimeMillis() - startTime;
System.out.println("应用启动耗时: " + elapsed + "ms");
// 打印启动阶段耗时
ApplicationContext context = event.getSource();
System.out.println("Bean数量: " + context.getBeanDefinitions().size());
}
}
// 启动时间对比(50个Bean的应用)
// Spring Boot: 2000-3000ms
// Micronaut JVM: 500-800ms
// Micronaut Native: 20-50ms
---
02.编译时代码生成
a.BeanIntrospection
a.内省机制
Micronaut提供编译时Bean内省(BeanIntrospection),无需反射即可读写Bean属性,适用于序列化、验证等场景。
b.内省示例
---
import io.micronaut.core.annotation.Introspected;
// 标记为可内省
@Introspected
public class User {
private Long id;
private String username;
private String email;
// getter和setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
// 编译时生成$User$Introspection类
// 使用内省API操作Bean
import io.micronaut.core.beans.BeanIntrospection;
import io.micronaut.core.beans.BeanProperty;
public class IntrospectionExample {
public static void main(String[] args) {
// 获取内省信息(无反射)
BeanIntrospection<User> introspection =
BeanIntrospection.getIntrospection(User.class);
// 创建实例(无反射)
User user = introspection.instantiate();
// 获取属性(无反射)
BeanProperty<User, String> usernameProperty =
introspection.getRequiredProperty("username", String.class);
// 设置属性值(无反射)
usernameProperty.set(user, "john_doe");
// 获取属性值(无反射)
String username = usernameProperty.get(user);
System.out.println("Username: " + username);
// 遍历所有属性
introspection.getBeanProperties().forEach(property -> {
System.out.println(property.getName() + ": " +
property.get(user));
});
}
}
---
b.配置属性绑定
a.编译时绑定
@ConfigurationProperties注解标记的类在编译期生成配置绑定代码,运行时直接加载配置值,无需反射。
b.配置绑定示例
---
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.core.annotation.Nullable;
import jakarta.validation.constraints.*;
@ConfigurationProperties("database")
public class DatabaseConfig {
@NotBlank
private String url;
@NotBlank
private String username;
@NotBlank
private String password;
@Min(1)
@Max(100)
private int poolSize = 10;
@Nullable
private Duration connectionTimeout;
// getter和setter省略
}
// application.yml
// database:
// url: jdbc:mysql://localhost:3306/mydb
// username: root
// password: secret
// pool-size: 20
// connection-timeout: 30s
// 编译时生成$DatabaseConfig$Definition类
// 包含配置路径映射、类型转换、验证规则
@Generated
public class $DatabaseConfig$Definition {
public void inject(BeanContext context, DatabaseConfig bean) {
// 编译时生成的配置注入代码
Environment env = context.getEnvironment();
bean.setUrl(env.getProperty("database.url", String.class).get());
bean.setUsername(env.getProperty("database.username", String.class).get());
bean.setPassword(env.getProperty("database.password", String.class).get());
bean.setPoolSize(env.getProperty("database.pool-size", Integer.class).orElse(10));
bean.setConnectionTimeout(
env.getProperty("database.connection-timeout", Duration.class).orElse(null));
}
}
---
03.元数据生成
a.注解元数据
a.元数据提取
Micronaut在编译期提取注解信息,生成AnnotationMetadata,运行时直接读取,避免反射解析注解。
b.元数据使用
---
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.inject.BeanDefinition;
@Singleton
public class MetadataInspector {
private final BeanContext beanContext;
public MetadataInspector(BeanContext beanContext) {
this.beanContext = beanContext;
}
public void inspectBean(Class<?> beanClass) {
// 获取Bean定义(编译时生成)
BeanDefinition<?> definition =
beanContext.getBeanDefinition(beanClass);
// 获取注解元数据(编译时提取)
AnnotationMetadata metadata = definition.getAnnotationMetadata();
// 检查注解(无反射)
if (metadata.hasAnnotation(Controller.class)) {
String value = metadata.stringValue(Controller.class).orElse("/");
System.out.println("Controller路径: " + value);
}
// 获取所有注解
metadata.getAnnotationNames().forEach(annotation -> {
System.out.println("注解: " + annotation);
});
}
}
---
b.类型元数据
a.泛型信息保留
Micronaut在编译期保留完整的泛型类型信息,运行时可以准确获取泛型参数类型。
b.泛型处理示例
---
import io.micronaut.core.type.Argument;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
@Singleton
public class GenericClient {
@Client("https://api.example.com")
private HttpClient httpClient;
// 泛型方法
public <T> T get(String path, Class<T> responseType) {
// Micronaut保留了泛型类型信息
return httpClient.toBlocking().retrieve(path, responseType);
}
// 复杂泛型类型
public <T> List<T> getList(String path, Class<T> elementType) {
// 构造List<T>类型
Argument<List<T>> argument =
Argument.listOf(elementType);
return httpClient.toBlocking().retrieve(path, argument);
}
}
// 使用示例
@Singleton
public class UserService {
private final GenericClient client;
public UserService(GenericClient client) {
this.client = client;
}
public User getUser(Long id) {
// 泛型类型精确匹配
return client.get("/users/" + id, User.class);
}
public List<User> getAllUsers() {
// 泛型集合类型精确匹配
return client.getList("/users", User.class);
}
}
---
04.构建时优化选项
a.增量编译
a.增量编译支持
Micronaut支持增量编译,仅重新处理修改的源文件,大幅减少开发时的编译时间。
b.增量编译配置
---
// Maven配置
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
<compilerArgs>
<!-- 启用增量编译 -->
<arg>-Amicronaut.processing.incremental=true</arg>
<!-- 启用增量隔离模式 -->
<arg>-Amicronaut.processing.incremental.isolating=true</arg>
</compilerArgs>
</configuration>
</plugin>
// Gradle配置
tasks.withType(JavaCompile) {
options.fork = true
options.forkOptions.jvmArgs << '-Dmicronaut.processing.incremental=true'
options.compilerArgs << '-Amicronaut.processing.incremental=true'
}
// 增量编译效果
// 首次编译:30-60秒
// 修改单个文件后编译:2-5秒
// 提升:85-90%
---
b.注解处理器优化
a.处理范围限制
可以限制注解处理器的扫描范围,仅处理特定包下的类,加快编译速度。
b.范围限制配置
---
// Maven配置
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<!-- 限制处理范围到com.example包 -->
<arg>-Amicronaut.processing.annotations=com.example.*</arg>
<!-- 排除测试包 -->
<arg>-Amicronaut.processing.exclude=com.example.test.*</arg>
</compilerArgs>
</configuration>
</plugin>
// 效果
// 扫描范围缩小:编译时间减少20-40%
// 生成代码减少:应用体积减小10-20%
---
c.编译时验证
a.循环依赖检测
Micronaut在编译期检测Bean之间的循环依赖,提前发现问题,避免运行时错误。
b.验证示例
---
// 循环依赖示例
@Singleton
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(ServiceB serviceB) { // 依赖ServiceB
this.serviceB = serviceB;
}
}
@Singleton
public class ServiceB {
private final ServiceA serviceA;
public ServiceB(ServiceA serviceA) { // 依赖ServiceA
this.serviceA = serviceA;
}
}
// 编译时报错
// Error: Circular dependency detected:
// ServiceA -> ServiceB -> ServiceA
// 解决方法:使用Provider延迟注入
@Singleton
public class ServiceA {
private final Provider<ServiceB> serviceB;
public ServiceA(Provider<ServiceB> serviceB) {
this.serviceB = serviceB;
}
public void doSomething() {
serviceB.get().process(); // 延迟获取
}
}
---
1.8 云原生支持
01.容器化支持
a.Docker镜像优化
a.分层构建
Micronaut应用支持Docker多阶段构建,可以生成极小的镜像,特别是使用GraalVM原生镜像时,镜像大小可降至50-80MB。
b.镜像构建实践
---
// JVM模式Dockerfile
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY target/micronaut-app-*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
// 镜像大小:150-200MB
// 启动时间:1-2秒
// 内存占用:50-100MB
// GraalVM原生镜像Dockerfile
FROM ghcr.io/graalvm/graalvm-ce:ol8-java17-22 AS builder
WORKDIR /app
COPY . .
RUN ./mvnw package -Dpackaging=native-image
FROM alpine:3.17
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /app/target/micronaut-app ./app
EXPOSE 8080
ENTRYPOINT ["./app"]
// 镜像大小:50-80MB
// 启动时间:20-50ms
// 内存占用:20-40MB
// Jib插件构建(无需Docker)
// pom.xml
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<to>
<image>myregistry/micronaut-app:latest</image>
</to>
<container>
<jvmFlags>
<jvmFlag>-Xms64m</jvmFlag>
<jvmFlag>-Xmx128m</jvmFlag>
</jvmFlags>
<ports>
<port>8080</port>
</ports>
</container>
</configuration>
</plugin>
// 构建命令:mvn compile jib:build
// 优点:不需要Docker daemon,构建速度快,镜像分层优化
---
b.健康检查与就绪探测
a.健康检查端点
Micronaut内置health端点,支持Kubernetes的liveness和readiness探测。
b.健康检查配置
---
// application.yml
// endpoints:
// health:
// enabled: true
// sensitive: false
// info:
// enabled: true
// 自定义健康指示器
import io.micronaut.management.health.indicator.HealthIndicator;
import io.micronaut.management.health.indicator.HealthResult;
import jakarta.inject.Singleton;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
@Singleton
public class DatabaseHealthIndicator implements HealthIndicator {
private final DataSource dataSource;
public DatabaseHealthIndicator(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Publisher<HealthResult> getResult() {
return Mono.fromCallable(() -> {
try (Connection conn = dataSource.getConnection()) {
boolean isValid = conn.isValid(2);
if (isValid) {
return HealthResult.builder("database")
.status(HealthStatus.UP)
.details(Map.of("connection", "active"))
.build();
} else {
return HealthResult.builder("database")
.status(HealthStatus.DOWN)
.details(Map.of("connection", "invalid"))
.build();
}
} catch (Exception e) {
return HealthResult.builder("database")
.status(HealthStatus.DOWN)
.exception(e)
.build();
}
});
}
}
// Kubernetes配置
// livenessProbe:
// httpGet:
// path: /health
// port: 8080
// initialDelaySeconds: 5
// periodSeconds: 10
// readinessProbe:
// httpGet:
// path: /health/readiness
// port: 8080
// initialDelaySeconds: 5
// periodSeconds: 5
---
02.服务发现
a.Consul集成
a.服务注册
Micronaut原生支持Consul服务发现,应用启动时自动注册到Consul,关闭时自动注销。
b.Consul配置
---
// Maven依赖
// <dependency>
// <groupId>io.micronaut.discovery</groupId>
// <artifactId>micronaut-discovery-client</artifactId>
// </dependency>
// application.yml
// consul:
// client:
// registration:
// enabled: true
// defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
// health-path: /health
// micronaut:
// application:
// name: user-service
// 服务自动注册
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String[] args) {
// 启动时自动注册到Consul
// 注册信息包括:服务名、IP、端口、健康检查路径
Micronaut.run(Application.class, args);
}
}
// 服务调用(通过服务名)
import io.micronaut.http.client.annotation.Client;
@Client("user-service") // 通过Consul解析服务地址
public interface UserClient {
@Get("/users/{id}")
User getUser(Long id);
}
// 负载均衡(客户端负载均衡)
// consul:
// client:
// load-balancer:
// enabled: true
// strategy: round-robin # 轮询策略
---
b.Eureka集成
a.Eureka支持
Micronaut也支持Netflix Eureka服务注册与发现,兼容Spring Cloud生态。
b.Eureka配置
---
// Maven依赖
// <dependency>
// <groupId>io.micronaut.discovery</groupId>
// <artifactId>micronaut-discovery-client</artifactId>
// </dependency>
// application.yml
// eureka:
// client:
// registration:
// enabled: true
// defaultZone: "${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}/eureka"
// health-check-url: http://${micronaut.server.host}:${micronaut.server.port}/health
// 服务调用示例
@Singleton
public class OrderService {
@Client("order-service")
private OrderClient orderClient;
public Order getOrder(Long id) {
return orderClient.findById(id);
}
}
---
03.配置中心
a.Consul配置中心
a.分布式配置
Micronaut支持从Consul KV存储读取配置,实现配置集中管理和动态更新。
b.配置中心示例
---
// application.yml
// consul:
// client:
// config:
// enabled: true
// format: YAML # 配置格式:YAML、JSON、PROPERTIES
// micronaut:
// application:
// name: order-service
// config-client:
// enabled: true
// Consul KV存储结构
// config/
// application/ # 全局配置
// data # database.url=jdbc:mysql://...
// order-service/ # 应用特定配置
// data # order.max-items=100
// order-service,prod/ # 环境特定配置
// data # database.url=jdbc:mysql://prod-db/...
// 配置类
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.context.annotation.Refreshable;
@ConfigurationProperties("order")
@Refreshable // 支持配置动态刷新
public class OrderConfig {
private int maxItems;
private Duration timeout;
// getter和setter
}
// 配置刷新
// POST /refresh
// 触发配置重新加载,标记@Refreshable的Bean会重新创建
---
b.Kubernetes ConfigMap
a.ConfigMap集成
Micronaut支持从Kubernetes ConfigMap和Secret读取配置,适合云原生部署。
b.ConfigMap配置
---
// Maven依赖
// <dependency>
// <groupId>io.micronaut.kubernetes</groupId>
// <artifactId>micronaut-kubernetes-discovery-client</artifactId>
// </dependency>
// application.yml
// kubernetes:
// client:
// config-maps:
// enabled: true
// paths:
// - /etc/config/app-config
// secrets:
// enabled: true
// paths:
// - /etc/secret/db-credentials
// Kubernetes ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
application.yml: |
datasource:
url: jdbc:postgresql://postgres:5432/mydb
pool-size: 20
// Kubernetes Secret
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: YWRtaW4= # base64编码
password: cGFzc3dvcmQ=
// Deployment挂载ConfigMap和Secret
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: app
volumeMounts:
- name: config
mountPath: /etc/config
- name: secret
mountPath: /etc/secret
volumes:
- name: config
configMap:
name: app-config
- name: secret
secret:
secretName: db-credentials
---
04.可观测性
a.分布式追踪
a.Zipkin集成
Micronaut内置Zipkin追踪支持,自动为HTTP请求生成追踪信息,无需修改业务代码。
b.追踪配置
---
// Maven依赖
// <dependency>
// <groupId>io.micronaut.tracing</groupId>
// <artifactId>micronaut-tracing-zipkin</artifactId>
// </dependency>
// application.yml
// tracing:
// zipkin:
// enabled: true
// http:
// url: http://zipkin:9411
// sampler:
// probability: 1.0 # 采样率100%
// 自动追踪HTTP请求
@Controller("/orders")
public class OrderController {
@Inject
private PaymentClient paymentClient;
@Post
public Order createOrder(@Body OrderRequest request) {
// Micronaut自动生成追踪ID并传播到下游服务
PaymentResult result = paymentClient.charge(request);
return orderService.create(request, result);
}
}
// 自定义追踪
import io.micronaut.tracing.annotation.NewSpan;
import io.micronaut.tracing.annotation.SpanTag;
@Singleton
public class InventoryService {
@NewSpan("check-inventory")
public boolean checkStock(
@SpanTag("product-id") Long productId,
@SpanTag("quantity") int quantity) {
// 该方法会生成独立的追踪span
return inventoryRepository.getStock(productId) >= quantity;
}
}
---
b.指标监控
a.Micrometer集成
Micronaut集成Micrometer指标库,支持Prometheus、InfluxDB等多种监控系统。
b.监控配置
---
// Maven依赖
// <dependency>
// <groupId>io.micronaut.micrometer</groupId>
// <artifactId>micronaut-micrometer-registry-prometheus</artifactId>
// </dependency>
// application.yml
// micronaut:
// metrics:
// enabled: true
// export:
// prometheus:
// enabled: true
// step: PT1M
// descriptions: true
// 自动暴露/prometheus端点
// 访问 http://localhost:8080/prometheus 获取指标
// 自定义指标
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Counter;
import jakarta.inject.Singleton;
@Singleton
public class OrderMetrics {
private final Counter orderCounter;
private final Timer orderProcessingTimer;
public OrderMetrics(MeterRegistry registry) {
this.orderCounter = Counter.builder("orders.created")
.description("订单创建总数")
.tag("type", "online")
.register(registry);
this.orderProcessingTimer = Timer.builder("orders.processing.time")
.description("订单处理耗时")
.register(registry);
}
public void recordOrderCreated() {
orderCounter.increment();
}
public void recordProcessingTime(Runnable task) {
orderProcessingTimer.record(task);
}
}
// Kubernetes ServiceMonitor(Prometheus Operator)
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: micronaut-app
spec:
selector:
matchLabels:
app: micronaut-app
endpoints:
- port: http
path: /prometheus
interval: 30s
---
c.日志聚合
a.结构化日志
Micronaut支持结构化日志输出,便于日志采集和分析系统处理。
b.日志配置
---
// logback.xml
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeContext>true</includeContext>
<includeMdc>true</includeMdc>
<customFields>{"app":"order-service","env":"prod"}</customFields>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
// 输出JSON格式日志
// {"@timestamp":"2024-01-15T10:30:45.123Z","level":"INFO",
// "thread":"default-nioEventLoopGroup-1-3","logger":"OrderController",
// "message":"创建订单","app":"order-service","env":"prod",
// "trace_id":"abc123","span_id":"def456"}
// 使用MDC传递上下文
import org.slf4j.MDC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
public Order createOrder(OrderRequest request) {
MDC.put("user_id", request.getUserId().toString());
MDC.put("order_id", UUID.randomUUID().toString());
log.info("开始创建订单");
// 业务逻辑
log.info("订单创建成功");
MDC.clear();
return order;
}
}
---
2 核心组件
2.1 汇总:6个核心组件
01.组件概览
a.核心组件列表
Micronaut框架由6个核心组件构成,分别负责应用上下文管理、依赖注入、AOP代理、HTTP通信、配置管理和Bean生命周期,这些组件协同工作,构建了完整的应用运行时环境。
b.组件关系图
---
// Micronaut核心组件架构
// ┌─────────────────────────────────────┐
// │ ApplicationContext │ 应用上下文(顶层容器)
// │ ┌───────────────────────────────┐ │
// │ │ BeanContext │ │ Bean上下文(依赖注入容器)
// │ │ ┌─────────────────────────┐ │ │
// │ │ │ BeanDefinitionRegistry │ │ │ Bean定义注册表
// │ │ └─────────────────────────┘ │ │
// │ │ ┌─────────────────────────┐ │ │
// │ │ │ InterceptorRegistry │ │ │ AOP拦截器注册表
// │ │ └─────────────────────────┘ │ │
// │ └───────────────────────────────┘ │
// │ ┌───────────────────────────────┐ │
// │ │ Environment │ │ 配置环境
// │ └───────────────────────────────┘ │
// │ ┌───────────────────────────────┐ │
// │ │ HttpServerConfiguration │ │ HTTP服务器
// │ └───────────────────────────────┘ │
// └─────────────────────────────────────┘
// 组件初始化顺序
// 1. Environment - 加载配置
// 2. BeanContext - 初始化Bean容器
// 3. BeanDefinitionRegistry - 注册Bean定义
// 4. InterceptorRegistry - 注册拦截器
// 5. ApplicationContext - 启动应用上下文
// 6. HttpServer - 启动HTTP服务器
---
02.组件依赖关系
a.ApplicationContext
a.作用
ApplicationContext是Micronaut的顶层容器,管理整个应用的生命周期,继承自BeanContext,提供额外的环境管理、事件发布等功能。
b.职责
负责应用启动、配置加载、Bean实例化、事件传播、优雅关闭等核心任务。
b.BeanContext
a.作用
BeanContext是依赖注入容器的核心,负责Bean的注册、实例化、依赖解析和生命周期管理。
b.职责
管理所有BeanDefinition,解析Bean依赖关系,按拓扑顺序创建Bean实例,处理作用域和单例缓存。
c.AOP代理机制
a.作用
InterceptorRegistry管理所有AOP拦截器,在方法调用时执行拦截器链,实现声明式事务、缓存、日志等横切关注点。
b.职责
注册拦截器、构建拦截器链、执行方法拦截、管理拦截器顺序。
d.HTTP客户端与服务器
a.作用
基于Netty的HTTP服务器和声明式HTTP客户端,提供高性能的非阻塞IO通信能力。
b.职责
处理HTTP请求和响应、路由分发、内容协商、客户端负载均衡。
e.配置管理
a.作用
Environment组件负责从多种来源加载配置,合并配置优先级,提供类型安全的配置访问。
b.职责
加载配置文件、解析环境变量、管理配置优先级、支持配置刷新。
f.Bean生命周期
a.作用
管理Bean的创建、初始化、使用和销毁全生命周期,支持生命周期回调和事件监听。
b.职责
执行@PostConstruct和@PreDestroy回调、发布生命周期事件、管理Bean销毁顺序。
03.组件交互流程
a.应用启动流程
a.启动阶段划分
Micronaut应用启动分为环境初始化、Bean容器启动、HTTP服务器启动三个主要阶段。
b.启动流程代码
---
import io.micronaut.runtime.Micronaut;
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.env.Environment;
public class Application {
public static void main(String[] args) {
// 创建并启动应用上下文
ApplicationContext context = Micronaut.run(Application.class, args);
// 内部执行流程
// 阶段1:环境初始化(50-100ms)
// 1.1 解析命令行参数
// 1.2 加载application.yml
// 1.3 解析环境变量
// 1.4 合并配置源
// 阶段2:Bean容器启动(200-400ms)
// 2.1 加载BeanDefinition类
// 2.2 解析Bean依赖关系
// 2.3 创建单例Bean实例
// 2.4 执行@PostConstruct回调
// 2.5 发布ApplicationStartedEvent
// 阶段3:HTTP服务器启动(100-200ms)
// 3.1 初始化Netty EventLoopGroup
// 3.2 绑定端口
// 3.3 注册路由处理器
// 3.4 发布ServerStartupEvent
// 总耗时:350-700ms(JVM模式)
// 总耗时:20-50ms(GraalVM原生镜像)
}
}
// 监听启动事件
import io.micronaut.runtime.event.ApplicationStartupEvent;
import io.micronaut.context.event.ApplicationEventListener;
@Singleton
public class StartupListener implements
ApplicationEventListener<ApplicationStartupEvent> {
@Override
public void onApplicationEvent(ApplicationStartupEvent event) {
System.out.println("应用启动完成");
ApplicationContext context = event.getSource();
Environment env = context.getEnvironment();
System.out.println("活跃环境: " + env.getActiveNames());
}
}
---
b.请求处理流程
a.HTTP请求流程
HTTP请求从Netty服务器接收,经过过滤器链、路由匹配、参数绑定、控制器处理、响应序列化等阶段。
b.请求流程示例
---
// HTTP请求处理流程
// 1. Netty接收HTTP请求
// 2. HttpServerFilter过滤器链处理
// 3. 路由匹配(UriRouteMatch)
// 4. 参数绑定(ArgumentBinderRegistry)
// 5. 执行控制器方法
// 6. AOP拦截器处理
// 7. 响应序列化(HttpResponseEncoder)
// 8. Netty发送HTTP响应
// 自定义过滤器
import io.micronaut.http.*;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.filter.HttpServerFilter;
import io.micronaut.http.filter.ServerFilterChain;
import org.reactivestreams.Publisher;
@Filter("/api/**")
public class LoggingFilter implements HttpServerFilter {
private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class);
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
long start = System.currentTimeMillis();
// 记录请求
log.info("收到请求: {} {}", request.getMethod(), request.getPath());
// 继续处理请求
return Flux.from(chain.proceed(request))
.doOnNext(response -> {
long duration = System.currentTimeMillis() - start;
log.info("响应状态: {}, 耗时: {}ms",
response.getStatus(), duration);
});
}
}
// 控制器处理
@Controller("/api/users")
public class UserController {
@Get("/{id}")
public User getUser(Long id) {
// 参数id已自动绑定
return userService.findById(id);
}
}
---
04.组件扩展机制
a.BeanCreatedEventListener
a.Bean创建事件
通过监听Bean创建事件,可以在Bean创建后、注入前对Bean进行自定义处理。
b.事件监听示例
---
import io.micronaut.context.event.BeanCreatedEvent;
import io.micronaut.context.event.BeanCreatedEventListener;
import jakarta.inject.Singleton;
@Singleton
public class DataSourceCustomizer implements
BeanCreatedEventListener<DataSource> {
@Override
public DataSource onCreated(BeanCreatedEvent<DataSource> event) {
DataSource dataSource = event.getBean();
// 对DataSource进行自定义配置
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikari = (HikariDataSource) dataSource;
hikari.setConnectionTimeout(30000);
hikari.setMaximumPoolSize(20);
System.out.println("DataSource配置已定制");
}
return dataSource;
}
}
// 应用场景
// 1. 数据源连接池配置
// 2. HTTP客户端拦截器注册
// 3. 缓存管理器初始化
// 4. 第三方组件定制
---
b.BeanDefinitionReference
a.延迟加载
BeanDefinitionReference提供Bean定义的引用,支持延迟加载,在需要时才实例化Bean。
b.延迟加载示例
---
import io.micronaut.context.BeanContext;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.BeanDefinitionReference;
@Singleton
public class LazyBeanLoader {
private final BeanContext beanContext;
public LazyBeanLoader(BeanContext beanContext) {
this.beanContext = beanContext;
}
public void loadBeansOnDemand() {
// 获取所有Bean定义引用(不实例化)
Collection<BeanDefinitionReference<?>> references =
beanContext.getBeanDefinitionReferences();
System.out.println("总共有 " + references.size() + " 个Bean定义");
// 按需加载Bean
for (BeanDefinitionReference<?> ref : references) {
if (ref.getBeanType().getPackage().getName().startsWith("com.example")) {
// 仅加载com.example包下的Bean
BeanDefinition<?> definition = ref.load(beanContext);
Object bean = beanContext.getBean(definition.getBeanType());
System.out.println("加载Bean: " + bean.getClass().getName());
}
}
}
}
---
2.2 应用上下文
01.ApplicationContext详解
a.上下文创建
a.创建方式
ApplicationContext是Micronaut的顶层容器,可以通过Micronaut.run()或ApplicationContext.builder()创建,前者用于启动Web应用,后者用于非Web场景。
b.创建示例
---
import io.micronaut.context.ApplicationContext;
import io.micronaut.runtime.Micronaut;
// 方式1:Web应用启动
public class WebApplication {
public static void main(String[] args) {
// 创建并启动Web应用上下文
ApplicationContext context = Micronaut.run(WebApplication.class, args);
// 自动启动HTTP服务器
}
}
// 方式2:非Web应用
public class CliApplication {
public static void main(String[] args) {
// 创建应用上下文(不启动HTTP服务器)
ApplicationContext context = ApplicationContext.builder()
.deduceEnvironment(true) // 自动推断环境
.banner(false) // 不显示banner
.start();
// 使用Bean
DataProcessor processor = context.getBean(DataProcessor.class);
processor.process();
// 关闭上下文
context.close();
}
}
// 方式3:自定义配置
public class CustomApplication {
public static void main(String[] args) {
ApplicationContext context = ApplicationContext.builder()
.environments("dev", "test") // 指定环境
.properties(Map.of( // 添加属性
"datasource.url", "jdbc:h2:mem:testdb",
"app.version", "1.0.0"
))
.singletons(new CustomBean()) // 注册单例
.packages("com.example") // 限制扫描包
.start();
context.close();
}
}
---
b.上下文配置
a.构建器模式
ApplicationContext.builder()提供流畅的API配置应用上下文,支持环境、属性、类加载器等自定义。
b.配置选项
---
import io.micronaut.context.ApplicationContextBuilder;
public class ContextConfiguration {
public static ApplicationContext createContext() {
ApplicationContextBuilder builder = ApplicationContext.builder();
return builder
// 环境配置
.environments("prod")
.deduceEnvironment(false)
// 类加载器
.classLoader(CustomClassLoader.class.getClassLoader())
// 自定义属性
.properties(Map.of(
"micronaut.server.port", "9090",
"micronaut.application.name", "my-service"
))
// 预注册Bean
.singletons(new MetricsRegistry())
// 包扫描限制
.packages("com.example.services", "com.example.controllers")
// 排除包
.excludePackages("com.example.test")
// 启动时不启动服务器
.eagerInitSingletons(false)
// Banner配置
.banner(true)
// 启动上下文
.start();
}
}
---
02.Bean访问与管理
a.Bean获取方法
a.类型获取
通过Bean的Class类型获取实例,如果存在多个同类型Bean会抛出异常,需要使用@Named或@Primary指定。
b.获取示例
---
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.exceptions.NoSuchBeanException;
import io.micronaut.context.exceptions.NonUniqueBeanException;
@Singleton
public class BeanAccessor {
private final ApplicationContext context;
public BeanAccessor(ApplicationContext context) {
this.context = context;
}
public void accessBeans() {
// 1. 通过类型获取Bean
UserService userService = context.getBean(UserService.class);
// 2. 通过类型和限定符获取
DataSource primaryDs = context.getBean(DataSource.class,
Qualifiers.byName("primary"));
// 3. 检查Bean是否存在
boolean exists = context.containsBean(EmailService.class);
// 4. 可选获取(不存在返回Optional.empty)
Optional<CacheService> cacheService =
context.findBean(CacheService.class);
// 5. 获取所有同类型Bean
Collection<MessageSender> senders =
context.getBeansOfType(MessageSender.class);
// 6. 通过Argument获取(支持泛型)
Argument<List<String>> arg = Argument.listOf(String.class);
List<String> list = context.getBean(arg);
}
public void handleErrors() {
try {
context.getBean(NonExistentService.class);
} catch (NoSuchBeanException e) {
System.out.println("Bean不存在");
}
try {
context.getBean(NotificationService.class);
} catch (NonUniqueBeanException e) {
System.out.println("存在多个同类型Bean,需要使用@Named指定");
}
}
}
---
b.Bean注册
a.动态注册Bean
除了通过注解自动注册Bean,还可以在运行时动态注册Bean实例或BeanDefinition。
b.动态注册示例
---
import io.micronaut.context.ApplicationContext;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.context.BeanRegistration;
public class DynamicBeanRegistration {
public static void main(String[] args) {
ApplicationContext context = ApplicationContext.run();
// 1. 注册单例实例
Configuration config = new Configuration("prod");
context.registerSingleton(Configuration.class, config);
// 2. 注册带限定符的Bean
DataSource primaryDs = createPrimaryDataSource();
context.registerSingleton(DataSource.class, primaryDs,
Qualifiers.byName("primary"));
DataSource secondaryDs = createSecondaryDataSource();
context.registerSingleton(DataSource.class, secondaryDs,
Qualifiers.byName("secondary"));
// 3. 获取注册的Bean
Configuration retrievedConfig = context.getBean(Configuration.class);
DataSource primary = context.getBean(DataSource.class,
Qualifiers.byName("primary"));
// 4. 销毁Bean
context.destroyBean(Configuration.class);
context.close();
}
private static DataSource createPrimaryDataSource() {
return new HikariDataSource(/*...*/);
}
private static DataSource createSecondaryDataSource() {
return new HikariDataSource(/*...*/);
}
}
---
03.环境管理
a.Environment组件
a.环境概念
Environment管理应用的配置环境,支持多环境配置、配置优先级、属性占位符解析等功能。
b.环境使用
---
import io.micronaut.context.env.Environment;
import io.micronaut.context.env.PropertySource;
import jakarta.inject.Singleton;
@Singleton
public class EnvironmentService {
private final Environment environment;
public EnvironmentService(Environment environment) {
this.environment = environment;
}
public void printEnvironmentInfo() {
// 获取活跃环境
Set<String> activeNames = environment.getActiveNames();
System.out.println("活跃环境: " + activeNames);
// 获取配置属性
String appName = environment.getProperty("micronaut.application.name",
String.class).orElse("unknown");
System.out.println("应用名称: " + appName);
Integer port = environment.getProperty("micronaut.server.port",
Integer.class).orElse(8080);
System.out.println("服务端口: " + port);
// 获取必需属性(不存在会抛异常)
String dbUrl = environment.getRequiredProperty("datasource.url",
String.class);
// 属性占位符解析
String value = environment.getPlaceholderResolver()
.resolveRequiredPlaceholders("${app.name}-${app.version}");
// 检查环境
boolean isDev = environment.getActiveNames().contains("dev");
boolean isProd = environment.getActiveNames().contains("prod");
}
public void listPropertySources() {
// 遍历所有配置源
for (PropertySource source : environment.getPropertySources()) {
System.out.println("配置源: " + source.getName());
System.out.println("优先级: " + source.getOrder());
}
}
}
// 配置优先级(从高到低)
// 1. 命令行参数
// 2. 系统属性
// 3. 环境变量
// 4. application-{env}.yml
// 5. application.yml
// 6. 默认值
---
b.多环境配置
a.环境激活
通过micronaut.environments属性或MICRONAUT_ENVIRONMENTS环境变量指定活跃环境。
b.环境配置示例
---
// application.yml(默认配置)
// micronaut:
// application:
// name: order-service
// datasource:
// url: jdbc:h2:mem:default
// driver-class-name: org.h2.Driver
// application-dev.yml(开发环境)
// datasource:
// url: jdbc:mysql://localhost:3306/dev_db
// username: dev_user
// password: dev_pass
// logger:
// levels:
// com.example: DEBUG
// application-prod.yml(生产环境)
// datasource:
// url: jdbc:mysql://prod-db:3306/prod_db
// username: prod_user
// password: ${DB_PASSWORD} # 从环境变量读取
// pool:
// maximum-pool-size: 50
// logger:
// levels:
// com.example: INFO
// 启动时指定环境
// java -Dmicronaut.environments=dev -jar app.jar
// java -Dmicronaut.environments=prod -jar app.jar
// MICRONAUT_ENVIRONMENTS=prod java -jar app.jar
// 多环境激活
// java -Dmicronaut.environments=cloud,prod -jar app.jar
// 环境条件Bean
import io.micronaut.context.annotation.Requires;
@Singleton
@Requires(env = "dev")
public class DevDataInitializer {
@PostConstruct
public void init() {
System.out.println("开发环境:初始化测试数据");
}
}
@Singleton
@Requires(env = "prod")
public class ProdMonitoringService {
@PostConstruct
public void init() {
System.out.println("生产环境:启动监控服务");
}
}
---
04.事件系统
a.应用事件
a.内置事件类型
Micronaut提供丰富的应用生命周期事件,可以监听这些事件执行自定义逻辑。
b.事件监听
---
import io.micronaut.context.event.ApplicationEventListener;
import io.micronaut.context.event.*;
import io.micronaut.runtime.event.*;
import jakarta.inject.Singleton;
// 监听应用启动事件
@Singleton
public class StartupListener implements
ApplicationEventListener<ApplicationStartupEvent> {
@Override
public void onApplicationEvent(ApplicationStartupEvent event) {
System.out.println("应用启动完成");
}
}
// 监听应用关闭事件
@Singleton
public class ShutdownListener implements
ApplicationEventListener<ShutdownEvent> {
@Override
public void onApplicationEvent(ShutdownEvent event) {
System.out.println("应用即将关闭,清理资源...");
}
}
// 监听HTTP服务器启动
@Singleton
public class ServerStartListener implements
ApplicationEventListener<ServerStartupEvent> {
@Override
public void onApplicationEvent(ServerStartupEvent event) {
int port = event.getSource().getPort();
System.out.println("HTTP服务器启动,端口: " + port);
}
}
// 监听Bean创建事件
@Singleton
public class BeanListener implements
ApplicationEventListener<BeanCreatedEvent<DataSource>> {
@Override
public void onApplicationEvent(BeanCreatedEvent<DataSource> event) {
System.out.println("DataSource Bean已创建");
}
}
---
b.自定义事件
a.事件发布
可以定义自定义事件并通过ApplicationEventPublisher发布,其他组件可以监听这些事件。
b.自定义事件示例
---
// 定义自定义事件
public class OrderCreatedEvent {
private final Order order;
private final long timestamp;
public OrderCreatedEvent(Order order) {
this.order = order;
this.timestamp = System.currentTimeMillis();
}
public Order getOrder() { return order; }
public long getTimestamp() { return timestamp; }
}
// 发布事件
import io.micronaut.context.event.ApplicationEventPublisher;
import jakarta.inject.Singleton;
@Singleton
public class OrderService {
private final ApplicationEventPublisher<OrderCreatedEvent> eventPublisher;
public OrderService(ApplicationEventPublisher<OrderCreatedEvent> eventPublisher) {
this.eventPublisher = eventPublisher;
}
public Order createOrder(OrderRequest request) {
Order order = new Order(request);
// 保存订单
orderRepository.save(order);
// 发布事件
eventPublisher.publishEvent(new OrderCreatedEvent(order));
return order;
}
}
// 监听自定义事件
@Singleton
public class OrderEventListener implements
ApplicationEventListener<OrderCreatedEvent> {
@Inject
private NotificationService notificationService;
@Inject
private InventoryService inventoryService;
@Override
public void onApplicationEvent(OrderCreatedEvent event) {
Order order = event.getOrder();
// 发送通知
notificationService.sendOrderConfirmation(order);
// 扣减库存
inventoryService.decreaseStock(order.getItems());
System.out.println("订单事件处理完成: " + order.getId());
}
}
// 异步事件监听
import io.micronaut.scheduling.annotation.Async;
@Singleton
public class AsyncOrderListener implements
ApplicationEventListener<OrderCreatedEvent> {
@Async // 异步执行
@Override
public void onApplicationEvent(OrderCreatedEvent event) {
// 在独立线程中处理事件
System.out.println("异步处理订单: " + event.getOrder().getId());
}
}
---
2.3 依赖注入容器
01.BeanContext详解
a.容器职责
a.核心功能
BeanContext是Micronaut的依赖注入容器,负责管理所有Bean的定义、实例化、依赖解析和生命周期,它在编译时获得完整的依赖关系图,运行时高效地创建和管理Bean。
b.容器操作
---
import io.micronaut.context.BeanContext;
import io.micronaut.context.ApplicationContext;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.qualifiers.Qualifiers;
public class BeanContextExample {
public static void main(String[] args) {
BeanContext context = ApplicationContext.run();
// 1. 获取Bean定义(不实例化)
BeanDefinition<UserService> definition =
context.getBeanDefinition(UserService.class);
System.out.println("Bean类型: " + definition.getBeanType());
System.out.println("是否单例: " + definition.isSingleton());
// 2. 获取所有Bean定义
Collection<BeanDefinition<?>> allDefinitions =
context.getBeanDefinitions();
System.out.println("总共有 " + allDefinitions.size() + " 个Bean");
// 3. 按类型查找Bean定义
Collection<BeanDefinition<DataSource>> dsDefinitions =
context.getBeanDefinitions(DataSource.class);
// 4. 创建Bean实例
UserService userService = context.createBean(UserService.class);
// 5. 销毁Bean
context.destroyBean(userService);
// 6. 获取或创建Bean
OrderService orderService = context.getBean(OrderService.class);
// 7. 刷新Bean(仅@Refreshable标记的Bean)
context.refreshBean(BeanIdentifier.of("configBean"));
context.close();
}
}
---
b.依赖解析
a.依赖图构建
BeanContext在启动时根据编译时生成的BeanDefinition构建完整的依赖关系图,确定Bean的创建顺序。
b.依赖解析示例
---
// 定义Bean依赖关系
@Singleton
public class RepositoryImpl implements Repository {
public RepositoryImpl() {
System.out.println("1. Repository创建");
}
}
@Singleton
public class ServiceImpl implements Service {
private final Repository repository;
public ServiceImpl(Repository repository) {
System.out.println("2. Service创建,依赖Repository");
this.repository = repository;
}
}
@Singleton
public class ControllerImpl implements Controller {
private final Service service;
public ControllerImpl(Service service) {
System.out.println("3. Controller创建,依赖Service");
this.service = service;
}
}
// 启动应用
// 输出顺序:
// 1. Repository创建
// 2. Service创建,依赖Repository
// 3. Controller创建,依赖Service
// 依赖关系图
// Controller -> Service -> Repository
// Micronaut按拓扑顺序创建:Repository -> Service -> Controller
// 复杂依赖示例
@Singleton
public class ComplexService {
private final Dependency1 dep1;
private final Dependency2 dep2;
private final Dependency3 dep3;
public ComplexService(Dependency1 dep1,
Dependency2 dep2,
Dependency3 dep3) {
this.dep1 = dep1;
this.dep2 = dep2;
this.dep3 = dep3;
}
}
// Micronaut在编译时分析:
// 1. ComplexService依赖Dependency1、Dependency2、Dependency3
// 2. 检查这3个依赖是否存在
// 3. 检查是否存在循环依赖
// 4. 生成创建顺序:先创建3个依赖,再创建ComplexService
---
02.Bean定义
a.BeanDefinition接口
a.元数据访问
BeanDefinition提供Bean的完整元数据,包括类型、作用域、注解、构造器参数、依赖关系等信息。
b.元数据查询
---
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.core.annotation.AnnotationMetadata;
@Singleton
public class BeanInspector {
private final BeanContext beanContext;
public BeanInspector(BeanContext beanContext) {
this.beanContext = beanContext;
}
public void inspectBean(Class<?> beanType) {
BeanDefinition<?> definition =
beanContext.getBeanDefinition(beanType);
// 基本信息
System.out.println("Bean类型: " + definition.getBeanType());
System.out.println("是否单例: " + definition.isSingleton());
System.out.println("是否可迭代: " + definition.isIterable());
// 作用域信息
Optional<Class<? extends Annotation>> scope = definition.getScope();
scope.ifPresent(s -> System.out.println("作用域: " + s.getName()));
// 注解元数据
AnnotationMetadata metadata = definition.getAnnotationMetadata();
System.out.println("注解: " + metadata.getAnnotationNames());
// 构造器参数
definition.getConstructor().getArguments()
.forEach(arg -> System.out.println("依赖: " + arg.getType()));
// 可执行方法(包含@PostConstruct等)
Collection<ExecutableMethod<?, ?>> methods =
definition.getExecutableMethods();
methods.forEach(method ->
System.out.println("方法: " + method.getName()));
// 注入字段
definition.getInjectedFields()
.forEach(field -> System.out.println("字段: " + field.getName()));
// 注入方法
definition.getInjectedMethods()
.forEach(method -> System.out.println("注入方法: " + method.getName()));
}
}
---
b.BeanDefinitionRegistry
a.注册表功能
BeanDefinitionRegistry管理所有Bean定义的注册、查找和移除,是BeanContext的核心组成部分。
b.注册表操作
---
import io.micronaut.inject.BeanDefinitionRegistry;
import io.micronaut.context.ApplicationContext;
public class RegistryExample {
public static void main(String[] args) {
ApplicationContext context = ApplicationContext.run();
BeanDefinitionRegistry registry = context;
// 1. 查找Bean定义
Optional<BeanDefinition<UserService>> definition =
registry.findBeanDefinition(UserService.class);
if (definition.isPresent()) {
BeanDefinition<UserService> def = definition.get();
System.out.println("找到Bean定义: " + def.getBeanType());
}
// 2. 查找所有Bean定义
Collection<BeanDefinition<?>> allBeans =
registry.getBeanDefinitions();
// 3. 按类型查找
Collection<BeanDefinition<Repository>> repositories =
registry.getBeanDefinitions(Repository.class);
// 4. 按限定符查找
BeanDefinition<DataSource> primaryDs =
registry.findBeanDefinition(DataSource.class,
Qualifiers.byName("primary")).orElse(null);
// 5. 检查Bean定义是否存在
boolean exists = registry.containsBean(EmailService.class);
// 6. 获取Bean定义数量
int count = registry.getBeanDefinitions().size();
System.out.println("注册的Bean总数: " + count);
context.close();
}
}
---
03.Bean实例化
a.实例化策略
a.单例缓存
@Singleton标记的Bean首次创建后缓存在BeanContext中,后续注入直接从缓存获取,保证全局唯一。
b.实例化流程
---
// Bean实例化流程示例
import io.micronaut.context.BeanContext;
import io.micronaut.context.BeanResolutionContext;
import io.micronaut.inject.BeanDefinition;
// 编译时生成的BeanDefinition实现
@Generated
public class $OrderService$Definition
extends AbstractInitializableBeanDefinition<OrderService> {
@Override
public OrderService build(BeanResolutionContext resolutionContext,
BeanContext context,
BeanDefinition<OrderService> definition) {
// 1. 解析依赖
OrderRepository repository =
(OrderRepository) super.getBeanForConstructorArgument(
resolutionContext, context, 0, null);
PaymentService paymentService =
(PaymentService) super.getBeanForConstructorArgument(
resolutionContext, context, 1, null);
// 2. 调用构造器创建实例(无反射)
OrderService bean = new OrderService(repository, paymentService);
// 3. 执行字段注入(如果有@Inject字段)
// super.injectBean(resolutionContext, context, bean);
// 4. 执行方法注入(如果有@Inject方法)
// super.injectBeanMethods(resolutionContext, context, bean);
// 5. 返回实例
return bean;
}
@Override
protected Object injectBean(BeanResolutionContext resolutionContext,
BeanContext context,
Object bean) {
OrderService orderService = (OrderService) bean;
// 执行@PostConstruct方法
orderService.init();
return orderService;
}
}
// 使用示例
public class InstantiationExample {
public static void main(String[] args) {
BeanContext context = BeanContext.run();
// 首次获取:触发实例化
OrderService service1 = context.getBean(OrderService.class);
// 再次获取:从缓存返回(单例)
OrderService service2 = context.getBean(OrderService.class);
// 验证是同一实例
System.out.println(service1 == service2); // true
context.close();
}
}
---
b.懒加载
a.懒加载配置
通过@Context(lazy=true)配置Bean懒加载,仅在首次使用时才创建实例。
b.懒加载示例
---
import io.micronaut.context.annotation.Context;
@Singleton
@Context(lazy = true) // 懒加载
public class HeavyService {
public HeavyService() {
System.out.println("HeavyService初始化(耗时操作)");
// 加载大量数据、建立连接等耗时操作
}
public void process() {
System.out.println("处理业务逻辑");
}
}
@Controller("/api")
public class ApiController {
private final HeavyService heavyService;
public ApiController(HeavyService heavyService) {
this.heavyService = heavyService;
// 此时HeavyService尚未初始化
}
@Get("/process")
public String process() {
// 首次调用时才初始化HeavyService
heavyService.process();
return "Done";
}
}
// 启动流程
// 1. 应用启动:HeavyService未初始化
// 2. 首次访问/api/process:触发HeavyService初始化
// 3. 后续访问:直接使用已创建的实例
---
04.作用域管理
a.作用域类型
a.支持的作用域
Micronaut支持多种Bean作用域,包括Singleton、Prototype、RequestScope、ThreadLocal、Refreshable等。
b.作用域示例
---
import io.micronaut.context.annotation.Prototype;
import io.micronaut.runtime.http.scope.RequestScope;
import io.micronaut.context.annotation.ThreadLocal;
import io.micronaut.context.annotation.Refreshable;
// 单例作用域(默认)
@Singleton
public class ConfigService {
// 应用生命周期内唯一实例
}
// 原型作用域(每次注入创建新实例)
@Prototype
public class TaskExecutor {
private String taskId = UUID.randomUUID().toString();
public String getTaskId() {
return taskId;
}
}
// 请求作用域(每个HTTP请求内唯一)
@RequestScope
public class RequestContext {
private String traceId = UUID.randomUUID().toString();
private Map<String, Object> attributes = new HashMap<>();
public void setAttribute(String key, Object value) {
attributes.put(key, value);
}
}
// 线程局部作用域
@ThreadLocal
public class ThreadContext {
private String threadId = Thread.currentThread().getName();
// 每个线程独立实例
}
// 可刷新作用域(配置变更时重新创建)
@Refreshable
@ConfigurationProperties("dynamic")
public class DynamicConfig {
private int maxConnections;
// POST /refresh端点触发时重新创建
}
// 使用示例
@Singleton
public class ScopeDemo {
private final BeanContext beanContext;
public ScopeDemo(BeanContext beanContext) {
this.beanContext = beanContext;
}
public void demonstrateScopes() {
// Singleton:同一实例
ConfigService config1 = beanContext.getBean(ConfigService.class);
ConfigService config2 = beanContext.getBean(ConfigService.class);
System.out.println("Singleton相同: " + (config1 == config2)); // true
// Prototype:不同实例
TaskExecutor task1 = beanContext.getBean(TaskExecutor.class);
TaskExecutor task2 = beanContext.getBean(TaskExecutor.class);
System.out.println("Prototype不同: " + (task1 != task2)); // true
System.out.println("Task1 ID: " + task1.getTaskId());
System.out.println("Task2 ID: " + task2.getTaskId());
}
}
---
b.自定义作用域
a.作用域注解
可以定义自定义作用域注解,实现特定的Bean生命周期管理。
b.自定义作用域示例
---
import io.micronaut.context.scope.CustomScope;
import io.micronaut.context.BeanResolutionContext;
import io.micronaut.inject.BeanIdentifier;
import jakarta.inject.Singleton;
import java.lang.annotation.*;
// 定义作用域注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Scope
public @interface TenantScope {
}
// 实现作用域逻辑
@Singleton
public class TenantScopeImpl implements CustomScope<TenantScope> {
private final Map<String, Map<BeanIdentifier, Object>> tenantBeans =
new ConcurrentHashMap<>();
@Override
public Class<TenantScope> annotationType() {
return TenantScope.class;
}
@Override
public <T> T get(BeanResolutionContext resolutionContext,
BeanDefinition<T> beanDefinition,
BeanIdentifier identifier,
BeanProvider<T> provider) {
// 获取当前租户ID
String tenantId = TenantContext.getCurrentTenantId();
// 获取或创建租户级别的Bean
Map<BeanIdentifier, Object> beans =
tenantBeans.computeIfAbsent(tenantId, k -> new ConcurrentHashMap<>());
return (T) beans.computeIfAbsent(identifier, k -> provider.get());
}
@Override
public <T> Optional<T> remove(BeanIdentifier identifier) {
String tenantId = TenantContext.getCurrentTenantId();
Map<BeanIdentifier, Object> beans = tenantBeans.get(tenantId);
if (beans != null) {
return Optional.ofNullable((T) beans.remove(identifier));
}
return Optional.empty();
}
}
// 使用自定义作用域
@TenantScope
public class TenantDataSource implements DataSource {
// 每个租户独立的数据源实例
}
---
2.4 AOP代理机制
01.拦截器系统
a.MethodInterceptor接口
a.拦截器定义
MethodInterceptor是Micronaut AOP的核心接口,所有方法拦截器都需要实现此接口,提供intercept方法来处理方法调用。
b.拦截器实现
---
import io.micronaut.aop.MethodInterceptor;
import io.micronaut.aop.MethodInvocationContext;
import jakarta.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class PerformanceInterceptor implements MethodInterceptor<Object, Object> {
private static final Logger log =
LoggerFactory.getLogger(PerformanceInterceptor.class);
@Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
long startTime = System.nanoTime();
// 获取方法信息
String className = context.getTarget().getClass().getSimpleName();
String methodName = context.getMethodName();
Object[] parameters = context.getParameterValues();
log.debug("开始执行: {}.{}()", className, methodName);
try {
// 执行原方法
Object result = context.proceed();
long duration = (System.nanoTime() - startTime) / 1_000_000;
log.info("执行完成: {}.{}() 耗时{}ms",
className, methodName, duration);
// 性能告警
if (duration > 1000) {
log.warn("方法执行超时: {}.{}() 耗时{}ms",
className, methodName, duration);
}
return result;
} catch (Exception e) {
long duration = (System.nanoTime() - startTime) / 1_000_000;
log.error("执行失败: {}.{}() 耗时{}ms, 错误: {}",
className, methodName, duration, e.getMessage());
throw e;
}
}
}
// 定义拦截注解
import io.micronaut.aop.Around;
import java.lang.annotation.*;
@Around
@Type(PerformanceInterceptor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Monitor {
}
// 应用拦截器
@Singleton
public class ProductService {
@Monitor // 该方法会被性能监控
public Product findById(Long id) {
// 模拟数据库查询
return productRepository.findById(id);
}
@Monitor
public List<Product> search(String keyword) {
// 模拟复杂查询
return productRepository.search(keyword);
}
}
---
b.拦截器链
a.多拦截器组合
一个方法可以被多个拦截器拦截,Micronaut按照拦截器的Order值构建拦截器链,依次执行。
b.拦截器链示例
---
import io.micronaut.core.order.Ordered;
// 拦截器1:认证(优先级最高)
@Singleton
public class AuthInterceptor implements MethodInterceptor<Object, Object>,
Ordered {
@Override
public int getOrder() {
return 100; // 优先级高(数值小)
}
@Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
System.out.println("1. 认证检查");
// 检查认证
return context.proceed();
}
}
// 拦截器2:日志
@Singleton
public class LogInterceptor implements MethodInterceptor<Object, Object>,
Ordered {
@Override
public int getOrder() {
return 200;
}
@Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
System.out.println("2. 日志记录");
Object result = context.proceed();
System.out.println("6. 日志记录完成");
return result;
}
}
// 拦截器3:缓存
@Singleton
public class CacheInterceptor implements MethodInterceptor<Object, Object>,
Ordered {
@Override
public int getOrder() {
return 300;
}
@Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
System.out.println("3. 缓存检查");
Object result = context.proceed();
System.out.println("5. 缓存更新");
return result;
}
}
// 应用多个拦截器
@Singleton
public class OrderService {
@Auth // AuthInterceptor
@Logged // LogInterceptor
@Cached // CacheInterceptor
public Order getOrder(Long id) {
System.out.println("4. 实际方法执行");
return orderRepository.findById(id);
}
}
// 执行顺序
// 1. 认证检查(Order=100)
// 2. 日志记录(Order=200)
// 3. 缓存检查(Order=300)
// 4. 实际方法执行
// 5. 缓存更新(Order=300)
// 6. 日志记录完成(Order=200)
// 认证检查完成隐含在最外层
---
02.Introduction通知
a.接口实现注入
a.Introduction概念
Introduction通知允许为接口动态提供实现,常用于声明式HTTP客户端、Repository等场景。
b.声明式客户端
---
// 定义HTTP客户端接口(无需实现类)
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.annotation.*;
@Client("https://api.github.com")
public interface GitHubApi {
@Get("/users/{username}")
User getUser(String username);
@Get("/repos/{owner}/{repo}")
Repository getRepository(String owner, String repo);
}
// Micronaut编译时生成实现类
@Generated
public class $GitHubApi$Intercepted implements GitHubApi {
private final HttpClient httpClient;
public $GitHubApi$Intercepted(HttpClient httpClient) {
this.httpClient = httpClient;
}
@Override
public User getUser(String username) {
// 自动生成HTTP调用代码
return httpClient.toBlocking().retrieve(
"/users/" + username, User.class);
}
@Override
public Repository getRepository(String owner, String repo) {
return httpClient.toBlocking().retrieve(
"/repos/" + owner + "/" + repo, Repository.class);
}
}
// 使用示例
@Singleton
public class GithubService {
private final GitHubApi githubApi;
public GithubService(GitHubApi githubApi) {
this.githubApi = githubApi; // 注入编译时生成的实现
}
public User getUserInfo(String username) {
return githubApi.getUser(username);
}
}
---
b.自定义Introduction
a.Introduction注解
通过@Introduction注解和MethodInterceptor可以实现自定义的接口代理。
b.自定义Introduction示例
---
import io.micronaut.aop.Introduction;
import io.micronaut.context.annotation.Type;
import java.lang.annotation.*;
// 定义Introduction注解
@Introduction
@Type(CrudRepositoryInterceptor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CrudRepository {
Class<?> entity();
}
// 实现Introduction拦截器
@Singleton
public class CrudRepositoryInterceptor implements MethodInterceptor<Object, Object> {
private final JdbcTemplate jdbcTemplate;
public CrudRepositoryInterceptor(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
String methodName = context.getMethodName();
// 根据方法名生成SQL
if (methodName.equals("findById")) {
Long id = (Long) context.getParameterValues()[0];
String sql = "SELECT * FROM users WHERE id = ?";
return jdbcTemplate.queryForObject(sql, User.class, id);
} else if (methodName.equals("save")) {
User user = (User) context.getParameterValues()[0];
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
jdbcTemplate.update(sql, user.getName(), user.getEmail());
return user;
}
return null;
}
}
// 定义Repository接口
@CrudRepository(entity = User.class)
public interface UserRepository {
User findById(Long id);
User save(User user);
void deleteById(Long id);
}
// 使用Repository(无需编写实现类)
@Singleton
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository; // 注入代理实现
}
public User getUser(Long id) {
return userRepository.findById(id);
}
}
---
03.内置AOP功能
a.声明式事务
a.@Transactional注解
Micronaut提供@Transactional注解实现声明式事务管理,通过AOP拦截器自动处理事务的开启、提交和回滚。
b.事务示例
---
import io.micronaut.transaction.annotation.Transactional;
import jakarta.inject.Singleton;
@Singleton
public class OrderService {
private final OrderRepository orderRepository;
private final InventoryService inventoryService;
private final PaymentService paymentService;
public OrderService(OrderRepository orderRepository,
InventoryService inventoryService,
PaymentService paymentService) {
this.orderRepository = orderRepository;
this.inventoryService = inventoryService;
this.paymentService = paymentService;
}
@Transactional // 方法执行在事务中
public Order createOrder(OrderRequest request) {
// 1. 创建订单
Order order = new Order(request);
orderRepository.save(order);
// 2. 扣减库存
inventoryService.decreaseStock(request.getItems());
// 3. 处理支付
PaymentResult payment = paymentService.charge(request.getAmount());
if (!payment.isSuccess()) {
// 抛出异常,事务自动回滚
throw new PaymentFailedException("支付失败");
}
order.setPaymentId(payment.getTransactionId());
orderRepository.update(order);
return order;
// 方法正常返回,事务自动提交
// 方法抛出异常,事务自动回滚
}
// 只读事务(优化性能)
@Transactional(readOnly = true)
public List<Order> findUserOrders(Long userId) {
return orderRepository.findByUserId(userId);
}
// 自定义事务传播
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordAuditLog(AuditLog log) {
// 在新事务中执行,不受外部事务影响
auditRepository.save(log);
}
}
// 事务配置
// application.yml
// datasources:
// default:
// transaction-manager: jdbc
// isolation-level: READ_COMMITTED
// timeout: 30s
---
b.声明式缓存
a.缓存注解
Micronaut提供@Cacheable、@CachePut、@CacheInvalidate注解实现声明式缓存,支持多种缓存后端。
b.缓存示例
---
import io.micronaut.cache.annotation.*;
import jakarta.inject.Singleton;
@Singleton
public class ProductService {
private final ProductRepository repository;
public ProductService(ProductRepository repository) {
this.repository = repository;
}
// 缓存查询结果
@Cacheable(value = "products", parameters = {"id"})
public Product findById(Long id) {
System.out.println("从数据库查询产品: " + id);
return repository.findById(id);
}
// 更新缓存
@CachePut(value = "products", parameters = {"id"})
public Product updateProduct(Long id, Product product) {
repository.update(product);
return product;
}
// 使缓存失效
@CacheInvalidate(value = "products", parameters = {"id"})
public void deleteProduct(Long id) {
repository.deleteById(id);
}
// 使所有缓存失效
@CacheInvalidate(value = "products", all = true)
public void clearAllCache() {
System.out.println("清除所有产品缓存");
}
// 异步缓存
@Cacheable(value = "products-async", parameters = {"id"})
public CompletableFuture<Product> findByIdAsync(Long id) {
return CompletableFuture.supplyAsync(() ->
repository.findById(id));
}
}
// 缓存配置(使用Redis)
// application.yml
// redis:
// uri: redis://localhost:6379
// micronaut:
// caches:
// products:
// charset: UTF-8
// expire-after-write: 10m
// expire-after-access: 5m
// Maven依赖
// <dependency>
// <groupId>io.micronaut.cache</groupId>
// <artifactId>micronaut-cache-redis</artifactId>
// </dependency>
---
c.重试机制
a.@Retryable注解
Micronaut提供@Retryable注解实现方法重试,自动处理瞬时故障。
b.重试示例
---
import io.micronaut.retry.annotation.Retryable;
import io.micronaut.retry.annotation.CircuitBreaker;
import jakarta.inject.Singleton;
@Singleton
public class ExternalApiService {
private final HttpClient httpClient;
public ExternalApiService(@Client("external-api") HttpClient httpClient) {
this.httpClient = httpClient;
}
// 失败时重试,最多3次,延迟1秒
@Retryable(attempts = "3", delay = "1s")
public ApiResponse callExternalApi(String endpoint) {
System.out.println("调用外部API: " + endpoint);
return httpClient.toBlocking()
.retrieve(endpoint, ApiResponse.class);
}
// 自定义重试条件
@Retryable(
attempts = "5",
delay = "2s",
multiplier = "2", // 延迟倍增:2s, 4s, 8s, 16s
includes = {IOException.class, TimeoutException.class}
)
public Data fetchData(String url) {
return httpClient.toBlocking().retrieve(url, Data.class);
}
// 熔断器模式
@CircuitBreaker(
attempts = "5",
delay = "5s",
reset = "30s" // 30秒后尝试恢复
)
public String callUnstableService() {
// 连续失败5次后,熔断器打开
// 熔断期间直接失败,不调用实际方法
// 30秒后尝试恢复(半开状态)
return httpClient.toBlocking().retrieve("/unstable");
}
}
// 重试事件监听
import io.micronaut.retry.event.RetryEvent;
import io.micronaut.context.event.ApplicationEventListener;
@Singleton
public class RetryEventListener implements
ApplicationEventListener<RetryEvent> {
@Override
public void onApplicationEvent(RetryEvent event) {
System.out.println("重试事件: 第" + event.getRetryCount() +
"次重试,异常: " + event.getThrowable().getMessage());
}
}
---
04.代理类生成
a.子类代理
a.生成机制
Micronaut在编译期为需要AOP的类生成子类代理,子类重写被拦截的方法,插入拦截器调用逻辑。
b.代理类结构
---
// 源代码
import jakarta.inject.Singleton;
@Singleton
public class UserService {
private final UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
@Monitor // 需要AOP拦截
public User findById(Long id) {
return repository.findById(id);
}
public void updateUser(User user) {
repository.update(user);
}
}
// 编译时生成的代理子类(简化版)
@Generated
public class $UserService$Intercepted extends UserService {
private final ExecutableMethod<UserService, User> findByIdMethod;
private final Interceptor<User>[] interceptors;
public $UserService$Intercepted(
UserRepository repository,
BeanContext beanContext) {
super(repository);
// 加载方法元数据
this.findByIdMethod = beanContext.getExecutableMethod(
UserService.class, "findById", Long.class);
// 加载拦截器
this.interceptors = beanContext.getBeansOfType(
Interceptor.class, Qualifiers.byStereotype(Monitor.class));
}
@Override
public User findById(Long id) {
// 构建拦截器上下文
MethodInvocationContext<UserService, User> context =
new MethodInvocationContext<>(
this, findByIdMethod, new Object[]{id});
// 执行拦截器链
return interceptors[0].intercept(context);
}
@Override
public void updateUser(User user) {
// 无拦截器的方法直接调用父类
super.updateUser(user);
}
}
// 依赖注入时注入代理类
@Controller("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
// 实际注入的是$UserService$Intercepted实例
this.userService = userService;
}
@Get("/{id}")
public User getUser(Long id) {
// 调用代理方法,触发拦截器
return userService.findById(id);
}
}
---
b.代理限制
a.Final类和方法
Micronaut使用子类代理,因此无法代理final类和final方法,需要避免使用final修饰需要AOP的类。
b.Private方法
私有方法无法被子类重写,因此不能被AOP拦截,拦截器只对public和protected方法有效。
2.5 HTTP客户端与服务器
01.HTTP服务器
a.Netty服务器架构
a.事件驱动模型
Micronaut HTTP服务器基于Netty实现,采用事件驱动的非阻塞IO模型,使用少量线程处理大量并发连接,提供卓越的吞吐量和低延迟。
b.服务器配置
---
// application.yml
// micronaut:
// server:
// port: 8080
// host: 0.0.0.0
// netty:
// max-header-size: 16KB
// max-initial-line-length: 8KB
// max-chunk-size: 16KB
// worker:
// threads: 8 # 工作线程数
// parent:
// threads: 2 # Boss线程数
// child-options:
// SO_KEEPALIVE: true
// TCP_NODELAY: true
// 服务器事件监听
import io.micronaut.runtime.server.event.*;
import io.micronaut.context.event.ApplicationEventListener;
import jakarta.inject.Singleton;
@Singleton
public class ServerEventListener implements
ApplicationEventListener<ServerStartupEvent> {
@Override
public void onApplicationEvent(ServerStartupEvent event) {
int port = event.getSource().getPort();
String host = event.getSource().getHost();
System.out.println("HTTP服务器已启动: " + host + ":" + port);
// 输出服务器配置
EmbeddedServer server = event.getSource();
System.out.println("服务器URL: " + server.getURL());
System.out.println("服务器协议: " + server.getScheme());
}
}
// 优雅关闭配置
// micronaut:
// server:
// shutdown:
// grace-period: 30s # 优雅关闭等待期
---
b.请求处理流程
a.Netty管道
HTTP请求在Netty管道中流转,经过解码器、路由器、过滤器、控制器处理器等多个Handler。
b.自定义Handler
---
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.micronaut.http.netty.channel.ChannelPipelineCustomizer;
import jakarta.inject.Singleton;
@Singleton
public class CustomChannelPipelineCustomizer
implements ChannelPipelineCustomizer {
@Override
public boolean isServerChannel() {
return true; // 作用于服务器端
}
@Override
public void doOnConnect(ChannelPipeline pipeline) {
// 在管道中添加自定义Handler
pipeline.addAfter(
ChannelPipelineCustomizer.HANDLER_HTTP_AGGREGATOR,
"custom-logger",
new CustomLoggingHandler()
);
}
}
// 自定义Handler
public class CustomLoggingHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof FullHttpRequest) {
FullHttpRequest request = (FullHttpRequest) msg;
System.out.println("收到请求: " + request.method() +
" " + request.uri());
}
ctx.fireChannelRead(msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
System.err.println("处理请求出错: " + cause.getMessage());
ctx.close();
}
}
---
02.HTTP客户端
a.声明式客户端
a.客户端定义
通过@Client注解定义HTTP客户端接口,Micronaut在编译时生成实现类,支持同步、异步和响应式调用。
b.客户端示例
---
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.annotation.*;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.core.Flowable;
@Client("https://jsonplaceholder.typicode.com")
public interface JsonPlaceholderClient {
// 同步GET请求
@Get("/posts/{id}")
Post getPost(Long id);
// 同步POST请求
@Post("/posts")
Post createPost(@Body Post post);
// 异步GET请求
@Get("/posts/{id}")
CompletableFuture<Post> getPostAsync(Long id);
// 响应式GET请求(单个结果)
@Get("/posts/{id}")
Single<Post> getPostReactive(Long id);
// 响应式GET请求(多个结果)
@Get("/posts")
Flowable<Post> getAllPosts();
// 自定义请求头
@Get("/posts/{id}")
@Header(name = "User-Agent", value = "Micronaut-Client")
Post getPostWithHeader(Long id);
// 查询参数
@Get("/posts")
List<Post> searchPosts(
@QueryValue("userId") Long userId,
@QueryValue("_limit") Integer limit
);
// DELETE请求
@Delete("/posts/{id}")
void deletePost(Long id);
// PUT请��
@Put("/posts/{id}")
Post updatePost(Long id, @Body Post post);
}
// 使用客户端
@Singleton
public class PostService {
private final JsonPlaceholderClient client;
public PostService(JsonPlaceholderClient client) {
this.client = client;
}
public Post getPost(Long id) {
return client.getPost(id);
}
public void processPostsAsync() {
// 异步调用
CompletableFuture<Post> future = client.getPostAsync(1L);
future.thenAccept(post ->
System.out.println("获取到文章: " + post.getTitle())
);
}
public void processPostsReactive() {
// 响应式调用
client.getAllPosts()
.filter(post -> post.getUserId() == 1)
.take(10)
.subscribe(post ->
System.out.println("文章: " + post.getTitle())
);
}
}
---
b.低级客户端
a.HttpClient接口
对于更复杂的场景,可以直接使用HttpClient接口进行低级HTTP操作,完全控制请求和响应。
b.低级客户端示例
---
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.*;
import jakarta.inject.Singleton;
@Singleton
public class CustomHttpService {
private final HttpClient httpClient;
public CustomHttpService(@Client("https://api.example.com") HttpClient httpClient) {
this.httpClient = httpClient;
}
public String makeCustomRequest() {
// 构建请求
MutableHttpRequest<String> request = HttpRequest
.POST("/api/data", "{\"key\":\"value\"}")
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer token123")
.header("X-Custom-Header", "custom-value");
// 同步调用
HttpResponse<String> response =
httpClient.toBlocking().exchange(request, String.class);
System.out.println("状态码: " + response.getStatus());
System.out.println("响应头: " + response.getHeaders().asMap());
return response.body();
}
public void makeReactiveRequest() {
// 响应式调用
Flux<HttpResponse<String>> flux = Flux.from(
httpClient.exchange(
HttpRequest.GET("/api/items"),
String.class
)
);
flux.subscribe(
response -> System.out.println("响应: " + response.body()),
error -> System.err.println("错误: " + error.getMessage()),
() -> System.out.println("完成")
);
}
public void streamingRequest() {
// 流式读取响应
Flux<ByteBuffer<?>> stream = Flux.from(
httpClient.dataStream(HttpRequest.GET("/api/large-file"))
);
stream.subscribe(buffer -> {
System.out.println("接收数据块: " + buffer.toByteArray().length + " bytes");
});
}
}
---
03.客户端配置
a.连接池管理
a.连接池配置
HTTP客户端使用连接池复用连接,可以配置连接池大小、超时时间、Keep-Alive等参数。
b.配置示例
---
// application.yml
// micronaut:
// http:
// client:
// pool:
// enabled: true
// max-connections: 50 # 最大连接数
// read-timeout: 30s # 读超时
// connect-timeout: 10s # 连接超时
// read-idle-timeout: 60s # 读空闲超时
// connection-pool-idle-timeout: 30s
// 针对特定服务的配置
// micronaut:
// http:
// services:
// user-service:
// urls:
// - http://user-service-1:8080
// - http://user-service-2:8080
// pool:
// max-connections: 20
// read-timeout: 10s
// load-balancer:
// strategy: round-robin
// 代码中配置客户端
import io.micronaut.http.client.HttpClientConfiguration;
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Bean;
@Factory
public class HttpClientFactory {
@Bean
@Named("custom-client")
public HttpClient createCustomClient() {
HttpClientConfiguration config = new HttpClientConfiguration();
config.setReadTimeout(Duration.ofSeconds(30));
config.setConnectTimeout(Duration.ofSeconds(10));
config.getPoolConfiguration().setMaxConnections(100);
return HttpClient.create(
new URL("https://api.example.com"),
config
);
}
}
---
b.负载均衡
a.客户端负载均衡
当配置多个服务实例URL时,Micronaut自动实现客户端负载均衡,支持轮询、随机等策略。
b.负载均衡示例
---
// application.yml配置多个服务实例
// micronaut:
// http:
// services:
// product-service:
// urls:
// - http://product-1:8080
// - http://product-2:8080
// - http://product-3:8080
// load-balancer:
// strategy: round-robin # 轮询策略
// 定义客户端
@Client(id = "product-service")
public interface ProductClient {
@Get("/products/{id}")
Product getProduct(Long id);
}
// Micronaut自动在3个实例间负载均衡
@Singleton
public class ProductService {
private final ProductClient productClient;
public ProductService(ProductClient productClient) {
this.productClient = productClient;
}
public Product getProduct(Long id) {
// 自动负载均衡到product-1/2/3
return productClient.getProduct(id);
}
}
// 自定义负载均衡策略
import io.micronaut.http.client.loadbalance.ServiceInstanceList;
import io.micronaut.discovery.ServiceInstance;
@Singleton
@Replaces(RoundRobinLoadBalancer.class)
public class WeightedLoadBalancer implements LoadBalancer {
@Override
public Publisher<ServiceInstance> select(ServiceInstanceList instances) {
// 根据权重选择实例
List<ServiceInstance> list = instances.getInstances();
ServiceInstance selected = selectByWeight(list);
return Publishers.just(selected);
}
private ServiceInstance selectByWeight(List<ServiceInstance> instances) {
// 权重选择逻辑
return instances.get(0);
}
}
---
02.服务器端点
a.Controller定义
a.控制器注解
@Controller注解定义HTTP端点,支持路径前缀、版本控制等配置。
b.控制器示例
---
import io.micronaut.http.annotation.*;
import io.micronaut.http.HttpResponse;
import jakarta.inject.Singleton;
@Controller("/api/v1/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
// GET请求
@Get("/{id}")
public User getUser(Long id) {
return userService.findById(id);
}
// POST请求
@Post
public HttpResponse<User> createUser(@Body UserRequest request) {
User user = userService.create(request);
return HttpResponse.created(user);
}
// PUT请求
@Put("/{id}")
public User updateUser(Long id, @Body UserRequest request) {
return userService.update(id, request);
}
// DELETE请求
@Delete("/{id}")
@Status(HttpStatus.NO_CONTENT)
public void deleteUser(Long id) {
userService.delete(id);
}
// 查询参数
@Get
public List<User> searchUsers(
@QueryValue("name") Optional<String> name,
@QueryValue("page") @DefaultValue("0") int page,
@QueryValue("size") @DefaultValue("20") int size) {
return userService.search(name.orElse(null), page, size);
}
// 请求头
@Get("/profile")
public User getCurrentUser(
@Header("Authorization") String authToken) {
String userId = extractUserId(authToken);
return userService.findById(Long.parseLong(userId));
}
// Cookie
@Get("/preferences")
public UserPreferences getPreferences(
@CookieValue("session_id") String sessionId) {
return userService.getPreferences(sessionId);
}
}
---
b.响应式端点
a.响应式返回类型
控制器方法可以返回响应式类型,实现非阻塞的异步处理。
b.响应式端点示例
---
import io.micronaut.http.annotation.*;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;
@Controller("/api/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
// 返回Mono(单个结果)
@Get("/{id}")
public Mono<Product> getProduct(Long id) {
return productService.findByIdAsync(id);
}
// 返回Flux(多个结果)
@Get
public Flux<Product> getAllProducts() {
return productService.findAllAsync();
}
// Server-Sent Events
@Get(uri = "/stream", produces = MediaType.TEXT_EVENT_STREAM)
public Flux<Product> streamProducts() {
// 持续推送产品更新
return productService.watchProductUpdates();
}
// 响应式POST
@Post
public Mono<HttpResponse<Product>> createProduct(@Body Mono<Product> product) {
return product
.flatMap(p -> productService.saveAsync(p))
.map(HttpResponse::created);
}
}
// 响应式服务实现
@Singleton
public class ProductService {
private final ProductRepository repository;
public ProductService(ProductRepository repository) {
this.repository = repository;
}
public Mono<Product> findByIdAsync(Long id) {
return Mono.fromCallable(() -> repository.findById(id))
.subscribeOn(Schedulers.boundedElastic());
}
public Flux<Product> findAllAsync() {
return Flux.fromIterable(repository.findAll())
.subscribeOn(Schedulers.boundedElastic());
}
public Flux<Product> watchProductUpdates() {
// 模拟实时推送
return Flux.interval(Duration.ofSeconds(5))
.map(i -> repository.findLatest());
}
}
---
03.客户端过滤器
a.HttpClientFilter
a.请求拦截
HttpClientFilter可以拦截所有HTTP客户端请求,用于添加认证头、日志记录、重试等通用逻辑。
b.过滤器示例
---
import io.micronaut.http.*;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.filter.ClientFilterChain;
import io.micronaut.http.filter.HttpClientFilter;
import org.reactivestreams.Publisher;
// 认证过滤器
@Filter("/api/**")
public class AuthFilter implements HttpClientFilter {
private final TokenProvider tokenProvider;
public AuthFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public Publisher<? extends HttpResponse<?>> doFilter(
MutableHttpRequest<?> request,
ClientFilterChain chain) {
// 添加认证头
String token = tokenProvider.getAccessToken();
request.header("Authorization", "Bearer " + token);
return chain.proceed(request);
}
}
// 日志过滤器
@Filter("/**")
public class LoggingClientFilter implements HttpClientFilter {
private static final Logger log =
LoggerFactory.getLogger(LoggingClientFilter.class);
@Override
public Publisher<? extends HttpResponse<?>> doFilter(
MutableHttpRequest<?> request,
ClientFilterChain chain) {
log.info("发送请求: {} {}", request.getMethod(), request.getUri());
return Flux.from(chain.proceed(request))
.doOnNext(response ->
log.info("收到响应: 状态={}", response.getStatus())
)
.doOnError(error ->
log.error("请求失败: {}", error.getMessage())
);
}
}
// 重试过滤器
@Filter("/**")
public class RetryClientFilter implements HttpClientFilter {
@Override
public Publisher<? extends HttpResponse<?>> doFilter(
MutableHttpRequest<?> request,
ClientFilterChain chain) {
return Flux.from(chain.proceed(request))
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
.filter(throwable -> throwable instanceof IOException)
);
}
}
---
b.自定义序列化
a.MediaTypeCodec
可以注册自定义的编解码器,支持自定义序列化格式。
b.自定义编解码器
---
import io.micronaut.http.codec.MediaTypeCodec;
import io.micronaut.http.MediaType;
import jakarta.inject.Singleton;
import java.io.*;
@Singleton
public class CustomXmlCodec implements MediaTypeCodec {
@Override
public Collection<MediaType> getMediaTypes() {
return Collections.singletonList(MediaType.APPLICATION_XML_TYPE);
}
@Override
public <T> T decode(Argument<T> type, InputStream inputStream)
throws CodecException {
// 自定义XML反序列化
try {
String xml = new String(inputStream.readAllBytes());
// 解析XML
return parseXml(xml, type.getType());
} catch (IOException e) {
throw new CodecException("XML解析失败", e);
}
}
@Override
public <T> void encode(T object, OutputStream outputStream)
throws CodecException {
// 自定义XML序列化
try {
String xml = toXml(object);
outputStream.write(xml.getBytes());
} catch (IOException e) {
throw new CodecException("XML生成失败", e);
}
}
private <T> T parseXml(String xml, Class<T> type) {
// XML解析逻辑
return null;
}
private String toXml(Object object) {
// XML生成逻辑
return "";
}
}
---
2.6 配置管理
01.配置加载
a.配置源优先级
a.优先级顺序
Micronaut从多个配置源加载配置,按优先级从高到低依次为命令行参数、系统属性、环境变量、application-{env}.yml、application.yml,高优先级配置会覆盖低优先级配置。
b.配置加载示例
---
// application.yml(优先级最低)
// datasource:
// url: jdbc:h2:mem:default
// username: sa
// password: ""
// pool:
// max-size: 10
// application-prod.yml(环境配置,优先级更高)
// datasource:
// url: jdbc:mysql://prod-db:3306/mydb
// username: prod_user
// password: ${DB_PASSWORD} # 从环境变量读取
// 环境变量(优先级更高)
// export DB_PASSWORD=secret123
// export DATASOURCE_POOL_MAX_SIZE=50
// 系统属性(优先级更高)
// java -Ddatasource.url=jdbc:mysql://override:3306/db -jar app.jar
// 命令行参数(优先级最高)
// java -jar app.jar --datasource.username=admin
// 最终生效的配置
import io.micronaut.context.env.Environment;
@Singleton
public class ConfigInspector {
private final Environment environment;
public ConfigInspector(Environment environment) {
this.environment = environment;
}
public void printConfig() {
// 读取最终生效的配置
String url = environment.getProperty("datasource.url", String.class)
.orElse("unknown");
String username = environment.getProperty("datasource.username", String.class)
.orElse("unknown");
Integer poolSize = environment.getProperty("datasource.pool.max-size",
Integer.class).orElse(10);
System.out.println("数据库URL: " + url);
System.out.println("用户名: " + username);
System.out.println("连接池大小: " + poolSize);
}
}
---
b.配置文件格式
a.支持的格式
Micronaut支持YAML、Properties、JSON、HOCON等多种配置文件格式,推荐使用YAML格式。
b.格式示例
---
// application.yml(YAML格式,推荐)
// micronaut:
// application:
// name: order-service
// server:
// port: 8080
// datasource:
// url: jdbc:mysql://localhost:3306/mydb
// pool:
// max-size: 20
// application.properties(Properties格式)
// micronaut.application.name=order-service
// micronaut.server.port=8080
// datasource.url=jdbc:mysql://localhost:3306/mydb
// datasource.pool.max-size=20
// application.json(JSON格式)
// {
// "micronaut": {
// "application": {
// "name": "order-service"
// },
// "server": {
// "port": 8080
// }
// },
// "datasource": {
// "url": "jdbc:mysql://localhost:3306/mydb",
// "pool": {
// "max-size": 20
// }
// }
// }
// 指定配置文件
// java -Dmicronaut.config.files=file:/path/to/config.yml -jar app.jar
---
02.配置绑定
a.@ConfigurationProperties
a.类型安全配置
@ConfigurationProperties注解将配置绑定到POJO类,提供类型安全的配置访问,支持嵌套配置和验证。
b.配置类示例
---
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.core.annotation.Nullable;
import jakarta.validation.constraints.*;
import java.time.Duration;
@ConfigurationProperties("app")
public class AppConfig {
@NotBlank
private String name;
@NotBlank
private String version;
@Min(1)
@Max(65535)
private int port = 8080;
@Nullable
private Duration requestTimeout;
private DatabaseConfig database;
private CacheConfig cache;
// getter和setter省略
@ConfigurationProperties("database")
public static class DatabaseConfig {
@NotBlank
private String url;
@NotBlank
private String username;
@NotBlank
private String password;
@Min(1)
@Max(1000)
private int maxConnections = 50;
private PoolConfig pool;
@ConfigurationProperties("pool")
public static class PoolConfig {
private Duration maxLifetime = Duration.ofMinutes(30);
private Duration idleTimeout = Duration.ofMinutes(10);
// getter和setter省略
}
// getter和setter省略
}
@ConfigurationProperties("cache")
public static class CacheConfig {
private boolean enabled = true;
private String provider = "caffeine";
private Duration ttl = Duration.ofMinutes(10);
// getter和setter省略
}
}
// application.yml
// app:
// name: my-service
// version: 1.0.0
// port: 9090
// request-timeout: 30s
// database:
// url: jdbc:postgresql://localhost:5432/mydb
// username: dbuser
// password: dbpass
// max-connections: 100
// pool:
// max-lifetime: 60m
// idle-timeout: 20m
// cache:
// enabled: true
// provider: redis
// ttl: 5m
// 使用配置
@Singleton
public class AppService {
private final AppConfig config;
public AppService(AppConfig config) {
this.config = config;
System.out.println("应用名称: " + config.getName());
System.out.println("数据库URL: " + config.getDatabase().getUrl());
System.out.println("缓存提供者: " + config.getCache().getProvider());
}
}
---
b.@Value注入
a.属性值注入
@Value注解直接将配置属性注入到字段或方法参数,支持占位符和默认值。
b.Value注入示例
---
import io.micronaut.context.annotation.Value;
import jakarta.inject.Singleton;
@Singleton
public class ServiceConfig {
@Value("${micronaut.application.name}")
private String appName;
@Value("${micronaut.server.port:8080}") // 默认值8080
private int serverPort;
@Value("${api.timeout:30s}")
private Duration apiTimeout;
@Value("${feature.enabled:false}")
private boolean featureEnabled;
@Value("${api.base-url}")
private String apiBaseUrl;
public void printConfig() {
System.out.println("应用名: " + appName);
System.out.println("端口: " + serverPort);
System.out.println("API超时: " + apiTimeout);
System.out.println("功能启用: " + featureEnabled);
System.out.println("API地址: " + apiBaseUrl);
}
}
// 构造器注入配置值
@Singleton
public class EmailService {
private final String smtpHost;
private final int smtpPort;
private final String from;
public EmailService(
@Value("${email.smtp.host}") String smtpHost,
@Value("${email.smtp.port:587}") int smtpPort,
@Value("${email.from}") String from) {
this.smtpHost = smtpHost;
this.smtpPort = smtpPort;
this.from = from;
}
public void sendEmail(String to, String subject, String body) {
System.out.println("SMTP服务器: " + smtpHost + ":" + smtpPort);
System.out.println("发件人: " + from);
}
}
---
03.动态配置
a.@Refreshable刷新
a.配置刷新机制
@Refreshable注解标记的Bean在配置变更时会被重新创建,实现配置的动态更新。
b.刷新示例
---
import io.micronaut.context.annotation.Refreshable;
import io.micronaut.context.annotation.ConfigurationProperties;
@Refreshable // 可刷新Bean
@ConfigurationProperties("dynamic")
public class DynamicConfig {
private int maxConnections;
private Duration timeout;
private boolean debugMode;
public DynamicConfig() {
System.out.println("DynamicConfig创建");
}
// getter和setter省略
}
@Singleton
public class ConnectionManager {
private final DynamicConfig config;
public ConnectionManager(DynamicConfig config) {
this.config = config;
}
public void createConnection() {
// 总是使用最新的配置值
int max = config.getMaxConnections();
System.out.println("最大连接数: " + max);
}
}
// 触发配置刷新
// POST http://localhost:8080/refresh
// 返回:{"changed": ["dynamic.max-connections", "dynamic.timeout"]}
// 配置刷新流程
// 1. 接收/refresh请求
// 2. 重新加载配置文件
// 3. 对比配置变更
// 4. 销毁@Refreshable Bean
// 5. 重新创建Bean(使用新配置)
// 6. 返回变更的配置项列表
// 启用refresh端点
// application.yml
// endpoints:
// refresh:
// enabled: true
// sensitive: false
---
b.配置监听
a.RefreshEvent
可以监听配置刷新事件,在配置变更时执行自定义逻辑。
b.刷新事件监听
---
import io.micronaut.runtime.context.scope.refresh.RefreshEvent;
import io.micronaut.context.event.ApplicationEventListener;
import jakarta.inject.Singleton;
@Singleton
public class ConfigRefreshListener implements
ApplicationEventListener<RefreshEvent> {
private final ConnectionPool connectionPool;
private final CacheManager cacheManager;
public ConfigRefreshListener(ConnectionPool connectionPool,
CacheManager cacheManager) {
this.connectionPool = connectionPool;
this.cacheManager = cacheManager;
}
@Override
public void onApplicationEvent(RefreshEvent event) {
Set<String> changedKeys = event.getChanges();
System.out.println("配置已刷新,变更项: " + changedKeys);
// 如果数据库配置变更,重新初始化连接池
if (changedKeys.stream().anyMatch(key -> key.startsWith("datasource"))) {
connectionPool.reinitialize();
}
// 如果缓存配置变更,清除缓存
if (changedKeys.stream().anyMatch(key -> key.startsWith("cache"))) {
cacheManager.clearAll();
}
}
}
---
04.外部配置中心
a.Consul配置
a.Consul KV集成
Micronaut支持从Consul KV存储读取配置,实现配置的集中管理和动态更新。
b.Consul配置示例
---
// Maven依赖
// <dependency>
// <groupId>io.micronaut.discovery</groupId>
// <artifactId>micronaut-discovery-client</artifactId>
// </dependency>
// bootstrap.yml(在application.yml之前加载)
// micronaut:
// application:
// name: order-service
// config-client:
// enabled: true
// consul:
// client:
// config:
// enabled: true
// format: YAML
// defaultZone: ${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}
// Consul KV存储结构
// config/
// application/ # 全局配置(所有应用共享)
// data: |
// logging:
// level: INFO
// order-service/ # 应用特定配置
// data: |
// order:
// max-items: 100
// timeout: 30s
// order-service,prod/ # 环境特定配置
// data: |
// datasource:
// url: jdbc:mysql://prod-db/orderdb
// 配置读取
@ConfigurationProperties("order")
@Refreshable // 支持Consul配置变更推送
public class OrderConfig {
private int maxItems;
private Duration timeout;
// getter和setter省略
}
// 使用配置
@Singleton
public class OrderService {
private final OrderConfig config;
public OrderService(OrderConfig config) {
this.config = config;
}
public void processOrder(Order order) {
int maxItems = config.getMaxItems(); // 从Consul读取
if (order.getItems().size() > maxItems) {
throw new IllegalArgumentException("订单商品数超过限制");
}
}
}
// Consul配置变更自动刷新
// 1. 在Consul UI修改配置
// 2. Micronaut自动检测变更
// 3. 发布RefreshEvent
// 4. 重新创建@Refreshable Bean
---
b.AWS Parameter Store
a.AWS集成
Micronaut支持从AWS Systems Manager Parameter Store读取配置,适合AWS云环境。
b.AWS配置示例
---
// Maven依赖
// <dependency>
// <groupId>io.micronaut.aws</groupId>
// <artifactId>micronaut-aws-parameter-store</artifactId>
// </dependency>
// bootstrap.yml
// aws:
// client:
// system-manager:
// parameterstore:
// enabled: true
// useSecureParameters: true
// micronaut:
// application:
// name: order-service
// config-client:
// enabled: true
// AWS Parameter Store结构
// /config/application/ # 全局配置
// database.url
// database.username
// /config/order-service/ # 应用配置
// order.max-items
// order.timeout
// /config/order-service_prod/ # 环境配置
// database.password # 使用SecureString加密
// 配置类
@ConfigurationProperties("order")
public class OrderConfig {
private int maxItems;
private Duration timeout;
// getter和setter省略
}
// AWS IAM权限要求
// {
// "Version": "2012-10-17",
// "Statement": [{
// "Effect": "Allow",
// "Action": [
// "ssm:GetParameter",
// "ssm:GetParameters",
// "ssm:GetParametersByPath"
// ],
// "Resource": "arn:aws:ssm:*:*:parameter/config/*"
// }]
// }
---
03.环境变量注入
a.环境变量映射
a.命名约定
环境变量名使用大写字母和下划线,Micronaut自动映射到配置属性,如DATASOURCE_URL映射到datasource.url。
b.环境变量示例
---
// 环境变量定义
// export MICRONAUT_APPLICATION_NAME=my-service
// export MICRONAUT_SERVER_PORT=9090
// export DATASOURCE_URL=jdbc:mysql://db:3306/mydb
// export DATASOURCE_USERNAME=dbuser
// export DATASOURCE_PASSWORD=secret123
// export APP_FEATURE_ENABLED=true
// export APP_MAX_CONNECTIONS=50
// 配置类
@ConfigurationProperties("app")
public class AppConfig {
private boolean featureEnabled;
private int maxConnections;
// getter和setter省略
}
// 读取配置
@Singleton
public class ConfigService {
private final AppConfig appConfig;
private final Environment environment;
public ConfigService(AppConfig appConfig, Environment environment) {
this.appConfig = appConfig;
this.environment = environment;
}
public void printConfig() {
// 通过ConfigurationProperties读取
System.out.println("功能启用: " + appConfig.isFeatureEnabled());
System.out.println("最大连接: " + appConfig.getMaxConnections());
// 直接从Environment读取
String appName = environment.getProperty("micronaut.application.name",
String.class).orElse("unknown");
System.out.println("应用名: " + appName);
// 读取原始环境变量
String javaHome = System.getenv("JAVA_HOME");
System.out.println("JAVA_HOME: " + javaHome);
}
}
// Docker Compose配置
// version: '3.8'
// services:
// app:
// image: micronaut-app:latest
// environment:
// - MICRONAUT_ENVIRONMENTS=prod
// - DATASOURCE_URL=jdbc:mysql://mysql:3306/proddb
// - DATASOURCE_USERNAME=produser
// - DATASOURCE_PASSWORD=${DB_PASSWORD}
// - APP_MAX_CONNECTIONS=100
// env_file:
// - .env.prod
---
b.占位符解析
a.属性占位符
配置中可以使用${...}占位符引用其他配置属性或环境变量。
b.占位符示例
---
// application.yml
// app:
// name: order-service
// version: 1.0.0
// display-name: ${app.name} v${app.version} # 引用其他属性
//
// datasource:
// host: ${DB_HOST:localhost} # 从环境变量读取,默认localhost
// port: ${DB_PORT:3306}
// database: ${DB_NAME:mydb}
// url: jdbc:mysql://${datasource.host}:${datasource.port}/${datasource.database}
// username: ${DB_USER:root}
// password: ${DB_PASSWORD:}
//
// api:
// base-url: ${API_PROTOCOL:http}://${API_HOST}:${API_PORT:8080}
// endpoints:
// users: ${api.base-url}/users
// orders: ${api.base-url}/orders
// 读取配置
@Singleton
public class ApiClient {
@Value("${api.endpoints.users}")
private String usersEndpoint;
@Value("${api.endpoints.orders}")
private String ordersEndpoint;
public void printEndpoints() {
System.out.println("用户API: " + usersEndpoint);
System.out.println("订单API: " + ordersEndpoint);
}
}
// 环境变量
// export DB_HOST=prod-db.example.com
// export DB_PORT=3306
// export DB_NAME=production
// export DB_USER=prod_user
// export DB_PASSWORD=secret123
// export API_HOST=api.example.com
// export API_PORT=443
// export API_PROTOCOL=https
// 最终配置值
// datasource.url=jdbc:mysql://prod-db.example.com:3306/production
// api.endpoints.users=https://api.example.com:443/users
---
2.7 附:Bean生命周期
01.生命周期阶段
a.Bean创建阶段
a.实例化顺序
BeanContext根据依赖关系图按拓扑顺序创建Bean,确保依赖的Bean先于依赖它的Bean创建,避免依赖未就绪的问题。
b.创建流程
---
// Bean创建流程示例
import jakarta.inject.Singleton;
import jakarta.annotation.PostConstruct;
// 第1个创建:无依赖
@Singleton
public class ConfigLoader {
public ConfigLoader() {
System.out.println("步骤1: ConfigLoader构造器执行");
}
@PostConstruct
public void init() {
System.out.println("步骤2: ConfigLoader初始化");
}
}
// 第2个创建:依赖ConfigLoader
@Singleton
public class DataSource {
private final ConfigLoader configLoader;
public DataSource(ConfigLoader configLoader) {
System.out.println("步骤3: DataSource构造器执行");
this.configLoader = configLoader;
}
@PostConstruct
public void init() {
System.out.println("步骤4: DataSource初始化");
}
}
// 第3个创建:依赖DataSource
@Singleton
public class UserRepository {
private final DataSource dataSource;
public UserRepository(DataSource dataSource) {
System.out.println("步骤5: UserRepository构造器执行");
this.dataSource = dataSource;
}
@PostConstruct
public void init() {
System.out.println("步骤6: UserRepository初始化");
}
}
// 第4个创建:依赖UserRepository
@Singleton
public class UserService {
private final UserRepository repository;
public UserService(UserRepository repository) {
System.out.println("步骤7: UserService构造器执行");
this.repository = repository;
}
@PostConstruct
public void init() {
System.out.println("步骤8: UserService初始化");
}
}
// 应用启动输出
// 步骤1: ConfigLoader构造器执行
// 步骤2: ConfigLoader初始化
// 步骤3: DataSource构造器执行
// 步骤4: DataSource初始化
// 步骤5: UserRepository构造器执行
// 步骤6: UserRepository初始化
// 步骤7: UserService构造器执行
// 步骤8: UserService初始化
---
b.初始化回调
a.@PostConstruct注解
@PostConstruct方法在Bean构造完成、依赖注入完成后执行,用于初始化资源、建立连接等操作。
b.PostConstruct示例
---
import jakarta.annotation.PostConstruct;
import jakarta.inject.Singleton;
import java.sql.Connection;
@Singleton
public class DatabaseInitializer {
private final DataSource dataSource;
private Connection connection;
public DatabaseInitializer(DataSource dataSource) {
this.dataSource = dataSource;
System.out.println("构造器:dataSource注入完成");
}
@PostConstruct
public void initialize() {
System.out.println("@PostConstruct:开始初始化");
try {
// 建立数据库连接
connection = dataSource.getConnection();
System.out.println("数据库连接已建立");
// 执行初始化SQL
Statement stmt = connection.createStatement();
stmt.execute("CREATE TABLE IF NOT EXISTS users (...)");
System.out.println("数据库表初始化完成");
} catch (SQLException e) {
throw new RuntimeException("数据库初始化失败", e);
}
}
public Connection getConnection() {
return connection;
}
}
// 多个@PostConstruct方法
@Singleton
public class MultiInitBean {
@PostConstruct
public void init1() {
System.out.println("初始化步骤1");
}
@PostConstruct
public void init2() {
System.out.println("初始化步骤2");
}
// 执行顺序不确定,避免依赖顺序
}
---
02.生命周期事件
a.ApplicationEventListener
a.事件类型
Micronaut提供丰富的生命周期事件,包括启动事件、关闭事件、Bean创建事件等,可以监听这些事件执行自定义逻辑。
b.事件监听示例
---
import io.micronaut.context.event.ApplicationEventListener;
import io.micronaut.runtime.event.*;
import io.micronaut.context.event.*;
import jakarta.inject.Singleton;
// 监听应用启动事件
@Singleton
public class StartupInitializer implements
ApplicationEventListener<ApplicationStartupEvent> {
@Inject
private DataMigrationService migrationService;
@Override
public void onApplicationEvent(ApplicationStartupEvent event) {
System.out.println("应用启动完成,执行数据迁移");
migrationService.migrate();
}
}
// 监听服务器启动事件
@Singleton
public class ServerReadyListener implements
ApplicationEventListener<ServerStartupEvent> {
@Override
public void onApplicationEvent(ServerStartupEvent event) {
EmbeddedServer server = event.getSource();
System.out.println("服务器已就绪: " + server.getURL());
// 注册到服务发现
registerToServiceDiscovery(server);
}
private void registerToServiceDiscovery(EmbeddedServer server) {
// 服务注册逻辑
}
}
// 监听应用关闭事件
@Singleton
public class ShutdownHandler implements
ApplicationEventListener<ShutdownEvent> {
@Inject
private ConnectionPool connectionPool;
@Override
public void onApplicationEvent(ShutdownEvent event) {
System.out.println("应用即将关闭,清理资源");
// 关闭连接池
connectionPool.close();
// 注销服务
deregisterFromServiceDiscovery();
}
private void deregisterFromServiceDiscovery() {
// 服务注销逻辑
}
}
// 监听Bean创建事件
import io.micronaut.context.event.BeanCreatedEvent;
@Singleton
public class DataSourceListener implements
ApplicationEventListener<BeanCreatedEvent<DataSource>> {
@Override
public void onApplicationEvent(BeanCreatedEvent<DataSource> event) {
DataSource dataSource = event.getBean();
System.out.println("DataSource Bean已创建");
// 配置连接池
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikari = (HikariDataSource) dataSource;
hikari.setMaximumPoolSize(50);
}
}
}
---
b.自定义生命周期事件
a.事件定义与发布
可以定义自定义生命周期事件,通过ApplicationEventPublisher发布,实现组件间解耦通信。
b.自定义事件示例
---
// 定义数据迁移事件
public class DataMigrationEvent {
private final String version;
private final boolean success;
private final long duration;
public DataMigrationEvent(String version, boolean success, long duration) {
this.version = version;
this.success = success;
this.duration = duration;
}
public String getVersion() { return version; }
public boolean isSuccess() { return success; }
public long getDuration() { return duration; }
}
// 发布事件
import io.micronaut.context.event.ApplicationEventPublisher;
@Singleton
public class DataMigrationService {
private final ApplicationEventPublisher<DataMigrationEvent> eventPublisher;
public DataMigrationService(
ApplicationEventPublisher<DataMigrationEvent> eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void migrate() {
long start = System.currentTimeMillis();
String version = "1.0.0";
try {
// 执行迁移
executeMigration(version);
long duration = System.currentTimeMillis() - start;
// 发布成功事件
eventPublisher.publishEvent(
new DataMigrationEvent(version, true, duration));
} catch (Exception e) {
long duration = System.currentTimeMillis() - start;
// 发布失败事件
eventPublisher.publishEvent(
new DataMigrationEvent(version, false, duration));
throw e;
}
}
private void executeMigration(String version) {
// 迁移逻辑
}
}
// 监听迁移事件
@Singleton
public class MigrationEventListener implements
ApplicationEventListener<DataMigrationEvent> {
@Inject
private AuditLogger auditLogger;
@Inject
private MetricsService metricsService;
@Override
public void onApplicationEvent(DataMigrationEvent event) {
// 记录审计日���
auditLogger.log("数据迁移",
"版本: " + event.getVersion() +
", 结果: " + (event.isSuccess() ? "成功" : "失败") +
", 耗时: " + event.getDuration() + "ms");
// 记录指标
metricsService.recordMigration(
event.getVersion(),
event.isSuccess(),
event.getDuration()
);
// 失败时告警
if (!event.isSuccess()) {
sendAlert("数据迁移失败: " + event.getVersion());
}
}
private void sendAlert(String message) {
// 发送告警
}
}
---
03.Bean销毁
a.@PreDestroy注解
a.销毁回调
@PreDestroy方法在Bean销毁前执行,用于关闭连接、释放资源、清理缓存等清理操作。
b.PreDestroy示例
---
import jakarta.annotation.PreDestroy;
import jakarta.inject.Singleton;
@Singleton
public class ConnectionManager {
private final List<Connection> connections = new ArrayList<>();
public Connection getConnection() {
Connection conn = createConnection();
connections.add(conn);
return conn;
}
@PreDestroy
public void cleanup() {
System.out.println("@PreDestroy:开始清理连接");
for (Connection conn : connections) {
try {
if (!conn.isClosed()) {
conn.close();
System.out.println("连接已关闭");
}
} catch (SQLException e) {
System.err.println("关闭连接失败: " + e.getMessage());
}
}
connections.clear();
System.out.println("所有连接已清理");
}
private Connection createConnection() {
// 创建连接
return null;
}
}
// 实现Closeable接口
@Singleton
public class ResourceHolder implements Closeable {
private InputStream inputStream;
public ResourceHolder() throws IOException {
this.inputStream = new FileInputStream("/data/resource.dat");
}
@Override
@PreDestroy
public void close() throws IOException {
if (inputStream != null) {
inputStream.close();
System.out.println("资源已关闭");
}
}
}
---
b.销毁顺序
a.依赖反向销毁
Bean销毁顺序与创建顺序相反,先销毁依赖其他Bean的Bean,后销毁被依赖的Bean,确保销毁时依赖仍可用。
b.销毁顺序示例
---
// Bean依赖关系:Controller -> Service -> Repository -> DataSource
@Singleton
public class DataSource {
@PreDestroy
public void close() {
System.out.println("4. DataSource销毁");
}
}
@Singleton
public class Repository {
private final DataSource dataSource;
public Repository(DataSource dataSource) {
this.dataSource = dataSource;
}
@PreDestroy
public void cleanup() {
System.out.println("3. Repository销毁");
}
}
@Singleton
public class Service {
private final Repository repository;
public Service(Repository repository) {
this.repository = repository;
}
@PreDestroy
public void cleanup() {
System.out.println("2. Service销毁");
}
}
@Controller("/api")
public class Controller {
private final Service service;
public Controller(Service service) {
this.service = service;
}
@PreDestroy
public void cleanup() {
System.out.println("1. Controller销毁");
}
}
// 应用关闭输出顺序
// 1. Controller销毁
// 2. Service销毁
// 3. Repository销毁
// 4. DataSource销毁
---
02.生命周期接口
a.Lifecycle接口
a.接口定义
实现io.micronaut.context.LifeCycle接口可以更精细地控制Bean的启动和停止逻辑。
b.Lifecycle实现
---
import io.micronaut.context.LifeCycle;
import jakarta.inject.Singleton;
@Singleton
public class BackgroundTaskManager implements LifeCycle<BackgroundTaskManager> {
private ScheduledExecutorService executor;
private volatile boolean running = false;
@Override
public BackgroundTaskManager start() {
System.out.println("启动后台任务管理器");
executor = Executors.newScheduledThreadPool(4);
running = true;
// 启动定时任务
executor.scheduleAtFixedRate(
() -> System.out.println("执行后台任务"),
0, 10, TimeUnit.SECONDS
);
return this;
}
@Override
public BackgroundTaskManager stop() {
System.out.println("停止后台任务管理器");
running = false;
if (executor != null) {
executor.shutdown();
try {
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
return this;
}
@Override
public boolean isRunning() {
return running;
}
}
// 启动和停止由BeanContext自动管理
// 应用启动时自动调用start()
// 应用关闭时自动调用stop()
---
b.DisposableBean接口
a.资源释放
实现DisposableBean接口的dispose()方法,在Bean销毁时释放资源。
b.DisposableBean示例
---
import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Factory;
import io.micronaut.core.io.Closeable;
@Factory
public class ResourceFactory {
@Bean
@Singleton
public CustomResource createResource() {
return new CustomResource();
}
}
public class CustomResource implements Closeable {
private final FileChannel channel;
public CustomResource() throws IOException {
this.channel = FileChannel.open(
Paths.get("/data/resource.dat"),
StandardOpenOption.READ
);
System.out.println("资源已打开");
}
public ByteBuffer read(int size) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(size);
channel.read(buffer);
return buffer;
}
@Override
public void close() throws IOException {
if (channel != null && channel.isOpen()) {
channel.close();
System.out.println("资源已关闭");
}
}
}
// BeanContext关闭时自动调用close()
---
03.作用域生命周期
a.RequestScope生命周期
a.请求级Bean
@RequestScope标记的Bean在每个HTTP请求开始时创建,请求结束时销毁,适合存储请求级上下文信息。
b.RequestScope示例
---
import io.micronaut.runtime.http.scope.RequestScope;
import jakarta.annotation.PreDestroy;
@RequestScope
public class RequestTraceContext {
private final String traceId;
private final long startTime;
private Map<String, Object> attributes;
public RequestTraceContext() {
this.traceId = UUID.randomUUID().toString();
this.startTime = System.currentTimeMillis();
this.attributes = new HashMap<>();
System.out.println("请求上下文创建: " + traceId);
}
public void setAttribute(String key, Object value) {
attributes.put(key, value);
}
public Object getAttribute(String key) {
return attributes.get(key);
}
public String getTraceId() {
return traceId;
}
public long getElapsedTime() {
return System.currentTimeMillis() - startTime;
}
@PreDestroy
public void cleanup() {
long duration = getElapsedTime();
System.out.println("请求完成: " + traceId + ", 耗时: " + duration + "ms");
attributes.clear();
}
}
// 在同一请求中共享RequestTraceContext
@Controller("/api")
public class ApiController {
private final RequestTraceContext traceContext;
private final LogService logService;
public ApiController(RequestTraceContext traceContext,
LogService logService) {
this.traceContext = traceContext;
this.logService = logService;
}
@Get("/users/{id}")
public User getUser(Long id) {
traceContext.setAttribute("user_id", id);
logService.log("查询用户: " + id);
return userService.findById(id);
}
}
@Singleton
public class LogService {
private final RequestTraceContext traceContext; // 同一实例
public LogService(RequestTraceContext traceContext) {
this.traceContext = traceContext;
}
public void log(String message) {
System.out.println("[" + traceContext.getTraceId() + "] " + message);
}
}
---
b.Refreshable生命周期
a.配置刷新重建
@Refreshable标记的Bean在配置刷新时会被销毁并重新创建,获取最新的配置值。
b.Refreshable示例
---
import io.micronaut.context.annotation.Refreshable;
import io.micronaut.context.annotation.ConfigurationProperties;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
@Refreshable
@ConfigurationProperties("cache")
public class CacheConfig {
private int maxSize;
private Duration ttl;
public CacheConfig() {
System.out.println("CacheConfig创建");
}
@PostConstruct
public void init() {
System.out.println("CacheConfig初始化: maxSize=" + maxSize +
", ttl=" + ttl);
}
@PreDestroy
public void cleanup() {
System.out.println("CacheConfig销毁(配置已变更)");
}
// getter和setter省略
}
@Singleton
public class CacheManager {
private final CacheConfig config;
public CacheManager(CacheConfig config) {
this.config = config;
}
public void printConfig() {
// 总是访问最新的CacheConfig实例
System.out.println("当前缓存配置: " + config.getMaxSize());
}
}
// 配置刷新流程
// 1. POST /refresh
// 2. 重新加载配置文件
// 3. 调用CacheConfig.cleanup()(@PreDestroy)
// 4. 销毁旧CacheConfig实例
// 5. 创建新CacheConfig实例
// 6. 调用CacheConfig.init()(@PostConstruct)
// 7. 重新注入到CacheManager
---
04.优雅关闭
a.关闭流程
a.优雅关闭机制
应用接收到关闭信号时,先停止接受新请求,等待现有请求处理完成,然后依次销毁Bean,最后关闭线程池。
b.关闭流程示例
---
// application.yml配置
// micronaut:
// server:
// shutdown:
// grace-period: 30s # 优雅关闭等待时间
// 监控关闭过程
import io.micronaut.runtime.event.ShutdownEvent;
import io.micronaut.runtime.server.event.ServerShutdownEvent;
@Singleton
public class ShutdownMonitor implements
ApplicationEventListener<ShutdownEvent> {
@Inject
private ActiveRequestTracker requestTracker;
@Override
public void onApplicationEvent(ShutdownEvent event) {
System.out.println("收到关闭信号");
// 等待活跃请求完成
int activeRequests = requestTracker.getActiveCount();
System.out.println("当前活跃请求数: " + activeRequests);
while (activeRequests > 0) {
try {
Thread.sleep(1000);
activeRequests = requestTracker.getActiveCount();
System.out.println("等待请求完成,剩余: " + activeRequests);
} catch (InterruptedException e) {
break;
}
}
System.out.println("所有请求已完成,开始销毁Bean");
}
}
// 关闭流程
// 1. 接收SIGTERM信号
// 2. 触发ShutdownEvent
// 3. HTTP服务器停止接受新连接
// 4. 等待活跃请求完成(最多30秒)
// 5. 按依赖反向顺序销毁Bean
// 6. 调用所有@PreDestroy方法
// 7. 关闭线程池
// 8. JVM退出
---
b.关闭Hook
a.JVM关闭Hook
Micronaut自动注册JVM关闭Hook,确保应用被kill时也能执行清理逻辑。
b.关闭Hook示例
---
import jakarta.inject.Singleton;
@Singleton
public class CustomShutdownHook {
public CustomShutdownHook() {
// 注册自定义关闭Hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("JVM即将退出,执行最后的清理");
// 刷新日志缓冲区
// 保存临时数据
// 发送关闭通知
}));
}
@PreDestroy
public void cleanup() {
System.out.println("Bean销毁前的清理");
}
}
// 关闭方式对比
// kill -15 <pid> (SIGTERM) → 优雅关闭,执行@PreDestroy和Hook
// kill -9 <pid> (SIGKILL) → 强制终止,无法执行清理
// Ctrl+C → 优雅关闭,执行@PreDestroy和Hook
// context.close() → 程序关闭,执行@PreDestroy
---
3 依赖注入详解
3.1 注解驱动注入
01.JSR-330标准注解
a.@Inject注解
a.注入标记
@Inject是JSR-330标准注解,用于标记需要依赖注入的构造器、字段或方法,Micronaut完全支持此标准,保证代码可移植性。
b.Inject使用示例
---
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@Singleton
public class OrderService {
// 字段注入
@Inject
private OrderRepository orderRepository;
@Inject
private PaymentService paymentService;
private NotificationService notificationService;
// 构造器注入(推荐方式)
// 单个构造器时@Inject可省略
public OrderService() {
System.out.println("无参构造器");
}
// 方法注入
@Inject
public void setNotificationService(NotificationService service) {
this.notificationService = service;
System.out.println("方法注入完成");
}
public Order createOrder(OrderRequest request) {
Order order = new Order(request);
orderRepository.save(order);
paymentService.process(order);
notificationService.notify(order);
return order;
}
}
// 注入执行顺序
// 1. 调用构造器
// 2. 注入@Inject字段
// 3. 调用@Inject方法
// 4. 调用@PostConstruct方法
// 构造器注入示例(推荐)
@Singleton
public class UserService {
private final UserRepository repository;
private final EmailService emailService;
// 单构造器时@Inject可省略
public UserService(UserRepository repository,
EmailService emailService) {
this.repository = repository;
this.emailService = emailService;
}
}
// 多构造器时必须标记@Inject
@Singleton
public class ProductService {
private final ProductRepository repository;
private final CacheService cacheService;
// 默认构造器
public ProductService() {
this(null, null);
}
// 注入构造器
@Inject
public ProductService(ProductRepository repository,
CacheService cacheService) {
this.repository = repository;
this.cacheService = cacheService;
}
}
---
b.@Singleton注解
a.单例作用域
@Singleton是JSR-330标准注解,标记Bean为单例作用域,整个应用生命周期内只创建一次。
b.Singleton示例
---
import jakarta.inject.Singleton;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Singleton
public class SessionManager {
private final Map<String, Session> sessions = new ConcurrentHashMap<>();
private final AtomicLong sessionCounter = new AtomicLong(0);
public String createSession(String userId) {
String sessionId = UUID.randomUUID().toString();
Session session = new Session(sessionId, userId);
sessions.put(sessionId, session);
sessionCounter.incrementAndGet();
System.out.println("创建会话: " + sessionId);
return sessionId;
}
public Session getSession(String sessionId) {
return sessions.get(sessionId);
}
public void invalidateSession(String sessionId) {
sessions.remove(sessionId);
System.out.println("会话失效: " + sessionId);
}
public long getActiveSessionCount() {
return sessions.size();
}
public long getTotalSessionCount() {
return sessionCounter.get();
}
}
// 任何地方注入的都是同一个SessionManager实例
@Controller("/auth")
public class AuthController {
private final SessionManager sessionManager;
public AuthController(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Post("/login")
public LoginResponse login(@Body LoginRequest request) {
String sessionId = sessionManager.createSession(request.getUserId());
return new LoginResponse(sessionId);
}
}
@Controller("/api")
public class ApiController {
private final SessionManager sessionManager; // 同一实例
public ApiController(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Get("/session/count")
public long getSessionCount() {
return sessionManager.getActiveSessionCount();
}
}
---
c.@Named注解
a.Bean限定符
@Named注解为Bean指定名称,用于区分同类型的多个Bean实现。
b.Named使用示例
---
import jakarta.inject.Named;
import jakarta.inject.Singleton;
// 定义接口
public interface DataSource {
Connection getConnection();
}
// 主数据源
@Singleton
@Named("primary")
public class PrimaryDataSource implements DataSource {
@Override
public Connection getConnection() {
System.out.println("获取主库连接");
return createConnection("jdbc:mysql://master:3306/db");
}
private Connection createConnection(String url) {
// 创建连接
return null;
}
}
// 从数据源
@Singleton
@Named("secondary")
public class SecondaryDataSource implements DataSource {
@Override
public Connection getConnection() {
System.out.println("获取从库连接");
return createConnection("jdbc:mysql://slave:3306/db");
}
private Connection createConnection(String url) {
return null;
}
}
// 注入指定名称的Bean
@Singleton
public class UserRepository {
private final DataSource primaryDataSource;
private final DataSource secondaryDataSource;
public UserRepository(
@Named("primary") DataSource primaryDataSource,
@Named("secondary") DataSource secondaryDataSource) {
this.primaryDataSource = primaryDataSource;
this.secondaryDataSource = secondaryDataSource;
}
public User findById(Long id) {
// 写操作使用主库
Connection conn = primaryDataSource.getConnection();
return queryUser(conn, id);
}
public List<User> search(String keyword) {
// 读操作使用从库
Connection conn = secondaryDataSource.getConnection();
return searchUsers(conn, keyword);
}
private User queryUser(Connection conn, Long id) {
return null;
}
private List<User> searchUsers(Connection conn, String keyword) {
return Collections.emptyList();
}
}
---
02.Micronaut扩展注解
a.@Primary注解
a.首选Bean
@Primary注解标记首选Bean,当存在多个同类型Bean且未指定@Named时,优先注入标记为@Primary的Bean。
b.Primary示例
---
import io.micronaut.context.annotation.Primary;
import jakarta.inject.Singleton;
public interface CacheProvider {
void put(String key, Object value);
Object get(String key);
}
// Redis缓存实现(首选)
@Singleton
@Primary
public class RedisCacheProvider implements CacheProvider {
@Override
public void put(String key, Object value) {
System.out.println("Redis缓存写入: " + key);
}
@Override
public Object get(String key) {
System.out.println("Redis缓存读取: " + key);
return null;
}
}
// 内存缓存实现
@Singleton
public class InMemoryCacheProvider implements CacheProvider {
private final Map<String, Object> cache = new ConcurrentHashMap<>();
@Override
public void put(String key, Object value) {
cache.put(key, value);
System.out.println("内存缓存写入: " + key);
}
@Override
public Object get(String key) {
System.out.println("内存缓存读取: " + key);
return cache.get(key);
}
}
// 不指定@Named时注入Primary Bean
@Singleton
public class ProductService {
private final CacheProvider cacheProvider; // 注入RedisCacheProvider
public ProductService(CacheProvider cacheProvider) {
this.cacheProvider = cacheProvider;
System.out.println("注入的缓存提供者: " +
cacheProvider.getClass().getSimpleName());
}
public Product getProduct(Long id) {
Object cached = cacheProvider.get("product:" + id);
if (cached != null) {
return (Product) cached;
}
Product product = productRepository.findById(id);
cacheProvider.put("product:" + id, product);
return product;
}
}
// 明确注入非Primary Bean
@Singleton
public class BackupService {
private final CacheProvider memoryCache;
public BackupService(@Named("inMemoryCacheProvider") CacheProvider memoryCache) {
this.memoryCache = memoryCache;
}
}
---
b.@Requires条件注入
a.条件Bean创建
@Requires注解根据条件决定是否创建Bean,条件包括配置属性、环境、类路径、Bean存在性等。
b.Requires示例
---
import io.micronaut.context.annotation.Requires;
import jakarta.inject.Singleton;
// 条件1:配置属性存在且为true
@Singleton
@Requires(property = "redis.enabled", value = "true")
public class RedisCacheService implements CacheService {
@Override
public void cache(String key, Object value) {
System.out.println("使用Redis缓存");
}
}
// 条件2:配置属性不为true(默认false)
@Singleton
@Requires(property = "redis.enabled", value = "false", defaultValue = "false")
public class LocalCacheService implements CacheService {
@Override
public void cache(String key, Object value) {
System.out.println("使用本地缓存");
}
}
// 条件3:类路径存在指定类
@Singleton
@Requires(classes = com.mysql.cj.jdbc.Driver.class)
public class MySQLDataSource implements DataSource {
// MySQL驱动存在时才创建
}
// 条件4:Bean存在
@Singleton
@Requires(beans = DataSource.class)
public class DatabaseMigration {
// 仅当DataSource Bean存在时创建
}
// 条件5:Bean不存在
@Singleton
@Requires(missingBeans = CustomAuthProvider.class)
public class DefaultAuthProvider implements AuthProvider {
// 仅当CustomAuthProvider不存在时创建
}
// 条件6:指定环境
@Singleton
@Requires(env = "dev")
public class MockEmailService implements EmailService {
@Override
public void send(String to, String subject, String body) {
System.out.println("Mock发送邮件到: " + to);
}
}
@Singleton
@Requires(env = "prod")
public class SmtpEmailService implements EmailService {
@Override
public void send(String to, String subject, String body) {
System.out.println("通过SMTP发送邮件到: " + to);
}
}
// 条件7:非指定环境
@Singleton
@Requires(notEnv = "test")
public class ProductionMetrics {
// 非test环境时创建
}
// 条件8:SDK版本
@Singleton
@Requires(sdk = Requires.Sdk.JAVA, value = "17")
public class Java17Feature {
// 仅Java 17及以上版本创建
}
// 条件9:配置属性匹配模式
@Singleton
@Requires(property = "datasource.url", pattern = "jdbc:mysql:.*")
public class MySQLSpecificConfig {
// 仅当数据库URL为MySQL时创建
}
// 条件10:多条件组合(AND关系)
@Singleton
@Requires(property = "feature.enabled", value = "true")
@Requires(env = "prod")
@Requires(beans = DataSource.class)
public class AdvancedFeature {
// 所有条件都满足时才创建
}
---
c.@Factory工厂类
a.工厂模式
@Factory注解标记工厂类,类中的@Bean方法可以创建和配置第三方Bean,适合无法添加@Singleton注解的外部类。
b.Factory示例
---
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Bean;
import jakarta.inject.Singleton;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
@Factory
public class DataSourceFactory {
// 创建HikariCP数据源
@Bean
@Singleton
@Named("primary")
public DataSource primaryDataSource(
@Value("${datasource.primary.url}") String url,
@Value("${datasource.primary.username}") String username,
@Value("${datasource.primary.password}") String password) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
System.out.println("创建主数据源: " + url);
return new HikariDataSource(config);
}
// 创建从数据源
@Bean
@Singleton
@Named("secondary")
public DataSource secondaryDataSource(
@Value("${datasource.secondary.url}") String url,
@Value("${datasource.secondary.username}") String username,
@Value("${datasource.secondary.password}") String password) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(10);
System.out.println("创建从数据源: " + url);
return new HikariDataSource(config);
}
// 创建ObjectMapper
@Bean
@Singleton
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.registerModule(new JavaTimeModule());
return mapper;
}
// 条件化工厂方法
@Bean
@Singleton
@Requires(property = "aws.enabled", value = "true")
public AmazonS3 amazonS3(
@Value("${aws.access-key}") String accessKey,
@Value("${aws.secret-key}") String secretKey,
@Value("${aws.region}") String region) {
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
return AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region)
.build();
}
}
// 使用工厂创建的Bean
@Singleton
public class UserRepository {
private final DataSource dataSource;
private final ObjectMapper objectMapper;
public UserRepository(
@Named("primary") DataSource dataSource,
ObjectMapper objectMapper) {
this.dataSource = dataSource;
this.objectMapper = objectMapper;
}
}
---
02.泛型注入
a.泛型Bean
a.泛型类型保留
Micronaut在编译期保留完整的泛型类型信息,支持注入泛型Bean,类型参数精确匹配。
b.泛型注入示例
---
import jakarta.inject.Singleton;
// 定义泛型接口
public interface Repository<T, ID> {
T findById(ID id);
List<T> findAll();
T save(T entity);
void deleteById(ID id);
}
// 泛型实现1:User Repository
@Singleton
public class UserRepository implements Repository<User, Long> {
@Override
public User findById(Long id) {
System.out.println("查询用户: " + id);
return new User(id, "User" + id);
}
@Override
public List<User> findAll() {
return Arrays.asList(new User(1L, "Alice"), new User(2L, "Bob"));
}
@Override
public User save(User entity) {
System.out.println("保存用户: " + entity.getName());
return entity;
}
@Override
public void deleteById(Long id) {
System.out.println("删除用户: " + id);
}
}
// 泛型实现2:Product Repository
@Singleton
public class ProductRepository implements Repository<Product, String> {
@Override
public Product findById(String id) {
System.out.println("查询产品: " + id);
return new Product(id, "Product-" + id);
}
@Override
public List<Product> findAll() {
return Arrays.asList(
new Product("P001", "商品1"),
new Product("P002", "商品2")
);
}
@Override
public Product save(Product entity) {
System.out.println("保存产品: " + entity.getName());
return entity;
}
@Override
public void deleteById(String id) {
System.out.println("删除产品: " + id);
}
}
// 注入泛型Bean(类型精确匹配)
@Singleton
public class UserService {
private final Repository<User, Long> userRepository;
public UserService(Repository<User, Long> userRepository) {
this.userRepository = userRepository; // 注入UserRepository
}
public User getUser(Long id) {
return userRepository.findById(id);
}
}
@Singleton
public class ProductService {
private final Repository<Product, String> productRepository;
public ProductService(Repository<Product, String> productRepository) {
this.productRepository = productRepository; // 注入ProductRepository
}
public Product getProduct(String id) {
return productRepository.findById(id);
}
}
// Micronaut根据泛型参数精确匹配Bean
// Repository<User, Long> → UserRepository
// Repository<Product, String> → ProductRepository
---
b.Provider注入
a.延迟获取
Provider<T>接口提供延迟获取Bean的能力,在需要时才从容器获取Bean实例,可以解决循环依赖和提升启动性能。
b.Provider示例
---
import jakarta.inject.Provider;
import jakarta.inject.Singleton;
// 场景1:解决循环依赖
@Singleton
public class ServiceA {
private final Provider<ServiceB> serviceBProvider;
public ServiceA(Provider<ServiceB> serviceBProvider) {
this.serviceBProvider = serviceBProvider;
System.out.println("ServiceA创建");
}
public void doSomething() {
// 延迟获取ServiceB
ServiceB serviceB = serviceBProvider.get();
serviceB.process();
}
}
@Singleton
public class ServiceB {
private final ServiceA serviceA;
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
System.out.println("ServiceB创建");
}
public void process() {
System.out.println("ServiceB处理业务");
}
}
// 场景2:获取Prototype Bean
@Singleton
public class TaskManager {
private final Provider<TaskExecutor> executorProvider;
public TaskManager(Provider<TaskExecutor> executorProvider) {
this.executorProvider = executorProvider;
}
public void executeTasks(List<Task> tasks) {
for (Task task : tasks) {
// 每个任务使用新的Executor实例
TaskExecutor executor = executorProvider.get();
executor.execute(task);
}
}
}
@Prototype
public class TaskExecutor {
private final String executorId = UUID.randomUUID().toString();
public void execute(Task task) {
System.out.println("Executor " + executorId + " 执行任务: " + task);
}
}
// 场景3:条件获取Bean
@Singleton
public class NotificationService {
private final Provider<EmailService> emailProvider;
private final Provider<SmsService> smsProvider;
public NotificationService(
Provider<EmailService> emailProvider,
Provider<SmsService> smsProvider) {
this.emailProvider = emailProvider;
this.smsProvider = smsProvider;
}
public void notify(String userId, String message, NotificationType type) {
switch (type) {
case EMAIL:
emailProvider.get().send(userId, message);
break;
case SMS:
smsProvider.get().send(userId, message);
break;
case BOTH:
emailProvider.get().send(userId, message);
smsProvider.get().send(userId, message);
break;
}
}
}
---
3.2 构造器注入
01.构造器注入最佳实践
a.推荐使用原因
a.不可变性保证
构造器注入允许将依赖字段声明为final,保证对象创建后依赖不可变,提高线程安全性和代码可预测性。
b.依赖明确性
所有依赖在构造器参数中明确声明,一眼就能看出类的依赖关系,便于理解和维护。
c.易于测试
---
import jakarta.inject.Singleton;
@Singleton
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentGateway paymentGateway;
private final NotificationService notificationService;
private final AuditLogger auditLogger;
// 构造器注入:所有依赖声明为final
public OrderService(OrderRepository orderRepository,
PaymentGateway paymentGateway,
NotificationService notificationService,
AuditLogger auditLogger) {
this.orderRepository = orderRepository;
this.paymentGateway = paymentGateway;
this.notificationService = notificationService;
this.auditLogger = auditLogger;
}
public Order createOrder(OrderRequest request) {
// 创建订单
Order order = new Order(request);
orderRepository.save(order);
// 处理支付
PaymentResult payment = paymentGateway.charge(
request.getUserId(),
request.getAmount()
);
if (!payment.isSuccess()) {
throw new PaymentFailedException("支付失败");
}
// 发送通知
notificationService.sendOrderConfirmation(order);
// 记录审计日志
auditLogger.log("订单创建", order.getId());
return order;
}
}
// 单元测试(无需Micronaut容器)
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
public class OrderServiceTest {
@Test
public void testCreateOrder() {
// 创建Mock依赖
OrderRepository mockRepository = Mockito.mock(OrderRepository.class);
PaymentGateway mockGateway = Mockito.mock(PaymentGateway.class);
NotificationService mockNotification = Mockito.mock(NotificationService.class);
AuditLogger mockLogger = Mockito.mock(AuditLogger.class);
// 直接通过构造器创建服务实例
OrderService service = new OrderService(
mockRepository,
mockGateway,
mockNotification,
mockLogger
);
// 配置Mock行为
Mockito.when(mockGateway.charge(Mockito.any(), Mockito.any()))
.thenReturn(PaymentResult.success());
// 测试
OrderRequest request = new OrderRequest(1L, 100.0);
Order order = service.createOrder(request);
// 验证
Mockito.verify(mockRepository).save(Mockito.any(Order.class));
Mockito.verify(mockGateway).charge(1L, 100.0);
}
}
---
b.多构造器处理
a.@Inject标记
当类有多个构造器时,必须使用@Inject标记用于依赖注入的构造器。
b.多构造器示例
---
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@Singleton
public class ReportService {
private final ReportRepository repository;
private final TemplateEngine templateEngine;
private final ExportService exportService;
// 默认构造器(用于测试)
public ReportService() {
this(null, null, null);
}
// 部分依赖构造器
public ReportService(ReportRepository repository) {
this(repository, new DefaultTemplateEngine(), null);
}
// 完整依赖构造器(标记@Inject)
@Inject
public ReportService(ReportRepository repository,
TemplateEngine templateEngine,
ExportService exportService) {
this.repository = repository;
this.templateEngine = templateEngine;
this.exportService = exportService;
System.out.println("使用完整依赖创建ReportService");
}
public Report generateReport(String templateName, Map<String, Object> data) {
String content = templateEngine.render(templateName, data);
Report report = new Report(content);
repository.save(report);
return report;
}
public byte[] exportToPdf(Long reportId) {
Report report = repository.findById(reportId);
return exportService.toPdf(report);
}
}
// Micronaut会调用标记@Inject的构造器
// 如果没有@Inject标记且有多个构造器,编译报错
---
02.构造器参数处理
a.可选依赖
a.@Nullable注解
构造器参数可以标记@Nullable,表示该依赖是可选的,容器中不存在时注入null。
b.可选依赖示例
---
import io.micronaut.core.annotation.Nullable;
import jakarta.inject.Singleton;
@Singleton
public class EmailService {
private final SmtpClient smtpClient;
private final EmailTemplate templateEngine;
private final AttachmentService attachmentService; // 可选依赖
public EmailService(
SmtpClient smtpClient,
EmailTemplate templateEngine,
@Nullable AttachmentService attachmentService) { // 可选
this.smtpClient = smtpClient;
this.templateEngine = templateEngine;
this.attachmentService = attachmentService;
if (attachmentService == null) {
System.out.println("AttachmentService不可用,禁用附件功能");
}
}
public void sendEmail(String to, String subject, String body) {
String html = templateEngine.render(body);
smtpClient.send(to, subject, html);
}
public void sendEmailWithAttachment(String to, String subject,
String body, File attachment) {
if (attachmentService == null) {
throw new UnsupportedOperationException("附件服务不可用");
}
String html = templateEngine.render(body);
byte[] attachmentData = attachmentService.readFile(attachment);
smtpClient.sendWithAttachment(to, subject, html, attachmentData);
}
}
// Optional包装
@Singleton
public class CacheService {
private final CacheProvider primaryCache;
private final Optional<CacheProvider> secondaryCache;
public CacheService(
@Named("primary") CacheProvider primaryCache,
@Named("secondary") Optional<CacheProvider> secondaryCache) {
this.primaryCache = primaryCache;
this.secondaryCache = secondaryCache;
}
public void put(String key, Object value) {
primaryCache.put(key, value);
// 双写到备用缓存
secondaryCache.ifPresent(cache -> cache.put(key, value));
}
}
---
b.默认值参数
a.@Value注入配置
构造器参数可以使用@Value注解注入配置值,支持默认值。
b.配置参数示例
---
import io.micronaut.context.annotation.Value;
import jakarta.inject.Singleton;
import java.time.Duration;
@Singleton
public class HttpClientService {
private final String baseUrl;
private final Duration timeout;
private final int maxRetries;
private final boolean enableLogging;
public HttpClientService(
@Value("${api.base-url}") String baseUrl,
@Value("${api.timeout:30s}") Duration timeout,
@Value("${api.max-retries:3}") int maxRetries,
@Value("${api.logging.enabled:false}") boolean enableLogging) {
this.baseUrl = baseUrl;
this.timeout = timeout;
this.maxRetries = maxRetries;
this.enableLogging = enableLogging;
System.out.println("HTTP客户端配置:");
System.out.println(" Base URL: " + baseUrl);
System.out.println(" Timeout: " + timeout);
System.out.println(" Max Retries: " + maxRetries);
System.out.println(" Logging: " + enableLogging);
}
public ApiResponse callApi(String endpoint) {
String url = baseUrl + endpoint;
if (enableLogging) {
System.out.println("调用API: " + url);
}
// API调用逻辑
return new ApiResponse();
}
}
// 混合注入Bean和配置
@Singleton
public class DatabaseService {
private final DataSource dataSource;
private final String schema;
private final int queryTimeout;
public DatabaseService(
DataSource dataSource, // 注入Bean
@Value("${database.schema:public}") String schema, // 注入配置
@Value("${database.query-timeout:30}") int queryTimeout) {
this.dataSource = dataSource;
this.schema = schema;
this.queryTimeout = queryTimeout;
}
public List<User> queryUsers() {
try (Connection conn = dataSource.getConnection()) {
Statement stmt = conn.createStatement();
stmt.setQueryTimeout(queryTimeout);
ResultSet rs = stmt.executeQuery(
"SELECT * FROM " + schema + ".users"
);
return mapResultSet(rs);
} catch (SQLException e) {
throw new RuntimeException("查询失败", e);
}
}
private List<User> mapResultSet(ResultSet rs) throws SQLException {
List<User> users = new ArrayList<>();
while (rs.next()) {
users.add(new User(rs.getLong("id"), rs.getString("name")));
}
return users;
}
}
---
03.构造器验证
a.参数验证
a.Bean Validation集成
构造器参数可以使用Bean Validation注解进行验证,Micronaut在Bean创建时自动执行验证。
b.验证示例
---
import io.micronaut.context.annotation.ConfigurationProperties;
import jakarta.validation.constraints.*;
import jakarta.inject.Singleton;
@ConfigurationProperties("server")
public class ServerConfig {
@NotBlank(message = "主机名不能为空")
private String host;
@Min(value = 1, message = "端口必须大于0")
@Max(value = 65535, message = "端口必须小于65536")
private int port;
@NotNull
@Pattern(regexp = "https?://.*", message = "URL格式无效")
private String baseUrl;
// 构造器验证
public ServerConfig(
@NotBlank String host,
@Min(1) @Max(65535) int port,
@NotNull String baseUrl) {
this.host = host;
this.port = port;
this.baseUrl = baseUrl;
}
// getter和setter省略
}
// 验证失败时抛出异常
// application.yml配置错误
// server:
// host: "" # 空字符串,违反@NotBlank
// port: 99999 # 超出范围,违反@Max
// base-url: "invalid" # 格式错误,违反@Pattern
// 启动时抛出异常
// jakarta.validation.ConstraintViolationException:
// - host: 主机名不能为空
// - port: 端口必须小于65536
// - baseUrl: URL格式无效
// 自定义验证器
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
@Constraint(validatedBy = UrlValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
public @interface ValidUrl {
String message() default "URL格式无效";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class UrlValidator implements
ConstraintValidator<ValidUrl, String> {
@Override
public boolean isValid(String value,
ConstraintValidatorContext context) {
if (value == null || value.isEmpty()) {
return false;
}
try {
new URL(value);
return true;
} catch (MalformedURLException e) {
return false;
}
}
}
// 使用自定义验证器
@Singleton
public class ApiClient {
private final String apiUrl;
public ApiClient(@ValidUrl String apiUrl) {
this.apiUrl = apiUrl;
}
}
---
b.依赖检查
a.编译时检查
Micronaut在编译期检查构造器参数的依赖是否存在,提前发现依赖缺失问题。
b.依赖检查示例
---
// 定义服务
@Singleton
public class UserService {
private final UserRepository repository;
private final EmailService emailService;
public UserService(UserRepository repository,
EmailService emailService) {
this.repository = repository;
this.emailService = emailService;
}
}
// 场景1:缺少EmailService Bean
// 如果项目中没有定义EmailService的实现
// 编译时报错:
// error: No bean of type [EmailService] exists.
// Make sure the bean is not disabled by bean requirements
// and if the bean is enabled then ensure the class is
// declared a bean.
// 场景2:循环依赖
@Singleton
public class ServiceA {
public ServiceA(ServiceB serviceB) {
}
}
@Singleton
public class ServiceB {
public ServiceB(ServiceA serviceA) {
}
}
// 编译时报错:
// error: Circular dependency detected:
// ServiceA -> ServiceB -> ServiceA
// 解决方案:使用Provider
@Singleton
public class ServiceA {
private final Provider<ServiceB> serviceBProvider;
public ServiceA(Provider<ServiceB> serviceBProvider) {
this.serviceBProvider = serviceBProvider;
}
public void doSomething() {
ServiceB serviceB = serviceBProvider.get();
serviceB.process();
}
}
---
02.构造器重载
a.多构造器场景
a.构造器选择
当类有多个构造器时,Micronaut优先选择参数最多的构造器,或选择标记@Inject的构造器。
b.构造器选择示例
---
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@Singleton
public class DataProcessor {
private final DataSource dataSource;
private final DataValidator validator;
private final DataTransformer transformer;
// 构造器1:最少依赖(用于测试)
public DataProcessor(DataSource dataSource) {
this(dataSource, new DefaultValidator(), new DefaultTransformer());
System.out.println("使用构造器1");
}
// 构造器2:部分依赖
public DataProcessor(DataSource dataSource, DataValidator validator) {
this(dataSource, validator, new DefaultTransformer());
System.out.println("使用构造器2");
}
// 构造器3:完整依赖(标记@Inject,优先使用)
@Inject
public DataProcessor(DataSource dataSource,
DataValidator validator,
DataTransformer transformer) {
this.dataSource = dataSource;
this.validator = validator;
this.transformer = transformer;
System.out.println("使用构造器3(完整依赖)");
}
public ProcessResult process(Data data) {
if (!validator.validate(data)) {
throw new ValidationException("数据验证失败");
}
Data transformed = transformer.transform(data);
saveToDatabase(dataSource, transformed);
return ProcessResult.success();
}
private void saveToDatabase(DataSource ds, Data data) {
// 保存逻辑
}
}
// Micronaut注入时使用构造器3
// 输出:使用构造器3(完整依赖)
---
b.Builder模式集成
a.工厂方法
对于使用Builder模式的类,可以通过@Factory方法创建Bean。
b.Builder示例
---
// 第三方库的Builder类
public class HttpClientConfig {
private String baseUrl;
private Duration timeout;
private int maxRetries;
private HttpClientConfig() {}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String baseUrl;
private Duration timeout = Duration.ofSeconds(30);
private int maxRetries = 3;
public Builder baseUrl(String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
public Builder timeout(Duration timeout) {
this.timeout = timeout;
return this;
}
public Builder maxRetries(int maxRetries) {
this.maxRetries = maxRetries;
return this;
}
public HttpClientConfig build() {
HttpClientConfig config = new HttpClientConfig();
config.baseUrl = this.baseUrl;
config.timeout = this.timeout;
config.maxRetries = this.maxRetries;
return config;
}
}
// getter方法省略
}
// 使用Factory创建Bean
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Bean;
@Factory
public class HttpClientConfigFactory {
@Bean
@Singleton
public HttpClientConfig httpClientConfig(
@Value("${http.client.base-url}") String baseUrl,
@Value("${http.client.timeout:30s}") Duration timeout,
@Value("${http.client.max-retries:3}") int maxRetries) {
return HttpClientConfig.builder()
.baseUrl(baseUrl)
.timeout(timeout)
.maxRetries(maxRetries)
.build();
}
}
// 注入配置Bean
@Singleton
public class ApiService {
private final HttpClientConfig config;
public ApiService(HttpClientConfig config) {
this.config = config;
System.out.println("API配置: " + config.getBaseUrl());
}
}
---
03.构造器依赖排序
a.依赖拓扑排序
a.创建顺序
Micronaut根据Bean之间的依赖关系进行拓扑排序,确保依赖的Bean先于依赖它的Bean创建。
b.排序示例
---
// 定义4层依赖关系
@Singleton
public class Layer1Config {
public Layer1Config() {
System.out.println("1. Layer1Config创建(无依赖)");
}
}
@Singleton
public class Layer2DataSource {
private final Layer1Config config;
public Layer2DataSource(Layer1Config config) {
this.config = config;
System.out.println("2. Layer2DataSource创建(依赖Layer1)");
}
}
@Singleton
public class Layer2Cache {
private final Layer1Config config;
public Layer2Cache(Layer1Config config) {
this.config = config;
System.out.println("3. Layer2Cache创建(依赖Layer1)");
}
}
@Singleton
public class Layer3Repository {
private final Layer2DataSource dataSource;
public Layer3Repository(Layer2DataSource dataSource) {
this.dataSource = dataSource;
System.out.println("4. Layer3Repository创建(依赖Layer2DataSource)");
}
}
@Singleton
public class Layer3Service {
private final Layer2DataSource dataSource;
private final Layer2Cache cache;
public Layer3Service(Layer2DataSource dataSource, Layer2Cache cache) {
this.dataSource = dataSource;
this.cache = cache;
System.out.println("5. Layer3Service创建(依赖Layer2DataSource和Layer2Cache)");
}
}
@Singleton
public class Layer4Controller {
private final Layer3Repository repository;
private final Layer3Service service;
public Layer4Controller(Layer3Repository repository, Layer3Service service) {
this.repository = repository;
this.service = service;
System.out.println("6. Layer4Controller创建(依赖Layer3)");
}
}
// 依赖关系图
// Layer1Config
// ├── Layer2DataSource
// │ ├── Layer3Repository
// │ │ └── Layer4Controller
// │ └── Layer3Service
// │ └── Layer4Controller
// └── Layer2Cache
// └── Layer3Service
// Micronaut创建顺序(拓扑排序)
// 1. Layer1Config
// 2. Layer2DataSource
// 3. Layer2Cache
// 4. Layer3Repository
// 5. Layer3Service
// 6. Layer4Controller
// 启动输出
// 1. Layer1Config创建(无依赖)
// 2. Layer2DataSource创建(依赖Layer1)
// 3. Layer2Cache创建(依赖Layer1)
// 4. Layer3Repository创建(依赖Layer2DataSource)
// 5. Layer3Service创建(依赖Layer2DataSource和Layer2Cache)
// 6. Layer4Controller创建(依赖Layer3)
---
b.并行实例化
a.无依赖Bean并行创建
对于没有依赖关系的Bean,Micronaut可以并行创建,提升启动速度。
b.并行创建示例
---
// 独立的服务Bean(无相互依赖)
@Singleton
public class MetricsService {
public MetricsService() {
System.out.println(Thread.currentThread().getName() +
": MetricsService创建");
simulateInitialization();
}
private void simulateInitialization() {
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
}
@Singleton
public class LoggingService {
public LoggingService() {
System.out.println(Thread.currentThread().getName() +
": LoggingService创建");
simulateInitialization();
}
private void simulateInitialization() {
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
}
@Singleton
public class CacheService {
public CacheService() {
System.out.println(Thread.currentThread().getName() +
": CacheService创建");
simulateInitialization();
}
private void simulateInitialization() {
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
}
// 这3个Bean无相互依赖,可以并行创建
// 输出示例(线程名可能不同)
// ForkJoinPool-1: MetricsService创建
// ForkJoinPool-2: LoggingService创建
// ForkJoinPool-3: CacheService创建
// 串行创建耗时:300ms
// 并行创建耗时:100ms
// 性能提升:3倍
---
3.3 字段注入与方法注入
01.字段注入
a.字段注入语法
a.@Inject字段
通过@Inject注解标记字段实现依赖注入,Micronaut在Bean创建后自动为字段赋值,但不推荐使用此方式。
b.字段注入示例
---
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@Singleton
public class ReportGenerator {
// 字段注入
@Inject
private TemplateEngine templateEngine;
@Inject
private DataSource dataSource;
@Inject
@Named("pdf")
private ExportService pdfExporter;
@Inject
@Named("excel")
private ExportService excelExporter;
public Report generateReport(String templateName, Map<String, Object> data) {
// 使用注入的依赖
String content = templateEngine.render(templateName, data);
Report report = new Report(content);
// 保存到数据库
try (Connection conn = dataSource.getConnection()) {
saveReport(conn, report);
} catch (SQLException e) {
throw new RuntimeException("保存报告失败", e);
}
return report;
}
public byte[] exportToPdf(Long reportId) {
Report report = loadReport(reportId);
return pdfExporter.export(report);
}
public byte[] exportToExcel(Long reportId) {
Report report = loadReport(reportId);
return excelExporter.export(report);
}
private void saveReport(Connection conn, Report report) {
// 保存逻辑
}
private Report loadReport(Long reportId) {
return new Report();
}
}
// 字段注入的缺点
// 1. 字段不能声明为final,对象可变
// 2. 依赖关系不明确,需要查看字段定义
// 3. 单元测试困难,无法直接new对象
// 4. 可能出现NullPointerException(依赖未注入就调用)
// 5. 破坏封装性,字段通常应该是private
---
b.字段注入陷阱
a.NullPointerException风险
字段注入在构造器执行时依赖尚未注入,如果在构造器中使用依赖字段会导致NPE。
b.NPE示例
---
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@Singleton
public class RiskyService {
@Inject
private ConfigService configService;
public RiskyService() {
// ❌ 错误:此时configService还未注入,值为null
// String value = configService.getValue(); // NullPointerException!
System.out.println("构造器执行,configService=" +
(configService == null ? "null" : "not null"));
}
@PostConstruct
public void init() {
// ✅ 正确:@PostConstruct时字段已注入
String value = configService.getValue();
System.out.println("配置值: " + value);
}
public void doSomething() {
// ✅ 正确:实例方法调用时字段已注入
configService.update("key", "value");
}
}
// 注入执行顺序
// 1. 调用构造器(此时@Inject字段为null)
// 2. 注入@Inject字段
// 3. 调用@PostConstruct方法
// 因此在构造器中不能使用@Inject字段!
// 推荐使用构造器注入避免此问题
@Singleton
public class SafeService {
private final ConfigService configService;
public SafeService(ConfigService configService) {
// ✅ 安全:构造器参数已经是注入的实例
this.configService = configService;
String value = configService.getValue(); // 不会NPE
System.out.println("配置值: " + value);
}
}
---
02.方法注入
a.Setter注入
a.方法注入语法
通过@Inject标记setter方法实现依赖注入,适合可选依赖或需要在Bean创建后动态配置的场景。
b.Setter注入示例
---
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import io.micronaut.core.annotation.Nullable;
@Singleton
public class EmailService {
private final SmtpConfig config;
private EmailTemplate defaultTemplate;
private AttachmentHandler attachmentHandler; // 可选依赖
// 构造器注入必需依赖
public EmailService(SmtpConfig config) {
this.config = config;
System.out.println("EmailService创建,config已注入");
}
// 方法注入:设置默认模板
@Inject
public void setDefaultTemplate(EmailTemplate defaultTemplate) {
this.defaultTemplate = defaultTemplate;
System.out.println("默认模板已注入");
}
// 方法注入:可选的附件处理器
@Inject
public void setAttachmentHandler(
@Nullable AttachmentHandler attachmentHandler) {
this.attachmentHandler = attachmentHandler;
if (attachmentHandler != null) {
System.out.println("附件处理器已注入");
} else {
System.out.println("附件处理器不可用");
}
}
public void sendEmail(String to, String subject, String body) {
String content = defaultTemplate.render(body);
send(to, subject, content);
}
public void sendEmailWithAttachment(String to, String subject,
String body, File file) {
if (attachmentHandler == null) {
throw new UnsupportedOperationException("附件功能不可用");
}
String content = defaultTemplate.render(body);
byte[] attachment = attachmentHandler.process(file);
sendWithAttachment(to, subject, content, attachment);
}
private void send(String to, String subject, String content) {
System.out.println("发送邮件到: " + to);
}
private void sendWithAttachment(String to, String subject,
String content, byte[] attachment) {
System.out.println("发送带附件的邮件到: " + to);
}
}
// 注入执行顺序
// 1. 调用构造器(config注入)
// 2. 调用setDefaultTemplate(defaultTemplate注入)
// 3. 调用setAttachmentHandler(attachmentHandler注入)
// 4. 调用@PostConstruct方法
---
b.多参数方法注入
a.方法注入多个依赖
@Inject方法可以有多个参数,Micronaut会注入所有参数。
b.多参数注入示例
---
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@Singleton
public class OrderProcessor {
private InventoryService inventoryService;
private PaymentService paymentService;
private ShippingService shippingService;
// 方法注入多个依赖
@Inject
public void setDependencies(
InventoryService inventoryService,
PaymentService paymentService,
ShippingService shippingService) {
this.inventoryService = inventoryService;
this.paymentService = paymentService;
this.shippingService = shippingService;
System.out.println("3个依赖已通过方法注入");
}
public void processOrder(Order order) {
// 检查库存
boolean inStock = inventoryService.checkStock(order.getItems());
if (!inStock) {
throw new OutOfStockException();
}
// 处理支付
PaymentResult payment = paymentService.charge(order);
if (!payment.isSuccess()) {
throw new PaymentFailedException();
}
// 安排发货
shippingService.schedule(order);
}
}
// 多个@Inject方法
@Singleton
public class ComplexService {
private DataSource dataSource;
private CacheService cacheService;
private MetricsService metricsService;
private LoggingService loggingService;
@Inject
public void setDataAccess(DataSource dataSource, CacheService cacheService) {
this.dataSource = dataSource;
this.cacheService = cacheService;
System.out.println("数据访问依赖已注入");
}
@Inject
public void setObservability(MetricsService metricsService,
LoggingService loggingService) {
this.metricsService = metricsService;
this.loggingService = loggingService;
System.out.println("可观测性依赖已注入");
}
// 所有@Inject方法都会被调用
}
---
03.注入方式对比
a.三种注入方式比较
a.构造器vs字段vs方法
构造器注入保证不可变性和依赖完整性,字段注入简洁但不安全,方法注入适合可选依赖,应根据场景选择。
b.对比示例
---
// 推荐:构造器注入
@Singleton
public class GoodService {
private final Repository repository; // final,不可变
private final Validator validator; // final,不可变
public GoodService(Repository repository, Validator validator) {
this.repository = repository;
this.validator = validator;
// 依赖在对象创建时就已确定
}
}
// 优点:
// ✅ 字段可以是final,保证不可变
// ✅ 依赖明确,构造器签名清晰展示依赖
// ✅ 便于测试,直接new对象传入mock
// ✅ 不会出现NPE,构造器执行时依赖已就绪
// ✅ 编译期检查依赖完整性
// 不推荐:字段注入
@Singleton
public class BadService {
@Inject
private Repository repository; // 不能是final
@Inject
private Validator validator; // 不能是final
public BadService() {
// 构造器中不能使用依赖(此时为null)
}
}
// 缺点:
// ❌ 字段不能是final,对象可变
// ❌ 依赖隐藏,需要查看字段定义
// ❌ 测试困难,必须通过容器创建
// ❌ 可能NPE,构造器中依赖未就绪
// ❌ 无编译期检查,运行时才发现依赖缺失
// 适用场景:方法注入
@Singleton
public class FlexibleService {
private final Repository repository; // 必需依赖
private CacheService cacheService; // 可选依赖
// 构造器注入必需依赖
public FlexibleService(Repository repository) {
this.repository = repository;
}
// 方法注入可选依赖
@Inject
public void setCacheService(@Nullable CacheService cacheService) {
this.cacheService = cacheService;
}
public Data getData(String key) {
// 尝试从缓存获取
if (cacheService != null) {
Data cached = cacheService.get(key);
if (cached != null) return cached;
}
// 从数据库查询
Data data = repository.findByKey(key);
// 写入缓存
if (cacheService != null) {
cacheService.put(key, data);
}
return data;
}
}
// 适用场景:
// ✅ 可选依赖(依赖可能不存在)
// ✅ 循环依赖(通过setter打破循环)
// ✅ 需要重新配置依赖的场景
---
b.性能对比
a.注入性能
构造器注入和字段注入的性能几乎相同,因为都是编译时生成代码,方法注入略有额外开销(多一次方法调用)。
b.性能测试
---
// 性能测试代码
import io.micronaut.context.ApplicationContext;
public class InjectionPerformanceTest {
public static void main(String[] args) {
ApplicationContext context = ApplicationContext.run();
// 测试构造器注入
long start1 = System.nanoTime();
for (int i = 0; i < 10000; i++) {
context.createBean(ConstructorInjectionService.class);
}
long duration1 = System.nanoTime() - start1;
System.out.println("构造器注入: " + duration1 / 1_000_000 + "ms");
// 测试字段注入
long start2 = System.nanoTime();
for (int i = 0; i < 10000; i++) {
context.createBean(FieldInjectionService.class);
}
long duration2 = System.nanoTime() - start2;
System.out.println("字段注入: " + duration2 / 1_000_000 + "ms");
// 测试方法注入
long start3 = System.nanoTime();
for (int i = 0; i < 10000; i++) {
context.createBean(MethodInjectionService.class);
}
long duration3 = System.nanoTime() - start3;
System.out.println("方法注入: " + duration3 / 1_000_000 + "ms");
context.close();
}
}
// 测试结果(10000次Bean创建)
// 构造器注入: 85ms
// 字段注入: 87ms
// 方法注入: 92ms
// 性能差异很小,优先考虑代码质量和可维护性
// 推荐使用构造器注入
---
02.Setter方法注入
a.标准Setter
a.Bean属性设置
Setter方法注入遵循JavaBean规范,方法名以set开头,接受一个参数。
b.Setter示例
---
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@Singleton
public class ConfigurableService {
private final DatabaseConfig dbConfig; // 必需,构造器注入
private CacheConfig cacheConfig; // 可选,setter注入
private int maxRetries = 3; // 可选,默认值
private Duration timeout = Duration.ofSeconds(30);
public ConfigurableService(DatabaseConfig dbConfig) {
this.dbConfig = dbConfig;
}
@Inject
public void setCacheConfig(@Nullable CacheConfig cacheConfig) {
this.cacheConfig = cacheConfig;
if (cacheConfig != null) {
System.out.println("缓存配置已设置");
}
}
@Inject
public void setMaxRetries(@Value("${service.max-retries:3}") int maxRetries) {
this.maxRetries = maxRetries;
System.out.println("最大重试次数: " + maxRetries);
}
@Inject
public void setTimeout(@Value("${service.timeout:30s}") Duration timeout) {
this.timeout = timeout;
System.out.println("超时时间: " + timeout);
}
public void execute() {
// 使用配置
System.out.println("数据库: " + dbConfig.getUrl());
if (cacheConfig != null) {
System.out.println("缓存: " + cacheConfig.getProvider());
}
System.out.println("最大重试: " + maxRetries);
System.out.println("超时: " + timeout);
}
}
---
b.自定义方法注入
a.任意方法名
@Inject可以标记任意public方法,不限于setter方法,方法可以有多个参数。
b.自定义方法示例
---
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@Singleton
public class OrderService {
private OrderRepository repository;
private PaymentService paymentService;
private NotificationService notificationService;
// 自定义方法名
@Inject
public void configureDependencies(
OrderRepository repository,
PaymentService paymentService) {
this.repository = repository;
this.paymentService = paymentService;
System.out.println("核心依赖已配置");
}
// 另一个注入方法
@Inject
public void setupNotification(NotificationService notificationService) {
this.notificationService = notificationService;
System.out.println("通知服务已配置");
}
// 初始化方法(在所有注入完成后执行)
@PostConstruct
public void initialize() {
System.out.println("所有依赖已就绪,初始化完成");
}
public Order createOrder(OrderRequest request) {
Order order = repository.save(new Order(request));
paymentService.process(order);
notificationService.notify(order);
return order;
}
}
// 注入执行顺序
// 1. 调用构造器
// 2. 调用configureDependencies方法
// 3. 调用setupNotification方法
// 4. 调用initialize方法(@PostConstruct)
---
03.注入方式选择指南
a.决策树
a.选择流程
根据依赖特性选择合适的注入方式,优先使用构造器注入,特殊场景才使用字段或方法注入。
b.选择建议
---
// 决策流程
// 1. 依赖是否必需?
// ├─ 是 → 使用构造器注入
// └─ 否 → 继续判断
//
// 2. 依赖是否需要声明为final?
// ├─ 是 → 使用构造器注入
// └─ 否 → 继续判断
//
// 3. 是否需要在Bean创建后重新配置?
// ├─ 是 → 使用方法注入
// └─ 否 → 继续判断
//
// 4. 是否存在循环依赖?
// ├─ 是 → 使用Provider或方法注入
// └─ 否 → 使用构造器注入
// 示例1:必需依赖 → 构造器注入
@Singleton
public class UserService {
private final UserRepository repository; // 必需
public UserService(UserRepository repository) {
this.repository = repository;
}
}
// 示例2:可选依赖 → 方法注入
@Singleton
public class EmailService {
private final SmtpClient smtpClient; // 必需
private AttachmentService attachmentService; // 可选
public EmailService(SmtpClient smtpClient) {
this.smtpClient = smtpClient;
}
@Inject
public void setAttachmentService(@Nullable AttachmentService service) {
this.attachmentService = service;
}
}
// 示例3:循环依赖 → Provider
@Singleton
public class ServiceA {
private final Provider<ServiceB> serviceBProvider;
public ServiceA(Provider<ServiceB> serviceBProvider) {
this.serviceBProvider = serviceBProvider;
}
}
// 示例4:大量依赖 → 考虑重构
@Singleton
public class GodService {
// ❌ 不推荐:依赖过多,违反单一职责原则
public GodService(
Dep1 d1, Dep2 d2, Dep3 d3, Dep4 d4, Dep5 d5,
Dep6 d6, Dep7 d7, Dep8 d8, Dep9 d9, Dep10 d10) {
// 依赖超过5个时应考虑拆分服务
}
}
// ✅ 推荐:拆分为多个职责单一的服务
@Singleton
public class UserService {
private final UserRepository repository;
private final UserValidator validator;
public UserService(UserRepository repository, UserValidator validator) {
this.repository = repository;
this.validator = validator;
}
}
@Singleton
public class OrderService {
private final OrderRepository repository;
private final PaymentService paymentService;
public OrderService(OrderRepository repository, PaymentService paymentService) {
this.repository = repository;
this.paymentService = paymentService;
}
}
---
b.最佳实践总结
a.推荐模式
优先使用构造器注入,依赖声明为final,每个类的依赖数量控制在5个以内,遵循单一职责原则。
b.反模式
---
// ❌ 反模式1:字段注入 + 在构造器中使用
@Singleton
public class BadService1 {
@Inject
private ConfigService config;
public BadService1() {
String value = config.getValue(); // NPE!
}
}
// ❌ 反模式2:字段注入 + 无法测试
@Singleton
public class BadService2 {
@Inject
private ExternalApi externalApi;
public void process() {
externalApi.call();
}
}
// 测试时无法mock externalApi
// ❌ 反模式3:方法注入 + 必需依赖
@Singleton
public class BadService3 {
private Repository repository;
@Inject
public void setRepository(Repository repository) {
this.repository = repository;
}
public void save(Entity entity) {
repository.save(entity); // 如果setRepository未调用会NPE
}
}
// ✅ 最佳实践
@Singleton
public class GoodService {
private final Repository repository; // 必需,final
private final Validator validator; // 必需,final
private CacheService cacheService; // 可选,非final
// 构造器注入必需依赖
public GoodService(Repository repository, Validator validator) {
this.repository = repository;
this.validator = validator;
}
// 方法注入可选依赖
@Inject
public void setCacheService(@Nullable CacheService cacheService) {
this.cacheService = cacheService;
}
public Entity save(Entity entity) {
if (!validator.validate(entity)) {
throw new ValidationException();
}
repository.save(entity);
if (cacheService != null) {
cacheService.put(entity.getId(), entity);
}
return entity;
}
}
---
3.4 作用域管理
01.内置作用域
a.@Singleton作用域
a.全局单例
@Singleton标记的Bean在整个应用生命周期内只创建一次,所有注入点共享同一实例,适合无状态服务。
b.单例示例
---
import jakarta.inject.Singleton;
@Singleton
public class ConfigurationService {
private final Map<String, String> config;
public ConfigurationService() {
System.out.println("ConfigurationService实例化");
this.config = loadConfig();
}
private Map<String, String> loadConfig() {
// 加载配置文件
Map<String, String> map = new HashMap<>();
map.put("app.name", "MyApp");
map.put("app.version", "1.0.0");
return map;
}
public String get(String key) {
return config.get(key);
}
}
// 验证单例行为
@Singleton
public class ServiceA {
private final ConfigurationService configService;
public ServiceA(ConfigurationService configService) {
this.configService = configService;
System.out.println("ServiceA获得ConfigService: " +
System.identityHashCode(configService));
}
}
@Singleton
public class ServiceB {
private final ConfigurationService configService;
public ServiceB(ConfigurationService configService) {
this.configService = configService;
System.out.println("ServiceB获得ConfigService: " +
System.identityHashCode(configService));
}
}
// 输出结果
// ConfigurationService实例化
// ServiceA获得ConfigService: 123456789
// ServiceB获得ConfigService: 123456789
// 两个Service获得的是同一个ConfigService实例
---
b.@Prototype作用域
a.多实例模式
@Prototype标记的Bean每次注入都会创建新实例,适合有状态的对象或需要隔离的场景。
b.Prototype示例
---
import io.micronaut.context.annotation.Prototype;
import java.util.UUID;
@Prototype
public class RequestProcessor {
private final String processorId;
private final long createTime;
private int requestCount = 0;
public RequestProcessor() {
this.processorId = UUID.randomUUID().toString();
this.createTime = System.currentTimeMillis();
System.out.println("创建处理器: " + processorId);
}
public void processRequest(String request) {
requestCount++;
System.out.println("处理器[" + processorId + "] 处理第" +
requestCount + "个请求: " + request);
}
public String getProcessorId() {
return processorId;
}
}
// 每次注入都创建新实例
@Singleton
public class TaskManager {
private final BeanContext beanContext;
public TaskManager(BeanContext beanContext) {
this.beanContext = beanContext;
}
public void executeTasks() {
// 获取3个独立的处理器实例
RequestProcessor processor1 = beanContext.getBean(RequestProcessor.class);
RequestProcessor processor2 = beanContext.getBean(RequestProcessor.class);
RequestProcessor processor3 = beanContext.getBean(RequestProcessor.class);
// 并行处理
processor1.processRequest("Task-1");
processor2.processRequest("Task-2");
processor3.processRequest("Task-3");
System.out.println("Processor1 ID: " + processor1.getProcessorId());
System.out.println("Processor2 ID: " + processor2.getProcessorId());
System.out.println("Processor3 ID: " + processor3.getProcessorId());
// 3个ID都不相同
}
}
// 输出结果
// 创建处理器: a1b2c3d4-...
// 创建处理器: e5f6g7h8-...
// 创建处理器: i9j0k1l2-...
// 处理器[a1b2c3d4-...] 处理第1个请求: Task-1
// 处理器[e5f6g7h8-...] 处理第1个请求: Task-2
// 处理器[i9j0k1l2-...] 处理第1个请求: Task-3
// Processor1 ID: a1b2c3d4-...
// Processor2 ID: e5f6g7h8-...
// Processor3 ID: i9j0k1l2-...
---
02.请求作用域
a.@RequestScope
a.HTTP请求级别
@RequestScope标记的Bean在每个HTTP请求开始时创建,请求结束时销毁,同一请求内所有注入点共享实例。
b.RequestScope示例
---
import io.micronaut.runtime.http.scope.RequestScope;
import jakarta.annotation.PreDestroy;
import java.util.UUID;
@RequestScope
public class RequestContext {
private final String requestId;
private final long startTime;
private final Map<String, Object> attributes;
public RequestContext() {
this.requestId = UUID.randomUUID().toString();
this.startTime = System.currentTimeMillis();
this.attributes = new HashMap<>();
System.out.println("请求上下文创建: " + requestId);
}
public void setAttribute(String key, Object value) {
attributes.put(key, value);
}
public Object getAttribute(String key) {
return attributes.get(key);
}
public String getRequestId() {
return requestId;
}
public long getElapsedTime() {
return System.currentTimeMillis() - startTime;
}
@PreDestroy
public void cleanup() {
long duration = getElapsedTime();
System.out.println("请求完成: " + requestId +
", 耗时: " + duration + "ms");
attributes.clear();
}
}
// 在Controller中使用
@Controller("/api/orders")
public class OrderController {
private final RequestContext requestContext;
private final OrderService orderService;
public OrderController(RequestContext requestContext,
OrderService orderService) {
this.requestContext = requestContext;
this.orderService = orderService;
}
@Post
public Order createOrder(@Body OrderRequest request) {
// 设置请求属性
requestContext.setAttribute("user_id", request.getUserId());
requestContext.setAttribute("ip", request.getIp());
// 处理订单
Order order = orderService.createOrder(request);
// 记录请求信息
System.out.println("请求ID: " + requestContext.getRequestId());
System.out.println("耗时: " + requestContext.getElapsedTime() + "ms");
return order;
}
}
// Service也可以注入同一RequestContext实例
@Singleton
public class OrderService {
private final RequestContext requestContext;
private final OrderRepository repository;
public OrderService(RequestContext requestContext,
OrderRepository repository) {
this.requestContext = requestContext;
this.repository = repository;
}
public Order createOrder(OrderRequest request) {
// 访问同一请求上下文
String requestId = requestContext.getRequestId();
Long userId = (Long) requestContext.getAttribute("user_id");
System.out.println("[" + requestId + "] 创建订单,用户ID: " + userId);
Order order = new Order();
order.setUserId(userId);
order.setRequestId(requestId);
return repository.save(order);
}
}
// 请求流程
// 1. 收到HTTP请求
// 2. 创建RequestContext实例
// 3. 注入到Controller和Service(同一实例)
// 4. 处理请求
// 5. 返回响应
// 6. 调用RequestContext.cleanup()(@PreDestroy)
// 7. 销毁RequestContext实例
---
b.请��作用域传播
a.异步场景
RequestScope在异步执行中不会自动传播,需要手动传递上下文或使用ThreadLocal。
b.异步处理示例
---
import io.micronaut.scheduling.annotation.Async;
import java.util.concurrent.CompletableFuture;
@Singleton
public class AsyncOrderService {
private final RequestContext requestContext;
public AsyncOrderService(RequestContext requestContext) {
this.requestContext = requestContext;
}
@Async
public CompletableFuture<Order> createOrderAsync(OrderRequest request) {
// ⚠️ 警告:异步方法中RequestContext可能不可用
// 因为异步执行在不同线程,请求作用域不会传播
// 解决方案1:在调用前保存需要的数据
String requestId = requestContext.getRequestId();
return CompletableFuture.supplyAsync(() -> {
// 使用保存的requestId
System.out.println("异步处理订单: " + requestId);
return new Order();
});
}
}
// 解决方案2:使用ThreadLocal传播上下文
import io.micronaut.core.propagation.PropagatedContext;
@Singleton
public class ImprovedAsyncService {
private final RequestContext requestContext;
public ImprovedAsyncService(RequestContext requestContext) {
this.requestContext = requestContext;
}
@Async
public CompletableFuture<Order> createOrderAsync(OrderRequest request) {
// 捕获当前上下文
try (PropagatedContext.Scope scope = PropagatedContext.getOrEmpty().plus(
"requestId", requestContext.getRequestId()).propagate()) {
return CompletableFuture.supplyAsync(() -> {
// 在异步线程中访问传播的上下文
String requestId = PropagatedContext.get()
.get("requestId", String.class).orElse("unknown");
System.out.println("异步处理订单: " + requestId);
return new Order();
});
}
}
}
---
03.ThreadLocal作用域
a.@ThreadLocal注解
a.线程级别隔离
@ThreadLocal标记的Bean为每个线程创建独立实例,不同线程间互不干扰,适合线程上下文管理。
b.ThreadLocal示例
---
import io.micronaut.context.annotation.ThreadLocal;
import java.util.HashMap;
import java.util.Map;
@ThreadLocal
public class ThreadContext {
private final String threadId;
private final Map<String, Object> data;
public ThreadContext() {
this.threadId = Thread.currentThread().getName();
this.data = new HashMap<>();
System.out.println("为线程创建上下文: " + threadId);
}
public void set(String key, Object value) {
data.put(key, value);
}
public Object get(String key) {
return data.get(key);
}
public String getThreadId() {
return threadId;
}
}
// 多线程使用示例
@Singleton
public class ParallelProcessor {
private final ThreadContext threadContext;
private final ExecutorService executor;
public ParallelProcessor(ThreadContext threadContext) {
this.threadContext = threadContext;
this.executor = Executors.newFixedThreadPool(3);
}
public void processInParallel() {
// 提交3个任务到不同线程
for (int i = 1; i <= 3; i++) {
int taskId = i;
executor.submit(() -> {
// 每个线程获得独立的ThreadContext实例
threadContext.set("task_id", taskId);
threadContext.set("start_time", System.currentTimeMillis());
System.out.println("线程 " + threadContext.getThreadId() +
" 处理任务 " + threadContext.get("task_id"));
// 模拟处理
Thread.sleep(100);
long duration = System.currentTimeMillis() -
(Long) threadContext.get("start_time");
System.out.println("线程 " + threadContext.getThreadId() +
" 完成,耗时: " + duration + "ms");
});
}
}
}
// 输出结果
// 为线程创建上下文: pool-1-thread-1
// 为线程创建上下文: pool-1-thread-2
// 为线程创建上下文: pool-1-thread-3
// 线程 pool-1-thread-1 处理任务 1
// 线程 pool-1-thread-2 处理任务 2
// 线程 pool-1-thread-3 处理任务 3
// 线程 pool-1-thread-1 完成,耗时: 102ms
// 线程 pool-1-thread-2 完成,耗时: 103ms
// 线程 pool-1-thread-3 完成,耗时: 101ms
---
b.线程池注意事项
a.线程复用问题
使用线程池时,ThreadLocal Bean会在线程首次执行时创建,线程复用时不会重新创建,可能导致状态污染。
b.清理示例
---
import jakarta.annotation.PreDestroy;
@ThreadLocal
public class SafeThreadContext {
private final Map<String, Object> data = new HashMap<>();
public void set(String key, Object value) {
data.put(key, value);
}
public Object get(String key) {
return data.get(key);
}
// 手动清理方法
public void clear() {
data.clear();
System.out.println("线程上下文已清理: " +
Thread.currentThread().getName());
}
@PreDestroy
public void cleanup() {
// 线程结束时自动清理
clear();
}
}
@Singleton
public class TaskExecutor {
private final SafeThreadContext threadContext;
public TaskExecutor(SafeThreadContext threadContext) {
this.threadContext = threadContext;
}
public void executeTask(String taskData) {
try {
// 使用上下文
threadContext.set("task_data", taskData);
processTask();
} finally {
// ✅ 重要:任务完成后清理上下文
threadContext.clear();
}
}
private void processTask() {
String data = (String) threadContext.get("task_data");
System.out.println("处理任务: " + data);
}
}
---
04.@Refreshable作用域
a.配置刷新
a.动态重载
@Refreshable标记的Bean在配置刷新时会被销毁并重新创建,自动获取最新配置值。
b.Refreshable示例
---
import io.micronaut.context.annotation.Refreshable;
import io.micronaut.context.annotation.ConfigurationProperties;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
@Refreshable
@ConfigurationProperties("database")
public class DatabaseConfig {
private String url;
private int maxConnections;
private Duration connectionTimeout;
public DatabaseConfig() {
System.out.println("DatabaseConfig实例创建");
}
@PostConstruct
public void init() {
System.out.println("DatabaseConfig初始化:");
System.out.println(" URL: " + url);
System.out.println(" 最大连接数: " + maxConnections);
System.out.println(" 超时时间: " + connectionTimeout);
}
@PreDestroy
public void cleanup() {
System.out.println("DatabaseConfig销毁(配置已变更)");
}
// Getter和Setter
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public int getMaxConnections() { return maxConnections; }
public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}
public Duration getConnectionTimeout() { return connectionTimeout; }
public void setConnectionTimeout(Duration connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
}
// 使用Refreshable Bean
@Singleton
public class ConnectionPool {
private final DatabaseConfig config;
public ConnectionPool(DatabaseConfig config) {
this.config = config;
}
public void printConfig() {
// 总是访问最新的DatabaseConfig实例
System.out.println("当前数据库URL: " + config.getUrl());
System.out.println("当前最大连接数: " + config.getMaxConnections());
}
}
// 配置文件 application.yml
// database:
// url: jdbc:mysql://localhost:3306/mydb
// max-connections: 10
// connection-timeout: 5s
// 配置刷新流程
// 1. 修改配置文件或环境变量
// 2. POST /refresh 触发刷新
// 3. 调用DatabaseConfig.cleanup()(@PreDestroy)
// 4. 销毁旧DatabaseConfig实例
// 5. 重新读取配置
// 6. 创建新DatabaseConfig实例
// 7. 调用DatabaseConfig.init()(@PostConstruct)
// 8. 重新注入到依赖它的Bean
---
b.刷新端点
a.启用刷新功能
需要添加management依赖并启用refresh端点,通过HTTP POST触发配置刷新。
b.刷新配置
---
// pom.xml
// <dependency>
// <groupId>io.micronaut</groupId>
// <artifactId>micronaut-management</artifactId>
// </dependency>
// application.yml
// endpoints:
// refresh:
// enabled: true
// sensitive: false # 开发环境,生产环境应设为true
// 触发刷新
// curl -X POST http://localhost:8080/refresh
// 监听刷新事件
import io.micronaut.runtime.context.scope.refresh.RefreshEvent;
import io.micronaut.context.event.ApplicationEventListener;
@Singleton
public class RefreshEventListener implements
ApplicationEventListener<RefreshEvent> {
@Override
public void onApplicationEvent(RefreshEvent event) {
System.out.println("配置刷新事件触发");
System.out.println("刷新的配置键: " + event.getSource());
// 执行刷新后的操作
// 例如:重新连接数据库、更新缓存配置等
}
}
// 编程式刷新
import io.micronaut.management.endpoint.refresh.RefreshEndpoint;
@Singleton
public class ConfigManager {
private final RefreshEndpoint refreshEndpoint;
public ConfigManager(RefreshEndpoint refreshEndpoint) {
this.refreshEndpoint = refreshEndpoint;
}
public void refreshConfig() {
// 编程式触发刷新
String[] refreshedKeys = refreshEndpoint.refresh(null);
System.out.println("刷新了 " + refreshedKeys.length + " 个配置");
for (String key : refreshedKeys) {
System.out.println(" - " + key);
}
}
}
---
05.自定义作用域
a.CustomScope接口
a.作用域定义
通过实现CustomScope接口可以创建自定义作用域,控制Bean的创建、缓存和销毁策略。
b.自定义作用域实现
---
import io.micronaut.context.scope.CustomScope;
import io.micronaut.context.BeanResolutionContext;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.BeanIdentifier;
import jakarta.inject.Singleton;
import java.lang.annotation.*;
// 1. 定义作用域注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Scope
public @interface TenantScope {
}
// 2. 实现作用域逻辑
@Singleton
public class TenantScopeImpl implements CustomScope<TenantScope> {
// 每个租户一个Bean实例缓存
private final Map<String, Map<BeanIdentifier, Object>> tenantBeans =
new ConcurrentHashMap<>();
@Override
public Class<TenantScope> annotationType() {
return TenantScope.class;
}
@Override
public <T> T get(BeanResolutionContext resolutionContext,
BeanDefinition<T> beanDefinition,
BeanIdentifier identifier,
Provider<T> provider) {
// 获取当前租户ID
String tenantId = getCurrentTenantId();
// 为每个租户维护独立的Bean缓存
Map<BeanIdentifier, Object> beans =
tenantBeans.computeIfAbsent(tenantId,
k -> new ConcurrentHashMap<>());
// 如果租户Bean不存在,创建新实例
return (T) beans.computeIfAbsent(identifier, k -> {
T bean = provider.get();
System.out.println("为租户[" + tenantId + "]创建Bean: " +
beanDefinition.getBeanType().getSimpleName());
return bean;
});
}
@Override
public <T> Optional<T> remove(BeanIdentifier identifier) {
String tenantId = getCurrentTenantId();
Map<BeanIdentifier, Object> beans = tenantBeans.get(tenantId);
if (beans != null) {
T removed = (T) beans.remove(identifier);
System.out.println("移除租户[" + tenantId + "]的Bean");
return Optional.ofNullable(removed);
}
return Optional.empty();
}
private String getCurrentTenantId() {
// 从ThreadLocal或请求头获取租户ID
return TenantContext.getCurrentTenantId();
}
// 清理指定租户的所有Bean
public void clearTenant(String tenantId) {
Map<BeanIdentifier, Object> beans = tenantBeans.remove(tenantId);
if (beans != null) {
System.out.println("清理租户[" + tenantId + "]的" +
beans.size() + "个Bean");
}
}
}
// 3. 租户上下文管理
@Singleton
public class TenantContext {
private static final ThreadLocal<String> currentTenant =
new ThreadLocal<>();
public static void setCurrentTenantId(String tenantId) {
currentTenant.set(tenantId);
}
public static String getCurrentTenantId() {
String tenantId = currentTenant.get();
if (tenantId == null) {
throw new IllegalStateException("租户ID未设置");
}
return tenantId;
}
public static void clear() {
currentTenant.remove();
}
}
---
b.使用自定义作用域
a.应用场景
自定义作用域适合多租户系统、会话管理、缓存策略等需要特殊Bean生命周期的场景。
b.使用示例
---
// 定义租户级别的服务
@TenantScope
public class TenantDataSource implements DataSource {
private final String tenantId;
private Connection connection;
public TenantDataSource() {
this.tenantId = TenantContext.getCurrentTenantId();
System.out.println("创建租户数据源: " + tenantId);
}
@PostConstruct
public void init() throws SQLException {
// 根据租户ID连接到对应数据库
String url = "jdbc:mysql://localhost:3306/tenant_" + tenantId;
this.connection = DriverManager.getConnection(url);
System.out.println("租户[" + tenantId + "]数据库连接已建立");
}
@Override
public Connection getConnection() throws SQLException {
return connection;
}
@PreDestroy
public void cleanup() throws SQLException {
if (connection != null && !connection.isClosed()) {
connection.close();
System.out.println("租户[" + tenantId + "]数据库连接已关闭");
}
}
}
// 租户级别的Repository
@TenantScope
public class TenantUserRepository {
private final TenantDataSource dataSource;
public TenantUserRepository(TenantDataSource dataSource) {
this.dataSource = dataSource;
System.out.println("创建租户Repository");
}
public List<User> findAll() throws SQLException {
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
List<User> users = new ArrayList<>();
while (rs.next()) {
users.add(mapUser(rs));
}
return users;
}
}
private User mapUser(ResultSet rs) throws SQLException {
// 映射逻辑
return new User();
}
}
// 使用示例
@Controller("/api/users")
public class UserController {
private final TenantUserRepository userRepository;
public UserController(TenantUserRepository userRepository) {
this.userRepository = userRepository;
}
@Get
public List<User> getUsers(@Header("X-Tenant-Id") String tenantId) {
try {
// 设置当前租户
TenantContext.setCurrentTenantId(tenantId);
// 获取租户数据(使用租户级别的Repository)
return userRepository.findAll();
} finally {
// 清理租户上下文
TenantContext.clear();
}
}
}
// 多租户请求示例
// 请求1: GET /api/users Header: X-Tenant-Id=tenant-001
// 输出:
// 创建租户数据源: tenant-001
// 租户[tenant-001]数据库连接已建立
// 创建租户Repository
// 返回tenant-001的用户列表
// 请求2: GET /api/users Header: X-Tenant-Id=tenant-002
// 输出:
// 创建租户数据源: tenant-002
// 租户[tenant-002]数据库连接已建立
// 创建租户Repository
// 返回tenant-002的用户列表
// 请求3: GET /api/users Header: X-Tenant-Id=tenant-001
// 输出:
// 返回tenant-001的用户列表(复用已创建的实例)
---
06.作用域选择指南
a.选择决策
a.作用域决策树
根据Bean的特性选择合适的作用域,考虑线程安全性、状态管理、性能和资源占用。
b.决策示例
---
// 决策流程
// 1. Bean是否有状态(包含可变字段)?
// ├─ 否(无状态)→ 使用@Singleton
// └─ 是(有状态)→ 继续判断
// 2. 状态是否与HTTP请求相关?
// ├─ 是 → 使用@RequestScope
// └─ 否 → 继续判断
// 3. 状态是否与线程相关?
// ├─ 是 → 使用@ThreadLocal
// └─ 否 → 继续判断
// 4. 是否需要独立实例?
// ├─ 是 → 使用@Prototype
// └─ 否 → 继续判断
// 5. 配置是否需要动态刷新?
// ├─ 是 → 使用@Refreshable
// └─ 否 → 使用@Singleton
// 示例1:无状态服务 → Singleton
@Singleton
public class MathService {
public int add(int a, int b) {
return a + b;
}
// 无状态,线程安全,使用单例
}
// 示例2:请求相关 → RequestScope
@RequestScope
public class UserSession {
private User currentUser;
private String sessionToken;
// 每个请求独立实例
}
// 示例3:线程相关 → ThreadLocal
@ThreadLocal
public class TransactionContext {
private String transactionId;
private List<String> operations;
// 每个线程独立实例
}
// 示例4:需要隔离 → Prototype
@Prototype
public class ReportGenerator {
private List<ReportSection> sections = new ArrayList<>();
public void addSection(ReportSection section) {
sections.add(section);
}
// 每次使用创建新实例,避免状态污染
}
// 示例5:动态配置 → Refreshable
@Refreshable
@ConfigurationProperties("feature-flags")
public class FeatureFlags {
private boolean newUIEnabled;
private boolean betaFeaturesEnabled;
// 配置变更时重新创建
}
// 示例6:有状态但需共享 → 需要重新设计
// ❌ 错误示例
@Singleton
public class BadCounter {
private int count = 0; // 多线程不安全!
public void increment() {
count++; // 竞态条件
}
}
// ✅ 正确方案1:使用原子类
@Singleton
public class SafeCounter {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
}
// ✅ 正确方案2:无状态设计
@Singleton
public class StatelessCounter {
public int increment(int current) {
return current + 1;
}
}
---
b.性能与资源权衡
a.性能对比
Singleton性能最优,Prototype和RequestScope会频繁创建实例,需要权衡性能和资源占用。
b.资源使用示例
---
// 性能测试
import io.micronaut.context.BeanContext;
public class ScopePerformanceTest {
public static void main(String[] args) {
BeanContext context = BeanContext.run();
// 测试1:Singleton
long start1 = System.nanoTime();
for (int i = 0; i < 100000; i++) {
SingletonBean bean = context.getBean(SingletonBean.class);
}
long duration1 = System.nanoTime() - start1;
System.out.println("Singleton获取10万次: " +
duration1 / 1_000_000 + "ms");
// 测试2:Prototype
long start2 = System.nanoTime();
for (int i = 0; i < 100000; i++) {
PrototypeBean bean = context.getBean(PrototypeBean.class);
}
long duration2 = System.nanoTime() - start2;
System.out.println("Prototype创建10万次: " +
duration2 / 1_000_000 + "ms");
context.close();
}
}
// 测试结果
// Singleton获取10万次: 12ms(缓存查找)
// Prototype创建10万次: 850ms(每次创建新实例)
// 内存占用对比
@Singleton
public class HeavyService {
private final byte[] data = new byte[1024 * 1024]; // 1MB
// 整个应用只占用1MB
}
@Prototype
public class HeavyPrototype {
private final byte[] data = new byte[1024 * 1024]; // 1MB
// 每次创建占用1MB,需要注意内存泄漏
}
// 使用建议
// ✅ 大对象、重量级资源 → 使用Singleton
// ✅ 频繁创建的轻量对象 → 使用Prototype
// ✅ 请求级别的小对象 → 使用RequestScope
// ❌ 避免大对象使用Prototype(内存占用高)
// ❌ 避免频繁创建重量级对象(性能问题)
---
07.作用域陷阱
a.作用域不匹配
a.依赖作用域问题
Singleton Bean不应依赖Prototype或RequestScope Bean,否则会导致作用域泄漏,需要使用Provider延迟获取。
b.作用域陷阱示例
---
// ❌ 错误:Singleton依赖Prototype
@Prototype
public class TaskHandler {
private String taskId = UUID.randomUUID().toString();
public void handle() {
System.out.println("处理任务: " + taskId);
}
}
@Singleton
public class TaskManager {
private final TaskHandler taskHandler;
public TaskManager(TaskHandler taskHandler) {
// ⚠️ 问题:Singleton在创建时注入一个Prototype实例
// 之后TaskManager一直使用这个实例,Prototype失去意义
this.taskHandler = taskHandler;
}
public void executeTask() {
// 总是使用同一个TaskHandler实例
taskHandler.handle(); // taskId永远相同!
}
}
// ✅ 正确:使用Provider
import jakarta.inject.Provider;
@Singleton
public class CorrectTaskManager {
private final Provider<TaskHandler> taskHandlerProvider;
public CorrectTaskManager(Provider<TaskHandler> taskHandlerProvider) {
this.taskHandlerProvider = taskHandlerProvider;
}
public void executeTask() {
// 每次调用时获取新的Prototype实例
TaskHandler handler = taskHandlerProvider.get();
handler.handle(); // taskId每次不同
}
}
// ❌ 错误:Singleton依赖RequestScope
@RequestScope
public class CurrentUser {
private User user;
// 每个请求独立实例
}
@Singleton
public class UserAuditService {
private final CurrentUser currentUser;
public UserAuditService(CurrentUser currentUser) {
// ⚠️ 问题:Singleton创建时注入第一个请求的CurrentUser
// 后续所有请求都使用这个实例,导致错误
this.currentUser = currentUser;
}
}
// ✅ 正确:使用Provider
@Singleton
public class CorrectUserAuditService {
private final Provider<CurrentUser> currentUserProvider;
public CorrectUserAuditService(Provider<CurrentUser> currentUserProvider) {
this.currentUserProvider = currentUserProvider;
}
public void audit(String action) {
// 每次调用时获取当前请求的CurrentUser
CurrentUser user = currentUserProvider.get();
log(user, action);
}
private void log(CurrentUser user, String action) {
// 记录审计日志
}
}
---
b.作用域混合最佳实践
a.依赖规则
遵循作用域依赖规则,长生命周期Bean可以依赖短生命周期Bean,但需要通过Provider获取,确保每次获取最新实例。
b.最佳实践示例
---
// 作用域依赖规则
// ✅ 允许:窄作用域 → 宽作用域
@RequestScope
public class RequestBean {
private final SingletonBean singletonBean; // OK
}
// ❌ 禁止:宽作用域 → 窄作用域(直接依赖)
@Singleton
public class SingletonBean {
private final RequestBean requestBean; // 错误!
}
// ✅ 允许:宽作用域 → 窄作用域(通过Provider)
@Singleton
public class CorrectSingletonBean {
private final Provider<RequestBean> requestBeanProvider; // OK
}
// 完整示例:电商系统
// 1. Singleton:配置服务
@Singleton
public class AppConfig {
public String getAppName() {
return "ECommerce";
}
}
// 2. RequestScope:当前用户
@RequestScope
public class CurrentUserContext {
private Long userId;
private String username;
public void setUser(Long userId, String username) {
this.userId = userId;
this.username = username;
}
public Long getUserId() { return userId; }
public String getUsername() { return username; }
}
// 3. Prototype:订单处理器
@Prototype
public class OrderProcessor {
private final String processorId = UUID.randomUUID().toString();
public Order process(OrderRequest request) {
System.out.println("处理器[" + processorId + "]处理订单");
return new Order(request);
}
}
// 4. Singleton服务混合使用
@Singleton
public class OrderService {
private final AppConfig appConfig; // Singleton
private final Provider<CurrentUserContext> userProvider; // RequestScope
private final Provider<OrderProcessor> processorProvider; // Prototype
public OrderService(
AppConfig appConfig,
Provider<CurrentUserContext> userProvider,
Provider<OrderProcessor> processorProvider) {
this.appConfig = appConfig;
this.userProvider = userProvider;
this.processorProvider = processorProvider;
}
public Order createOrder(OrderRequest request) {
// 访问Singleton(直接使用)
System.out.println("应用: " + appConfig.getAppName());
// 访问RequestScope(通过Provider)
CurrentUserContext user = userProvider.get();
System.out.println("当前用户: " + user.getUsername());
// 访问Prototype(通过Provider,每次获取新实例)
OrderProcessor processor = processorProvider.get();
return processor.process(request);
}
}
// 作用域总结
// @Singleton → 无状态服务、配置、工具类
// @Prototype → 有状态对象、需要隔离的对象
// @RequestScope → 请求级上下文、用户会话
// @ThreadLocal → 线程级上下文、事务管理
// @Refreshable → 动态配置、特性开关
// 自定义作用域 → 多租户、特殊生命周期需求
---
3.5 条件化Bean
01.@Requires注解
a.条件创建Bean
a.基本条件判断
@Requires注解允许根据配置、环境、类存在等条件决定是否创建Bean,实现灵活的组件装配。
b.基础条件示例
---
import io.micronaut.context.annotation.Requires;
import jakarta.inject.Singleton;
// 示例1:根据配置属性决定是否创建
@Singleton
@Requires(property = "redis.enabled", value = "true")
public class RedisCache implements CacheService {
public RedisCache() {
System.out.println("Redis缓存已启用");
}
@Override
public void put(String key, Object value) {
// Redis缓存实现
}
@Override
public Object get(String key) {
return null;
}
}
// 示例2:配置不存在时创建(默认实现)
@Singleton
@Requires(property = "redis.enabled", value = "false", defaultValue = "false")
public class InMemoryCache implements CacheService {
private final Map<String, Object> cache = new ConcurrentHashMap<>();
public InMemoryCache() {
System.out.println("内存缓存已启用");
}
@Override
public void put(String key, Object value) {
cache.put(key, value);
}
@Override
public Object get(String key) {
return cache.get(key);
}
}
// application.yml场景1
// redis:
// enabled: true
// 结果:创建RedisCache
// application.yml场景2
// redis:
// enabled: false
// 结果:创建InMemoryCache
// application.yml场景3(未配置redis.enabled)
// 结果:创建InMemoryCache(defaultValue="false")
---
b.配置条件
a.属性匹配
@Requires支持多种配置条件,包括属性存在、属性值匹配、属性不存在等。
b.配置条件示例
---
import io.micronaut.context.annotation.Requires;
// 条件1:属性存在
@Singleton
@Requires(property = "database.url")
public class DatabaseService {
// 仅当database.url配置存在时创建
}
// 条件2:属性值匹配
@Singleton
@Requires(property = "app.mode", value = "production")
public class ProductionMonitor {
// 仅在生产环境创建
}
// 条件3:属性值不匹配
@Singleton
@Requires(property = "app.mode", notEquals = "production")
public class DevelopmentDebugger {
// 非生产环境创建
}
// 条件4:属性不存在
@Singleton
@Requires(missingProperty = "external.service.url")
public class MockExternalService implements ExternalService {
// 外部服务未配置时使用Mock
@Override
public String call() {
return "Mock Response";
}
}
// 条件5:多个属性条件(AND关系)
@Singleton
@Requires(property = "oauth.enabled", value = "true")
@Requires(property = "oauth.provider", value = "google")
public class GoogleOAuthService {
// 同时满足:oauth启用 且 provider为google
}
// 条件6:属性值模式匹配
@Singleton
@Requires(property = "storage.type", pattern = "s3|oss|obs")
public class CloudStorageService {
private final String storageType;
public CloudStorageService(@Value("${storage.type}") String storageType) {
this.storageType = storageType;
System.out.println("云存储类型: " + storageType);
}
}
// 配置示例
// application-dev.yml
// app:
// mode: development
// 结果:创建DevelopmentDebugger
// application-prod.yml
// app:
// mode: production
// oauth:
// enabled: true
// provider: google
// storage:
// type: s3
// 结果:创建ProductionMonitor, GoogleOAuthService, CloudStorageService
---
02.环境条件
a.@Requires(env)
a.环境判断
根据当前激活的环境(dev、test、prod等)决定是否创建Bean,实现环境特定的组件配置。
b.环境条件示例
---
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.Environment;
// 示例1:仅在开发环境创建
@Singleton
@Requires(env = Environment.DEVELOPMENT)
public class H2DatabaseInitializer {
@PostConstruct
public void init() {
System.out.println("初始化H2内存数据库(开发环境)");
// 创建测试数据
createTestData();
}
private void createTestData() {
// 插入测试数据
}
}
// 示例2:仅在生产环境创建
@Singleton
@Requires(env = Environment.PRODUCTION)
public class ProductionDataSource implements DataSource {
public ProductionDataSource() {
System.out.println("连接生产数据库");
}
@Override
public Connection getConnection() throws SQLException {
return DriverManager.getConnection(
"jdbc:mysql://prod-db:3306/mydb");
}
}
// 示例3:多环境条件
@Singleton
@Requires(env = {Environment.DEVELOPMENT, Environment.TEST})
public class MockEmailService implements EmailService {
@Override
public void send(String to, String subject, String body) {
// 开发和测试环境不发送真实邮件
System.out.println("模拟发送邮件到: " + to);
System.out.println("主题: " + subject);
}
}
// 示例4:排除特定环境
@Singleton
@Requires(notEnv = Environment.TEST)
public class RealPaymentGateway implements PaymentGateway {
// 非测试环境使用真实支付网关
@Override
public PaymentResult charge(BigDecimal amount) {
System.out.println("调用真实支付接口");
return new PaymentResult(true);
}
}
@Singleton
@Requires(env = Environment.TEST)
public class MockPaymentGateway implements PaymentGateway {
// 测试环境使用Mock支付
@Override
public PaymentResult charge(BigDecimal amount) {
System.out.println("Mock支付: " + amount);
return new PaymentResult(true);
}
}
// 启动命令与环境
// java -jar app.jar → 默认环境
// java -jar app.jar -Dmicronaut.environments=dev → 开发环境
// java -jar app.jar -Dmicronaut.environments=prod → 生产环境
// java -jar app.jar -Dmicronaut.environments=test → 测试环境
---
b.自定义环境
a.环境定义
可以定义自定义环境名称,根据部署场景创建不同的Bean配置。
b.自定义环境示例
---
// 定义自定义环境
// application.yml
// micronaut:
// environments:
// - staging
// - aws-cn
// 使用自定义环境
@Singleton
@Requires(env = "staging")
public class StagingMonitor {
public StagingMonitor() {
System.out.println("预发布环境监控已启用");
}
}
@Singleton
@Requires(env = "aws-cn")
public class ChinaRegionService {
public ChinaRegionService() {
System.out.println("中国区服务已启用");
}
}
// 多环境组合
@Singleton
@Requires(env = {"staging", "production"})
public class PerformanceMonitor {
// 在预发布和生产环境启用性能监控
}
// 启动应用
// java -jar app.jar -Dmicronaut.environments=staging
// 结果:创建StagingMonitor和PerformanceMonitor
// java -jar app.jar -Dmicronaut.environments=staging,aws-cn
// 结果:创建StagingMonitor、ChinaRegionService、PerformanceMonitor
---
03.类存在条件
a.@Requires(classes)
a.依赖类检测
根据classpath中是否存在特定类来决定是否创建Bean,实现可选依赖的自动配置。
b.类存在示例
---
import io.micronaut.context.annotation.Requires;
// 示例1:Redis客户端存在时启用Redis缓存
@Singleton
@Requires(classes = {
io.lettuce.core.RedisClient.class,
io.micronaut.redis.RedisConfiguration.class
})
public class RedisConfiguration {
public RedisConfiguration() {
System.out.println("检测到Redis依赖,启用Redis配置");
}
@Bean
public RedisClient redisClient() {
return RedisClient.create("redis://localhost:6379");
}
}
// 示例2:Kafka客户端存在时启用消息队列
@Singleton
@Requires(classes = org.apache.kafka.clients.producer.KafkaProducer.class)
public class KafkaMessagePublisher implements MessagePublisher {
private final KafkaProducer<String, String> producer;
public KafkaMessagePublisher() {
System.out.println("Kafka客户端可用,启用Kafka发布器");
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
this.producer = new KafkaProducer<>(props);
}
@Override
public void publish(String topic, String message) {
producer.send(new ProducerRecord<>(topic, message));
}
}
// 示例3:类不存在时使用备用方案
@Singleton
@Requires(missingClasses = "org.apache.kafka.clients.producer.KafkaProducer")
public class InMemoryMessagePublisher implements MessagePublisher {
public InMemoryMessagePublisher() {
System.out.println("Kafka不可用,使用内存队列");
}
@Override
public void publish(String topic, String message) {
System.out.println("发布消息到内存队列[" + topic + "]: " + message);
}
}
// pom.xml包含Redis依赖时
// <dependency>
// <groupId>io.micronaut.redis</groupId>
// <artifactId>micronaut-redis-lettuce</artifactId>
// </dependency>
// 结果:创建RedisConfiguration
// pom.xml包含Kafka依赖时
// <dependency>
// <groupId>org.apache.kafka</groupId>
// <artifactId>kafka-clients</artifactId>
// </dependency>
// 结果:创建KafkaMessagePublisher
// pom.xml不包含Kafka依赖时
// 结果:创建InMemoryMessagePublisher
---
b.Bean存在条件
a.@Requires(beans)
根据容器中是否存在特定Bean来决定是否创建当前Bean,实现Bean间的条件依赖。
b.Bean条件示例
---
// 示例1:依赖DataSource存在
@Singleton
@Requires(beans = DataSource.class)
public class DatabaseHealthIndicator implements HealthIndicator {
private final DataSource dataSource;
public DatabaseHealthIndicator(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public HealthStatus check() {
try (Connection conn = dataSource.getConnection()) {
return HealthStatus.UP;
} catch (SQLException e) {
return HealthStatus.DOWN;
}
}
}
// 示例2:某Bean不存在时创建备用Bean
@Singleton
@Requires(missingBeans = RedisClient.class)
public class LocalCacheManager implements CacheManager {
public LocalCacheManager() {
System.out.println("Redis客户端不存在,使用本地缓存");
}
}
// 示例3:组合条件
@Singleton
@Requires(beans = {DataSource.class, TransactionManager.class})
@Requires(property = "jpa.enabled", value = "true")
public class JpaConfiguration {
// 同时满足:
// 1. DataSource Bean存在
// 2. TransactionManager Bean存在
// 3. jpa.enabled=true
public JpaConfiguration() {
System.out.println("JPA配置已启用");
}
}
// 示例4:条件化@Primary
@Primary
@Singleton
@Requires(beans = RedisClient.class)
public class RedisCacheManager implements CacheManager {
// 如果Redis可用,优先使用Redis缓存
}
@Singleton
@Requires(missingBeans = RedisClient.class)
public class SimpleCacheManager implements CacheManager {
// Redis不可用时使用简单缓存
}
---
04.SDK版本条件
a.@Requires(sdk)
a.JDK版本判断
根据当前运行的JDK版本决定是否创建Bean,适配不同Java版本的API。
b.SDK版本示例
---
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.version.SemanticVersion;
// 示例1:要求最低JDK版本
@Singleton
@Requires(sdk = Requires.Sdk.JAVA, value = "17")
public class ModernJavaService {
public ModernJavaService() {
System.out.println("使用Java 17+特性");
}
public String processWithRecords(String input) {
// 使用Java 17 Records
record Result(String value, long timestamp) {}
Result result = new Result(input, System.currentTimeMillis());
return result.value();
}
public String usePatternMatching(Object obj) {
// 使用Java 17 Pattern Matching
if (obj instanceof String s && s.length() > 0) {
return s.toUpperCase();
}
return "";
}
}
// 示例2:特定版本范围
@Singleton
@Requires(sdk = Requires.Sdk.JAVA, value = "11", configuration = "11-16")
public class Java11To16Service {
// 仅在JDK 11-16版本创建
}
// 示例3:JDK 8兼容实现
@Singleton
@Requires(sdk = Requires.Sdk.JAVA, value = "1.8")
public class LegacyJavaService {
public LegacyJavaService() {
System.out.println("使用Java 8兼容实现");
}
public String process(String input) {
// 避免使用新版本特性
return input.toUpperCase();
}
}
// 示例4:GraalVM条件
@Singleton
@Requires(condition = GraalVMCondition.class)
public class NativeImageOptimizedService {
// 仅在GraalVM Native Image中创建
}
import io.micronaut.context.condition.Condition;
import io.micronaut.context.condition.ConditionContext;
public class GraalVMCondition implements Condition {
@Override
public boolean matches(ConditionContext context) {
// 检测是否运行在GraalVM Native Image
String vmName = System.getProperty("java.vm.name");
return vmName != null && vmName.contains("Substrate VM");
}
}
---
b.运行时条件
a.@Requires(condition)
通过自定义Condition实现复杂的运行时判断逻辑。
b.自定义条件示例
---
import io.micronaut.context.condition.Condition;
import io.micronaut.context.condition.ConditionContext;
// 条件1:工作日条件
public class WorkdayCondition implements Condition {
@Override
public boolean matches(ConditionContext context) {
LocalDate today = LocalDate.now();
DayOfWeek day = today.getDayOfWeek();
boolean isWorkday = day != DayOfWeek.SATURDAY &&
day != DayOfWeek.SUNDAY;
System.out.println("今天是" + (isWorkday ? "工作日" : "周末"));
return isWorkday;
}
}
@Singleton
@Requires(condition = WorkdayCondition.class)
public class BusinessHoursService {
// 仅在工作日创建
public BusinessHoursService() {
System.out.println("工作日服务已启动");
}
}
// 条件2:文件存在条件
public class LicenseFileCondition implements Condition {
@Override
public boolean matches(ConditionContext context) {
Path licensePath = Paths.get("/etc/app/license.key");
boolean exists = Files.exists(licensePath);
System.out.println("许可证文件" +
(exists ? "存在" : "不存在"));
return exists;
}
}
@Singleton
@Requires(condition = LicenseFileCondition.class)
public class EnterpriseFeatures {
// 仅当许可证文件存在时启用企业功能
public EnterpriseFeatures() {
System.out.println("企业版功能已启用");
}
public void advancedAnalytics() {
// 高级分析功能
}
}
// 条件3:网络连接条件
public class InternetConnectivityCondition implements Condition {
@Override
public boolean matches(ConditionContext context) {
try {
URL url = new URL("https://www.google.com");
HttpURLConnection conn =
(HttpURLConnection) url.openConnection();
conn.setRequestMethod("HEAD");
conn.setConnectTimeout(2000);
int responseCode = conn.getResponseCode();
return responseCode == 200;
} catch (IOException e) {
return false;
}
}
}
@Singleton
@Requires(condition = InternetConnectivityCondition.class)
public class OnlineUpdateService {
// 有网络连接时启用在线更新
}
@Singleton
@Requires(condition = OfflineCondition.class)
public class OfflineFallbackService {
// 无网络连接时使用离线模式
}
---
05.操作系统条件
a.@Requires(os)
a.平台特定Bean
根据运行的操作系统决定是否创建Bean,实现跨平台适配。
b.OS条件示例
---
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.util.OperatingSystem;
// 示例1:Linux特定实现
@Singleton
@Requires(os = Requires.Family.LINUX)
public class LinuxSystemService implements SystemService {
public LinuxSystemService() {
System.out.println("Linux系统服务已启动");
}
@Override
public String getSystemInfo() {
// 使用Linux特定命令
return executeCommand("uname -a");
}
@Override
public void optimizePerformance() {
// Linux性能优化
executeCommand("sysctl -w net.core.somaxconn=1024");
}
private String executeCommand(String command) {
// 执行shell命令
return "";
}
}
// 示例2:Windows特定实现
@Singleton
@Requires(os = Requires.Family.WINDOWS)
public class WindowsSystemService implements SystemService {
public WindowsSystemService() {
System.out.println("Windows系统服务已启动");
}
@Override
public String getSystemInfo() {
// 使用Windows特定命令
return executeCommand("systeminfo");
}
@Override
public void optimizePerformance() {
// Windows性能优化
executeCommand("netsh int tcp set global autotuninglevel=normal");
}
private String executeCommand(String command) {
// 执行cmd命令
return "";
}
}
// 示例3:macOS特定实现
@Singleton
@Requires(os = Requires.Family.MAC_OS)
public class MacSystemService implements SystemService {
public MacSystemService() {
System.out.println("macOS系统服务已启动");
}
@Override
public String getSystemInfo() {
return executeCommand("sw_vers");
}
@Override
public void optimizePerformance() {
// macOS性能优化
}
private String executeCommand(String command) {
return "";
}
}
// 示例4:多平台支持
@Singleton
@Requires(os = {Requires.Family.LINUX, Requires.Family.MAC_OS})
public class UnixFileWatcher {
// Linux和macOS都支持inotify/fsevents
public UnixFileWatcher() {
System.out.println("Unix文件监控已启用");
}
}
// 跨平台最佳实践
// 1. 定义统一接口
public interface SystemService {
String getSystemInfo();
void optimizePerformance();
}
// 2. 为每个平台提供实现,使用@Requires(os)
// 3. 业务代码依赖接口,运行时自动注入对应平台实现
@Controller("/system")
public class SystemController {
private final SystemService systemService;
public SystemController(SystemService systemService) {
// 根据运行平台自动注入对应实现
this.systemService = systemService;
}
@Get("/info")
public String getInfo() {
return systemService.getSystemInfo();
}
}
---
b.云平台条件
a.@Requires(cloudPlatform)
根据云平台环境(AWS、GCP、Azure等)决定是否创建Bean。
b.云平台示例
---
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.util.CloudPlatform;
// AWS特定服务
@Singleton
@Requires(cloudPlatform = CloudPlatform.AMAZON_EC2)
public class AwsS3StorageService implements StorageService {
public AwsS3StorageService() {
System.out.println("AWS S3存储服务已启用");
}
@Override
public void upload(String key, byte[] data) {
// 使用AWS SDK上传到S3
AmazonS3 s3Client = AmazonS3ClientBuilder.defaultClient();
s3Client.putObject("my-bucket", key, new ByteArrayInputStream(data));
}
}
// GCP特定服务
@Singleton
@Requires(cloudPlatform = CloudPlatform.GOOGLE_COMPUTE)
public class GcsStorageService implements StorageService {
public GcsStorageService() {
System.out.println("Google Cloud Storage服务已启用");
}
@Override
public void upload(String key, byte[] data) {
// 使用GCP SDK上传到GCS
Storage storage = StorageOptions.getDefaultInstance().getService();
storage.create(BlobInfo.newBuilder("my-bucket", key).build(), data);
}
}
// Azure特定服务
@Singleton
@Requires(cloudPlatform = CloudPlatform.AZURE)
public class AzureBlobStorageService implements StorageService {
public AzureBlobStorageService() {
System.out.println("Azure Blob Storage服务已启用");
}
@Override
public void upload(String key, byte[] data) {
// 使用Azure SDK上传到Blob Storage
}
}
// 本地开发环境(无云平台)
@Singleton
@Requires(missingProperty = "KUBERNETES_SERVICE_HOST")
@Requires(notEnv = Environment.CLOUD)
public class LocalFileStorageService implements StorageService {
public LocalFileStorageService() {
System.out.println("本地文件存储服务已启用");
}
@Override
public void upload(String key, byte[] data) throws IOException {
Path path = Paths.get("/tmp/storage", key);
Files.createDirectories(path.getParent());
Files.write(path, data);
}
}
// 云平台检测原理
// AWS: 检测EC2元数据服务 http://169.254.169.254
// GCP: 检测元数据服务器 metadata.google.internal
// Azure: 检测环境变量 WEBSITE_INSTANCE_ID
// Kubernetes: 检测环境变量 KUBERNETES_SERVICE_HOST
---
06.组合条件
a.复杂条件组合
a.AND条件
多个@Requires注解默认是AND关系,所有条件都满足才创建Bean。
b.AND条件示例
---
// 示例:企业版功能
@Singleton
@Requires(property = "app.edition", value = "enterprise")
@Requires(property = "license.valid", value = "true")
@Requires(beans = DatabaseConnectionPool.class)
@Requires(env = Environment.PRODUCTION)
public class EnterpriseAnalytics {
// 必须同时满足:
// 1. 版本为企业版
// 2. 许可证有效
// 3. 数据库连接池存在
// 4. 生产环境
public EnterpriseAnalytics() {
System.out.println("企业分析功能已启用");
}
public Report generateAdvancedReport() {
// 高级报表功能
return new Report();
}
}
// 示例:安全审计
@Singleton
@Requires(property = "security.audit.enabled", value = "true")
@Requires(beans = {AuditLogger.class, SecurityEventPublisher.class})
@Requires(classes = "org.springframework.security.core.Authentication")
public class SecurityAuditService {
private final AuditLogger auditLogger;
private final SecurityEventPublisher eventPublisher;
public SecurityAuditService(AuditLogger auditLogger,
SecurityEventPublisher eventPublisher) {
this.auditLogger = auditLogger;
this.eventPublisher = eventPublisher;
System.out.println("安全审计服务已启用");
}
public void auditLogin(String username) {
auditLogger.log("用户登录: " + username);
eventPublisher.publishLoginEvent(username);
}
}
---
b.条件优先级
a.条件求值顺序
Micronaut按照特定顺序求值条件,优先检查快速失败的条件以提高性能。
b.优先级示例
---
// 条件求值顺序
// 1. 环境条件(env, notEnv)
// 2. 配置属性条件(property, missingProperty)
// 3. Bean条件(beans, missingBeans)
// 4. 类条件(classes, missingClasses)
// 5. SDK条件(sdk)
// 6. 自定义条件(condition)
@Singleton
@Requires(env = Environment.PRODUCTION) // 1. 首先检查环境
@Requires(property = "feature.enabled") // 2. 然后检查配置
@Requires(beans = DataSource.class) // 3. 检查Bean存在
@Requires(classes = "com.example.AdvancedLib") // 4. 检查类存在
@Requires(sdk = Requires.Sdk.JAVA, value = "17") // 5. 检查JDK版本
@Requires(condition = CustomCondition.class) // 6. 最后执行自定义条件
public class ComplexFeature {
// 按顺序求值,任一条件不满足即停止检查
}
// 性能优化建议
@Singleton
@Requires(property = "expensive.feature.enabled", value = "true") // 快速检查
@Requires(condition = ExpensiveCondition.class) // 耗时检查放后面
public class ExpensiveFeature {
// 如果配置未启用,不会执行ExpensiveCondition
}
public class ExpensiveCondition implements Condition {
@Override
public boolean matches(ConditionContext context) {
// 耗时的条件检查(网络请求、文件扫描等)
return checkLicenseServer();
}
private boolean checkLicenseServer() {
// 网络请求验证许可证
return true;
}
}
---
07.条件化配置
a.@RequiresConfiguration
a.配置类条件
为整个@ConfigurationProperties类添加条件,仅在满足条件时加载配置。
b.配置条件示例
---
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.context.annotation.Requires;
// 示例1:Redis配置(仅在启用时加载)
@ConfigurationProperties("redis")
@Requires(property = "redis.enabled", value = "true")
public class RedisConfiguration {
private String host;
private int port;
private String password;
private int database;
public RedisConfiguration() {
System.out.println("加载Redis配置");
}
// Getter和Setter
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public int getDatabase() { return database; }
public void setDatabase(int database) { this.database = database; }
}
// 示例2:Kafka配置(仅在类存在时加载)
@ConfigurationProperties("kafka")
@Requires(classes = "org.apache.kafka.clients.producer.KafkaProducer")
public class KafkaConfiguration {
private String bootstrapServers;
private String groupId;
private Map<String, Object> producerConfig;
private Map<String, Object> consumerConfig;
public KafkaConfiguration() {
System.out.println("加载Kafka配置");
}
// Getter和Setter省略
}
// 示例3:多条件配置
@ConfigurationProperties("monitoring.prometheus")
@Requires(property = "monitoring.enabled", value = "true")
@Requires(property = "monitoring.type", value = "prometheus")
@Requires(classes = "io.micrometer.prometheus.PrometheusMeterRegistry")
public class PrometheusConfiguration {
private int port;
private String path;
private boolean enabled;
public PrometheusConfiguration() {
System.out.println("加载Prometheus监控配置");
}
// Getter和Setter省略
}
// application.yml
// redis:
// enabled: true
// host: localhost
// port: 6379
//
// monitoring:
// enabled: true
// type: prometheus
// prometheus:
// port: 9090
// path: /metrics
// 结果:
// - RedisConfiguration加载(redis.enabled=true)
// - KafkaConfiguration不加载(类不存在)
// - PrometheusConfiguration加载(所有条件满足)
---
b.条件化Bean工厂
a.@Factory方法条件
@Factory中的@Bean方法也可以使用@Requires,实现条件化Bean创建。
b.工厂条件示例
---
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Requires;
@Factory
public class DataSourceFactory {
// 开发环境:H2内存数据库
@Bean
@Singleton
@Requires(env = Environment.DEVELOPMENT)
public DataSource h2DataSource() {
System.out.println("创建H2内存数据库");
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
// 测试环境:PostgreSQL测试库
@Bean
@Singleton
@Requires(env = Environment.TEST)
@Requires(property = "test.db.url")
public DataSource testDataSource(@Value("${test.db.url}") String url) {
System.out.println("连接测试数据库: " + url);
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
return new HikariDataSource(config);
}
// 生产环境:MySQL主从集群
@Bean
@Singleton
@Requires(env = Environment.PRODUCTION)
@Requires(property = "datasource.master.url")
@Requires(property = "datasource.slave.url")
public DataSource productionDataSource(
@Value("${datasource.master.url}") String masterUrl,
@Value("${datasource.slave.url}") String slaveUrl) {
System.out.println("创建主从数据源");
return createMasterSlaveDataSource(masterUrl, slaveUrl);
}
private DataSource createMasterSlaveDataSource(
String masterUrl, String slaveUrl) {
// 创建读写分离数据源
return null;
}
}
// 不同环境启动结果
// 开发环境: 创建H2内存数据库
// 测试环境: 连接测试数据库: jdbc:postgresql://test-db:5432/testdb
// 生产环境: 创建主从数据源
---
3.6 Bean工厂与Provider
01.@Factory工厂类
a.工厂模式
a.Bean工厂定义
@Factory类用于创建和配置Bean,通过@Bean方法返回Bean实例,适合需要复杂初始化逻辑或第三方类的Bean创建。
b.基础工厂示例
---
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Bean;
import jakarta.inject.Singleton;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
@Factory
public class DataSourceFactory {
// 创建数据源Bean
@Bean
@Singleton
public DataSource dataSource(
@Value("${datasource.url}") String url,
@Value("${datasource.username}") String username,
@Value("${datasource.password}") String password) {
System.out.println("创建数据源: " + url);
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(10);
config.setMinimumIdle(2);
config.setConnectionTimeout(30000);
return new HikariDataSource(config);
}
// 创建多个相关Bean
@Bean
@Singleton
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
System.out.println("创建JdbcTemplate");
return new JdbcTemplate(dataSource);
}
@Bean
@Singleton
public TransactionManager transactionManager(DataSource dataSource) {
System.out.println("创建事务管理器");
return new DataSourceTransactionManager(dataSource);
}
}
// 使用工厂创建的Bean
@Singleton
public class UserRepository {
private final JdbcTemplate jdbcTemplate;
public UserRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public User findById(Long id) {
return jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = ?",
new Object[]{id},
new UserRowMapper()
);
}
}
// Bean创建流程
// 1. BeanContext扫描到DataSourceFactory
// 2. 调用dataSource()方法创建DataSource
// 3. 调用jdbcTemplate()方法,注入DataSource
// 4. 调用transactionManager()方法,注入DataSource
// 5. UserRepository注入JdbcTemplate
---
b.工厂方法依赖注入
a.方法参数注入
@Factory中的@Bean方法可以声明参数,Micronaut会自动注入所需依赖。
b.依赖注入示例
---
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Bean;
@Factory
public class MessagingFactory {
// 无参数工厂方法
@Bean
@Singleton
public MessageQueue messageQueue() {
System.out.println("创建消息队列");
return new InMemoryMessageQueue();
}
// 单个依赖
@Bean
@Singleton
public MessageProducer messageProducer(MessageQueue queue) {
System.out.println("创建消息生产者");
return new DefaultMessageProducer(queue);
}
// 多个依赖
@Bean
@Singleton
public MessageConsumer messageConsumer(
MessageQueue queue,
MessageHandler handler,
@Value("${consumer.threads:4}") int threads) {
System.out.println("创建消息消费者,线程数: " + threads);
return new DefaultMessageConsumer(queue, handler, threads);
}
// 可选依赖
@Bean
@Singleton
public MessageRouter messageRouter(
MessageQueue queue,
@Nullable RetryPolicy retryPolicy,
@Nullable DeadLetterQueue deadLetterQueue) {
System.out.println("创建消息路由");
MessageRouter router = new MessageRouter(queue);
if (retryPolicy != null) {
router.setRetryPolicy(retryPolicy);
System.out.println(" - 重试策略已配置");
}
if (deadLetterQueue != null) {
router.setDeadLetterQueue(deadLetterQueue);
System.out.println(" - 死信队列已配置");
}
return router;
}
}
// Bean创建顺序
// 1. messageQueue() → MessageQueue
// 2. messageProducer(queue) → MessageProducer(依赖MessageQueue)
// 3. messageConsumer(...) → MessageConsumer(依赖多个Bean)
// 4. messageRouter(...) → MessageRouter(包含可选依赖)
---
02.@Bean方法特性
a.返回类型与泛型
a.泛型Bean创建
@Bean方法可以返回泛型类型,Micronaut会保留完整的泛型信息用于依赖注入。
b.泛型Bean示例
---
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Bean;
@Factory
public class ConverterFactory {
// 创建String到Integer的转换器
@Bean
@Singleton
@Named("stringToInteger")
public Converter<String, Integer> stringToIntegerConverter() {
return new Converter<String, Integer>() {
@Override
public Integer convert(String source) {
return Integer.parseInt(source);
}
};
}
// 创建String到Date的转换器
@Bean
@Singleton
@Named("stringToDate")
public Converter<String, Date> stringToDateConverter() {
return new Converter<String, Date>() {
@Override
public Date convert(String source) {
return new SimpleDateFormat("yyyy-MM-dd").parse(source);
}
};
}
// 创建泛型集合Bean
@Bean
@Singleton
@Named("userValidators")
public List<Validator<User>> userValidators() {
return Arrays.asList(
new EmailValidator(),
new PasswordValidator(),
new AgeValidator()
);
}
// 创建泛型Map Bean
@Bean
@Singleton
public Map<String, CommandHandler> commandHandlers(
List<CommandHandler> handlers) {
Map<String, CommandHandler> map = new HashMap<>();
for (CommandHandler handler : handlers) {
map.put(handler.getCommandName(), handler);
}
return map;
}
}
// 注入泛型Bean
@Singleton
public class DataProcessor {
private final Converter<String, Integer> intConverter;
private final Converter<String, Date> dateConverter;
private final List<Validator<User>> validators;
public DataProcessor(
@Named("stringToInteger") Converter<String, Integer> intConverter,
@Named("stringToDate") Converter<String, Date> dateConverter,
@Named("userValidators") List<Validator<User>> validators) {
this.intConverter = intConverter;
this.dateConverter = dateConverter;
this.validators = validators;
}
public void process(String input) {
Integer number = intConverter.convert(input);
System.out.println("转换的数字: " + number);
}
public void validateUser(User user) {
for (Validator<User> validator : validators) {
validator.validate(user);
}
}
}
---
b.Bean方法作用域
a.方法级作用域
@Bean方法可以指定不同的作用域,独立于工厂类的作用域。
b.作用域组合示例
---
@Factory
public class MixedScopeFactory {
// Singleton Bean
@Bean
@Singleton
public CacheService cacheService() {
System.out.println("创建单例缓存服务");
return new RedisCacheService();
}
// Prototype Bean
@Bean
@Prototype
public TaskExecutor taskExecutor() {
System.out.println("创建任务执行器实例");
return new TaskExecutor(UUID.randomUUID().toString());
}
// RequestScope Bean
@Bean
@RequestScope
public RequestLogger requestLogger() {
System.out.println("创建请求日志记录器");
return new RequestLogger();
}
// Refreshable Bean
@Bean
@Refreshable
public FeatureToggle featureToggle(
@Value("${features.new-ui:false}") boolean newUI,
@Value("${features.beta:false}") boolean beta) {
System.out.println("创建特性开关");
return new FeatureToggle(newUI, beta);
}
}
// 使用不同作用域的Bean
@Singleton
public class ApplicationService {
private final CacheService cacheService; // Singleton
private final Provider<TaskExecutor> executorProvider; // Prototype
private final Provider<RequestLogger> loggerProvider; // RequestScope
public ApplicationService(
CacheService cacheService,
Provider<TaskExecutor> executorProvider,
Provider<RequestLogger> loggerProvider) {
this.cacheService = cacheService;
this.executorProvider = executorProvider;
this.loggerProvider = loggerProvider;
}
public void executeTask(String taskData) {
// 获取新的Prototype实例
TaskExecutor executor = executorProvider.get();
executor.execute(taskData);
// 获取当前请求的Logger
RequestLogger logger = loggerProvider.get();
logger.log("Task executed");
}
}
---
03.Provider模式
a.jakarta.inject.Provider
a.延迟获取Bean
Provider<T>接口提供延迟获取Bean的能力,调用get()方法时才实例化Bean,适合循环依赖和作用域不匹配场景。
b.Provider基础示例
---
import jakarta.inject.Provider;
import jakarta.inject.Singleton;
@Singleton
public class ReportService {
private final Provider<ReportGenerator> generatorProvider;
public ReportService(Provider<ReportGenerator> generatorProvider) {
// 构造器执行时不创建ReportGenerator
this.generatorProvider = generatorProvider;
System.out.println("ReportService创建(ReportGenerator未创建)");
}
public Report generateReport(ReportRequest request) {
// 调用时才获取ReportGenerator实例
ReportGenerator generator = generatorProvider.get();
System.out.println("获取ReportGenerator: " +
System.identityHashCode(generator));
return generator.generate(request);
}
}
@Prototype
public class ReportGenerator {
private final String generatorId = UUID.randomUUID().toString();
public ReportGenerator() {
System.out.println("创建ReportGenerator: " + generatorId);
}
public Report generate(ReportRequest request) {
System.out.println("生成报告: " + generatorId);
return new Report();
}
}
// 使用流程
// 1. 应用启动:创建ReportService(ReportGenerator未创建)
// 2. 调用generateReport()
// → 调用generatorProvider.get()
// → 创建新的ReportGenerator实例
// → 生成报告
// 3. 再次调用generateReport()
// → 再次调用generatorProvider.get()
// → 创建新的ReportGenerator实例(Prototype作用域)
---
b.Provider解决循环依赖
a.循环依赖问题
当两个Bean相互依赖时会形成循环,使用Provider可以打破循环,延迟一方的依赖获取。
b.循环依赖解决示例
---
// ❌ 错误:循环依赖
@Singleton
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Singleton
public class ServiceB {
private final ServiceA serviceA;
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
// 错误:BeanInstantiationException: Circular dependency
// ✅ 解决方案1:使用Provider
@Singleton
public class FixedServiceA {
private final Provider<ServiceB> serviceBProvider;
public FixedServiceA(Provider<ServiceB> serviceBProvider) {
this.serviceBProvider = serviceBProvider;
System.out.println("ServiceA创建");
}
public void doSomething() {
// 使用时才获取ServiceB
ServiceB serviceB = serviceBProvider.get();
serviceB.process();
}
}
@Singleton
public class FixedServiceB {
private final FixedServiceA serviceA;
public FixedServiceB(FixedServiceA serviceA) {
this.serviceA = serviceA;
System.out.println("ServiceB创建");
}
public void process() {
System.out.println("ServiceB处理");
}
}
// 创建顺序
// 1. 创建ServiceA(注入Provider<ServiceB>,ServiceB未创建)
// 2. 创建ServiceB(注入ServiceA)
// 3. 循环打破
// ✅ 解决方案2:重新设计(推荐)
// 循环依赖通常表明设计问题,应考虑重构
@Singleton
public class ServiceA {
private final SharedComponent sharedComponent;
public ServiceA(SharedComponent sharedComponent) {
this.sharedComponent = sharedComponent;
}
}
@Singleton
public class ServiceB {
private final SharedComponent sharedComponent;
public ServiceB(SharedComponent sharedComponent) {
this.sharedComponent = sharedComponent;
}
}
@Singleton
public class SharedComponent {
// 提取公共逻辑,消除循环依赖
}
---
04.高级Provider用法
a.条件化获取
a.动态Bean选择
通过Provider配合运行时条件动态选择Bean实例。
b.动态选择示例
---
import jakarta.inject.Provider;
import jakarta.inject.Named;
@Singleton
public class StorageService {
private final Provider<CloudStorage> s3Provider;
private final Provider<CloudStorage> ossProvider;
private final Provider<CloudStorage> localProvider;
public StorageService(
@Named("s3") Provider<CloudStorage> s3Provider,
@Named("oss") Provider<CloudStorage> ossProvider,
@Named("local") Provider<CloudStorage> localProvider) {
this.s3Provider = s3Provider;
this.ossProvider = ossProvider;
this.localProvider = localProvider;
}
public void upload(String key, byte[] data, String region) {
// 根据区域动态选择存储
CloudStorage storage = selectStorage(region);
storage.upload(key, data);
}
private CloudStorage selectStorage(String region) {
switch (region) {
case "us":
case "eu":
return s3Provider.get(); // 使用AWS S3
case "cn":
return ossProvider.get(); // 使用阿里云OSS
default:
return localProvider.get(); // 使用本地存储
}
}
}
// 定义各个存储实现
@Singleton
@Named("s3")
public class S3Storage implements CloudStorage {
@Override
public void upload(String key, byte[] data) {
System.out.println("上传到S3: " + key);
}
}
@Singleton
@Named("oss")
public class OssStorage implements CloudStorage {
@Override
public void upload(String key, byte[] data) {
System.out.println("上传到OSS: " + key);
}
}
@Singleton
@Named("local")
public class LocalStorage implements CloudStorage {
@Override
public void upload(String key, byte[] data) throws IOException {
System.out.println("保存到本地: " + key);
Files.write(Paths.get("/tmp", key), data);
}
}
---
b.懒加载优化
a.按需创建
使用Provider延迟创建重量级Bean,减少应用启动时间和内存占用。
b.懒加载示例
---
@Singleton
public class ApplicationInitializer {
private final Provider<HeavyAnalyticsEngine> analyticsProvider;
private final Provider<MachineLearningModel> mlModelProvider;
private final Provider<BigDataProcessor> bigDataProvider;
public ApplicationInitializer(
Provider<HeavyAnalyticsEngine> analyticsProvider,
Provider<MachineLearningModel> mlModelProvider,
Provider<BigDataProcessor> bigDataProvider) {
this.analyticsProvider = analyticsProvider;
this.mlModelProvider = mlModelProvider;
this.bigDataProvider = bigDataProvider;
System.out.println("ApplicationInitializer创建完成");
System.out.println("重量级组件未初始化,应用快速启动");
}
public void runAnalytics(AnalyticsRequest request) {
// 首次调用时才创建HeavyAnalyticsEngine
HeavyAnalyticsEngine engine = analyticsProvider.get();
engine.analyze(request);
}
public void runPrediction(PredictionRequest request) {
// 首次调用时才加载机器学习模型
MachineLearningModel model = mlModelProvider.get();
model.predict(request);
}
}
@Singleton
public class HeavyAnalyticsEngine {
public HeavyAnalyticsEngine() {
System.out.println("初始化分析引擎(耗时3秒)");
// 模拟耗时初始化
try {
Thread.sleep(3000);
} catch (InterruptedException e) {}
}
public AnalyticsResult analyze(AnalyticsRequest request) {
return new AnalyticsResult();
}
}
@Singleton
public class MachineLearningModel {
public MachineLearningModel() {
System.out.println("加载机器学习模型(耗时5秒)");
// 加载大型模型文件
try {
Thread.sleep(5000);
} catch (InterruptedException e) {}
}
public PredictionResult predict(PredictionRequest request) {
return new PredictionResult();
}
}
// 启动时间对比
// 不使用Provider: 应用启动耗时8秒(3+5)
// 使用Provider: 应用启动耗时<1秒,按需加载组件
---
05.@EachBean动态Bean
a.基于配置创建多个Bean
a.@EachBean注解
@EachBean根据配置中的每个条目创建对应的Bean实例,适合多数据源、多租户等场景。
b.EachBean示例
---
import io.micronaut.context.annotation.EachBean;
import io.micronaut.context.annotation.ConfigurationProperties;
// 1. 定义数据源配置类
@ConfigurationProperties("datasources")
public class DataSourceConfiguration {
private Map<String, DataSourceSettings> datasources;
public Map<String, DataSourceSettings> getDatasources() {
return datasources;
}
public void setDatasources(Map<String, DataSourceSettings> datasources) {
this.datasources = datasources;
}
public static class DataSourceSettings {
private String url;
private String username;
private String password;
private int maxPoolSize;
// Getter和Setter省略
}
}
// 2. 为每个数据源创建Bean
@EachBean(DataSourceConfiguration.DataSourceSettings.class)
public class DynamicDataSource {
private final String name;
private final HikariDataSource dataSource;
public DynamicDataSource(
BeanIdentifier identifier,
DataSourceConfiguration.DataSourceSettings settings) {
this.name = identifier.getName();
System.out.println("创建数据源: " + name);
HikariConfig config = new HikariConfig();
config.setJdbcUrl(settings.getUrl());
config.setUsername(settings.getUsername());
config.setPassword(settings.getPassword());
config.setMaximumPoolSize(settings.getMaxPoolSize());
this.dataSource = new HikariDataSource(config);
}
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public String getName() {
return name;
}
@PreDestroy
public void close() {
System.out.println("关闭数据源: " + name);
dataSource.close();
}
}
// application.yml配置
// datasources:
// primary:
// url: jdbc:mysql://localhost:3306/main
// username: root
// password: secret
// max-pool-size: 10
// secondary:
// url: jdbc:mysql://localhost:3306/backup
// username: root
// password: secret
// max-pool-size: 5
// analytics:
// url: jdbc:postgresql://localhost:5432/analytics
// username: analyst
// password: secret
// max-pool-size: 20
// 创建结果
// 创建数据源: primary
// 创建数据源: secondary
// 创建数据源: analytics
// 使用动态创建的数据源
@Singleton
public class MultiDataSourceManager {
private final Map<String, DynamicDataSource> dataSources;
public MultiDataSourceManager(List<DynamicDataSource> dataSourceList) {
this.dataSources = new HashMap<>();
for (DynamicDataSource ds : dataSourceList) {
dataSources.put(ds.getName(), ds);
}
System.out.println("管理 " + dataSources.size() + " 个数据源");
}
public Connection getConnection(String name) throws SQLException {
DynamicDataSource ds = dataSources.get(name);
if (ds == null) {
throw new IllegalArgumentException("数据源不存在: " + name);
}
return ds.getConnection();
}
}
---
b.@EachProperty详解
a.配置项遍历
@EachProperty遍历配置中的每个子项,为每个子项创建Bean实例。
b.EachProperty示例
---
import io.micronaut.context.annotation.EachProperty;
import io.micronaut.context.annotation.Parameter;
// 1. 定义租户配置Bean
@EachProperty("tenants")
public class TenantConfiguration {
private final String tenantId;
private String name;
private String database;
private String schema;
private boolean enabled;
public TenantConfiguration(@Parameter String tenantId) {
this.tenantId = tenantId;
System.out.println("加载租户配置: " + tenantId);
}
public String getTenantId() { return tenantId; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDatabase() { return database; }
public void setDatabase(String database) { this.database = database; }
public String getSchema() { return schema; }
public void setSchema(String schema) { this.schema = schema; }
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
}
// 2. 为每个租户创建数据源
@EachBean(TenantConfiguration.class)
public class TenantDataSource {
private final TenantConfiguration config;
private final HikariDataSource dataSource;
public TenantDataSource(TenantConfiguration config) {
this.config = config;
if (!config.isEnabled()) {
System.out.println("租户未启用: " + config.getTenantId());
this.dataSource = null;
return;
}
System.out.println("创建租户数据源: " + config.getTenantId());
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(config.getDatabase());
hikariConfig.setSchema(config.getSchema());
this.dataSource = new HikariDataSource(hikariConfig);
}
public Connection getConnection() throws SQLException {
if (dataSource == null) {
throw new IllegalStateException("租户未启用");
}
return dataSource.getConnection();
}
public String getTenantId() {
return config.getTenantId();
}
}
// application.yml配置
// tenants:
// tenant-001:
// name: "客户A"
// database: "jdbc:mysql://localhost:3306/tenant_001"
// schema: "public"
// enabled: true
// tenant-002:
// name: "客户B"
// database: "jdbc:mysql://localhost:3306/tenant_002"
// schema: "public"
// enabled: true
// tenant-003:
// name: "客户C"
// database: "jdbc:mysql://localhost:3306/tenant_003"
// schema: "public"
// enabled: false
// 创建结果
// 加载租户配置: tenant-001
// 创建租户数据源: tenant-001
// 加载租户配置: tenant-002
// 创建租户数据源: tenant-002
// 加载租户配置: tenant-003
// 租户未启用: tenant-003
// 3. 使用租户数据源
@Singleton
public class MultiTenantService {
private final Map<String, TenantDataSource> tenantDataSources;
public MultiTenantService(List<TenantDataSource> dataSourceList) {
this.tenantDataSources = new HashMap<>();
for (TenantDataSource ds : dataSourceList) {
tenantDataSources.put(ds.getTenantId(), ds);
}
System.out.println("管理 " + tenantDataSources.size() + " 个租户");
}
public List<User> getUsersByTenant(String tenantId) throws SQLException {
TenantDataSource ds = tenantDataSources.get(tenantId);
if (ds == null) {
throw new IllegalArgumentException("租户不存在: " + tenantId);
}
try (Connection conn = ds.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
List<User> users = new ArrayList<>();
while (rs.next()) {
users.add(mapUser(rs));
}
return users;
}
}
private User mapUser(ResultSet rs) throws SQLException {
return new User();
}
}
---
06.Bean替换
a.@Replaces注解
a.Bean覆盖
@Replaces允许新Bean替换已存在的Bean,常用于测试、自定义实现或库的默认Bean覆盖。
b.Bean替换示例
---
import io.micronaut.context.annotation.Replaces;
// 原始Bean
@Singleton
public class DefaultEmailService implements EmailService {
public DefaultEmailService() {
System.out.println("创建默认邮件服务");
}
@Override
public void send(String to, String subject, String body) {
System.out.println("发送邮件到: " + to);
// SMTP发送逻辑
}
}
// 测试环境替换为Mock实现
@Singleton
@Replaces(DefaultEmailService.class)
@Requires(env = Environment.TEST)
public class MockEmailService implements EmailService {
private final List<Email> sentEmails = new ArrayList<>();
public MockEmailService() {
System.out.println("创建Mock邮件服务(测试环境)");
}
@Override
public void send(String to, String subject, String body) {
System.out.println("Mock发送邮件到: " + to);
sentEmails.add(new Email(to, subject, body));
}
public List<Email> getSentEmails() {
return sentEmails;
}
}
// 使用场景
@Singleton
public class UserService {
private final EmailService emailService;
public UserService(EmailService emailService) {
this.emailService = emailService;
// 开发/生产环境: 注入DefaultEmailService
// 测试环境: 注入MockEmailService
}
public void registerUser(User user) {
// 保存用户
saveUser(user);
// 发送欢迎邮件
emailService.send(
user.getEmail(),
"欢迎注册",
"欢迎加入我们的平台!"
);
}
private void saveUser(User user) {
// 保存逻辑
}
}
// 测试代码
@MicronautTest(environments = Environment.TEST)
class UserServiceTest {
@Inject
UserService userService;
@Inject
MockEmailService mockEmailService; // 可以注入Mock服务验证
@Test
void testRegisterUser() {
User user = new User();
user.setEmail("[email protected]");
userService.registerUser(user);
// 验证邮件已发送
List<Email> emails = mockEmailService.getSentEmails();
assertEquals(1, emails.size());
assertEquals("[email protected]", emails.get(0).getTo());
}
}
---
b.@Replaces工厂方法
a.替换工厂Bean
可以替换@Factory中的@Bean方法创建的Bean。
b.工厂替换示例
---
// 原始工厂
@Factory
public class DefaultCacheFactory {
@Bean
@Singleton
@Named("userCache")
public Cache userCache() {
System.out.println("创建默认用户缓存(Caffeine)");
return Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
}
}
// 生产环境替换为Redis缓存
@Factory
@Requires(env = Environment.PRODUCTION)
public class ProductionCacheFactory {
@Bean
@Singleton
@Named("userCache")
@Replaces(bean = Cache.class, named = "userCache")
public Cache redisUserCache(RedisClient redisClient) {
System.out.println("创建Redis用户缓存(生产环境)");
return new RedisCache(redisClient, "users");
}
}
// 使用Bean
@Singleton
public class UserService {
private final Cache userCache;
public UserService(@Named("userCache") Cache userCache) {
this.userCache = userCache;
// 开发环境: Caffeine缓存
// 生产环境: Redis缓存
}
public User findUser(Long userId) {
// 从缓存获取
User user = userCache.getIfPresent(userId);
if (user != null) {
return user;
}
// 从数据库查询
user = userRepository.findById(userId);
userCache.put(userId, user);
return user;
}
}
---
07.工厂与Provider最佳实践
a.使用场景选择
a.选择指南
@Factory适合第三方类、复杂初始化、多Bean创建,Provider适合循环依赖、懒加载、作用域不匹配。
b.场景对比
---
// 场景1:第三方类 → 使用@Factory
@Factory
public class ThirdPartyFactory {
@Bean
@Singleton
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
mapper.registerModule(new JavaTimeModule());
return mapper;
}
}
// 场景2:循环依赖 → 使用Provider
@Singleton
public class ServiceWithCircular {
private final Provider<DependentService> dependentProvider;
public ServiceWithCircular(Provider<DependentService> dependentProvider) {
this.dependentProvider = dependentProvider;
}
}
// 场景3:Prototype懒加载 → 使用Provider
@Singleton
public class TaskManager {
private final Provider<TaskHandler> handlerProvider;
public TaskManager(Provider<TaskHandler> handlerProvider) {
this.handlerProvider = handlerProvider;
}
public void handleTask() {
TaskHandler handler = handlerProvider.get(); // 每次新实例
handler.handle();
}
}
// 场景4:条件化创建 → 使用@Requires + @Factory
@Factory
public class ConditionalBeanFactory {
@Bean
@Singleton
@Requires(property = "cache.type", value = "redis")
public CacheService redisCache() {
return new RedisCacheService();
}
@Bean
@Singleton
@Requires(property = "cache.type", value = "memcached")
public CacheService memcachedCache() {
return new MemcachedCacheService();
}
}
// 场景5:多实例管理 → 使用@EachBean
@EachProperty("services")
public class ServiceConfiguration {
private String endpoint;
private int timeout;
// 配置省略
}
@EachBean(ServiceConfiguration.class)
public class ExternalServiceClient {
// 为每个配置项创建客户端
}
---
b.性能考虑
a.工厂方法性能
@Factory方法在Bean首次创建时执行,Singleton Bean只执行一次,性能影响可忽略。
b.Provider性能
---
// Provider性能测试
@Singleton
public class ProviderPerformanceTest {
private final SingletonBean singletonBean;
private final Provider<SingletonBean> singletonProvider;
private final Provider<PrototypeBean> prototypeProvider;
public ProviderPerformanceTest(
SingletonBean singletonBean,
Provider<SingletonBean> singletonProvider,
Provider<PrototypeBean> prototypeProvider) {
this.singletonBean = singletonBean;
this.singletonProvider = singletonProvider;
this.prototypeProvider = prototypeProvider;
}
public void testPerformance() {
// 测试1:直接访问Singleton
long start1 = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
singletonBean.doSomething();
}
long duration1 = System.nanoTime() - start1;
System.out.println("直接访问: " + duration1 / 1_000_000 + "ms");
// 测试2:通过Provider访问Singleton
long start2 = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
singletonProvider.get().doSomething();
}
long duration2 = System.nanoTime() - start2;
System.out.println("Provider访问Singleton: " +
duration2 / 1_000_000 + "ms");
// 测试3:通过Provider创建Prototype
long start3 = System.nanoTime();
for (int i = 0; i < 10000; i++) {
PrototypeBean bean = prototypeProvider.get();
bean.doSomething();
}
long duration3 = System.nanoTime() - start3;
System.out.println("Provider创建Prototype: " +
duration3 / 1_000_000 + "ms");
}
}
// 测试结果
// 直接访问: 15ms
// Provider访问Singleton: 18ms(略慢,因为多一次方法调用)
// Provider创建Prototype: 580ms(每次创建实例)
// 性能建议
// ✅ Singleton直接依赖性能最优
// ✅ Provider<Singleton>开销很小,可以使用
// ⚠️ Provider<Prototype>频繁调用会影响性能
// ❌ 避免在循环中频繁调用prototypeProvider.get()
---
08.工厂最佳实践
a.工厂设计原则
a.单一职责
每个@Factory类应该只负责创建相关的一组Bean,遵循单一职责原则。
b.工厂组织示例
---
// ✅ 推荐:按功能组织工厂
@Factory
public class DatabaseFactory {
@Bean
@Singleton
public DataSource dataSource() { return null; }
@Bean
@Singleton
public JdbcTemplate jdbcTemplate(DataSource ds) { return null; }
@Bean
@Singleton
public TransactionManager transactionManager(DataSource ds) { return null; }
}
@Factory
public class CacheFactory {
@Bean
@Singleton
public CacheManager cacheManager() { return null; }
@Bean
@Singleton
public Cache userCache() { return null; }
@Bean
@Singleton
public Cache productCache() { return null; }
}
// ❌ 不推荐:混合不相关的Bean
@Factory
public class MixedFactory {
@Bean public DataSource dataSource() { return null; }
@Bean public EmailService emailService() { return null; }
@Bean public FileStorage fileStorage() { return null; }
@Bean public MessageQueue messageQueue() { return null; }
// 职责混乱,难以维护
}
---
b.资源管理
a.PreDestroy处理
@Factory创建的Bean应该正确处理资源释放,通过@PreDestroy或实现Closeable接口。
b.资源管理示例
---
@Factory
public class ResourceFactory {
// 方案1:返回实现Closeable的Bean
@Bean
@Singleton
public CloseableHttpClient httpClient() {
System.out.println("创建HTTP客户端");
return HttpClients.custom()
.setMaxConnTotal(100)
.setMaxConnPerRoute(20)
.build();
// BeanContext关闭时自动调用close()
}
// 方案2:创建包装类处理清理
@Bean
@Singleton
public ExecutorServiceWrapper executorService() {
System.out.println("创建线程池");
ExecutorService executor = Executors.newFixedThreadPool(10);
return new ExecutorServiceWrapper(executor);
}
}
public class ExecutorServiceWrapper implements Closeable {
private final ExecutorService executor;
public ExecutorServiceWrapper(ExecutorService executor) {
this.executor = executor;
}
public void execute(Runnable task) {
executor.execute(task);
}
@Override
public void close() throws IOException {
System.out.println("关闭线程池");
executor.shutdown();
try {
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
}
// 方案3:使用@PreDestroy方法
@Factory
public class ConnectionFactory {
private Connection connection;
@Bean
@Singleton
public Connection databaseConnection() throws SQLException {
System.out.println("创建数据库连接");
this.connection = DriverManager.getConnection("jdbc:...");
return connection;
}
@PreDestroy
public void cleanup() throws SQLException {
if (connection != null && !connection.isClosed()) {
System.out.println("关闭数据库连接");
connection.close();
}
}
}
---
09.总结与对比
a.工厂vs构造器
a.使用场景
构造器注入适合自有类,@Factory适合第三方类、需要复杂配置或条件化创建的Bean。
b.对比示例
---
// 场景1:自有类 → 直接使用@Singleton
@Singleton
public class UserService {
private final UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
}
// 场景2:第三方类 → 使用@Factory
@Factory
public class JacksonFactory {
@Bean
@Singleton
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
// 无法在第三方类上添加@Singleton
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
return mapper;
}
}
// 场景3:复杂初始化 → 使用@Factory
@Factory
public class SecurityFactory {
@Bean
@Singleton
public SecurityManager securityManager() {
SecurityManager manager = new SecurityManager();
// 复杂配置
manager.setRealm(new JdbcRealm());
manager.setSessionManager(new DefaultSessionManager());
manager.setCacheManager(new RedisCacheManager());
// 初始化
manager.init();
return manager;
}
}
// 场景4:条件化创建 → 使用@Factory + @Requires
@Factory
public class StorageFactory {
@Bean
@Singleton
@Requires(property = "storage.type", value = "s3")
public Storage s3Storage() {
return new S3Storage();
}
@Bean
@Singleton
@Requires(property = "storage.type", value = "local")
public Storage localStorage() {
return new LocalStorage();
}
}
---
b.Provider最佳实践
a.使用时机
仅在确实需要延迟获取或解决循环依赖时使用Provider,过度使用会降低代码可读性。
b.Provider建议
---
// ✅ 推荐:解决循环依赖
@Singleton
public class ServiceA {
private final Provider<ServiceB> serviceBProvider;
public ServiceA(Provider<ServiceB> serviceBProvider) {
this.serviceBProvider = serviceBProvider;
}
}
// ✅ 推荐:Singleton依赖Prototype
@Singleton
public class TaskManager {
private final Provider<TaskHandler> handlerProvider;
public TaskManager(Provider<TaskHandler> handlerProvider) {
this.handlerProvider = handlerProvider;
}
public void executeTask() {
TaskHandler handler = handlerProvider.get(); // 每次新实例
handler.handle();
}
}
// ✅ 推荐:懒加载重量级Bean
@Singleton
public class Application {
private final Provider<HeavyEngine> engineProvider;
public Application(Provider<HeavyEngine> engineProvider) {
this.engineProvider = engineProvider;
}
public void startEngine() {
HeavyEngine engine = engineProvider.get(); // 仅在需要时创建
engine.start();
}
}
// ❌ 不推荐:Singleton依赖Singleton使用Provider
@Singleton
public class ServiceX {
private final Provider<ServiceY> serviceYProvider; // 不必要
public ServiceX(Provider<ServiceY> serviceYProvider) {
this.serviceYProvider = serviceYProvider;
}
public void doSomething() {
ServiceY serviceY = serviceYProvider.get(); // 多余的间接访问
serviceY.process();
}
}
// ✅ 推荐:Singleton直接依赖Singleton
@Singleton
public class ImprovedServiceX {
private final ServiceY serviceY; // 直接注入
public ImprovedServiceX(ServiceY serviceY) {
this.serviceY = serviceY;
}
public void doSomething() {
serviceY.process(); // 直接访问
}
}
// 总结
// Provider使用场景:
// ✅ 解决循环依赖
// ✅ 长生命周期依赖短生命周期(Singleton → Prototype/RequestScope)
// ✅ 懒加载优化
// ✅ 运行时动态选择Bean
// ❌ 避免无意义的Provider使用
---
4 HTTP服务开发
4.1 @Controller控制器
01.控制器基础
a.@Controller注解
a.控制器定义
@Controller注解将类标记为HTTP端点处理器,通过路径映射接收HTTP请求并返回响应,是构建REST API的核心组件。
b.基础控制器示例
---
import io.micronaut.http.annotation.*;
import io.micronaut.http.HttpStatus;
import jakarta.inject.Singleton;
@Controller("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
// GET请求
@Get("/{id}")
public User getUser(Long id) {
System.out.println("GET /api/users/" + id);
return userService.findById(id);
}
// POST请求
@Post
public User createUser(@Body User user) {
System.out.println("POST /api/users");
return userService.save(user);
}
// PUT请求
@Put("/{id}")
public User updateUser(Long id, @Body User user) {
System.out.println("PUT /api/users/" + id);
user.setId(id);
return userService.update(user);
}
// DELETE请求
@Delete("/{id}")
@Status(HttpStatus.NO_CONTENT)
public void deleteUser(Long id) {
System.out.println("DELETE /api/users/" + id);
userService.delete(id);
}
// 列表查询
@Get
public List<User> listUsers(
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "20") int size) {
System.out.println("GET /api/users?page=" + page + "&size=" + size);
return userService.findAll(page, size);
}
}
// URL映射示例
// GET /api/users/123 → getUser(123)
// POST /api/users → createUser(user)
// PUT /api/users/123 → updateUser(123, user)
// DELETE /api/users/123 → deleteUser(123)
// GET /api/users?page=0&size=10 → listUsers(0, 10)
---
b.路径变量
a.@PathVariable映射
通过{}语法在路径中定义变量,方法参数自动接收路径中对应位置的值。
b.路径变量示例
---
@Controller("/api")
public class ResourceController {
// 单个路径变量
@Get("/users/{userId}")
public User getUser(Long userId) {
// userId自动从路径提取
return userService.findById(userId);
}
// 多个路径变量
@Get("/users/{userId}/orders/{orderId}")
public Order getUserOrder(Long userId, Long orderId) {
return orderService.findByUserAndOrder(userId, orderId);
}
// 字符串路径变量
@Get("/categories/{category}/products/{productCode}")
public Product getProduct(String category, String productCode) {
System.out.println("分类: " + category + ", 产品代码: " + productCode);
return productService.findByCode(category, productCode);
}
// 可选路径变量
@Get("/search{/keyword}")
public List<Product> search(@Nullable String keyword) {
if (keyword == null) {
// GET /api/search → 返回全部
return productService.findAll();
} else {
// GET /api/search/手机 → 搜索"手机"
return productService.search(keyword);
}
}
// 正则表达式约束
@Get("/products/{id:[0-9]+}")
public Product getProductById(Long id) {
// id必须是数字
return productService.findById(id);
}
@Get("/users/{username:[a-zA-Z0-9_]+}")
public User getUserByUsername(String username) {
// username只能包含字母数字下划线
return userService.findByUsername(username);
}
// 路径变量类型转换
@Get("/orders/{date}")
public List<Order> getOrdersByDate(
@Format("yyyy-MM-dd") LocalDate date) {
// 自动将字符串转换为LocalDate
System.out.println("查询日期: " + date);
return orderService.findByDate(date);
}
}
// URL示例
// GET /api/users/123 → userId=123
// GET /api/users/123/orders/456 → userId=123, orderId=456
// GET /api/categories/electronics/products/P001 → category="electronics", productCode="P001"
// GET /api/search → keyword=null
// GET /api/search/手机 → keyword="手机"
// GET /api/orders/2024-01-15 → date=LocalDate(2024-01-15)
---
02.请求方法映射
a.HTTP方法注解
a.RESTful方法
Micronaut提供@Get、@Post、@Put、@Delete、@Patch、@Head、@Options等注解对应HTTP方法。
b.方法注解示例
---
@Controller("/api/products")
public class ProductController {
// GET: 查询单个资源
@Get("/{id}")
public Product getProduct(Long id) {
return productService.findById(id);
}
// GET: 查询资源列表
@Get
public List<Product> listProducts() {
return productService.findAll();
}
// POST: 创建资源
@Post
@Status(HttpStatus.CREATED)
public Product createProduct(@Body Product product) {
return productService.create(product);
}
// PUT: 完整更新资源
@Put("/{id}")
public Product updateProduct(Long id, @Body Product product) {
product.setId(id);
return productService.update(product);
}
// PATCH: 部分更新资源
@Patch("/{id}")
public Product patchProduct(Long id, @Body Map<String, Object> updates) {
return productService.partialUpdate(id, updates);
}
// DELETE: 删除资源
@Delete("/{id}")
@Status(HttpStatus.NO_CONTENT)
public void deleteProduct(Long id) {
productService.delete(id);
}
// HEAD: 检查资源是否存在
@Head("/{id}")
public HttpResponse<?> checkProduct(Long id) {
boolean exists = productService.exists(id);
return exists ?
HttpResponse.ok() :
HttpResponse.notFound();
}
// OPTIONS: 获取支持的HTTP方法
@Options("/{id}")
public HttpResponse<?> options() {
return HttpResponse.ok()
.header("Allow", "GET, PUT, PATCH, DELETE, HEAD, OPTIONS");
}
}
// RESTful API设计
// GET /api/products → 列表查询
// GET /api/products/123 → 查询单个
// POST /api/products → 创建
// PUT /api/products/123 → 完整更新
// PATCH /api/products/123 → 部分更新
// DELETE /api/products/123 → 删除
// HEAD /api/products/123 → 检查存在
// OPTIONS /api/products/123 → 查询支持的方法
---
b.自定义HTTP方法
a.@CustomHttpMethod
支持自定义HTTP方法,用于扩展标准HTTP方法集。
b.自定义方法示例
---
import io.micronaut.http.annotation.CustomHttpMethod;
import io.micronaut.http.HttpMethod;
@Controller("/api/cache")
public class CacheController {
private final CacheService cacheService;
public CacheController(CacheService cacheService) {
this.cacheService = cacheService;
}
// 自定义PURGE方法(清除缓存)
@CustomHttpMethod(method = "PURGE")
@Status(HttpStatus.NO_CONTENT)
public void purgeCache(@QueryValue String key) {
System.out.println("PURGE /api/cache?key=" + key);
cacheService.invalidate(key);
}
// 自定义LOCK方法(锁定资源)
@CustomHttpMethod(method = "LOCK", value = "/{resource}")
public LockResponse lockResource(String resource) {
System.out.println("LOCK /api/cache/" + resource);
String lockToken = cacheService.lock(resource);
return new LockResponse(lockToken);
}
// 自定义UNLOCK方法(解锁资源)
@CustomHttpMethod(method = "UNLOCK", value = "/{resource}")
@Status(HttpStatus.NO_CONTENT)
public void unlockResource(
String resource,
@Header("Lock-Token") String lockToken) {
System.out.println("UNLOCK /api/cache/" + resource);
cacheService.unlock(resource, lockToken);
}
}
// 使用示例
// PURGE /api/cache?key=user:123
// LOCK /api/cache/report-123
// UNLOCK /api/cache/report-123 (Header: Lock-Token: xxx)
---
03.请求参数绑定
a.查询参数
a.@QueryValue注解
通过@QueryValue绑定URL查询参数,支持默认值、类型转换和可选参数。
b.查询参数示例
---
@Controller("/api/search")
public class SearchController {
// 必需参数
@Get
public List<Product> search(@QueryValue String keyword) {
// GET /api/search?keyword=手机
System.out.println("搜索: " + keyword);
return productService.search(keyword);
}
// 可选参数
@Get("/advanced")
public List<Product> advancedSearch(
@QueryValue @Nullable String keyword,
@QueryValue @Nullable String category,
@QueryValue @Nullable BigDecimal minPrice,
@QueryValue @Nullable BigDecimal maxPrice) {
System.out.println("高级搜索:");
System.out.println(" 关键词: " + keyword);
System.out.println(" 分类: " + category);
System.out.println(" 价格区间: " + minPrice + " - " + maxPrice);
return productService.advancedSearch(
keyword, category, minPrice, maxPrice);
}
// 默认值
@Get("/list")
public List<Product> listProducts(
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "20") int size,
@QueryValue(defaultValue = "id") String sortBy,
@QueryValue(defaultValue = "asc") String order) {
System.out.println("分页查询: page=" + page +
", size=" + size +
", sortBy=" + sortBy +
", order=" + order);
return productService.findAll(page, size, sortBy, order);
}
// 数组参数
@Get("/filter")
public List<Product> filterProducts(
@QueryValue("tag") List<String> tags,
@QueryValue("status") List<String> statuses) {
System.out.println("标签: " + tags);
System.out.println("状态: " + statuses);
return productService.filter(tags, statuses);
}
// 日期参数
@Get("/orders")
public List<Order> getOrders(
@QueryValue @Format("yyyy-MM-dd") LocalDate startDate,
@QueryValue @Format("yyyy-MM-dd") LocalDate endDate) {
System.out.println("查询订单: " + startDate + " 至 " + endDate);
return orderService.findByDateRange(startDate, endDate);
}
// Boolean参数
@Get("/products")
public List<Product> getProducts(
@QueryValue(defaultValue = "false") boolean includeDeleted,
@QueryValue(defaultValue = "true") boolean inStock) {
return productService.find(includeDeleted, inStock);
}
}
// URL示例
// GET /api/search?keyword=手机
// GET /api/search/advanced?keyword=手机&category=电子&minPrice=1000&maxPrice=5000
// GET /api/search/list (使用默认值: page=0, size=20, sortBy=id, order=asc)
// GET /api/search/list?page=1&size=50&sortBy=price&order=desc
// GET /api/search/filter?tag=hot&tag=new&status=active&status=promoted
// GET /api/search/orders?startDate=2024-01-01&endDate=2024-12-31
---
b.请求头参数
a.@Header注解
通过@Header绑定HTTP请求头,获取认证token、客户端信息等元数据。
b.请求头示例
---
@Controller("/api")
public class SecureController {
// 必需请求头
@Get("/profile")
public UserProfile getProfile(@Header String authorization) {
System.out.println("Authorization: " + authorization);
// 解析token
String token = authorization.replace("Bearer ", "");
Long userId = jwtService.getUserIdFromToken(token);
return userService.getProfile(userId);
}
// 可选请求头
@Get("/content")
public String getContent(
@Header("Accept-Language") @Nullable String language,
@Header("User-Agent") @Nullable String userAgent) {
System.out.println("语言: " + (language != null ? language : "default"));
System.out.println("客户端: " + userAgent);
return contentService.getContent(language);
}
// 自定义请求头
@Post("/upload")
public UploadResponse upload(
@Body byte[] data,
@Header("X-File-Name") String fileName,
@Header("X-File-Type") String fileType,
@Header(value = "X-Checksum", defaultValue = "") String checksum) {
System.out.println("上传文件: " + fileName);
System.out.println("文件类型: " + fileType);
System.out.println("校验和: " + checksum);
return fileService.upload(fileName, fileType, data, checksum);
}
// 多个请求头
@Get("/api-call")
public ApiResponse callApi(
@Header("X-API-Key") String apiKey,
@Header("X-Request-Id") String requestId,
@Header("X-Tenant-Id") String tenantId) {
System.out.println("API密钥: " + apiKey);
System.out.println("请求ID: " + requestId);
System.out.println("租户ID: " + tenantId);
return apiService.call(apiKey, requestId, tenantId);
}
// HttpHeaders对象(获取所有请求头)
@Get("/headers")
public Map<String, String> getAllHeaders(HttpHeaders headers) {
Map<String, String> result = new HashMap<>();
for (String name : headers.names()) {
result.put(name, headers.get(name));
}
System.out.println("接收到 " + result.size() + " 个请求头");
return result;
}
}
// 请求示例
// curl -H "Authorization: Bearer eyJhbGc..." http://localhost:8080/api/profile
// curl -H "Accept-Language: zh-CN" http://localhost:8080/api/content
// curl -X POST -H "X-File-Name: doc.pdf" -H "X-File-Type: application/pdf" \
// --data-binary @doc.pdf http://localhost:8080/api/upload
---
04.请求体处理
a.@Body注解
a.请求体绑定
@Body注解将HTTP请求体自动反序列化为Java对象,支持JSON、XML等格式。
b.请求体示例
---
@Controller("/api/users")
public class UserManagementController {
// JSON请求体
@Post
public User createUser(@Body User user) {
System.out.println("创建用户: " + user.getUsername());
return userService.save(user);
}
// 嵌套对象
@Post("/register")
public RegistrationResponse register(@Body RegistrationRequest request) {
System.out.println("注册用户: " + request.getUsername());
System.out.println("邮箱: " + request.getEmail());
System.out.println("密码长度: " + request.getPassword().length());
User user = userService.register(request);
return new RegistrationResponse(user.getId(), "注册成功");
}
// 泛型请求体
@Post("/batch")
public BatchResponse<User> batchCreate(@Body List<User> users) {
System.out.println("批量创建 " + users.size() + " 个用户");
List<User> created = new ArrayList<>();
for (User user : users) {
created.add(userService.save(user));
}
return new BatchResponse<>(created.size(), created);
}
// Map请求体
@Patch("/{id}")
public User partialUpdate(Long id, @Body Map<String, Object> updates) {
System.out.println("更新用户 " + id + ": " + updates);
User user = userService.findById(id);
// 应用部分更新
if (updates.containsKey("email")) {
user.setEmail((String) updates.get("email"));
}
if (updates.containsKey("phone")) {
user.setPhone((String) updates.get("phone"));
}
return userService.update(user);
}
// 可选请求体
@Post("/search")
public List<User> search(@Body @Nullable SearchCriteria criteria) {
if (criteria == null) {
// 无请求体,返回所有用户
return userService.findAll();
}
return userService.search(criteria);
}
}
// 请求体类定义
public class RegistrationRequest {
private String username;
private String email;
private String password;
private UserProfile profile;
// Getter和Setter省略
}
public class UserProfile {
private String firstName;
private String lastName;
private LocalDate birthDate;
// Getter和Setter省略
}
// JSON请求示例
// POST /api/users
// Content-Type: application/json
// {
// "username": "john",
// "email": "[email protected]",
// "phone": "13800138000"
// }
// POST /api/users/register
// {
// "username": "john",
// "email": "[email protected]",
// "password": "secret123",
// "profile": {
// "firstName": "John",
// "lastName": "Doe",
// "birthDate": "1990-01-15"
// }
// }
// PATCH /api/users/123
// {
// "email": "[email protected]",
// "phone": "13900139000"
// }
---
b.内容类型协商
a.@Consumes和@Produces
通过@Consumes指定接受的请求类型,@Produces指定返回的响应类型。
b.内容类型示例
---
import io.micronaut.http.annotation.Consumes;
import io.micronaut.http.annotation.Produces;
import io.micronaut.http.MediaType;
@Controller("/api/data")
public class DataController {
// 接受JSON,返回JSON(默认)
@Post("/json")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public DataResponse processJson(@Body DataRequest request) {
return new DataResponse(request.getData());
}
// 接受XML,返回XML
@Post("/xml")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public DataResponse processXml(@Body DataRequest request) {
return new DataResponse(request.getData());
}
// 接受表单数据
@Post("/form")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public String processForm(
@Body("username") String username,
@Body("email") String email) {
System.out.println("用户名: " + username);
System.out.println("邮箱: " + email);
return "表单已提交";
}
// 文件上传
@Post("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public UploadResponse uploadFile(
@Part("file") CompletedFileUpload file,
@Part("description") @Nullable String description) throws IOException {
System.out.println("文件名: " + file.getFilename());
System.out.println("大小: " + file.getSize() + " bytes");
System.out.println("描述: " + description);
byte[] bytes = file.getBytes();
String fileId = fileService.save(file.getFilename(), bytes);
return new UploadResponse(fileId, file.getFilename(), file.getSize());
}
// 返回纯文本
@Get("/text")
@Produces(MediaType.TEXT_PLAIN)
public String getText() {
return "这是纯文本响应";
}
// 返回HTML
@Get("/page")
@Produces(MediaType.TEXT_HTML)
public String getPage() {
return "<html><body><h1>Hello Micronaut</h1></body></html>";
}
// 返回二进制数据
@Get("/download/{fileId}")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public byte[] downloadFile(String fileId) {
return fileService.load(fileId);
}
}
// 请求示例
// POST /api/data/json
// Content-Type: application/json
// {"data": "test"}
// POST /api/data/form
// Content-Type: application/x-www-form-urlencoded
// username=john&[email protected]
// POST /api/data/upload
// Content-Type: multipart/form-data
// [email protected]
// description=重要文档
---
05.响应处理
a.返回类型
a.多种返回方式
控制器方法可以返回对象、HttpResponse、CompletableFuture等多种类型。
b.返回类型示例
---
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MutableHttpResponse;
@Controller("/api/responses")
public class ResponseController {
// 1. 返回对象(自动序列化为JSON)
@Get("/user/{id}")
public User getUser(Long id) {
return userService.findById(id);
}
// 2. 返回HttpResponse(自定义状态码和头)
@Post("/users")
public HttpResponse<User> createUser(@Body User user) {
User created = userService.save(user);
return HttpResponse.created(created)
.header("X-User-Id", created.getId().toString())
.header("Location", "/api/users/" + created.getId());
}
// 3. 返回MutableHttpResponse(构建响应)
@Get("/download/{fileId}")
public MutableHttpResponse<byte[]> downloadFile(String fileId) {
byte[] data = fileService.load(fileId);
String fileName = fileService.getFileName(fileId);
return HttpResponse.ok(data)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header("Content-Disposition",
"attachment; filename=\"" + fileName + "\"");
}
// 4. 返回Optional(404处理)
@Get("/optional/{id}")
public Optional<User> getUserOptional(Long id) {
// 返回Optional.empty()时自动返回404
return userService.findByIdOptional(id);
}
// 5. 返回CompletableFuture(异步响应)
@Get("/async/{id}")
public CompletableFuture<User> getUserAsync(Long id) {
return CompletableFuture.supplyAsync(() ->
userService.findById(id));
}
// 6. 返回void(204 No Content)
@Delete("/{id}")
public void deleteUser(Long id) {
userService.delete(id);
// 自动返回204 No Content
}
// 7. 返回String(纯文本)
@Get("/hello")
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello World";
}
// 8. 返回流式数据
@Get("/stream")
@Produces(MediaType.APPLICATION_JSON_STREAM)
public Publisher<Product> streamProducts() {
return productService.streamAll();
}
}
// 响应示例
// GET /api/responses/user/123
// 200 OK
// Content-Type: application/json
// {"id": 123, "username": "john"}
// POST /api/responses/users
// 201 Created
// X-User-Id: 456
// Location: /api/users/456
// {"id": 456, "username": "jane"}
// GET /api/responses/optional/999 (不存在)
// 404 Not Found
---
b.状态码控制
a.@Status注解
通过@Status注解指定成功响应的HTTP状态码。
b.状态码示例
---
@Controller("/api/resources")
public class ResourceController {
// 201 Created
@Post
@Status(HttpStatus.CREATED)
public Resource createResource(@Body Resource resource) {
return resourceService.create(resource);
}
// 202 Accepted(异步处理)
@Post("/async")
@Status(HttpStatus.ACCEPTED)
public TaskResponse submitTask(@Body Task task) {
String taskId = taskService.submitAsync(task);
return new TaskResponse(taskId, "任务已提交");
}
// 204 No Content
@Put("/{id}")
@Status(HttpStatus.NO_CONTENT)
public void updateResource(Long id, @Body Resource resource) {
resourceService.update(id, resource);
}
// 206 Partial Content
@Get("/{id}/partial")
@Status(HttpStatus.PARTIAL_CONTENT)
public PartialData getPartialData(
Long id,
@Header("Range") String range) {
return resourceService.getPartial(id, range);
}
// 动态状态码
@Get("/{id}")
public HttpResponse<Resource> getResource(Long id) {
Optional<Resource> resource = resourceService.findById(id);
if (resource.isPresent()) {
return HttpResponse.ok(resource.get()); // 200
} else {
return HttpResponse.notFound(); // 404
}
}
// 条件化状态码
@Delete("/{id}")
public HttpResponse<?> deleteResource(Long id) {
boolean deleted = resourceService.delete(id);
if (deleted) {
return HttpResponse.noContent(); // 204
} else {
return HttpResponse.notFound(); // 404
}
}
}
---
06.异常处理
a.@Error注解
a.异常映射
通过@Error注解捕获特定异常,返回自定义错误响应。
b.异常处理示例
---
import io.micronaut.http.annotation.Error;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
@Controller("/api/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@Get("/{id}")
public Product getProduct(Long id) {
// 可能抛出ProductNotFoundException
return productService.findById(id);
}
// 处理特定异常
@Error(exception = ProductNotFoundException.class)
public HttpResponse<ErrorResponse> handleNotFound(
HttpRequest request,
ProductNotFoundException ex) {
System.out.println("产品未找到: " + ex.getMessage());
ErrorResponse error = new ErrorResponse(
"PRODUCT_NOT_FOUND",
ex.getMessage(),
request.getPath()
);
return HttpResponse.notFound(error);
}
// 处理验证异常
@Error(exception = ValidationException.class)
public HttpResponse<ErrorResponse> handleValidation(
HttpRequest request,
ValidationException ex) {
System.out.println("验证失败: " + ex.getErrors());
ErrorResponse error = new ErrorResponse(
"VALIDATION_ERROR",
"请求数据验证失败",
ex.getErrors()
);
return HttpResponse.badRequest(error);
}
// 处理所有异常
@Error
public HttpResponse<ErrorResponse> handleGenericError(
HttpRequest request,
Throwable ex) {
System.err.println("未处理的异常: " + ex.getMessage());
ex.printStackTrace();
ErrorResponse error = new ErrorResponse(
"INTERNAL_ERROR",
"服务器内部错误",
request.getPath()
);
return HttpResponse.serverError(error);
}
}
// 错误响应类
public class ErrorResponse {
private String code;
private String message;
private String path;
private Object details;
private long timestamp;
public ErrorResponse(String code, String message, String path) {
this.code = code;
this.message = message;
this.path = path;
this.timestamp = System.currentTimeMillis();
}
public ErrorResponse(String code, String message, Object details) {
this.code = code;
this.message = message;
this.details = details;
this.timestamp = System.currentTimeMillis();
}
// Getter省略
}
// 错误响应示例
// GET /api/products/999 (不存在)
// 404 Not Found
// {
// "code": "PRODUCT_NOT_FOUND",
// "message": "产品不存在: 999",
// "path": "/api/products/999",
// "timestamp": 1704096000000
// }
---
b.全局异常处理
a.@ServerFilter异常处理
通过全局异常处理器统一处理所有控制器的异常。
b.全局处理器示例
---
import io.micronaut.http.annotation.ServerFilter;
import io.micronaut.http.filter.HttpServerFilter;
import io.micronaut.http.filter.ServerFilterChain;
import org.reactivestreams.Publisher;
@Singleton
public class GlobalExceptionHandler implements ExceptionHandler<Exception, HttpResponse<?>> {
private static final Logger log =
LoggerFactory.getLogger(GlobalExceptionHandler.class);
@Override
public HttpResponse<?> handle(HttpRequest request, Exception exception) {
log.error("全局异常处理", exception);
// 业务异常
if (exception instanceof BusinessException) {
BusinessException bex = (BusinessException) exception;
return HttpResponse.badRequest(new ErrorResponse(
bex.getCode(),
bex.getMessage()
));
}
// 认证异常
if (exception instanceof UnauthorizedException) {
return HttpResponse.unauthorized();
}
// 权限异常
if (exception instanceof ForbiddenException) {
return HttpResponse.forbidden();
}
// 资源未找到
if (exception instanceof ResourceNotFoundException) {
return HttpResponse.notFound(new ErrorResponse(
"NOT_FOUND",
exception.getMessage()
));
}
// 其他异常
return HttpResponse.serverError(new ErrorResponse(
"INTERNAL_ERROR",
"服务器内部错误"
));
}
}
---
4.2 路由与请求映射
01.路由配置
a.路由规则
a.路径匹配机制
Micronaut使用URI模板语法定义路由规则,支持静态路径、路径变量、可选路径段和正则表达式约束,实现灵活的URL映射。路径匹配采用前缀匹配和精确匹配相结合的方式,确保请求正确路由到对应的处理器方法。
b.路由规则示例
---
import io.micronaut.http.annotation.*;
@Controller("/api")
public class RouteController {
// 静态路径 - 精确匹配
@Get("/products")
public List<Product> getAllProducts() {
System.out.println("匹配: /api/products");
return productService.findAll();
}
// 路径变量 - 动态匹配
@Get("/products/{id}")
public Product getProduct(Long id) {
System.out.println("匹配: /api/products/" + id);
return productService.findById(id);
}
// 多级路径变量
@Get("/categories/{category}/products/{id}")
public Product getProductByCategory(String category, Long id) {
System.out.println("匹配: /api/categories/" + category + "/products/" + id);
return productService.findByCategoryAndId(category, id);
}
// 可选路径段 - {/variable} 语法
@Get("/search{/keyword}")
public List<Product> search(@Nullable String keyword) {
if (keyword == null) {
System.out.println("匹配: /api/search (无关键词)");
return productService.findAll();
}
System.out.println("匹配: /api/search/" + keyword);
return productService.search(keyword);
}
// 正则表达式约束 - 只匹配数字ID
@Get("/orders/{orderId:[0-9]+}")
public Order getOrder(Long orderId) {
System.out.println("匹配: /api/orders/" + orderId + " (数字ID)");
return orderService.findById(orderId);
}
// 正则表达式约束 - 匹配用户名格式
@Get("/users/{username:[a-zA-Z0-9_]{3,20}}")
public User getUserByUsername(String username) {
System.out.println("匹配: /api/users/" + username + " (用户名格式)");
return userService.findByUsername(username);
}
}
// URL匹配示例
// GET /api/products → getAllProducts()
// GET /api/products/123 → getProduct(123)
// GET /api/categories/electronics/products/456 → getProductByCategory("electronics", 456)
// GET /api/search → search(null)
// GET /api/search/手机 → search("手机")
// GET /api/orders/789 → getOrder(789) ✓
// GET /api/orders/abc → 404 (不匹配正则) ✗
// GET /api/users/john_doe → getUserByUsername("john_doe") ✓
// GET /api/users/ab → 404 (用户名太短) ✗
---
b.路由优先级
a.匹配优先级规则
当多个路由规则匹配同一请求时,Micronaut按照路径具体程度、HTTP方法、参数数量等因素确定优先级。优先级从高到低依次为:静态路径>带约束的路径变量>普通路径变量>通配符,具体匹配>泛型匹配,确保最精确的路由被选中执行。
b.优先级示例
---
@Controller("/api")
public class PriorityController {
// 优先级1: 静态路径 (最高优先级)
@Get("/products/featured")
public List<Product> getFeaturedProducts() {
System.out.println("路由1: 静态路径 /products/featured");
return productService.findFeatured();
}
// 优先级2: 带正则约束的路径变量
@Get("/products/{id:[0-9]+}")
public Product getProductById(Long id) {
System.out.println("路由2: 数字ID /products/" + id);
return productService.findById(id);
}
// 优先级3: 普通路径变量 (最低优先级)
@Get("/products/{slug}")
public Product getProductBySlug(String slug) {
System.out.println("路由3: 文字标识符 /products/" + slug);
return productService.findBySlug(slug);
}
// HTTP方法优先级: 具体方法>泛型方法
@Get("/users/{id}")
public User getUser(Long id) {
System.out.println("GET /users/" + id);
return userService.findById(id);
}
@Post("/users/{id}")
public User updateUser(Long id, @Body User user) {
System.out.println("POST /users/" + id);
return userService.update(id, user);
}
// 参数数量影响优先级
@Get("/search")
public List<Product> searchAll() {
System.out.println("搜索: 无参数");
return productService.findAll();
}
@Get("/search")
public List<Product> searchWithKeyword(
@QueryValue String keyword) {
System.out.println("搜索: 关键词=" + keyword);
return productService.search(keyword);
}
}
// 路由选择示例
// GET /api/products/featured → 路由1 (静态路径优先)
// GET /api/products/123 → 路由2 (数字匹配正则约束)
// GET /api/products/gaming-mouse → 路由3 (字符串作为slug)
// GET /api/users/456 → getUser(456)
// POST /api/users/456 → updateUser(456, user)
// GET /api/search → searchAll() (无参数版本)
// GET /api/search?keyword=手机 → searchWithKeyword("手机")
---
02.URI模板高级特性
a.多路径映射
a.路径别名
单个方法可以映射多个路径,通过在注解中使用数组定义多个路径字符串,实现路径别名和版本兼容。这对于API版本演进、保持向后兼容性、提供简写路径等场景非常有用。
b.多路��示例
---
@Controller("/api")
public class MultiPathController {
// 多个路径映射到同一方法
@Get({"/products", "/items"})
public List<Product> getProducts() {
System.out.println("访问产品列表 (支持两个路径)");
return productService.findAll();
}
// API版本兼容
@Get({"/v1/users/{id}", "/v2/users/{id}"})
public User getUser(Long id) {
System.out.println("获取用户 (v1和v2都支持)");
return userService.findById(id);
}
// 简写路径和完整路径
@Post({"/register", "/users/register"})
public User register(@Body RegistrationRequest request) {
System.out.println("用户注册 (支持简写和完整路径)");
return userService.register(request);
}
// 国际化路径
@Get({"/profile", "/zh/个人资料", "/en/profile"})
public UserProfile getProfile(
@Header("Authorization") String token) {
System.out.println("获取个人资料 (支持多语言路径)");
Long userId = authService.getUserIdFromToken(token);
return userService.getProfile(userId);
}
// 移动端和Web端路径统一
@Post({"/mobile/orders", "/web/orders", "/orders"})
public Order createOrder(@Body OrderRequest request) {
System.out.println("创建订单 (移动/Web/通用路径)");
return orderService.create(request);
}
}
// 路由映射示例
// GET /api/products → getProducts() ✓
// GET /api/items → getProducts() ✓
// GET /api/v1/users/1 → getUser(1) ✓
// GET /api/v2/users/1 → getUser(1) ✓
// POST /api/register → register(...) ✓
// POST /api/users/register → register(...) ✓
---
b.路径前缀与组合
a.控制器级前缀
@Controller注解的value参数定义所有方法的公共路径前缀,方法级注解的路径与控制器级路径拼接形成完整请求路径,实现路径的层次化管理和逻辑分组。
b.路径组合示例
---
// 控制器级前缀: /api/v1
@Controller("/api/v1")
public class UserManagementController {
// 完整路径: /api/v1/users
@Get("/users")
public List<User> listUsers() {
System.out.println("GET /api/v1/users");
return userService.findAll();
}
// 完整路径: /api/v1/users/{id}
@Get("/users/{id}")
public User getUser(Long id) {
System.out.println("GET /api/v1/users/" + id);
return userService.findById(id);
}
// 完整路径: /api/v1/users
@Post("/users")
public User createUser(@Body User user) {
System.out.println("POST /api/v1/users");
return userService.create(user);
}
// 空路径 - 完整路径: /api/v1
@Get
public ApiInfo getApiInfo() {
System.out.println("GET /api/v1 (API信息)");
return new ApiInfo("User API", "v1");
}
}
// 嵌套资源路径
@Controller("/api/users/{userId}")
public class UserOrderController {
// 完整路径: /api/users/{userId}/orders
@Get("/orders")
public List<Order> getUserOrders(Long userId) {
System.out.println("GET /api/users/" + userId + "/orders");
return orderService.findByUserId(userId);
}
// 完整路径: /api/users/{userId}/orders/{orderId}
@Get("/orders/{orderId}")
public Order getUserOrder(Long userId, Long orderId) {
System.out.println("GET /api/users/" + userId + "/orders/" + orderId);
return orderService.findByUserAndOrder(userId, orderId);
}
// 完整路径: /api/users/{userId}/orders
@Post("/orders")
public Order createUserOrder(
Long userId,
@Body OrderRequest request) {
System.out.println("POST /api/users/" + userId + "/orders");
request.setUserId(userId);
return orderService.create(request);
}
}
// 路由示例
// GET /api/v1 → getApiInfo()
// GET /api/v1/users → listUsers()
// GET /api/v1/users/123 → getUser(123)
// POST /api/v1/users → createUser(user)
// GET /api/users/123/orders → getUserOrders(123)
// GET /api/users/123/orders/456 → getUserOrder(123, 456)
// POST /api/users/123/orders → createUserOrder(123, request)
---
03.HTTP方法路由
a.标准HTTP方法
a.RESTful方法映射
Micronaut提供@Get、@Post、@Put、@Delete、@Patch、@Head、@Options、@Trace等注解对应HTTP标准方法,实现完整的RESTful API支持。每个方法有明确的语义:GET查询资源、POST创建资源、PUT完整更新、PATCH部分更新、DELETE删除资源。
b.方法路由示例
---
@Controller("/api/articles")
public class ArticleController {
private final ArticleService articleService;
public ArticleController(ArticleService articleService) {
this.articleService = articleService;
}
// GET: 查询单个资源
@Get("/{id}")
public Article getArticle(Long id) {
System.out.println("GET /api/articles/" + id);
return articleService.findById(id);
}
// GET: 查询资源列表
@Get
public List<Article> listArticles(
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "10") int size) {
System.out.println("GET /api/articles?page=" + page + "&size=" + size);
return articleService.findAll(page, size);
}
// POST: 创建新资源
@Post
@Status(HttpStatus.CREATED)
public Article createArticle(@Body ArticleRequest request) {
System.out.println("POST /api/articles - 标题: " + request.getTitle());
return articleService.create(request);
}
// PUT: 完整更新资源 (替换所有字段)
@Put("/{id}")
public Article updateArticle(Long id, @Body ArticleRequest request) {
System.out.println("PUT /api/articles/" + id + " - 完整更新");
return articleService.update(id, request);
}
// PATCH: 部分更新资源 (只更新提供的字段)
@Patch("/{id}")
public Article patchArticle(Long id, @Body Map<String, Object> updates) {
System.out.println("PATCH /api/articles/" + id + " - 部分更新: " + updates.keySet());
return articleService.partialUpdate(id, updates);
}
// DELETE: 删除资源
@Delete("/{id}")
@Status(HttpStatus.NO_CONTENT)
public void deleteArticle(Long id) {
System.out.println("DELETE /api/articles/" + id);
articleService.delete(id);
}
// HEAD: 检查资源是否存在 (不返回响应体)
@Head("/{id}")
public HttpResponse<?> checkArticleExists(Long id) {
System.out.println("HEAD /api/articles/" + id);
boolean exists = articleService.exists(id);
return exists ? HttpResponse.ok() : HttpResponse.notFound();
}
// OPTIONS: 返回支持的HTTP方法
@Options("/{id}")
public HttpResponse<?> getOptions() {
System.out.println("OPTIONS /api/articles/{id}");
return HttpResponse.ok()
.header("Allow", "GET, PUT, PATCH, DELETE, HEAD, OPTIONS");
}
}
// RESTful API示例
// GET /api/articles → 列表查询
// GET /api/articles/123 → 查询单个文章
// POST /api/articles → 创建文章 (201 Created)
// PUT /api/articles/123 → 完整更新文章
// PATCH /api/articles/123 → 部分更新文章
// DELETE /api/articles/123 → 删除文章 (204 No Content)
// HEAD /api/articles/123 → 检查文章是否存在
// OPTIONS /api/articles/123 → 获取支持的HTTP方法
---
b.自定义HTTP方法
a.扩展HTTP方法
通过@CustomHttpMethod注解支持自定义HTTP方法,用于实现WebDAV协议、缓存清除、资源锁定等扩展功能,突破标准HTTP方法的限制。
b.自定义方法示例
---
import io.micronaut.http.annotation.CustomHttpMethod;
@Controller("/api/cache")
public class CacheManagementController {
private final CacheService cacheService;
public CacheManagementController(CacheService cacheService) {
this.cacheService = cacheService;
}
// 自定义PURGE方法 - 清除缓存
@CustomHttpMethod(method = "PURGE")
@Status(HttpStatus.NO_CONTENT)
public void purgeCache(
@QueryValue String key,
@QueryValue(defaultValue = "false") boolean pattern) {
if (pattern) {
System.out.println("PURGE /api/cache?key=" + key + "&pattern=true (模式匹配)");
cacheService.invalidatePattern(key);
} else {
System.out.println("PURGE /api/cache?key=" + key + " (精确匹配)");
cacheService.invalidate(key);
}
}
// 自定义LOCK方法 - 锁定资源
@CustomHttpMethod(method = "LOCK", value = "/{resource}")
public LockResponse lockResource(
String resource,
@Body LockRequest request) {
System.out.println("LOCK /api/cache/" + resource +
" - 超时: " + request.getTimeout() + "秒");
String lockToken = cacheService.acquireLock(
resource,
request.getTimeout()
);
return new LockResponse(lockToken, resource, request.getTimeout());
}
// 自定义UNLOCK方法 - 解锁资源
@CustomHttpMethod(method = "UNLOCK", value = "/{resource}")
@Status(HttpStatus.NO_CONTENT)
public void unlockResource(
String resource,
@Header("Lock-Token") String lockToken) {
System.out.println("UNLOCK /api/cache/" + resource +
" - Token: " + lockToken);
boolean released = cacheService.releaseLock(resource, lockToken);
if (!released) {
throw new IllegalStateException("无效的锁令牌");
}
}
// 自定义REFRESH方法 - 刷新缓存
@CustomHttpMethod(method = "REFRESH", value = "/{key}")
public RefreshResponse refreshCache(String key) {
System.out.println("REFRESH /api/cache/" + key);
Object value = cacheService.refresh(key);
return new RefreshResponse(key, value != null,
value != null ? "刷新成功" : "缓存未命中");
}
}
// 锁请求对象
public class LockRequest {
private int timeout = 30; // 默认30秒
private String owner;
// Getter和Setter
public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; }
public String getOwner() { return owner; }
public void setOwner(String owner) { this.owner = owner; }
}
// 锁响应对象
public class LockResponse {
private String token;
private String resource;
private int timeout;
private long expiresAt;
public LockResponse(String token, String resource, int timeout) {
this.token = token;
this.resource = resource;
this.timeout = timeout;
this.expiresAt = System.currentTimeMillis() + timeout * 1000;
}
// Getter省略
}
// 刷新响应对象
public class RefreshResponse {
private String key;
private boolean success;
private String message;
public RefreshResponse(String key, boolean success, String message) {
this.key = key;
this.success = success;
this.message = message;
}
// Getter省略
}
// 使用示例
// PURGE /api/cache?key=user:123
// PURGE /api/cache?key=user:*&pattern=true
// LOCK /api/cache/report-2024 (Body: {"timeout": 60, "owner": "job-123"})
// UNLOCK /api/cache/report-2024 (Header: Lock-Token: abc123def456)
// REFRESH /api/cache/statistics
---
04.路由版本控制
a.URI路径版本
a.版本路径设计
在URI路径中包含版本号,如/api/v1/users、/api/v2/users,通过不同的控制器类或方法处理不同版本的API请求,实现版本隔离和独立演进。
b.版本路径示例
---
// V1版本 - 原始用户API
@Controller("/api/v1/users")
public class UserControllerV1 {
private final UserService userService;
public UserControllerV1(UserService userService) {
this.userService = userService;
}
@Get("/{id}")
public UserV1Response getUser(Long id) {
System.out.println("API v1: GET /api/v1/users/" + id);
User user = userService.findById(id);
// V1响应格式: 简单字段
return new UserV1Response(
user.getId(),
user.getUsername(),
user.getEmail()
);
}
@Post
public UserV1Response createUser(@Body UserV1Request request) {
System.out.println("API v1: POST /api/v1/users");
User user = userService.create(request);
return new UserV1Response(
user.getId(),
user.getUsername(),
user.getEmail()
);
}
}
// V2版本 - 增强的用户API
@Controller("/api/v2/users")
public class UserControllerV2 {
private final UserService userService;
public UserControllerV2(UserService userService) {
this.userService = userService;
}
@Get("/{id}")
public UserV2Response getUser(
Long id,
@QueryValue(defaultValue = "false") boolean includeProfile) {
System.out.println("API v2: GET /api/v2/users/" + id +
" - includeProfile=" + includeProfile);
User user = userService.findById(id);
// V2响应格式: 增强字段和可选信息
UserV2Response response = new UserV2Response(
user.getId(),
user.getUsername(),
user.getEmail(),
user.getCreatedAt(),
user.getStatus()
);
if (includeProfile) {
response.setProfile(user.getProfile());
}
return response;
}
@Post
public UserV2Response createUser(@Body UserV2Request request) {
System.out.println("API v2: POST /api/v2/users (支持更多字段)");
// V2请求支持额外字段: profile, preferences
User user = userService.createWithProfile(request);
return new UserV2Response(
user.getId(),
user.getUsername(),
user.getEmail(),
user.getCreatedAt(),
user.getStatus()
);
}
// V2新增功能: 批量操作
@Post("/batch")
public BatchResponse<UserV2Response> batchCreate(
@Body List<UserV2Request> requests) {
System.out.println("API v2: POST /api/v2/users/batch - " +
requests.size() + "个用户");
List<User> users = userService.batchCreate(requests);
List<UserV2Response> responses = users.stream()
.map(u -> new UserV2Response(
u.getId(), u.getUsername(), u.getEmail(),
u.getCreatedAt(), u.getStatus()))
.collect(Collectors.toList());
return new BatchResponse<>(responses.size(), responses);
}
}
// API版本对比
// V1: GET /api/v1/users/123 → 返回基本信息(id, username, email)
// V2: GET /api/v2/users/123 → 返回增强信息(id, username, email, createdAt, status)
// V2: GET /api/v2/users/123?includeProfile=true → 返回完整信息(包含profile)
// V2: POST /api/v2/users/batch → 批量创建(V1不支持)
---
b.请求头版本控制
a.版本协商
通过自定义请求头(如Accept-Version或X-API-Version)传递版本信息,服务端根据版本号路由到对应的处理逻辑,保持URL统一。
b.版本头示例
---
import io.micronaut.http.annotation.Header;
@Controller("/api/products")
public class VersionedProductController {
private final ProductService productService;
public VersionedProductController(ProductService productService) {
this.productService = productService;
}
// 默认版本(无版本头或版本1)
@Get("/{id}")
public HttpResponse<?> getProduct(
Long id,
@Header(value = "Accept-Version", defaultValue = "1") String version) {
System.out.println("GET /api/products/" + id + " - 版本: " + version);
Product product = productService.findById(id);
switch (version) {
case "1":
// V1: 基本产品信息
return HttpResponse.ok(new ProductV1Response(
product.getId(),
product.getName(),
product.getPrice()
));
case "2":
// V2: 增加库存和描述
return HttpResponse.ok(new ProductV2Response(
product.getId(),
product.getName(),
product.getPrice(),
product.getStock(),
product.getDescription()
));
case "3":
// V3: 增加评分和图片
return HttpResponse.ok(new ProductV3Response(
product.getId(),
product.getName(),
product.getPrice(),
product.getStock(),
product.getDescription(),
product.getRating(),
product.getImages()
));
default:
return HttpResponse.badRequest(
new ErrorResponse("UNSUPPORTED_VERSION",
"不支持的API版本: " + version)
);
}
}
// 创建产品 - 根据版本接受不同的请求格式
@Post
public HttpResponse<?> createProduct(
@Header(value = "Accept-Version", defaultValue = "1") String version,
@Body String body) {
System.out.println("POST /api/products - 版本: " + version);
try {
switch (version) {
case "1":
ProductV1Request reqV1 =
objectMapper.readValue(body, ProductV1Request.class);
Product p1 = productService.create(reqV1);
return HttpResponse.created(new ProductV1Response(
p1.getId(), p1.getName(), p1.getPrice()
));
case "2":
ProductV2Request reqV2 =
objectMapper.readValue(body, ProductV2Request.class);
Product p2 = productService.createWithDetails(reqV2);
return HttpResponse.created(new ProductV2Response(
p2.getId(), p2.getName(), p2.getPrice(),
p2.getStock(), p2.getDescription()
));
case "3":
ProductV3Request reqV3 =
objectMapper.readValue(body, ProductV3Request.class);
Product p3 = productService.createWithMedia(reqV3);
return HttpResponse.created(new ProductV3Response(
p3.getId(), p3.getName(), p3.getPrice(),
p3.getStock(), p3.getDescription(),
p3.getRating(), p3.getImages()
));
default:
return HttpResponse.badRequest(
new ErrorResponse("UNSUPPORTED_VERSION",
"不支持的API版本: " + version)
);
}
} catch (Exception e) {
return HttpResponse.serverError(
new ErrorResponse("PARSE_ERROR", e.getMessage())
);
}
}
}
// 使用示例
// curl -H "Accept-Version: 1" http://localhost:8080/api/products/123
// 返回: {"id": 123, "name": "商品名", "price": 99.99}
// curl -H "Accept-Version: 2" http://localhost:8080/api/products/123
// 返回: {"id": 123, "name": "商品名", "price": 99.99, "stock": 100, "description": "..."}
// curl -H "Accept-Version: 3" http://localhost:8080/api/products/123
// 返回: {"id": 123, "name": "商品名", "price": 99.99, "stock": 100,
// "description": "...", "rating": 4.5, "images": ["url1", "url2"]}
// 无版本头或默认版本1
// curl http://localhost:8080/api/products/123
// 返回: {"id": 123, "name": "商品名", "price": 99.99}
---
4.3 参数绑定
01.查询参数绑定
a.@QueryValue基础
a.查询参数映射
@QueryValue注解将URL查询参数绑定到方法参数,支持基本类型、包装类型、字符串、日期等多种类型的自动转换。框架会自动解析URL中?后的参数,按名称匹配到方法参数,并进行类型转换和验证。
b.基础查询参数示例
---
import io.micronaut.http.annotation.*;
import io.micronaut.core.annotation.Nullable;
@Controller("/api/search")
public class SearchController {
private final ProductService productService;
public SearchController(ProductService productService) {
this.productService = productService;
}
// 必需查询参数
@Get
public List<Product> search(@QueryValue String keyword) {
// GET /api/search?keyword=手机
System.out.println("搜索关键词: " + keyword);
return productService.search(keyword);
}
// 可选查询参数 - 使用@Nullable
@Get("/optional")
public List<Product> searchOptional(
@QueryValue @Nullable String keyword,
@QueryValue @Nullable String category) {
// GET /api/search/optional?keyword=手机
// GET /api/search/optional?keyword=手机&category=电子
// GET /api/search/optional (两个参数都是null)
System.out.println("关键词: " + (keyword != null ? keyword : "全部"));
System.out.println("分类: " + (category != null ? category : "全部"));
if (keyword == null && category == null) {
return productService.findAll();
} else if (keyword != null && category != null) {
return productService.searchByKeywordAndCategory(keyword, category);
} else if (keyword != null) {
return productService.search(keyword);
} else {
return productService.findByCategory(category);
}
}
// 带默认值的查询参数
@Get("/paginated")
public List<Product> searchPaginated(
@QueryValue(defaultValue = "") String keyword,
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "20") int size,
@QueryValue(defaultValue = "id") String sortBy,
@QueryValue(defaultValue = "asc") String order) {
// GET /api/search/paginated?keyword=手机
// 使用默认值: page=0, size=20, sortBy=id, order=asc
System.out.println("搜索参数:");
System.out.println(" 关键词: " + keyword);
System.out.println(" 页码: " + page);
System.out.println(" 每页: " + size);
System.out.println(" 排序: " + sortBy + " " + order);
return productService.search(keyword, page, size, sortBy, order);
}
// 重命名查询参数 - 参数名与方法参数名不同
@Get("/renamed")
public List<Product> searchRenamed(
@QueryValue("q") String keyword,
@QueryValue("cat") @Nullable String category,
@QueryValue("p") int page) {
// GET /api/search/renamed?q=手机&cat=电子&p=1
System.out.println("q=" + keyword + ", cat=" + category + ", p=" + page);
return productService.search(keyword, category, page);
}
}
// URL示例
// GET /api/search?keyword=手机
// GET /api/search/optional (返回全部)
// GET /api/search/optional?keyword=手机
// GET /api/search/paginated?keyword=手机&page=2&size=50
// GET /api/search/renamed?q=laptop&cat=electronics&p=0
---
b.复杂类型查询参数
a.集合参数绑定
使用List或数组接收同名的多个查询参数值,实现多选过滤、标签筛选等功能。框架自动将所有同名参数聚合为集合类型,便于处理批量条件。
b.复杂类型示例
---
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.math.BigDecimal;
import io.micronaut.core.convert.format.Format;
@Controller("/api/products")
public class ProductQueryController {
private final ProductService productService;
public ProductQueryController(ProductService productService) {
this.productService = productService;
}
// 多值参数 - List接收
@Get("/filter")
public List<Product> filterByTags(
@QueryValue("tag") List<String> tags,
@QueryValue("status") List<String> statuses) {
// GET /api/products/filter?tag=hot&tag=new&tag=featured&status=active&status=promoted
System.out.println("标签: " + tags); // [hot, new, featured]
System.out.println("状态: " + statuses); // [active, promoted]
return productService.filterByTagsAndStatuses(tags, statuses);
}
// 日期参数 - 使用@Format指定格式
@Get("/by-date")
public List<Product> findByDate(
@QueryValue @Format("yyyy-MM-dd") LocalDate startDate,
@QueryValue @Format("yyyy-MM-dd") LocalDate endDate) {
// GET /api/products/by-date?startDate=2024-01-01&endDate=2024-12-31
System.out.println("日期范围: " + startDate + " 至 " + endDate);
return productService.findByDateRange(startDate, endDate);
}
// 日期时间参数 - ISO 8601格式
@Get("/by-datetime")
public List<Product> findByDateTime(
@QueryValue @Format("yyyy-MM-dd'T'HH:mm:ss") LocalDateTime start,
@QueryValue @Format("yyyy-MM-dd'T'HH:mm:ss") LocalDateTime end) {
// GET /api/products/by-datetime?start=2024-01-01T00:00:00&end=2024-12-31T23:59:59
System.out.println("时间范围: " + start + " 至 " + end);
return productService.findByDateTimeRange(start, end);
}
// 数值范围参数 - BigDecimal精确计算
@Get("/by-price")
public List<Product> findByPriceRange(
@QueryValue @Nullable BigDecimal minPrice,
@QueryValue @Nullable BigDecimal maxPrice) {
// GET /api/products/by-price?minPrice=100.00&maxPrice=999.99
System.out.println("价格范围: " + minPrice + " - " + maxPrice);
if (minPrice != null && maxPrice != null) {
return productService.findByPriceRange(minPrice, maxPrice);
} else if (minPrice != null) {
return productService.findByMinPrice(minPrice);
} else if (maxPrice != null) {
return productService.findByMaxPrice(maxPrice);
} else {
return productService.findAll();
}
}
// 枚举参数 - 自动转换
@Get("/by-status")
public List<Product> findByStatus(
@QueryValue ProductStatus status,
@QueryValue(defaultValue = "false") boolean includeDeleted) {
// GET /api/products/by-status?status=ACTIVE&includeDeleted=true
System.out.println("状态: " + status); // ACTIVE枚举值
System.out.println("包含已删除: " + includeDeleted);
return productService.findByStatus(status, includeDeleted);
}
// Boolean参数 - 多种格式支持
@Get("/flags")
public List<Product> findWithFlags(
@QueryValue(defaultValue = "false") boolean inStock,
@QueryValue(defaultValue = "false") boolean onSale,
@QueryValue(defaultValue = "true") boolean published) {
// GET /api/products/flags?inStock=true&onSale=1&published=yes
// 支持: true/false, 1/0, yes/no, on/off
System.out.println("有货: " + inStock);
System.out.println("促销: " + onSale);
System.out.println("已发布: " + published);
return productService.findWithFlags(inStock, onSale, published);
}
// 高级搜索 - 组合多种类型
@Get("/advanced-search")
public List<Product> advancedSearch(
@QueryValue @Nullable String keyword,
@QueryValue("category") @Nullable List<String> categories,
@QueryValue @Nullable BigDecimal minPrice,
@QueryValue @Nullable BigDecimal maxPrice,
@QueryValue @Format("yyyy-MM-dd") @Nullable LocalDate startDate,
@QueryValue @Format("yyyy-MM-dd") @Nullable LocalDate endDate,
@QueryValue("tag") @Nullable List<String> tags,
@QueryValue(defaultValue = "ACTIVE") ProductStatus status,
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "20") int size) {
System.out.println("=== 高级搜索参数 ===");
System.out.println("关键词: " + keyword);
System.out.println("分类: " + categories);
System.out.println("价格区间: " + minPrice + " - " + maxPrice);
System.out.println("日期区间: " + startDate + " - " + endDate);
System.out.println("标签: " + tags);
System.out.println("状态: " + status);
System.out.println("分页: page=" + page + ", size=" + size);
SearchCriteria criteria = new SearchCriteria();
criteria.setKeyword(keyword);
criteria.setCategories(categories);
criteria.setMinPrice(minPrice);
criteria.setMaxPrice(maxPrice);
criteria.setStartDate(startDate);
criteria.setEndDate(endDate);
criteria.setTags(tags);
criteria.setStatus(status);
return productService.advancedSearch(criteria, page, size);
}
}
// 枚举定义
public enum ProductStatus {
DRAFT, // 草稿
ACTIVE, // 激活
INACTIVE, // 未激活
DELETED // 已删除
}
// URL示例
// GET /api/products/filter?tag=hot&tag=new&status=active&status=promoted
// GET /api/products/by-date?startDate=2024-01-01&endDate=2024-12-31
// GET /api/products/by-price?minPrice=99.99&maxPrice=999.99
// GET /api/products/by-status?status=ACTIVE&includeDeleted=false
// GET /api/products/advanced-search?keyword=手机&category=电子&category=数码&minPrice=1000&tag=5G&tag=旗舰
---
02.路径参数绑定
a.路径变量提取
a.路径参数映射
路径中{}定义的变量自动绑定到同名方法参数,框架负责类型转换和验证。支持基本类型、包装类型、字符串、UUID等类型,转换失败时返回400 Bad Request错误。
b.路径参数示例
---
import java.util.UUID;
@Controller("/api")
public class PathVariableController {
private final ResourceService resourceService;
public PathVariableController(ResourceService resourceService) {
this.resourceService = resourceService;
}
// Long类型路径参数
@Get("/users/{id}")
public User getUser(Long id) {
// GET /api/users/123
System.out.println("用户ID: " + id);
return resourceService.findUserById(id);
}
// String类型路径参数
@Get("/users/username/{username}")
public User getUserByUsername(String username) {
// GET /api/users/username/john_doe
System.out.println("用户名: " + username);
return resourceService.findUserByUsername(username);
}
// UUID类型路径参数
@Get("/orders/{orderId}")
public Order getOrder(UUID orderId) {
// GET /api/orders/550e8400-e29b-41d4-a716-446655440000
System.out.println("订单UUID: " + orderId);
return resourceService.findOrderByUUID(orderId);
}
// 多个路径参数
@Get("/users/{userId}/posts/{postId}")
public Post getUserPost(Long userId, Long postId) {
// GET /api/users/123/posts/456
System.out.println("用户ID: " + userId + ", 文章ID: " + postId);
return resourceService.findPostByUserAndId(userId, postId);
}
// 路径参数 + 查询参数组合
@Get("/categories/{category}/products/{productId}")
public Product getProduct(
String category,
Long productId,
@QueryValue(defaultValue = "false") boolean includeReviews) {
// GET /api/categories/electronics/products/789?includeReviews=true
System.out.println("分类: " + category);
System.out.println("产品ID: " + productId);
System.out.println("包含评论: " + includeReviews);
Product product = resourceService.findProduct(category, productId);
if (includeReviews) {
product.setReviews(resourceService.getProductReviews(productId));
}
return product;
}
// 正则表达式约束的路径参数
@Get("/posts/{slug:[a-z0-9-]+}")
public Post getPostBySlug(String slug) {
// GET /api/posts/my-first-blog-post
// 只匹配小写字母、数字、连字符
System.out.println("文章标识: " + slug);
return resourceService.findPostBySlug(slug);
}
// 日期格式路径参数
@Get("/reports/{date}")
public Report getReport(
@Format("yyyy-MM-dd") LocalDate date) {
// GET /api/reports/2024-01-15
System.out.println("报表日期: " + date);
return resourceService.generateReport(date);
}
// 可选路径段
@Get("/search{/keyword}")
public List<Article> search(@Nullable String keyword) {
// GET /api/search → keyword=null
// GET /api/search/java → keyword="java"
if (keyword == null) {
System.out.println("搜索: 全部文章");
return resourceService.findAllArticles();
} else {
System.out.println("搜索关键词: " + keyword);
return resourceService.searchArticles(keyword);
}
}
}
// URL示例
// GET /api/users/123
// GET /api/users/username/alice
// GET /api/orders/550e8400-e29b-41d4-a716-446655440000
// GET /api/users/100/posts/500
// GET /api/categories/books/products/999?includeReviews=true
// GET /api/posts/hello-world
// GET /api/reports/2024-12-25
// GET /api/search
// GET /api/search/spring-boot
---
b.路径参数验证
a.类型转换与约束
路径参数自动进行类型转换,转换失败返回400错误。可以使用正则表达式约束路径格式,在路由层面实现输入验证,提前拦截非法请求,减轻业务层压力。
b.参数验证示例
---
import jakarta.validation.constraints.*;
@Controller("/api/validated")
public class ValidatedPathController {
private final ValidationService validationService;
public ValidatedPathController(ValidationService validationService) {
this.validationService = validationService;
}
// 正则约束 - 只接受数字ID
@Get("/users/{id:[0-9]+}")
public User getUserById(Long id) {
// GET /api/validated/users/123 ✓
// GET /api/validated/users/abc → 404 Not Found
System.out.println("数字ID: " + id);
return validationService.findUser(id);
}
// 正则约束 - 用户名格式
@Get("/users/{username:[a-zA-Z0-9_]{3,20}}")
public User getUserByUsername(String username) {
// 用户名: 3-20个字符,只能包含字母、数字、下划线
// GET /api/validated/users/john_doe ✓
// GET /api/validated/users/ab → 404 (太短)
// GET /api/validated/users/user@123 → 404 (包含非法字符)
System.out.println("合法用户名: " + username);
return validationService.findUserByUsername(username);
}
// 正则约束 - 邮箱格式
@Get("/verify/{email:.+@.+\\..+}")
public VerificationResult verifyEmail(String email) {
// GET /api/validated/verify/[email protected] ✓
// GET /api/validated/verify/invalid-email → 404
System.out.println("邮箱: " + email);
return validationService.verifyEmail(email);
}
// 正则约束 - 日期格式 YYYY-MM-DD
@Get("/orders/{date:\\d{4}-\\d{2}-\\d{2}}")
public List<Order> getOrdersByDate(
@Format("yyyy-MM-dd") LocalDate date) {
// GET /api/validated/orders/2024-01-15 ✓
// GET /api/validated/orders/2024-1-5 → 404 (格式不对)
System.out.println("订单日期: " + date);
return validationService.findOrdersByDate(date);
}
// 正则约束 - 手机号格式
@Get("/sms/{phone:1[3-9]\\d{9}}")
public SmsResult sendSms(String phone) {
// 中国手机号: 1开头,第二位3-9,共11位
// GET /api/validated/sms/13812345678 ✓
// GET /api/validated/sms/12812345678 → 404 (第二位不对)
// GET /api/validated/sms/138123456 → 404 (位数不够)
System.out.println("手机号: " + phone);
return validationService.sendSms(phone);
}
// 正则约束 - 产品代码格式
@Get("/products/{code:[A-Z]{2}\\d{6}}")
public Product getProductByCode(String code) {
// 产品代码: 2个大写字母 + 6个数字
// GET /api/validated/products/AB123456 ✓
// GET /api/validated/products/ab123456 → 404 (小写)
// GET /api/validated/products/ABC12345 → 404 (格式不对)
System.out.println("产品代码: " + code);
return validationService.findProductByCode(code);
}
// 多路径参数组合验证
@Get("/transactions/{year:\\d{4}}/{month:0[1-9]|1[0-2]}/{id:[0-9]+}")
public Transaction getTransaction(int year, int month, Long id) {
// year: 4位数字
// month: 01-12
// id: 纯数字
// GET /api/validated/transactions/2024/03/12345 ✓
// GET /api/validated/transactions/2024/13/12345 → 404 (月份超范围)
System.out.println("交易: " + year + "年" + month + "月, ID=" + id);
return validationService.findTransaction(year, month, id);
}
}
// 路由匹配示例
// ✓ GET /api/validated/users/123
// ✗ GET /api/validated/users/abc (404 - 不是数字)
// ✓ GET /api/validated/users/john_doe
// ✗ GET /api/validated/users/ab (404 - 太短)
// ✓ GET /api/validated/verify/[email protected]
// ✗ GET /api/validated/verify/notanemail (404 - 不是邮箱格式)
// ✓ GET /api/validated/orders/2024-01-15
// ✗ GET /api/validated/orders/2024-1-5 (404 - 日期格式错误)
// ✓ GET /api/validated/sms/13812345678
// ✗ GET /api/validated/sms/12345678901 (404 - 不是合法手机号)
---
03.请求头绑定
a.@Header注解
a.请求头访问
@Header注解绑定HTTP请求头到方法参数,用于获取认证信息、客户端元数据、自定义业务参数等。支持必需请求头和可选请求头,可以设置默认值。
b.请求头示例
---
@Controller("/api/secure")
public class HeaderBindingController {
private final AuthService authService;
private final ContentService contentService;
public HeaderBindingController(AuthService authService,
ContentService contentService) {
this.authService = authService;
this.contentService = contentService;
}
// 必需请求头 - Authorization
@Get("/profile")
public UserProfile getProfile(@Header String authorization) {
// curl -H "Authorization: Bearer eyJhbGc..." http://localhost:8080/api/secure/profile
System.out.println("Authorization: " + authorization);
// 解析Bearer Token
String token = authorization.replace("Bearer ", "");
Long userId = authService.getUserIdFromToken(token);
return contentService.getUserProfile(userId);
}
// 可选请求头 - Accept-Language
@Get("/content")
public Content getContent(
@Header("Accept-Language") @Nullable String language,
@Header("User-Agent") @Nullable String userAgent) {
// curl -H "Accept-Language: zh-CN" -H "User-Agent: Mozilla/5.0..." \
// http://localhost:8080/api/secure/content
String lang = language != null ? language : "en";
System.out.println("语言: " + lang);
System.out.println("客户端: " + userAgent);
return contentService.getLocalizedContent(lang);
}
// 带默认值的请求头
@Get("/api-call")
public ApiResponse callApi(
@Header("X-API-Key") String apiKey,
@Header(value = "X-Request-Id", defaultValue = "") String requestId,
@Header(value = "X-Trace-Id", defaultValue = "none") String traceId) {
// curl -H "X-API-Key: abc123" -H "X-Request-Id: req-001" \
// http://localhost:8080/api/secure/api-call
System.out.println("API密钥: " + apiKey);
System.out.println("请求ID: " + requestId);
System.out.println("追踪ID: " + traceId);
return contentService.performApiCall(apiKey, requestId, traceId);
}
// 自定义业务请求头
@Post("/upload")
public UploadResponse upload(
@Body byte[] data,
@Header("X-File-Name") String fileName,
@Header("X-File-Type") String fileType,
@Header("X-File-Size") long fileSize,
@Header(value = "X-Checksum", defaultValue = "") String checksum) {
// curl -X POST -H "X-File-Name: document.pdf" \
// -H "X-File-Type: application/pdf" \
// -H "X-File-Size: 1024000" \
// -H "X-Checksum: md5hash..." \
// --data-binary @document.pdf \
// http://localhost:8080/api/secure/upload
System.out.println("文件名: " + fileName);
System.out.println("文件类型: " + fileType);
System.out.println("文件大小: " + fileSize + " bytes");
System.out.println("校验和: " + checksum);
// 验证校验和
if (!checksum.isEmpty()) {
String actualChecksum = contentService.calculateChecksum(data);
if (!actualChecksum.equals(checksum)) {
throw new IllegalArgumentException("文件校验和不匹配");
}
}
String fileId = contentService.saveFile(fileName, fileType, data);
return new UploadResponse(fileId, fileName, fileSize);
}
// 多租户请求头
@Get("/tenant-data")
public TenantData getTenantData(
@Header("X-Tenant-Id") String tenantId,
@Header("X-User-Id") Long userId,
@Header(value = "X-Org-Id", defaultValue = "default") String orgId) {
// curl -H "X-Tenant-Id: tenant-123" \
// -H "X-User-Id: 456" \
// -H "X-Org-Id: org-789" \
// http://localhost:8080/api/secure/tenant-data
System.out.println("租户ID: " + tenantId);
System.out.println("用户ID: " + userId);
System.out.println("组织ID: " + orgId);
return contentService.getTenantData(tenantId, userId, orgId);
}
// 访问所有请求头 - HttpHeaders对象
@Get("/all-headers")
public Map<String, String> getAllHeaders(HttpHeaders headers) {
Map<String, String> result = new LinkedHashMap<>();
for (String name : headers.names()) {
result.put(name, headers.get(name));
}
System.out.println("接收到 " + result.size() + " 个请求头");
for (Map.Entry<String, String> entry : result.entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
return result;
}
// 条件化处理 - 根据请求头选择处理逻辑
@Get("/content-negotiation")
public HttpResponse<?> getContentNegotiation(
@Header("Accept") String accept,
@Header(value = "Accept-Encoding", defaultValue = "identity") String encoding) {
// curl -H "Accept: application/json" http://localhost:8080/api/secure/content-negotiation
// curl -H "Accept: application/xml" http://localhost:8080/api/secure/content-negotiation
System.out.println("Accept: " + accept);
System.out.println("Accept-Encoding: " + encoding);
Object data = contentService.getData();
if (accept.contains("application/json")) {
return HttpResponse.ok(data)
.contentType(MediaType.APPLICATION_JSON);
} else if (accept.contains("application/xml")) {
String xml = contentService.toXml(data);
return HttpResponse.ok(xml)
.contentType(MediaType.APPLICATION_XML);
} else if (accept.contains("text/plain")) {
String text = data.toString();
return HttpResponse.ok(text)
.contentType(MediaType.TEXT_PLAIN);
} else {
return HttpResponse.notAcceptable();
}
}
}
// curl示例
// curl -H "Authorization: Bearer token123" http://localhost:8080/api/secure/profile
// curl -H "Accept-Language: zh-CN" http://localhost:8080/api/secure/content
// curl -H "X-API-Key: key123" -H "X-Request-Id: req-001" http://localhost:8080/api/secure/api-call
// curl -X POST -H "X-File-Name: doc.pdf" -H "X-File-Type: application/pdf" \
// --data-binary @doc.pdf http://localhost:8080/api/secure/upload
---
04.请求体绑定
a.@Body对象绑定
a.JSON反序列化
@Body注解将请求体JSON自动反序列化为Java对象,支持简单对象、嵌套对象、泛型对象、集合类型的绑定。框架使用Jackson进行反序列化,自动处理日期格式、命名转换等。
b.请求体绑定示例
---
@Controller("/api/users")
public class RequestBodyController {
private final UserService userService;
public RequestBodyController(UserService userService) {
this.userService = userService;
}
// 简单对象绑定
@Post
public User createUser(@Body User user) {
// POST /api/users
// Content-Type: application/json
// {"username": "john", "email": "[email protected]", "age": 30}
System.out.println("创建用户:");
System.out.println(" 用户名: " + user.getUsername());
System.out.println(" 邮箱: " + user.getEmail());
System.out.println(" 年龄: " + user.getAge());
return userService.create(user);
}
// 嵌套对象绑定
@Post("/register")
public RegistrationResponse register(@Body RegistrationRequest request) {
// POST /api/users/register
// {
// "username": "john",
// "email": "[email protected]",
// "password": "secret123",
// "profile": {
// "firstName": "John",
// "lastName": "Doe",
// "birthDate": "1990-01-15",
// "address": {
// "street": "123 Main St",
// "city": "New York",
// "zipCode": "10001"
// }
// },
// "preferences": {
// "language": "en",
// "timezone": "America/New_York",
// "notifications": true
// }
// }
System.out.println("注册用户:");
System.out.println(" 用户名: " + request.getUsername());
System.out.println(" 邮箱: " + request.getEmail());
System.out.println(" 姓名: " + request.getProfile().getFirstName() +
" " + request.getProfile().getLastName());
System.out.println(" 地址: " + request.getProfile().getAddress().getCity());
System.out.println(" 语言: " + request.getPreferences().getLanguage());
User user = userService.register(request);
return new RegistrationResponse(user.getId(), "注册成功");
}
// 集合类型绑定 - List
@Post("/batch")
public BatchResponse<User> batchCreate(@Body List<User> users) {
// POST /api/users/batch
// [
// {"username": "user1", "email": "[email protected]"},
// {"username": "user2", "email": "[email protected]"},
// {"username": "user3", "email": "[email protected]"}
// ]
System.out.println("批量创建 " + users.size() + " 个用户");
for (int i = 0; i < users.size(); i++) {
System.out.println(" 用户" + (i+1) + ": " + users.get(i).getUsername());
}
List<User> created = userService.batchCreate(users);
return new BatchResponse<>(created.size(), created);
}
// Map类型绑定 - 动态字段
@Patch("/{id}")
public User partialUpdate(Long id, @Body Map<String, Object> updates) {
// PATCH /api/users/123
// {
// "email": "[email protected]",
// "phone": "13900139000",
// "profile.city": "Beijing"
// }
System.out.println("部分更新用户 " + id);
System.out.println("更新字段: " + updates.keySet());
User user = userService.findById(id);
// 应用更新
if (updates.containsKey("email")) {
user.setEmail((String) updates.get("email"));
}
if (updates.containsKey("phone")) {
user.setPhone((String) updates.get("phone"));
}
if (updates.containsKey("age")) {
user.setAge(((Number) updates.get("age")).intValue());
}
return userService.update(user);
}
// 泛型请求体绑定
@Post("/action")
public ActionResponse<User> performAction(@Body ActionRequest<User> request) {
// POST /api/users/action
// {
// "action": "update",
// "data": {"username": "john", "email": "[email protected]"},
// "options": {"notify": true, "async": false}
// }
System.out.println("执行操作: " + request.getAction());
System.out.println("数据: " + request.getData().getUsername());
System.out.println("选项: " + request.getOptions());
User result = userService.performAction(request.getAction(), request.getData());
return new ActionResponse<>(request.getAction(), result, "操作成功");
}
// 可选请求体
@Post("/search")
public List<User> search(@Body @Nullable SearchCriteria criteria) {
// POST /api/users/search
// 可以不传请求体,或传递搜索条件
if (criteria == null) {
System.out.println("搜索: 返回所有用户");
return userService.findAll();
}
System.out.println("搜索条件:");
System.out.println(" 关键词: " + criteria.getKeyword());
System.out.println(" 状态: " + criteria.getStatus());
return userService.search(criteria);
}
}
// 请求对象定义
public class RegistrationRequest {
private String username;
private String email;
private String password;
private UserProfile profile;
private UserPreferences preferences;
// Getter和Setter省略
}
public class UserProfile {
private String firstName;
private String lastName;
private LocalDate birthDate;
private Address address;
// Getter和Setter省略
}
public class Address {
private String street;
private String city;
private String zipCode;
// Getter和Setter省略
}
public class UserPreferences {
private String language;
private String timezone;
private boolean notifications;
// Getter和Setter省略
}
public class ActionRequest<T> {
private String action;
private T data;
private Map<String, Object> options;
// Getter和Setter省略
}
public class ActionResponse<T> {
private String action;
private T result;
private String message;
public ActionResponse(String action, T result, String message) {
this.action = action;
this.result = result;
this.message = message;
}
// Getter省略
}
// curl示例
// curl -X POST -H "Content-Type: application/json" \
// -d '{"username":"john","email":"[email protected]","age":30}' \
// http://localhost:8080/api/users
// curl -X PATCH -H "Content-Type: application/json" \
// -d '{"email":"[email protected]","phone":"13900139000"}' \
// http://localhost:8080/api/users/123
---
05.Cookie和表单绑定
a.Cookie绑定
a.@CookieValue注解
@CookieValue注解绑定HTTP Cookie到方法参数,用于读取会话标识、用户偏好、记住登录状态等存储在Cookie中的数据。支持必需Cookie和可选Cookie,可以设置默认值。
b.Cookie绑定示例
---
import io.micronaut.http.annotation.CookieValue;
import io.micronaut.http.cookie.Cookie;
@Controller("/api")
public class CookieController {
private final SessionService sessionService;
public CookieController(SessionService sessionService) {
this.sessionService = sessionService;
}
// 必需Cookie
@Get("/session")
public SessionInfo getSession(@CookieValue("SESSIONID") String sessionId) {
// Cookie: SESSIONID=abc123def456
System.out.println("会话ID: " + sessionId);
return sessionService.getSessionInfo(sessionId);
}
// 可选Cookie
@Get("/preferences")
public UserPreferences getPreferences(
@CookieValue("theme") @Nullable String theme,
@CookieValue("language") @Nullable String language) {
// Cookie: theme=dark; language=zh-CN
String userTheme = theme != null ? theme : "light";
String userLang = language != null ? language : "en";
System.out.println("主题: " + userTheme);
System.out.println("语言: " + userLang);
return new UserPreferences(userTheme, userLang);
}
// 带默认值的Cookie
@Get("/settings")
public Settings getSettings(
@CookieValue(value = "fontSize", defaultValue = "14") int fontSize,
@CookieValue(value = "autoSave", defaultValue = "true") boolean autoSave) {
System.out.println("字体大小: " + fontSize);
System.out.println("自动保存: " + autoSave);
return new Settings(fontSize, autoSave);
}
// 设置Cookie - 返回HttpResponse
@Post("/login")
public HttpResponse<LoginResponse> login(@Body LoginRequest request) {
String sessionId = sessionService.createSession(request.getUsername());
Cookie sessionCookie = Cookie.of("SESSIONID", sessionId)
.maxAge(Duration.ofHours(24))
.path("/")
.httpOnly(true)
.secure(true);
Cookie rememberMeCookie = Cookie.of("remember-me", "true")
.maxAge(Duration.ofDays(30))
.path("/");
return HttpResponse.ok(new LoginResponse("登录成功"))
.cookie(sessionCookie)
.cookie(rememberMeCookie);
}
// 删除Cookie
@Post("/logout")
public HttpResponse<Void> logout() {
// 设置maxAge为0删除Cookie
Cookie deleteCookie = Cookie.of("SESSIONID", "")
.maxAge(Duration.ZERO)
.path("/");
return HttpResponse.noContent()
.cookie(deleteCookie);
}
}
// curl示例
// curl -b "SESSIONID=abc123" http://localhost:8080/api/session
// curl -b "theme=dark; language=zh-CN" http://localhost:8080/api/preferences
// curl -X POST -H "Content-Type: application/json" \
// -d '{"username":"john","password":"secret"}' \
// http://localhost:8080/api/login
---
b.表单数据绑定
a.@Body表单绑定
通过@Body绑定application/x-www-form-urlencoded格式的表单数据,自动将表单字段映射到对象属性或方法参数。支持嵌套对象和集合属性的表单提交。
b.表单绑定示例
---
import io.micronaut.http.annotation.Part;
import io.micronaut.http.multipart.CompletedFileUpload;
@Controller("/api/forms")
public class FormController {
private final FormService formService;
public FormController(FormService formService) {
this.formService = formService;
}
// 表单字段绑定到对象
@Post("/submit")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public FormResponse submitForm(@Body ContactForm form) {
// POST /api/forms/submit
// Content-Type: application/x-www-form-urlencoded
// name=John+Doe&email=john%40example.com&message=Hello&subscribe=true
System.out.println("表单提交:");
System.out.println(" 姓名: " + form.getName());
System.out.println(" 邮箱: " + form.getEmail());
System.out.println(" 消息: " + form.getMessage());
System.out.println(" 订阅: " + form.isSubscribe());
formService.processContact(form);
return new FormResponse("表单已提交");
}
// 单文件上传
@Post("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public UploadResponse uploadFile(
@Part("file") CompletedFileUpload file,
@Part("description") @Nullable String description) throws IOException {
// POST /api/forms/upload
// Content-Type: multipart/form-data
// [email protected]
// description=重要文档
System.out.println("文件上传:");
System.out.println(" 文件名: " + file.getFilename());
System.out.println(" 大小: " + file.getSize() + " bytes");
System.out.println(" 类型: " + file.getContentType().orElse("unknown"));
System.out.println(" 描述: " + description);
byte[] bytes = file.getBytes();
String fileId = formService.saveFile(file.getFilename(), bytes);
return new UploadResponse(fileId, file.getFilename(), file.getSize());
}
// 多文件上传
@Post("/upload-multiple")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public BatchUploadResponse uploadMultipleFiles(
@Part("files") List<CompletedFileUpload> files,
@Part("category") String category) throws IOException {
System.out.println("批量上传 " + files.size() + " 个文件, 分类: " + category);
List<String> fileIds = new ArrayList<>();
for (CompletedFileUpload file : files) {
System.out.println(" 文件: " + file.getFilename() +
" (" + file.getSize() + " bytes)");
byte[] bytes = file.getBytes();
String fileId = formService.saveFile(file.getFilename(), bytes);
fileIds.add(fileId);
}
return new BatchUploadResponse(fileIds.size(), fileIds);
}
// 混合表单数据和文件
@Post("/submit-with-file")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public SubmitResponse submitWithFile(
@Part("title") String title,
@Part("content") String content,
@Part("tags") List<String> tags,
@Part("attachment") @Nullable CompletedFileUpload attachment) throws IOException {
System.out.println("提交文章:");
System.out.println(" 标题: " + title);
System.out.println(" 内容: " + content);
System.out.println(" 标签: " + tags);
String attachmentId = null;
if (attachment != null) {
System.out.println(" 附件: " + attachment.getFilename());
byte[] bytes = attachment.getBytes();
attachmentId = formService.saveFile(attachment.getFilename(), bytes);
}
Long articleId = formService.createArticle(title, content, tags, attachmentId);
return new SubmitResponse(articleId, "文章已创建");
}
}
// 表单对象定义
public class ContactForm {
private String name;
private String email;
private String message;
private boolean subscribe;
// Getter和Setter省略
}
// curl示例
// curl -X POST -H "Content-Type: application/x-www-form-urlencoded" \
// -d "name=John Doe&[email protected]&message=Hello&subscribe=true" \
// http://localhost:8080/api/forms/submit
// curl -X POST -F "[email protected]" -F "description=重要文档" \
// http://localhost:8080/api/forms/upload
// curl -X POST -F "[email protected]" -F "[email protected]" -F "category=documents" \
// http://localhost:8080/api/forms/upload-multiple
// curl -X POST -F "title=文章标题" -F "content=文章内容" \
// -F "tags=java" -F "tags=micronaut" -F "[email protected]" \
// http://localhost:8080/api/forms/submit-with-file
---
4.4 响应处理
01.响应类型
a.直接返回对象
a.自动序列化
控制器方法直接返回Java对象时,框架自动将对象序列化为JSON格式响应,Content-Type默认为application/json。支持返回POJO、集合、Map等类型,使用Jackson或配置的序列化器进行转换。
b.对象返回示例
---
import io.micronaut.http.annotation.*;
import java.util.*;
@Controller("/api/data")
public class DataResponseController {
private final DataService dataService;
public DataResponseController(DataService dataService) {
this.dataService = dataService;
}
// 返回单个对象
@Get("/user/{id}")
public User getUser(Long id) {
// GET /api/data/user/123
// 响应: {"id": 123, "username": "john", "email": "[email protected]"}
System.out.println("查询用户: " + id);
return dataService.findUserById(id);
}
// 返回List集合
@Get("/users")
public List<User> getUsers() {
// GET /api/data/users
// 响应: [{"id": 1, "username": "john"}, {"id": 2, "username": "jane"}]
System.out.println("查询所有用户");
return dataService.findAllUsers();
}
// 返回Map
@Get("/statistics")
public Map<String, Object> getStatistics() {
// GET /api/data/statistics
// 响应: {"total": 100, "active": 85, "inactive": 15, "ratio": 0.85}
Map<String, Object> stats = new HashMap<>();
stats.put("total", 100);
stats.put("active", 85);
stats.put("inactive", 15);
stats.put("ratio", 0.85);
return stats;
}
// 返回嵌套对象
@Get("/order/{id}")
public Order getOrder(Long id) {
// GET /api/data/order/456
// 响应包含嵌套的用户、商品、地址对象
System.out.println("查询订单: " + id);
return dataService.findOrderById(id);
}
// 返回分页对象
@Get("/products")
public Page<Product> getProducts(
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "20") int size) {
// GET /api/data/products?page=0&size=20
// 响应: {
// "content": [...],
// "page": 0,
// "size": 20,
// "totalElements": 150,
// "totalPages": 8
// }
return dataService.findProducts(page, size);
}
}
// 分页对象定义
public class Page<T> {
private List<T> content;
private int page;
private int size;
private long totalElements;
private int totalPages;
public Page(List<T> content, int page, int size, long totalElements) {
this.content = content;
this.page = page;
this.size = size;
this.totalElements = totalElements;
this.totalPages = (int) Math.ceil((double) totalElements / size);
}
// Getter省略
}
---
b.HttpResponse返回
a.完全控制响应
返回HttpResponse对象可以完全控制响应内容、状态码、响应头、Cookie等,提供最灵活的响应定制能力。使用工厂方法快速构建标准HTTP响应。
b.HttpResponse示例
---
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.MediaType;
@Controller("/api/responses")
public class HttpResponseController {
private final ResponseService responseService;
public HttpResponseController(ResponseService responseService) {
this.responseService = responseService;
}
// 200 OK响应
@Get("/ok")
public HttpResponse<Message> ok() {
Message message = new Message("操作成功");
return HttpResponse.ok(message);
}
// 201 Created响应 + Location头
@Post("/users")
public HttpResponse<User> createUser(@Body User user) {
User created = responseService.createUser(user);
return HttpResponse.created(created)
.header("Location", "/api/responses/users/" + created.getId())
.header("X-User-Id", created.getId().toString());
}
// 204 No Content响应
@Delete("/users/{id}")
public HttpResponse<Void> deleteUser(Long id) {
responseService.deleteUser(id);
// 返回204,没有响应体
return HttpResponse.noContent();
}
// 202 Accepted响应 - 异步任务
@Post("/tasks")
public HttpResponse<TaskResponse> submitTask(@Body TaskRequest request) {
String taskId = responseService.submitAsyncTask(request);
TaskResponse response = new TaskResponse(taskId, "任务已提交");
return HttpResponse.accepted()
.body(response)
.header("X-Task-Id", taskId)
.header("Location", "/api/responses/tasks/" + taskId);
}
// 404 Not Found响应
@Get("/users/{id}")
public HttpResponse<User> getUser(Long id) {
Optional<User> user = responseService.findUserById(id);
if (user.isPresent()) {
return HttpResponse.ok(user.get());
} else {
return HttpResponse.notFound();
}
}
// 400 Bad Request响应
@Post("/validate")
public HttpResponse<?> validate(@Body ValidationRequest request) {
List<String> errors = responseService.validate(request);
if (errors.isEmpty()) {
return HttpResponse.ok(new Message("验证通过"));
} else {
ErrorResponse error = new ErrorResponse(
"VALIDATION_ERROR",
"请求数据验证失败",
errors
);
return HttpResponse.badRequest(error);
}
}
// 自定义状态码
@Get("/custom")
public HttpResponse<Message> customStatus() {
return HttpResponse.status(HttpStatus.I_AM_A_TEAPOT)
.body(new Message("我是茶壶"));
}
// 构建复杂响应
@Get("/download/{fileId}")
public MutableHttpResponse<byte[]> downloadFile(String fileId) {
byte[] data = responseService.getFileData(fileId);
String fileName = responseService.getFileName(fileId);
return HttpResponse.ok(data)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header("Content-Disposition",
"attachment; filename=\"" + fileName + "\"")
.header("Content-Length", String.valueOf(data.length))
.header("X-File-Id", fileId);
}
// 重定向响应
@Get("/redirect")
public HttpResponse<?> redirect() {
// 302临时重定向
return HttpResponse.redirect(URI.create("/api/responses/ok"));
}
@Get("/permanent-redirect")
public HttpResponse<?> permanentRedirect() {
// 301永久重定向
return HttpResponse.permanentRedirect(URI.create("/api/responses/ok"));
}
}
// 响应对象定义
public class Message {
private String message;
private long timestamp;
public Message(String message) {
this.message = message;
this.timestamp = System.currentTimeMillis();
}
// Getter省略
}
public class TaskResponse {
private String taskId;
private String message;
private String status;
public TaskResponse(String taskId, String message) {
this.taskId = taskId;
this.message = message;
this.status = "PENDING";
}
// Getter省略
}
public class ErrorResponse {
private String code;
private String message;
private Object details;
private long timestamp;
public ErrorResponse(String code, String message, Object details) {
this.code = code;
this.message = message;
this.details = details;
this.timestamp = System.currentTimeMillis();
}
// Getter省略
}
---
c.响应式返���
a.异步响应
返回CompletableFuture、Publisher、Flux、Mono等响应式类型,支持流式响应和异步处理,提升高并发场景的性能,控制器方法立即返回。
b.响应式示例
---
import java.util.concurrent.CompletableFuture;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.reactivestreams.Publisher;
@Controller("/api/reactive")
public class ReactiveResponseController {
private final ReactiveService reactiveService;
public ReactiveResponseController(ReactiveService reactiveService) {
this.reactiveService = reactiveService;
}
// CompletableFuture - 异步单个对象
@Get("/user/{id}")
public CompletableFuture<User> getUserAsync(Long id) {
// 异步查询用户,立即返回Future
System.out.println("异步查询用户: " + id);
return CompletableFuture.supplyAsync(() -> {
// 在后台线程执行
return reactiveService.findUserById(id);
});
}
// Mono - 单个元素的响应式流
@Get("/order/{id}")
public Mono<Order> getOrderMono(Long id) {
System.out.println("Mono查询订单: " + id);
return reactiveService.findOrderByIdMono(id);
}
// Flux - 多个元素的响应式流
@Get("/products")
@Produces(MediaType.APPLICATION_JSON_STREAM)
public Flux<Product> streamProducts() {
// 流式返回产品列表,逐个发送
System.out.println("流式传输产品列表");
return reactiveService.streamAllProducts();
}
// Publisher - 标准响应式流接口
@Get("/events")
@Produces(MediaType.TEXT_EVENT_STREAM)
public Publisher<ServerSentEvent<String>> streamEvents() {
// Server-Sent Events流式推送
System.out.println("开始推送事件流");
return Flux.interval(Duration.ofSeconds(1))
.map(seq -> ServerSentEvent.<String>builder()
.id(String.valueOf(seq))
.data("事件 " + seq)
.comment("序列号: " + seq)
.build());
}
// 异步处理后返回
@Post("/process")
public CompletableFuture<ProcessResult> processAsync(@Body ProcessRequest request) {
System.out.println("提交异步处理任务");
return CompletableFuture.supplyAsync(() -> {
// 模拟耗时处理
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
ProcessResult result = new ProcessResult();
result.setStatus("完成");
result.setData(request.getData());
result.setProcessedAt(LocalDateTime.now());
return result;
});
}
// 异步流式处理
@Post("/batch-process")
@Produces(MediaType.APPLICATION_JSON_STREAM)
public Flux<BatchResult> processBatch(@Body List<String> items) {
System.out.println("批量处理 " + items.size() + " 项");
return Flux.fromIterable(items)
.flatMap(item -> reactiveService.processItem(item))
.map(result -> new BatchResult(result.getId(), result.getStatus()));
}
}
// Server-Sent Event类
import io.micronaut.http.sse.Event;
public class ServerSentEvent<T> implements Event<T> {
private String id;
private T data;
private String comment;
public static <T> Builder<T> builder() {
return new Builder<>();
}
public static class Builder<T> {
private String id;
private T data;
private String comment;
public Builder<T> id(String id) {
this.id = id;
return this;
}
public Builder<T> data(T data) {
this.data = data;
return this;
}
public Builder<T> comment(String comment) {
this.comment = comment;
return this;
}
public ServerSentEvent<T> build() {
ServerSentEvent<T> event = new ServerSentEvent<>();
event.id = this.id;
event.data = this.data;
event.comment = this.comment;
return event;
}
}
// Getter省略
}
// 处理结果对象
public class ProcessResult {
private String status;
private Object data;
private LocalDateTime processedAt;
// Getter和Setter省略
}
public class BatchResult {
private Long id;
private String status;
public BatchResult(Long id, String status) {
this.id = id;
this.status = status;
}
// Getter省略
}
---
02.状态码控制
a.@Status注解
a.固定状态码
通过@Status注解指定方法成功响应的HTTP状态码,简化状态码设置。常用状态码:200 OK、201 Created、204 No Content、202 Accepted。
b.Status注解示例
---
@Controller("/api/resources")
public class StatusCodeController {
private final ResourceService resourceService;
public StatusCodeController(ResourceService resourceService) {
this.resourceService = resourceService;
}
// 201 Created - 创建成功
@Post
@Status(HttpStatus.CREATED)
public Resource createResource(@Body ResourceRequest request) {
System.out.println("创建资源: " + request.getName());
return resourceService.create(request);
}
// 204 No Content - 更新成功无返回
@Put("/{id}")
@Status(HttpStatus.NO_CONTENT)
public void updateResource(Long id, @Body ResourceRequest request) {
System.out.println("更新资源: " + id);
resourceService.update(id, request);
// 返回void,响应204 No Content
}
// 202 Accepted - 异步任务已接受
@Post("/async")
@Status(HttpStatus.ACCEPTED)
public TaskInfo submitAsyncTask(@Body TaskRequest request) {
System.out.println("提交异步任务");
String taskId = resourceService.submitTask(request);
return new TaskInfo(taskId, "任务已接受");
}
// 206 Partial Content - 部分内容
@Get("/{id}/partial")
@Status(HttpStatus.PARTIAL_CONTENT)
public PartialData getPartialData(
Long id,
@Header("Range") String range) {
System.out.println("获取部分数据: " + id + ", 范围: " + range);
return resourceService.getPartialData(id, range);
}
// 默认200 OK - 无需显式指定
@Get("/{id}")
public Resource getResource(Long id) {
System.out.println("查询资源: " + id);
return resourceService.findById(id);
}
}
// 任务信息对象
public class TaskInfo {
private String taskId;
private String message;
private String status;
private long timestamp;
public TaskInfo(String taskId, String message) {
this.taskId = taskId;
this.message = message;
this.status = "ACCEPTED";
this.timestamp = System.currentTimeMillis();
}
// Getter省略
}
// 部分数据对象
public class PartialData {
private String range;
private byte[] data;
private long totalSize;
public PartialData(String range, byte[] data, long totalSize) {
this.range = range;
this.data = data;
this.totalSize = totalSize;
}
// Getter省略
}
---
b.动态状态码
a.条件响应
根据业务逻辑动态选择状态码,使用HttpResponse构建不同状态的响应。常见场景:资源是否存在、验证是否通过、权限检查等。
b.动态状态码示例
---
@Controller("/api/dynamic")
public class DynamicStatusController {
private final DynamicService dynamicService;
public DynamicStatusController(DynamicService dynamicService) {
this.dynamicService = dynamicService;
}
// 根据资源是否存在返回不同状态
@Get("/resource/{id}")
public HttpResponse<Resource> getResource(Long id) {
Optional<Resource> resource = dynamicService.findById(id);
if (resource.isPresent()) {
return HttpResponse.ok(resource.get()); // 200 OK
} else {
return HttpResponse.notFound(); // 404 Not Found
}
}
// 根据删除结果返回不同状态
@Delete("/resource/{id}")
public HttpResponse<Void> deleteResource(Long id) {
boolean deleted = dynamicService.delete(id);
if (deleted) {
return HttpResponse.noContent(); // 204 No Content
} else {
return HttpResponse.notFound(); // 404 Not Found
}
}
// 根据验证结果返回不同状态
@Post("/submit")
public HttpResponse<?> submitData(@Body DataRequest request) {
ValidationResult validation = dynamicService.validate(request);
if (validation.isValid()) {
Data data = dynamicService.save(request);
return HttpResponse.created(data); // 201 Created
} else {
ErrorResponse error = new ErrorResponse(
"VALIDATION_ERROR",
"数据验证失败",
validation.getErrors()
);
return HttpResponse.badRequest(error); // 400 Bad Request
}
}
// 根据权限返回不同状态
@Get("/protected/{id}")
public HttpResponse<?> getProtectedResource(
Long id,
@Header("Authorization") @Nullable String authorization) {
if (authorization == null) {
// 未提供认证信息
return HttpResponse.unauthorized(); // 401 Unauthorized
}
boolean authenticated = dynamicService.verifyToken(authorization);
if (!authenticated) {
return HttpResponse.unauthorized(); // 401 Unauthorized
}
boolean hasPermission = dynamicService.checkPermission(authorization, id);
if (!hasPermission) {
// 已认证但无权限
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"您没有权限访问此资源",
null
);
return HttpResponse.status(HttpStatus.FORBIDDEN)
.body(error); // 403 Forbidden
}
Resource resource = dynamicService.getResource(id);
return HttpResponse.ok(resource); // 200 OK
}
// 根据资源状态返回不同响应
@Put("/resource/{id}/status")
public HttpResponse<?> updateStatus(
Long id,
@Body StatusUpdate update) {
try {
ResourceState result = dynamicService.updateStatus(id, update.getStatus());
switch (result) {
case UPDATED:
return HttpResponse.ok(new Message("状态已更新")); // 200 OK
case NOT_FOUND:
return HttpResponse.notFound(); // 404 Not Found
case CONFLICT:
ErrorResponse conflict = new ErrorResponse(
"CONFLICT",
"状态冲突,资源已被修改",
null
);
return HttpResponse.status(HttpStatus.CONFLICT)
.body(conflict); // 409 Conflict
case PRECONDITION_FAILED:
ErrorResponse precondition = new ErrorResponse(
"PRECONDITION_FAILED",
"前置条件不满足",
null
);
return HttpResponse.status(HttpStatus.PRECONDITION_FAILED)
.body(precondition); // 412 Precondition Failed
default:
return HttpResponse.serverError(); // 500 Internal Server Error
}
} catch (Exception e) {
ErrorResponse error = new ErrorResponse(
"INTERNAL_ERROR",
"服务器内部错误",
e.getMessage()
);
return HttpResponse.serverError(error); // 500 Internal Server Error
}
}
}
// 资源状态枚举
public enum ResourceState {
UPDATED,
NOT_FOUND,
CONFLICT,
PRECONDITION_FAILED
}
// 状态更新请求
public class StatusUpdate {
private String status;
private String reason;
// Getter和Setter省略
}
// 验证结果对象
public class ValidationResult {
private boolean valid;
private List<String> errors;
public ValidationResult(boolean valid, List<String> errors) {
this.valid = valid;
this.errors = errors;
}
public boolean isValid() { return valid; }
public List<String> getErrors() { return errors; }
}
---
03.响应头设置
a.固定响应头
a.@Header方法级注解
在方法上使用@Header注解添加固定响应头,如CORS头、缓存控制头、API版本头等。适用于添加固定的响应头,简化响应头配置。
b.固定响应头示例
---
@Controller("/api/headers")
public class ResponseHeaderController {
private final HeaderService headerService;
public ResponseHeaderController(HeaderService headerService) {
this.headerService = headerService;
}
// 单个固定响应头
@Get("/version")
@Header(name = "X-API-Version", value = "1.0")
public ApiInfo getVersion() {
return new ApiInfo("API", "1.0");
}
// 多个固定响应头
@Get("/cors-enabled")
@Header(name = "Access-Control-Allow-Origin", value = "*")
@Header(name = "Access-Control-Allow-Methods", value = "GET, POST, PUT, DELETE")
@Header(name = "Access-Control-Max-Age", value = "3600")
public Message getCorsEnabled() {
return new Message("支持CORS跨域访问");
}
// 缓存控制头
@Get("/cacheable")
@Header(name = "Cache-Control", value = "public, max-age=3600")
@Header(name = "ETag", value = "v1.0")
public CacheableData getCacheableData() {
return headerService.getCacheableData();
}
// 自定义业务响应头
@Get("/business")
@Header(name = "X-Request-Id", value = "generated-id")
@Header(name = "X-Tenant-Id", value = "default")
@Header(name = "X-Response-Time", value = "50ms")
public BusinessData getBusinessData() {
return headerService.getBusinessData();
}
}
// API信息对象
public class ApiInfo {
private String name;
private String version;
public ApiInfo(String name, String version) {
this.name = name;
this.version = version;
}
// Getter省略
}
// 可缓存数据对象
public class CacheableData {
private String data;
private long timestamp;
private String etag;
// Getter和Setter省略
}
// 业务数据对象
public class BusinessData {
private String content;
private Map<String, Object> metadata;
// Getter和Setter省略
}
---
b.动态响应头
a.HttpResponse响应头
通过HttpResponse.header()方法动态添加响应头,根据请求内容或业务逻辑设置不同的响应头值。支持链式调用,设置多个响应头。
b.动态响应头示例
---
@Controller("/api/dynamic-headers")
public class DynamicHeaderController {
private final DynamicHeaderService headerService;
public DynamicHeaderController(DynamicHeaderService headerService) {
this.headerService = headerService;
}
// 动态设置Content-Type
@Get("/content/{format}")
public HttpResponse<?> getContent(String format) {
Object data = headerService.getData();
switch (format.toLowerCase()) {
case "json":
return HttpResponse.ok(data)
.contentType(MediaType.APPLICATION_JSON);
case "xml":
String xml = headerService.toXml(data);
return HttpResponse.ok(xml)
.contentType(MediaType.APPLICATION_XML);
case "text":
String text = data.toString();
return HttpResponse.ok(text)
.contentType(MediaType.TEXT_PLAIN);
default:
return HttpResponse.badRequest();
}
}
// 动态设置缓存头
@Get("/cache/{resource}")
public HttpResponse<Resource> getCachedResource(String resource) {
Resource data = headerService.getResource(resource);
String etag = headerService.calculateETag(data);
boolean cacheable = headerService.isCacheable(resource);
MutableHttpResponse<Resource> response = HttpResponse.ok(data)
.header("ETag", etag);
if (cacheable) {
response.header("Cache-Control", "public, max-age=3600");
} else {
response.header("Cache-Control", "no-cache, no-store, must-revalidate");
}
return response;
}
// 动态设置Location头
@Post("/items")
public HttpResponse<Item> createItem(@Body ItemRequest request) {
Item item = headerService.createItem(request);
String location = "/api/dynamic-headers/items/" + item.getId();
return HttpResponse.created(item)
.header("Location", location)
.header("X-Item-Id", item.getId().toString())
.header("X-Created-At", item.getCreatedAt().toString());
}
// 根据请求动态设置响应头
@Get("/conditional")
public HttpResponse<ConditionalData> getConditional(
@Header("Accept-Language") @Nullable String language,
@Header("Accept-Encoding") @Nullable String encoding) {
String lang = language != null ? language : "en";
ConditionalData data = headerService.getLocalizedData(lang);
MutableHttpResponse<ConditionalData> response = HttpResponse.ok(data)
.header("Content-Language", lang)
.header("X-Data-Version", data.getVersion());
// 根据Accept-Encoding设置压缩
if (encoding != null && encoding.contains("gzip")) {
response.header("Content-Encoding", "gzip");
}
return response;
}
// 设置多个自定义头
@Get("/tracking/{id}")
public HttpResponse<TrackingInfo> getTracking(Long id) {
TrackingInfo info = headerService.getTrackingInfo(id);
String requestId = UUID.randomUUID().toString();
String correlationId = headerService.getCorrelationId();
return HttpResponse.ok(info)
.header("X-Request-Id", requestId)
.header("X-Correlation-Id", correlationId)
.header("X-Processing-Time", info.getProcessingTime() + "ms")
.header("X-Cache-Status", info.isCached() ? "HIT" : "MISS")
.header("X-Server-Region", "us-west-1");
}
// 文件下载响应头
@Get("/download/{fileId}")
public HttpResponse<byte[]> downloadFile(String fileId) {
byte[] data = headerService.getFileData(fileId);
String fileName = headerService.getFileName(fileId);
String contentType = headerService.getContentType(fileId);
String checksum = headerService.calculateChecksum(data);
return HttpResponse.ok(data)
.contentType(contentType)
.header("Content-Disposition",
"attachment; filename=\"" + fileName + "\"")
.header("Content-Length", String.valueOf(data.length))
.header("X-File-Id", fileId)
.header("X-Checksum", checksum)
.header("X-Downloaded-At", LocalDateTime.now().toString());
}
}
// 条件数据对象
public class ConditionalData {
private String content;
private String version;
private String language;
// Getter和Setter省略
}
// 跟踪信息对象
public class TrackingInfo {
private Long id;
private String status;
private long processingTime;
private boolean cached;
// Getter和Setter省略
}
---
04.内容协商与流式响应
a.内容协商
a.@Produces注解
@Produces注解指定方法返回的内容类型,支持同一资源返回多种格式。根据客户端Accept请求头自动选择响应格式,实现内容协商。
b.内容协商示例
---
@Controller("/api/content")
public class ContentNegotiationController {
private final ContentService contentService;
public ContentNegotiationController(ContentService contentService) {
this.contentService = contentService;
}
// 返回JSON格式(默认)
@Get("/data")
@Produces(MediaType.APPLICATION_JSON)
public DataObject getDataJson() {
System.out.println("返回JSON格式");
return contentService.getData();
}
// 返回XML格式
@Get("/data")
@Produces(MediaType.APPLICATION_XML)
public String getDataXml() {
System.out.println("返回XML格式");
DataObject data = contentService.getData();
return contentService.toXml(data);
}
// 返回纯文本
@Get("/data")
@Produces(MediaType.TEXT_PLAIN)
public String getDataText() {
System.out.println("返回纯文本格式");
DataObject data = contentService.getData();
return data.toString();
}
// 返回HTML
@Get("/page")
@Produces(MediaType.TEXT_HTML)
public String getHtmlPage() {
return "<html><body><h1>Hello Micronaut</h1></body></html>";
}
// 手动内容协商
@Get("/manual")
public HttpResponse<?> manualContentNegotiation(
@Header("Accept") String accept) {
DataObject data = contentService.getData();
if (accept.contains("application/json")) {
return HttpResponse.ok(data)
.contentType(MediaType.APPLICATION_JSON);
} else if (accept.contains("application/xml")) {
String xml = contentService.toXml(data);
return HttpResponse.ok(xml)
.contentType(MediaType.APPLICATION_XML);
} else if (accept.contains("text/plain")) {
return HttpResponse.ok(data.toString())
.contentType(MediaType.TEXT_PLAIN);
} else {
// 不支持的格式
return HttpResponse.status(HttpStatus.NOT_ACCEPTABLE)
.body(new Message("不支持的格式: " + accept));
}
}
}
// 数据对象
public class DataObject {
private Long id;
private String name;
private String description;
private Map<String, Object> attributes;
@Override
public String toString() {
return "DataObject{id=" + id + ", name=" + name +
", description=" + description + "}";
}
// Getter和Setter省略
}
// curl示例
// curl -H "Accept: application/json" http://localhost:8080/api/content/data
// curl -H "Accept: application/xml" http://localhost:8080/api/content/data
// curl -H "Accept: text/plain" http://localhost:8080/api/content/data
---
b.流式响应
a.Server-Sent Events
返回Publisher<Event>实现服务器推送事件,建立持久连接向客户端推送实时消息。使用text/event-stream格式,客户端通过EventSource API接收。
b.流式响应示例
---
import io.micronaut.http.sse.Event;
@Controller("/api/stream")
public class StreamResponseController {
private final StreamService streamService;
public StreamResponseController(StreamService streamService) {
this.streamService = streamService;
}
// Server-Sent Events流
@Get("/events")
@Produces(MediaType.TEXT_EVENT_STREAM)
public Publisher<Event<String>> streamEvents() {
System.out.println("开始推送事件流");
return Flux.interval(Duration.ofSeconds(1))
.map(seq -> Event.of("事件 " + seq)
.id(String.valueOf(seq))
.comment("序列号: " + seq));
}
// 流式JSON数据
@Get("/products")
@Produces(MediaType.APPLICATION_JSON_STREAM)
public Flux<Product> streamProducts() {
System.out.println("流式传输产品列表");
return streamService.streamAllProducts();
}
// 分块传输大文件
@Get("/download/{fileId}")
public HttpResponse<StreamedFile> downloadLargeFile(String fileId) {
File file = streamService.getFile(fileId);
String fileName = file.getName();
StreamedFile streamedFile = new StreamedFile(file, MediaType.APPLICATION_OCTET_STREAM_TYPE);
return HttpResponse.ok(streamedFile)
.header("Content-Disposition",
"attachment; filename=\"" + fileName + "\"")
.header("Transfer-Encoding", "chunked");
}
// 实时进度推送
@Post("/long-running")
@Produces(MediaType.TEXT_EVENT_STREAM)
public Publisher<Event<ProgressUpdate>> longRunningTask(@Body TaskRequest request) {
System.out.println("启动长时间运行任务");
return Flux.create(emitter -> {
streamService.executeLongTask(request, progress -> {
ProgressUpdate update = new ProgressUpdate(
progress.getPercent(),
progress.getMessage()
);
emitter.next(Event.of(update));
if (progress.isCompleted()) {
emitter.complete();
}
});
});
}
// 日志流式推送
@Get("/logs/{serviceId}")
@Produces(MediaType.TEXT_EVENT_STREAM)
public Flux<Event<LogEntry>> streamLogs(String serviceId) {
System.out.println("开始推送日志: " + serviceId);
return streamService.watchLogs(serviceId)
.map(log -> Event.of(log)
.id(log.getId())
.event("log")
.comment("Level: " + log.getLevel()));
}
}
// 进度更新对象
public class ProgressUpdate {
private int percent;
private String message;
private long timestamp;
public ProgressUpdate(int percent, String message) {
this.percent = percent;
this.message = message;
this.timestamp = System.currentTimeMillis();
}
// Getter省略
}
// 日志条目对象
public class LogEntry {
private String id;
private String level;
private String message;
private LocalDateTime timestamp;
// Getter和Setter省略
}
// JavaScript客户端示例
// const eventSource = new EventSource('http://localhost:8080/api/stream/events');
// eventSource.onmessage = (event) => {
// console.log('收到事件:', event.data);
// };
---
4.5 过滤器与拦截器
01.HTTP过滤器
a.@Filter注解
a.过滤器定义
@Filter注解标记HTTP过滤器类,实现HttpFilter接口拦截HTTP请求和响应。过滤器可以修改请求、响应、添加日志、认证授权等,按照定义的patterns匹配URL路径执行过滤逻辑。
b.基础过滤器示例
---
import io.micronaut.http.HttpRequest;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.filter.HttpServerFilter;
import io.micronaut.http.filter.ServerFilterChain;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
// 拦截所有/api/**路径的请求
@Filter("/api/**")
public class LoggingFilter implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
long startTime = System.currentTimeMillis();
System.out.println("=== 请求开始 ===");
System.out.println("方法: " + request.getMethod());
System.out.println("路径: " + request.getPath());
System.out.println("参数: " + request.getParameters().asMap());
// 继续执行请求链
return Flux.from(chain.proceed(request))
.doOnNext(response -> {
long duration = System.currentTimeMillis() - startTime;
System.out.println("状态码: " + response.getStatus());
System.out.println("耗时: " + duration + "ms");
System.out.println("=== 请求结束 ===\n");
});
}
}
// 拦截特定路径的过滤器
@Filter("/api/secure/**")
public class SecurityFilter implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
System.out.println("安全过滤器: 检查访问权限");
// 获取Authorization请求头
String authorization = request.getHeaders().get("Authorization");
if (authorization == null || !authorization.startsWith("Bearer ")) {
// 未提供认证信息,返回401
System.out.println("认证失败: 缺少Authorization头");
MutableHttpResponse<?> response = HttpResponse.unauthorized();
return Flux.just(response);
}
String token = authorization.substring(7);
if (!isValidToken(token)) {
// Token无效,返回401
System.out.println("认证失败: Token无效");
return Flux.just(HttpResponse.unauthorized());
}
System.out.println("认证成功,继续执行请求");
// Token有效,继续执行
return chain.proceed(request);
}
private boolean isValidToken(String token) {
// 简化的Token验证逻辑
return token != null && token.length() > 10;
}
}
// 多路径匹配的过滤器
@Filter(patterns = {"/api/admin/**", "/api/management/**"})
public class AdminFilter implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
System.out.println("管理员过滤器: 检查管理员权限");
String token = request.getHeaders().get("Authorization");
if (!hasAdminRole(token)) {
System.out.println("权限不足: 需要管理员权限");
ErrorResponse error = new ErrorResponse(
"FORBIDDEN",
"需要管理员权限",
null
);
return Flux.just(HttpResponse.status(HttpStatus.FORBIDDEN).body(error));
}
System.out.println("管理员权限验证通过");
return chain.proceed(request);
}
private boolean hasAdminRole(String token) {
// 检查用户是否有管理员角色
return token != null && token.contains("admin");
}
}
---
b.过滤器顺序
a.执行优先级
使用@Order注解控制过滤器执行顺序,数字越小优先级越高。未指定Order的过滤器默认优先级为0,可以使用负数提高优先级。
b.过滤器顺序示例
---
import io.micronaut.core.order.Ordered;
// 优先级最高: 先执行
@Filter("/api/**")
@Order(Ordered.HIGHEST_PRECEDENCE)
public class FirstFilter implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
System.out.println("1. FirstFilter: 最高优先级过滤器");
return chain.proceed(request);
}
}
// 优先级-100: 第二执行
@Filter("/api/**")
@Order(-100)
public class AuthenticationFilter implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
System.out.println("2. AuthenticationFilter: 认证过滤器 (优先级-100)");
// 认证逻辑
String token = request.getHeaders().get("X-Auth-Token");
if (token == null) {
System.out.println(" 认证失败: 缺少token");
return Flux.just(HttpResponse.unauthorized());
}
System.out.println(" 认证通过");
return chain.proceed(request);
}
}
// 优先级0(默认): 第三执行
@Filter("/api/**")
public class LoggingFilter implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
System.out.println("3. LoggingFilter: 日志过滤器 (默认优先级0)");
long start = System.currentTimeMillis();
return Flux.from(chain.proceed(request))
.doOnNext(response -> {
long duration = System.currentTimeMillis() - start;
System.out.println(" 请求耗时: " + duration + "ms");
});
}
}
// 优先级100: 第四执行
@Filter("/api/**")
@Order(100)
public class CacheFilter implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
System.out.println("4. CacheFilter: 缓存过滤器 (优先级100)");
// 检查缓存
String cacheKey = request.getPath();
Object cached = checkCache(cacheKey);
if (cached != null) {
System.out.println(" 缓存命中");
return Flux.just(HttpResponse.ok(cached));
}
System.out.println(" 缓存未命中,继续执行");
return chain.proceed(request);
}
private Object checkCache(String key) {
// 简化的缓存检查逻辑
return null;
}
}
// 优先级最低: 最后执行
@Filter("/api/**")
@Order(Ordered.LOWEST_PRECEDENCE)
public class LastFilter implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
System.out.println("5. LastFilter: 最低优先级过滤器");
return chain.proceed(request);
}
}
// 执行顺序示例输出:
// 1. FirstFilter: 最高优先级过滤器
// 2. AuthenticationFilter: 认证过滤器 (优先级-100)
// 认证通过
// 3. LoggingFilter: 日志过滤器 (默认优先级0)
// 4. CacheFilter: 缓存过滤器 (优先级100)
// 缓存未命中,继续执行
// 5. LastFilter: 最低优先级过滤器
// [Controller处理请求]
// 请求耗时: 45ms
---
02.请求拦截
a.请求修改
a.修改请求内容
过滤器可以修改请求的头部、参数、路径等信息,实现请求预处理、参数转换、请求重写等功能。通过创建新的MutableHttpRequest对象实现请求修改。
b.请求修改示例
---
@Filter("/api/**")
public class RequestModificationFilter implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
System.out.println("原始请求: " + request.getPath());
// 1. 添加请求头
MutableHttpRequest<?> modifiedRequest = request.mutate()
.header("X-Request-Id", UUID.randomUUID().toString())
.header("X-Processing-Time", String.valueOf(System.currentTimeMillis()))
.header("X-Client-IP", getClientIp(request));
System.out.println("添加请求头: X-Request-Id, X-Processing-Time, X-Client-IP");
// 2. 继续执行修改后的请求
return chain.proceed(modifiedRequest);
}
private String getClientIp(HttpRequest<?> request) {
// 获取客户端真实IP
String forwarded = request.getHeaders().get("X-Forwarded-For");
if (forwarded != null) {
return forwarded.split(",")[0].trim();
}
return request.getRemoteAddress().getAddress().getHostAddress();
}
}
// 参数转换过滤器
@Filter("/api/**")
public class ParameterTransformFilter implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
// 将查询参数转换为小写
MutableHttpRequest<?> modifiedRequest = request.mutate();
request.getParameters().forEach((key, values) -> {
List<String> lowercaseValues = values.stream()
.map(String::toLowerCase)
.collect(Collectors.toList());
System.out.println("参数转换: " + key + " = " + values + " → " + lowercaseValues);
});
return chain.proceed(modifiedRequest);
}
}
// 用户上下文注入过滤器
@Filter("/api/**")
@Order(-50)
public class UserContextFilter implements HttpServerFilter {
private final JwtService jwtService;
public UserContextFilter(JwtService jwtService) {
this.jwtService = jwtService;
}
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
String authorization = request.getHeaders().get("Authorization");
if (authorization != null && authorization.startsWith("Bearer ")) {
String token = authorization.substring(7);
try {
// 解析JWT Token
UserInfo userInfo = jwtService.parseToken(token);
System.out.println("用户上下文: ID=" + userInfo.getId() +
", 用户名=" + userInfo.getUsername() +
", 角色=" + userInfo.getRoles());
// 将用户信息添加到请求属性中
MutableHttpRequest<?> modifiedRequest = request.mutate()
.header("X-User-Id", userInfo.getId().toString())
.header("X-Username", userInfo.getUsername())
.header("X-User-Roles", String.join(",", userInfo.getRoles()));
return chain.proceed(modifiedRequest);
} catch (Exception e) {
System.out.println("Token解析失败: " + e.getMessage());
return Flux.just(HttpResponse.unauthorized());
}
}
// 没有认证信息,继续执行
return chain.proceed(request);
}
}
// 用户信息对象
public class UserInfo {
private Long id;
private String username;
private List<String> roles;
public UserInfo(Long id, String username, List<String> roles) {
this.id = id;
this.username = username;
this.roles = roles;
}
// Getter省略
}
---
b.请求验证
a.输入验证
在过滤器中对请求进行验证,检查请求头、参数、内容类型等是否符合要求,实现统一的请求验证和安全防护。
b.请求验证示例
---
@Filter("/api/**")
@Order(-200)
public class RequestValidationFilter implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
System.out.println("请求验证过滤器");
// 1. 验证Content-Type
if (request.getMethod().equals(HttpMethod.POST) ||
request.getMethod().equals(HttpMethod.PUT)) {
String contentType = request.getContentType().orElse(null);
if (contentType == null) {
System.out.println("验证失败: 缺少Content-Type");
ErrorResponse error = new ErrorResponse(
"INVALID_CONTENT_TYPE",
"POST/PUT请求必须指定Content-Type",
null
);
return Flux.just(HttpResponse.badRequest(error));
}
}
// 2. 验证必需的请求头
if (!request.getHeaders().contains("X-API-Version")) {
System.out.println("验证失败: 缺少X-API-Version头");
ErrorResponse error = new ErrorResponse(
"MISSING_API_VERSION",
"请求必须包含X-API-Version头",
null
);
return Flux.just(HttpResponse.badRequest(error));
}
// 3. 验证User-Agent
String userAgent = request.getHeaders().get("User-Agent");
if (userAgent == null || userAgent.isEmpty()) {
System.out.println("验证失败: 缺少User-Agent");
ErrorResponse error = new ErrorResponse(
"MISSING_USER_AGENT",
"请求必须包含User-Agent头",
null
);
return Flux.just(HttpResponse.badRequest(error));
}
// 4. 验证请求路径长度
if (request.getPath().length() > 2000) {
System.out.println("验证失败: URL过长");
ErrorResponse error = new ErrorResponse(
"URI_TOO_LONG",
"请求URL长度不能超过2000字符",
null
);
return Flux.just(
HttpResponse.status(HttpStatus.URI_TOO_LONG).body(error)
);
}
System.out.println("请求验证通过");
return chain.proceed(request);
}
}
// SQL注入防护过滤器
@Filter("/api/**")
public class SqlInjectionFilter implements HttpServerFilter {
private static final Pattern SQL_INJECTION_PATTERN =
Pattern.compile("('.*(or|and|union|select|insert|update|delete|drop|create|alter).*')",
Pattern.CASE_INSENSITIVE);
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
// 检查查询参数中的SQL注入
for (Map.Entry<String, List<String>> entry : request.getParameters().asMap().entrySet()) {
for (String value : entry.getValue()) {
if (SQL_INJECTION_PATTERN.matcher(value).find()) {
System.out.println("SQL注入检测: 参数=" + entry.getKey() + ", 值=" + value);
ErrorResponse error = new ErrorResponse(
"SQL_INJECTION_DETECTED",
"检测到潜在的SQL注入攻击",
"参数: " + entry.getKey()
);
return Flux.just(HttpResponse.badRequest(error));
}
}
}
return chain.proceed(request);
}
}
// XSS防护过滤器
@Filter("/api/**")
public class XssFilter implements HttpServerFilter {
private static final Pattern XSS_PATTERN =
Pattern.compile("<script[^>]*>.*?</script>|javascript:|onerror=|onclick=",
Pattern.CASE_INSENSITIVE);
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
// 检查查询参数中的XSS攻击
for (Map.Entry<String, List<String>> entry : request.getParameters().asMap().entrySet()) {
for (String value : entry.getValue()) {
if (XSS_PATTERN.matcher(value).find()) {
System.out.println("XSS检测: 参数=" + entry.getKey());
ErrorResponse error = new ErrorResponse(
"XSS_DETECTED",
"检测到潜在的XSS攻击",
"参数: " + entry.getKey()
);
return Flux.just(HttpResponse.badRequest(error));
}
}
}
return chain.proceed(request);
}
}
---
03.响应拦截
a.响应修改
a.修改响应内容
过滤器可以修改响应的状态码、头部、响应体等信息,实现响应后处理、添加通用响应头、响应转换等功能。
b.响应修改示例
---
@Filter("/api/**")
public class ResponseModificationFilter implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
return Flux.from(chain.proceed(request))
.map(response -> {
System.out.println("修改响应");
// 1. 添加通用响应头
response.header("X-Powered-By", "Micronaut")
.header("X-Response-Time", String.valueOf(System.currentTimeMillis()))
.header("X-Server-Region", "us-west-1");
// 2. 添加CORS头
response.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
.header("Access-Control-Allow-Headers", "Content-Type, Authorization")
.header("Access-Control-Max-Age", "3600");
// 3. 添加安全头
response.header("X-Content-Type-Options", "nosniff")
.header("X-Frame-Options", "DENY")
.header("X-XSS-Protection", "1; mode=block")
.header("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
System.out.println("响应头已添加");
return response;
});
}
}
// 响应压缩标记过滤器
@Filter("/api/**")
public class CompressionFilter implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
String acceptEncoding = request.getHeaders().get("Accept-Encoding");
return Flux.from(chain.proceed(request))
.map(response -> {
// 如果客户端支持gzip压缩
if (acceptEncoding != null && acceptEncoding.contains("gzip")) {
// 获取响应体大小
Object body = response.body();
if (body != null) {
String bodyStr = body.toString();
if (bodyStr.length() > 1024) {
// 响应体大于1KB,标记为可压缩
response.header("Content-Encoding", "gzip");
System.out.println("响应将被压缩: 原始大小=" + bodyStr.length());
}
}
}
return response;
});
}
}
// 响应缓存过滤器
@Filter("/api/**")
public class CacheResponseFilter implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
return Flux.from(chain.proceed(request))
.map(response -> {
// 只缓存成功的GET请求
if (request.getMethod().equals(HttpMethod.GET) &&
response.getStatus().equals(HttpStatus.OK)) {
String path = request.getPath();
// 根据路径设置不同的缓存策略
if (path.startsWith("/api/public/")) {
// 公共资源,缓存1小时
response.header("Cache-Control", "public, max-age=3600");
System.out.println("公共资源缓存: 1小时");
} else if (path.startsWith("/api/static/")) {
// 静态资源,缓存1天
response.header("Cache-Control", "public, max-age=86400");
System.out.println("静态资源缓存: 1天");
} else {
// 私有资源,不缓存
response.header("Cache-Control", "private, no-cache, no-store, must-revalidate");
System.out.println("私有资源: 禁止缓存");
}
// 添加ETag
String etag = generateETag(response.body());
response.header("ETag", etag);
}
return response;
});
}
private String generateETag(Object body) {
// 简化的ETag生成逻辑
if (body != null) {
return "W/\"" + body.hashCode() + "\"";
}
return "W/\"empty\"";
}
}
// 统一响应包装过滤器
@Filter("/api/**")
@Order(200)
public class ResponseWrapperFilter implements HttpServerFilter {
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
return Flux.from(chain.proceed(request))
.map(response -> {
// 只包装成功的响应
if (response.getStatus().getCode() >= 200 &&
response.getStatus().getCode() < 300) {
Object originalBody = response.body();
// 创建统一响应格式
ApiResponse<?> wrappedResponse = new ApiResponse<>(
200,
"success",
originalBody,
System.currentTimeMillis()
);
System.out.println("响应已包装为统一格式");
return response.body(wrappedResponse);
}
return response;
});
}
}
// 统一响应格式
public class ApiResponse<T> {
private int code;
private String message;
private T data;
private long timestamp;
public ApiResponse(int code, String message, T data, long timestamp) {
this.code = code;
this.message = message;
this.data = data;
this.timestamp = timestamp;
}
// Getter省略
}
---
b.性能监控
a.请求度量
通过过滤器收集请求处理时间、响应大小、错误率等性能指标,实现请求监控和性能分析。
b.性能监控示例
---
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Singleton
@Filter("/api/**")
@Order(-500)
public class PerformanceMonitorFilter implements HttpServerFilter {
// 请求计数器
private final AtomicLong requestCount = new AtomicLong(0);
private final AtomicLong errorCount = new AtomicLong(0);
// 路径统计
private final ConcurrentHashMap<String, PathStats> pathStatsMap = new ConcurrentHashMap<>();
@Override
public Publisher<MutableHttpResponse<?>> doFilter(
HttpRequest<?> request,
ServerFilterChain chain) {
long requestId = requestCount.incrementAndGet();
long startTime = System.currentTimeMillis();
String path = request.getPath();
String method = request.getMethod().name();
String key = method + " " + path;
System.out.println("=== 请求 #" + requestId + " 开始 ===");
System.out.println(method + " " + path);
return Flux.from(chain.proceed(request))
.doOnNext(response -> {
// 请求成功
long duration = System.currentTimeMillis() - startTime;
// 更新统计信息
PathStats stats = pathStatsMap.computeIfAbsent(key, k -> new PathStats());
stats.incrementCount();
stats.addDuration(duration);
// 计算响应大小
long responseSize = calculateResponseSize(response);
stats.addResponseSize(responseSize);
System.out.println("状态: " + response.getStatus().getCode());
System.out.println("耗时: " + duration + "ms");
System.out.println("响应大小: " + responseSize + " bytes");
System.out.println("平均耗时: " + stats.getAverageDuration() + "ms");
System.out.println("=== 请求 #" + requestId + " 完成 ===\n");
// 添加性能头到响应
response.header("X-Response-Time", duration + "ms")
.header("X-Request-Id", String.valueOf(requestId));
})
.doOnError(error -> {
// 请求失败
long duration = System.currentTimeMillis() - startTime;
errorCount.incrementAndGet();
PathStats stats = pathStatsMap.computeIfAbsent(key, k -> new PathStats());
stats.incrementErrorCount();
System.err.println("请求失败 #" + requestId);
System.err.println("错误: " + error.getMessage());
System.err.println("耗时: " + duration + "ms");
System.err.println("=== 请求 #" + requestId + " 失败 ===\n");
});
}
private long calculateResponseSize(MutableHttpResponse<?> response) {
Object body = response.body();
if (body == null) {
return 0;
}
if (body instanceof byte[]) {
return ((byte[]) body).length;
}
if (body instanceof String) {
return ((String) body).length();
}
// 估算JSON序列化后的大小
return body.toString().length();
}
// 获取统计信息的端点
@Get("/api/stats")
public Map<String, Object> getStats() {
Map<String, Object> stats = new HashMap<>();
stats.put("totalRequests", requestCount.get());
stats.put("totalErrors", errorCount.get());
stats.put("errorRate", String.format("%.2f",
(usedMemory * 100.0 / maxMemory)));
}
// 对比传统JPA
public void compareWithTraditionalJPA() {
System.out.println("\n=== Micronaut Data vs 传统JPA ===");
System.out.println("\nMicronaut Data优势:");
System.out.println("1. 编译时生成 - 无运行时反射");
System.out.println("2. 更快的启动速度 - 通常快50-90%");
System.out.println("3. 更低的内存占用 - 减少30-50%");
System.out.println("4. 更小的应用体积 - 减少依赖");
System.out.println("5. AOT编译友好 - 支持GraalVM Native Image");
System.out.println("\n传统JPA特点:");
System.out.println("1. 运行时生成代理 - 使用反射");
System.out.println("2. 较慢的启动速度 - 需要初始化EntityManager");
System.out.println("3. 较高的内存占用 - 代理和缓存开销");
System.out.println("4. 成熟的生态系统 - 更多工具和集成");
}
}
// 性能测试输出示例
// === 启动性能测试 ===
// 启动时间: 1200ms
// Bean数量: 245
//
// === 查询性能测试 ===
// findAll() x1000: 450ms
// 平均: 0.45ms
//
// findByUsername() x1000: 520ms
// 平均: 0.52ms
//
// save() x100: 180ms
// 平均: 1.8ms
//
// === 内存占用测试 ===
// 已用内存: 128 MB
// 最大内存: 512 MB
// 使用率: 25.00%
---
02.支持的数据库
a.关系型数据库
a.主流数据库支持
Micronaut Data支持MySQL、PostgreSQL、Oracle、SQL Server、H2等主流关系型数据库,通过JDBC或R2DBC访问。提供数据库方言自动适配,简化多数据库支持。
b.多数据库配置示例
---
// application.yml - 多数据源配置
datasources:
# 主数据源 - MySQL
default:
url: jdbc:mysql://localhost:3306/app_db
driverClassName: com.mysql.cj.jdbc.Driver
username: root
password: ${DB_PASSWORD}
dialect: MYSQL
maximum-pool-size: 10
minimum-idle: 2
# 从数据源 - PostgreSQL
analytics:
url: jdbc:postgresql://localhost:5432/analytics_db
driverClassName: org.postgresql.Driver
username: analytics_user
password: ${ANALYTICS_DB_PASSWORD}
dialect: POSTGRES
maximum-pool-size: 5
# H2内存数据库 - 用于测试
test:
url: jdbc:h2:mem:testDb
driverClassName: org.h2.Driver
username: sa
password: ''
dialect: H2
jpa:
default:
properties:
hibernate:
hbm2ddl:
auto: update
show_sql: true
// MySQL实体示例
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(precision = 10, scale = 2)
private BigDecimal price;
@Column(columnDefinition = "TEXT")
private String description;
@Column(name = "created_at", columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private LocalDateTime createdAt;
// Getter和Setter省略
}
// MySQL Repository
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query("SELECT p FROM Product p WHERE p.price BETWEEN :minPrice AND :maxPrice")
List<Product> findByPriceRange(
@Param("minPrice") BigDecimal minPrice,
@Param("maxPrice") BigDecimal maxPrice
);
@Query("SELECT p FROM Product p WHERE LOWER(p.name) LIKE LOWER(CONCAT('%', :keyword, '%'))")
List<Product> searchByName(@Param("keyword") String keyword);
}
// PostgreSQL实体示例
@Entity
@Table(name = "analytics_events")
public class AnalyticsEvent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "event_type", nullable = false)
private String eventType;
@Column(name = "user_id")
private Long userId;
@Column(columnDefinition = "jsonb")
private String properties; // PostgreSQL JSON类型
@Column(name = "occurred_at", nullable = false)
private LocalDateTime occurredAt;
// Getter和Setter省略
}
// PostgreSQL Repository - 使用特定数据源
@Repository("analytics")
@JdbcRepository(dialect = Dialect.POSTGRES)
public interface AnalyticsEventRepository extends CrudRepository<AnalyticsEvent, Long> {
@Query("SELECT * FROM analytics_events WHERE occurred_at >= :startDate " +
"AND occurred_at < :endDate ORDER BY occurred_at DESC")
List<AnalyticsEvent> findEventsByDateRange(
LocalDateTime startDate,
LocalDateTime endDate
);
@Query("SELECT event_type, COUNT(*) as count FROM analytics_events " +
"WHERE occurred_at >= :date GROUP BY event_type")
List<Map<String, Object>> getEventCountsByType(LocalDateTime date);
}
// 多数据源服务
@Singleton
public class MultiDataSourceService {
private final ProductRepository productRepository;
private final AnalyticsEventRepository analyticsRepository;
public MultiDataSourceService(ProductRepository productRepository,
AnalyticsEventRepository analyticsRepository) {
this.productRepository = productRepository;
this.analyticsRepository = analyticsRepository;
System.out.println("多数据源服务初始化");
}
@Transactional("default") // 使用主数据源事务
public Product createProduct(String name, BigDecimal price) {
System.out.println("创建产品: " + name);
Product product = new Product();
product.setName(name);
product.setPrice(price);
product.setCreatedAt(LocalDateTime.now());
Product saved = productRepository.save(product);
// 记录分析事件到PostgreSQL
recordAnalyticsEvent("product_created", saved.getId());
return saved;
}
@Transactional("analytics") // 使用分析数据源事务
public void recordAnalyticsEvent(String eventType, Long entityId) {
System.out.println("记录分析事件: " + eventType);
AnalyticsEvent event = new AnalyticsEvent();
event.setEventType(eventType);
event.setUserId(entityId);
event.setOccurredAt(LocalDateTime.now());
event.setProperties("{\"entity_id\": " + entityId + "}");
analyticsRepository.save(event);
}
public List<Product> searchProducts(String keyword) {
System.out.println("搜索产品: " + keyword);
return productRepository.searchByName(keyword);
}
public Map<String, Object> getAnalyticsSummary(LocalDateTime startDate) {
System.out.println("获取分析摘要");
List<Map<String, Object>> eventCounts =
analyticsRepository.getEventCountsByType(startDate);
Map<String, Object> summary = new HashMap<>();
summary.put("startDate", startDate);
summary.put("eventCounts", eventCounts);
summary.put("generatedAt", LocalDateTime.now());
return summary;
}
}
// 数据库方言特定功能
@Singleton
public class DatabaseDialectService {
// MySQL特定 - 全文搜索
@Query(value = "SELECT * FROM products WHERE MATCH(name, description) " +
"AGAINST(:keyword IN NATURAL LANGUAGE MODE)",
nativeQuery = true)
public List<Product> fullTextSearch(String keyword) {
// MySQL全文搜索功能
return Collections.emptyList();
}
// PostgreSQL特定 - 数组操作
@Query(value = "SELECT * FROM users WHERE :tag = ANY(tags)",
nativeQuery = true)
public List<User> findByTag(String tag) {
// PostgreSQL数组查询
return Collections.emptyList();
}
// Oracle特定 - 分页查询
@Query(value = "SELECT * FROM (SELECT a.*, ROWNUM rnum FROM " +
"(SELECT * FROM products ORDER BY id) a WHERE ROWNUM <= :end) " +
"WHERE rnum > :start",
nativeQuery = true)
public List<Product> findWithOraclePagination(int start, int end) {
// Oracle特定分页
return Collections.emptyList();
}
}
---
b.NoSQL支持
a.MongoDB集成
通过Micronaut Data MongoDB模块支持MongoDB,提供与关系型数据库相同的Repository抽象。支持文档映射、查询构建、聚合操作等MongoDB特性。
b.MongoDB配置示例
---
// build.gradle - MongoDB依赖
dependencies {
annotationProcessor("io.micronaut.data:micronaut-data-processor")
implementation("io.micronaut.data:micronaut-data-mongodb")
runtimeOnly("org.mongodb:mongodb-driver-sync")
}
// application.yml - MongoDB配置
mongodb:
uri: mongodb://localhost:27017/mydb
package-names:
- com.example.domain
// MongoDB文档实体
import io.micronaut.data.mongodb.annotation.MongoRepository;
import org.bson.types.ObjectId;
import io.micronaut.data.annotation.Id;
import io.micronaut.data.annotation.MappedEntity;
@MappedEntity
public class Article {
@Id
private ObjectId id;
private String title;
private String content;
private String author;
private List<String> tags;
private LocalDateTime publishedAt;
private int viewCount;
private Map<String, Object> metadata;
// 构造函数
public Article() {}
public Article(String title, String content, String author) {
this.title = title;
this.content = content;
this.author = author;
this.tags = new ArrayList<>();
this.publishedAt = LocalDateTime.now();
this.viewCount = 0;
this.metadata = new HashMap<>();
}
// Getter和Setter
public ObjectId getId() { return id; }
public void setId(ObjectId id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public List<String> getTags() { return tags; }
public void setTags(List<String> tags) { this.tags = tags; }
public LocalDateTime getPublishedAt() { return publishedAt; }
public void setPublishedAt(LocalDateTime publishedAt) { this.publishedAt = publishedAt; }
public int getViewCount() { return viewCount; }
public void setViewCount(int viewCount) { this.viewCount = viewCount; }
public Map<String, Object> getMetadata() { return metadata; }
public void setMetadata(Map<String, Object> metadata) { this.metadata = metadata; }
}
// MongoDB Repository
@MongoRepository
public interface ArticleRepository extends CrudRepository<Article, ObjectId> {
// 方法名查询
List<Article> findByAuthor(String author);
List<Article> findByTitleContaining(String keyword);
List<Article> findByPublishedAtAfter(LocalDateTime date);
List<Article> findByViewCountGreaterThan(int count);
// 自定义查询
@Query("{\"tags\": {\"$in\": :tags}}")
List<Article> findByTags(List<String> tags);
@Query("{\"author\": :author, \"publishedAt\": {\"$gte\": :startDate, \"$lt\": :endDate}}")
List<Article> findByAuthorAndDateRange(
String author,
LocalDateTime startDate,
LocalDateTime endDate
);
// 聚合查询
@Query(value = "{\"$group\": {\"_id\": \"$author\", \"count\": {\"$sum\": 1}}}")
List<Map<String, Object>> countArticlesByAuthor();
// 更新操作
@Query(value = "{\"_id\": :id}",
update = "{\"$inc\": {\"viewCount\": 1}}")
void incrementViewCount(ObjectId id);
}
// MongoDB服务
@Singleton
public class ArticleService {
private final ArticleRepository articleRepository;
public ArticleService(ArticleRepository articleRepository) {
this.articleRepository = articleRepository;
System.out.println("ArticleService初始化");
}
public Article createArticle(String title, String content, String author) {
System.out.println("创建文章: " + title);
Article article = new Article(title, content, author);
Article saved = articleRepository.save(article);
System.out.println("文章已保存, ID: " + saved.getId());
return saved;
}
public void addTags(ObjectId articleId, List<String> tags) {
System.out.println("添加标签: " + tags);
Optional<Article> optionalArticle = articleRepository.findById(articleId);
if (optionalArticle.isPresent()) {
Article article = optionalArticle.get();
article.getTags().addAll(tags);
articleRepository.update(article);
System.out.println("标签已添加");
}
}
public List<Article> findByTags(List<String> tags) {
System.out.println("按标签查询: " + tags);
return articleRepository.findByTags(tags);
}
public void incrementViewCount(ObjectId articleId) {
System.out.println("增加浏览次数: " + articleId);
articleRepository.incrementViewCount(articleId);
}
public List<Article> getPopularArticles(int minViews) {
System.out.println("查询热门文章 (浏览 > " + minViews + ")");
return articleRepository.findByViewCountGreaterThan(minViews);
}
public Map<String, Long> getArticleCountByAuthor() {
System.out.println("统计作者文章数");
List<Map<String, Object>> results = articleRepository.countArticlesByAuthor();
Map<String, Long> counts = new HashMap<>();
for (Map<String, Object> result : results) {
String author = (String) result.get("_id");
Number count = (Number) result.get("count");
counts.put(author, count.longValue());
}
return counts;
}
}
// MongoDB控制器
@Controller("/api/articles")
public class ArticleController {
private final ArticleService articleService;
public ArticleController(ArticleService articleService) {
this.articleService = articleService;
}
@Post
public Article create(@Body CreateArticleRequest request) {
return articleService.createArticle(
request.getTitle(),
request.getContent(),
request.getAuthor()
);
}
@Post("/{id}/tags")
public void addTags(String id, @Body List<String> tags) {
articleService.addTags(new ObjectId(id), tags);
}
@Get("/tags")
public List<Article> findByTags(@QueryValue List<String> tags) {
return articleService.findByTags(tags);
}
@Get("/popular")
public List<Article> getPopular(
@QueryValue(defaultValue = "100") int minViews) {
return articleService.getPopularArticles(minViews);
}
@Get("/stats/authors")
public Map<String, Long> getAuthorStats() {
return articleService.getArticleCountByAuthor();
}
}
// 请求对象
public class CreateArticleRequest {
private String title;
private String content;
private String author;
// Getter和Setter省略
}
---
03.查询方法
a.方法名派生
a.命名规范
根据方法名自动生成查询实现,支持丰富的关键字如findBy、countBy、deleteBy、existsBy等。支持And、Or、Between、LessThan、GreaterThan等条件组合。
b.方法名查询示例
---
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// === 基础查询 ===
// 按单个字段查询
Optional<User> findByUsername(String username);
List<User> findByEmail(String email);
// 查询所有
List<User> findByActive(boolean active);
// 计数
long countByActive(boolean active);
long countByCreatedAtAfter(LocalDateTime date);
// 存在性检查
boolean existsByUsername(String username);
boolean existsByEmail(String email);
// 删除
void deleteByUsername(String username);
long deleteByActiveAndCreatedAtBefore(boolean active, LocalDateTime date);
// === 条件组合 ===
// And条件
Optional<User> findByUsernameAndEmail(String username, String email);
List<User> findByActiveAndCreatedAtAfter(boolean active, LocalDateTime date);
// Or条件
List<User> findByUsernameOrEmail(String username, String email);
List<User> findByActiveOrVerified(boolean active, boolean verified);
// === 比较操作 ===
// 大于/小于
List<User> findByAgeGreaterThan(int age);
List<User> findByAgeLessThan(int age);
List<User> findByAgeGreaterThanEqual(int age);
List<User> findByAgeLessThanEqual(int age);
// Between
List<User> findByAgeBetween(int minAge, int maxAge);
List<User> findByCreatedAtBetween(LocalDateTime start, LocalDateTime end);
// === 字符串操作 ===
// Like
List<User> findByUsernameLike(String pattern);
List<User> findByEmailContaining(String emailPart);
List<User> findByUsernameStartingWith(String prefix);
List<User> findByUsernameEndingWith(String suffix);
// IgnoreCase
Optional<User> findByUsernameIgnoreCase(String username);
List<User> findByEmailContainingIgnoreCase(String emailPart);
// === 空值处理 ===
List<User> findByPhoneIsNull();
List<User> findByPhoneIsNotNull();
// === 集合操作 ===
List<User> findByIdIn(List<Long> ids);
List<User> findByUsernameIn(Collection<String> usernames);
List<User> findByIdNotIn(List<Long> ids);
// === 排序 ===
List<User> findByActiveOrderByCreatedAtDesc(boolean active);
List<User> findByActiveOrderByUsernameAscCreatedAtDesc(boolean active);
// === 分页和限制 ===
// 限制结果数量
List<User> findTop10ByActive(boolean active);
List<User> findFirst5ByActiveOrderByCreatedAtDesc(boolean active);
// 使用Pageable分页
Page<User> findByActive(boolean active, Pageable pageable);
Slice<User> findByEmailContaining(String emailPart, Pageable pageable);
// === 投��查询 ===
// 只查询特定字段
List<String> findUsernameByActive(boolean active);
List<UserProjection> findProjectionByActive(boolean active);
// === Distinct ===
List<String> findDistinctEmailByActive(boolean active);
// === 复杂组合示例 ===
List<User> findByActiveAndAgeBetweenAndEmailContainingIgnoreCaseOrderByCreatedAtDesc(
boolean active,
int minAge,
int maxAge,
String emailPart
);
List<User> findTop10ByActiveOrVerifiedAndCreatedAtAfterOrderByUsernameAsc(
boolean active,
boolean verified,
LocalDateTime date
);
}
// 投影接口
public interface UserProjection {
String getUsername();
String getEmail();
LocalDateTime getCreatedAt();
}
// 使用示例
@Singleton
public class QueryMethodService {
private final UserRepository userRepository;
public QueryMethodService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void demonstrateQueryMethods() {
System.out.println("=== 查询方法演示 ===\n");
// 基础查询
System.out.println("1. 按用户名查询:");
Optional<User> user = userRepository.findByUsername("john");
user.ifPresent(u -> System.out.println(" 找到: " + u));
// 条件组合
System.out.println("\n2. And条件查询:");
LocalDateTime cutoffDate = LocalDateTime.now().minusDays(30);
List<User> activeUsers = userRepository.findByActiveAndCreatedAtAfter(
true, cutoffDate
);
System.out.println(" 找到 " + activeUsers.size() + " 个活跃用户");
// 字符串模糊查询
System.out.println("\n3. 邮箱模糊查询:");
List<User> gmailUsers = userRepository.findByEmailContaining("gmail");
System.out.println(" 找到 " + gmailUsers.size() + " 个Gmail用户");
// 范围查询
System.out.println("\n4. 年龄范围查询:");
List<User> adults = userRepository.findByAgeBetween(18, 65);
System.out.println(" 找到 " + adults.size() + " 个成年用户");
// 排序查询
System.out.println("\n5. 排序查询:");
List<User> sortedUsers = userRepository
.findByActiveOrderByCreatedAtDesc(true);
System.out.println(" 按时间倒序查询到 " + sortedUsers.size() + " 个用户");
// 分页查询
System.out.println("\n6. 分页查询:");
Pageable pageable = Pageable.from(0, 10);
Page<User> page = userRepository.findByActive(true, pageable);
System.out.println(" 第1页: " + page.getContent().size() + " 个用户");
System.out.println(" 总数: " + page.getTotalSize());
System.out.println(" 总页数: " + page.getTotalPages());
// 限制结果
System.out.println("\n7. Top N查询:");
List<User> topUsers = userRepository
.findTop10ByActive(true);
System.out.println(" 查询到前 " + topUsers.size() + " 个用户");
// 存在性检查
System.out.println("\n8. 存在性检查:");
boolean exists = userRepository.existsByUsername("admin");
System.out.println(" admin用户" + (exists ? "存在" : "不存在"));
// 计数
System.out.println("\n9. 计数查询:");
long count = userRepository.countByActive(true);
System.out.println(" 活跃用户数: " + count);
}
}
// 查询方法命名规则总结
// 前缀: find, read, get, query, search, stream
// 条件: By + 字段名 + 操作符
// 操作符:
// - 相等: 无后缀 (findByName)
// - Like: Like, Containing, StartingWith, EndingWith
// - 比较: GreaterThan, LessThan, Between
// - 空值: IsNull, IsNotNull
// - 集合: In, NotIn
// - 逻辑: And, Or
// - 忽略大小写: IgnoreCase
// 排序: OrderBy + 字段名 + Asc/Desc
// 限制: Top/First + 数字
---
b.@Query注解
a.自定义查询
使用@Query注解编写JPQL或原生SQL查询,实现复杂查询逻辑。支持命名参数、位置参数、动态查询等,提供更灵活的查询能力。
b.自定义查询示例
---
@Repository
public interface AdvancedUserRepository extends JpaRepository<User, Long> {
// === JPQL查询 ===
// 基础JPQL
@Query("SELECT u FROM User u WHERE u.username = :username")
Optional<User> findUserByUsername(@Param("username") String username);
@Query("SELECT u FROM User u WHERE u.active = :active ORDER BY u.createdAt DESC")
List<User> findActiveUsers(@Param("active") boolean active);
// 联接查询
@Query("SELECT u FROM User u LEFT JOIN FETCH u.roles WHERE u.id = :id")
Optional<User> findUserWithRoles(@Param("id") Long id);
@Query("SELECT u FROM User u JOIN u.orders o WHERE o.status = :status")
List<User> findUsersWithOrderStatus(@Param("status") String status);
// 聚合查询
@Query("SELECT COUNT(u) FROM User u WHERE u.createdAt >= :date")
long countNewUsers(@Param("date") LocalDateTime date);
@Query("SELECT u.department, COUNT(u) FROM User u GROUP BY u.department")
List<Object[]> countUsersByDepartment();
// 子查询
@Query("SELECT u FROM User u WHERE u.id IN " +
"(SELECT o.userId FROM Order o WHERE o.total > :amount)")
List<User> findUsersWithLargeOrders(@Param("amount") BigDecimal amount);
// === 原生SQL查询 ===
@Query(value = "SELECT * FROM users WHERE username LIKE CONCAT(:prefix, '%')",
nativeQuery = true)
List<User> findByUsernamePrefix(@Param("prefix") String prefix);
@Query(value = "SELECT u.*, COUNT(o.id) as order_count FROM users u " +
"LEFT JOIN orders o ON u.id = o.user_id " +
"GROUP BY u.id HAVING COUNT(o.id) > :minOrders",
nativeQuery = true)
List<Object[]> findUsersWithMinOrders(@Param("minOrders") int minOrders);
// 数据库特定功能
@Query(value = "SELECT * FROM users WHERE MATCH(username, email) " +
"AGAINST(:keyword IN BOOLEAN MODE)",
nativeQuery = true)
List<User> fullTextSearch(@Param("keyword") String keyword);
// === 修改查询 ===
@Modifying
@Query("UPDATE User u SET u.active = :active WHERE u.lastLoginAt < :date")
int deactivateInactiveUsers(
@Param("active") boolean active,
@Param("date") LocalDateTime date
);
@Modifying
@Query("DELETE FROM User u WHERE u.active = false AND u.createdAt < :date")
int deleteInactiveUsers(@Param("date") LocalDateTime date);
@Modifying
@Query("UPDATE User u SET u.loginCount = u.loginCount + 1, " +
"u.lastLoginAt = :now WHERE u.id = :id")
void updateLoginInfo(@Param("id") Long id, @Param("now") LocalDateTime now);
// === 动态查询 ===
@Query("SELECT u FROM User u WHERE " +
"(:username IS NULL OR u.username LIKE CONCAT('%', :username, '%')) AND " +
"(:email IS NULL OR u.email LIKE CONCAT('%', :email, '%')) AND " +
"(:active IS NULL OR u.active = :active)")
List<User> dynamicSearch(
@Param("username") @Nullable String username,
@Param("email") @Nullable String email,
@Param("active") @Nullable Boolean active
);
// === 投影查询 ===
@Query("SELECT new map(u.username as username, u.email as email, " +
"u.createdAt as createdAt) FROM User u WHERE u.active = true")
List<Map<String, Object>> findActiveUserProjections();
@Query("SELECT u.username FROM User u WHERE u.department = :dept")
List<String> findUsernamesByDepartment(@Param("dept") String department);
// DTO投影
@Query("SELECT new com.example.dto.UserSummaryDTO(u.id, u.username, u.email) " +
"FROM User u WHERE u.active = true")
List<UserSummaryDTO> findUserSummaries();
// === 分页查询 ===
@Query("SELECT u FROM User u WHERE u.department = :dept ORDER BY u.createdAt DESC")
Page<User> findByDepartment(@Param("dept") String department, Pageable pageable);
@Query(value = "SELECT * FROM users WHERE department = :dept",
countQuery = "SELECT COUNT(*) FROM users WHERE department = :dept",
nativeQuery = true)
Page<User> findByDepartmentNative(@Param("dept") String department, Pageable pageable);
// === 命名参数 vs 位置参数 ===
// 命名参数 (推荐)
@Query("SELECT u FROM User u WHERE u.username = :username AND u.email = :email")
Optional<User> findByUsernameAndEmail(
@Param("username") String username,
@Param("email") String email
);
// 位置参数
@Query("SELECT u FROM User u WHERE u.username = ?1 AND u.email = ?2")
Optional<User> findByUsernameAndEmailPositional(String username, String email);
// === 集合参数 ===
@Query("SELECT u FROM User u WHERE u.id IN :ids")
List<User> findByIds(@Param("ids") List<Long> ids);
@Query("SELECT u FROM User u WHERE u.role IN :roles AND u.active = true")
List<User> findByRoles(@Param("roles") Collection<String> roles);
}
// DTO对象
public class UserSummaryDTO {
private Long id;
private String username;
private String email;
public UserSummaryDTO(Long id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
}
// Getter省略
}
// 使用示例
@Singleton
public class CustomQueryService {
private final AdvancedUserRepository userRepository;
public CustomQueryService(AdvancedUserRepository userRepository) {
this.userRepository = userRepository;
}
public void demonstrateCustomQueries() {
System.out.println("=== 自定义查询演示 ===\n");
// JPQL查询
System.out.println("1. JPQL查询:");
List<User> activeUsers = userRepository.findActiveUsers(true);
System.out.println(" 找到 " + activeUsers.size() + " 个活跃用户");
// 聚合查询
System.out.println("\n2. 聚合查询:");
List<Object[]> deptCounts = userRepository.countUsersByDepartment();
deptCounts.forEach(row ->
System.out.println(" " + row[0] + ": " + row[1] + " 人")
);
// 动态查���
System.out.println("\n3. 动态查询:");
List<User> searchResults = userRepository.dynamicSearch(
"john",
null,
true
);
System.out.println(" 找到 " + searchResults.size() + " 个匹配用户");
// 投影查询
System.out.println("\n4. 投影查询:");
List<UserSummaryDTO> summaries = userRepository.findUserSummaries();
System.out.println(" 查询到 " + summaries.size() + " 个用户摘要");
// 修改查询
System.out.println("\n5. 批量更新:");
LocalDateTime cutoffDate = LocalDateTime.now().minusMonths(6);
int deactivated = userRepository.deactivateInactiveUsers(false, cutoffDate);
System.out.println(" 停用了 " + deactivated + " 个不活跃用户");
// 分页查询
System.out.println("\n6. 分页查询:");
Pageable pageable = Pageable.from(0, 10);
Page<User> page = userRepository.findByDepartment("Engineering", pageable);
System.out.println(" 第1页: " + page.getContent().size() + " 个用户");
System.out.println(" 总数: " + page.getTotalSize());
}
}
---
5.2 Repository模式
01.Repository接口
a.CrudRepository
a.基础CRUD操作
CrudRepository提供基本的增删改查操作,包括save、findById、findAll、delete等方法。作为最基础的Repository接口,适合简单的数据访问需求。
b.CrudRepository示例
---
import io.micronaut.data.repository.CrudRepository;
import io.micronaut.data.annotation.Repository;
// 基础CRUD Repository
@Repository
public interface ProductRepository extends CrudRepository<Product, Long> {
// 继承的方法:
// - <S extends Product> S save(S entity)
// - <S extends Product> Iterable<S> saveAll(Iterable<S> entities)
// - Optional<Product> findById(Long id)
// - boolean existsById(Long id)
// - Iterable<Product> findAll()
// - long count()
// - void deleteById(Long id)
// - void delete(Product entity)
// - void deleteAll(Iterable<? extends Product> entities)
// - void deleteAll()
}
// 产品实体
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(precision = 10, scale = 2)
private BigDecimal price;
@Column
private Integer stock;
@Column
private String category;
@Column(name = "created_at")
private LocalDateTime createdAt;
// 构造函数
public Product() {}
public Product(String name, BigDecimal price, Integer stock) {
this.name = name;
this.price = price;
this.stock = stock;
this.createdAt = LocalDateTime.now();
}
// Getter和Setter省略
}
// 使用CrudRepository
@Singleton
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
System.out.println("ProductService初始化");
}
// 创建产品
public Product createProduct(String name, BigDecimal price, Integer stock) {
System.out.println("创建产品: " + name);
Product product = new Product(name, price, stock);
Product saved = productRepository.save(product);
System.out.println(" 产品已保存, ID: " + saved.getId());
return saved;
}
// 批量创建
public List<Product> createProducts(List<Product> products) {
System.out.println("批量创建产品: " + products.size() + " 个");
Iterable<Product> saved = productRepository.saveAll(products);
List<Product> result = new ArrayList<>();
saved.forEach(result::add);
System.out.println(" 批量保存完成");
return result;
}
// 查询单个产品
public Optional<Product> findProduct(Long id) {
System.out.println("查询产品: " + id);
return productRepository.findById(id);
}
// 查询所有产品
public List<Product> findAllProducts() {
System.out.println("查询所有产品");
Iterable<Product> products = productRepository.findAll();
List<Product> result = new ArrayList<>();
products.forEach(result::add);
System.out.println(" 找到 " + result.size() + " 个产品");
return result;
}
// 检查产品是否存在
public boolean productExists(Long id) {
System.out.println("检查产品是否存在: " + id);
return productRepository.existsById(id);
}
// 统计产品数量
public long countProducts() {
long count = productRepository.count();
System.out.println("产品总数: " + count);
return count;
}
// 更新产品
public Product updateProduct(Long id, String name, BigDecimal price) {
System.out.println("更新产品: " + id);
Optional<Product> optionalProduct = productRepository.findById(id);
if (optionalProduct.isEmpty()) {
throw new IllegalArgumentException("产品不存在: " + id);
}
Product product = optionalProduct.get();
product.setName(name);
product.setPrice(price);
Product updated = productRepository.update(product);
System.out.println(" 产品已更新");
return updated;
}
// 删除产品
public void deleteProduct(Long id) {
System.out.println("删除产品: " + id);
productRepository.deleteById(id);
System.out.println(" 产品已删除");
}
// 批量删除
public void deleteProducts(List<Long> ids) {
System.out.println("批量删除产品: " + ids.size() + " 个");
List<Product> products = ids.stream()
.map(id -> {
Product p = new Product();
p.setId(id);
return p;
})
.collect(Collectors.toList());
productRepository.deleteAll(products);
System.out.println(" 批量删除完成");
}
// 清空所有产品
public void deleteAllProducts() {
System.out.println("删除所有产品");
productRepository.deleteAll();
System.out.println(" 所有产品已删除");
}
}
// 控制器
@Controller("/api/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@Post
public Product create(@Body CreateProductRequest request) {
return productService.createProduct(
request.getName(),
request.getPrice(),
request.getStock()
);
}
@Get("/{id}")
public Optional<Product> get(Long id) {
return productService.findProduct(id);
}
@Get
public List<Product> getAll() {
return productService.findAllProducts();
}
@Put("/{id}")
public Product update(Long id, @Body UpdateProductRequest request) {
return productService.updateProduct(
id,
request.getName(),
request.getPrice()
);
}
@Delete("/{id}")
@Status(HttpStatus.NO_CONTENT)
public void delete(Long id) {
productService.deleteProduct(id);
}
@Get("/count")
public Map<String, Long> count() {
return Map.of("count", productService.countProducts());
}
}
// 请求对象
public class CreateProductRequest {
private String name;
private BigDecimal price;
private Integer stock;
// Getter和Setter省略
}
public class UpdateProductRequest {
private String name;
private BigDecimal price;
// Getter和Setter省略
}
---
b.JpaRepository
a.JPA扩展功能
JpaRepository扩展CrudRepository,添加flush、saveAndFlush等JPA特定方法。提供批量操作优化,支持实体状态管理,适合使用JPA的应用。
b.JpaRepository示例
---
import io.micronaut.data.jpa.repository.JpaRepository;
import io.micronaut.data.jpa.repository.criteria.Specification;
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// 继承CrudRepository的所有方法,额外提供:
// - void flush()
// - <S extends Order> S saveAndFlush(S entity)
// - void deleteInBatch(Iterable<Order> entities)
// - void deleteAllInBatch()
// - Order getOne(Long id)
// - List<Order> findAll(Specification<Order> spec)
}
// 订单实体
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "order_number", unique = true)
private String orderNumber;
@Column(name = "user_id")
private Long userId;
@Column
private String status; // PENDING, CONFIRMED, SHIPPED, DELIVERED
@Column(precision = 10, scale = 2)
private BigDecimal total;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<OrderItem> items = new ArrayList<>();
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// Getter和Setter省略
}
// 订单项实体
@Entity
@Table(name = "order_items")
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private Order order;
@Column(name = "product_id")
private Long productId;
@Column
private Integer quantity;
@Column(precision = 10, scale = 2)
private BigDecimal price;
// Getter和Setter省略
}
// 使用JpaRepository
@Singleton
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
System.out.println("OrderService初始化");
}
// 创建订单并立即刷新
@Transactional
public Order createOrderAndFlush(Long userId, List<OrderItem> items) {
System.out.println("创建订单: 用户ID=" + userId);
Order order = new Order();
order.setOrderNumber(generateOrderNumber());
order.setUserId(userId);
order.setStatus("PENDING");
// 添加订单项
BigDecimal total = BigDecimal.ZERO;
for (OrderItem item : items) {
item.setOrder(order);
order.getItems().add(item);
total = total.add(item.getPrice().multiply(
BigDecimal.valueOf(item.getQuantity())
));
}
order.setTotal(total);
// 保存并立即刷新到数据库
Order saved = orderRepository.saveAndFlush(order);
System.out.println(" 订单已保存并刷新, ID: " + saved.getId());
System.out.println(" 订单号: " + saved.getOrderNumber());
return saved;
}
// 批量删除订单
@Transactional
public void deleteOrdersInBatch(List<Long> orderIds) {
System.out.println("批量删除订单: " + orderIds.size() + " 个");
List<Order> orders = orderIds.stream()
.map(id -> {
Order order = new Order();
order.setId(id);
return order;
})
.collect(Collectors.toList());
// 批量删除,减少数据库交互
orderRepository.deleteInBatch(orders);
System.out.println(" 批量删除完成");
}
// 手动刷新
@Transactional
public void updateOrderStatus(Long orderId, String newStatus) {
System.out.println("更新订单状态: " + orderId + " -> " + newStatus);
Optional<Order> optionalOrder = orderRepository.findById(orderId);
if (optionalOrder.isEmpty()) {
throw new IllegalArgumentException("订单不存在: " + orderId);
}
Order order = optionalOrder.get();
order.setStatus(newStatus);
// 保存但不立即刷新
orderRepository.save(order);
// 执行其他操作...
// 手动刷新到数据库
orderRepository.flush();
System.out.println(" 订单状态已更新并刷新");
}
// 获取订单引用
public Order getOrderReference(Long id) {
System.out.println("获取订单引用: " + id);
// getOne返回懒加载代理,不立即查询数据库
return orderRepository.getOne(id);
}
// 查询订单
public Optional<Order> findOrder(Long id) {
System.out.println("查询订单: " + id);
return orderRepository.findById(id);
}
// 统计订单
public Map<String, Object> getOrderStatistics() {
System.out.println("统计订单信息");
long totalOrders = orderRepository.count();
Map<String, Object> stats = new HashMap<>();
stats.put("totalOrders", totalOrders);
stats.put("timestamp", LocalDateTime.now());
return stats;
}
private String generateOrderNumber() {
return "ORD-" + System.currentTimeMillis();
}
}
// 控制器
@Controller("/api/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@Post
public Order create(@Body CreateOrderRequest request) {
return orderService.createOrderAndFlush(
request.getUserId(),
request.getItems()
);
}
@Get("/{id}")
public Optional<Order> get(Long id) {
return orderService.findOrder(id);
}
@Put("/{id}/status")
public void updateStatus(Long id, @Body UpdateStatusRequest request) {
orderService.updateOrderStatus(id, request.getStatus());
}
@Delete("/batch")
@Status(HttpStatus.NO_CONTENT)
public void deleteBatch(@Body List<Long> orderIds) {
orderService.deleteOrdersInBatch(orderIds);
}
@Get("/statistics")
public Map<String, Object> getStatistics() {
return orderService.getOrderStatistics();
}
}
// 请求对象
public class CreateOrderRequest {
private Long userId;
private List<OrderItem> items;
// Getter和Setter省略
}
public class UpdateStatusRequest {
private String status;
// Getter和Setter省略
}
---
02.分页与排序
a.Pageable接口
a.分页查询
使用Pageable接口实现分页查询,支持页码、页大小、排序等参数。返回Page对象包含数据列表、总数、总页数等分页信息,简化分页实现。
b.分页查询示例
---
import io.micronaut.data.model.Page;
import io.micronaut.data.model.Pageable;
import io.micronaut.data.model.Sort;
@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
// 基础分页
Page<Book> findAll(Pageable pageable);
// 条件分页
Page<Book> findByCategory(String category, Pageable pageable);
Page<Book> findByAuthor(String author, Pageable pageable);
Page<Book> findByPriceBetween(BigDecimal minPrice, BigDecimal maxPrice, Pageable pageable);
// 复杂条件分页
Page<Book> findByTitleContainingAndCategoryOrderByPublishedDateDesc(
String title,
String category,
Pageable pageable
);
}
// 图书实体
@Entity
@Table(name = "books")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
@Column
private String author;
@Column
private String category;
@Column
private String isbn;
@Column(precision = 10, scale = 2)
private BigDecimal price;
@Column(name = "published_date")
private LocalDate publishedDate;
@Column
private Integer pageCount;
@Column(columnDefinition = "TEXT")
private String description;
// Getter和Setter省略
}
// 分页服务
@Singleton
public class BookService {
private final BookRepository bookRepository;
public BookService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
System.out.println("BookService初始化");
}
// 基础分页查询
public Page<Book> findBooks(int page, int size) {
System.out.println("分页查询图书: page=" + page + ", size=" + size);
Pageable pageable = Pageable.from(page, size);
Page<Book> result = bookRepository.findAll(pageable);
System.out.println(" 当前页: " + result.getPageNumber());
System.out.println(" 页大小: " + result.getSize());
System.out.println(" 总记录数: " + result.getTotalSize());
System.out.println(" 总页数: " + result.getTotalPages());
System.out.println(" 当前页记录数: " + result.getContent().size());
return result;
}
// 带排序的分页
public Page<Book> findBooksWithSort(int page, int size, String sortBy, String direction) {
System.out.println("排序分页查询: page=" + page + ", size=" + size +
", sort=" + sortBy + " " + direction);
Sort.Order order = direction.equalsIgnoreCase("ASC") ?
Sort.Order.asc(sortBy) :
Sort.Order.desc(sortBy);
Pageable pageable = Pageable.from(page, size, Sort.of(order));
Page<Book> result = bookRepository.findAll(pageable);
System.out.println(" 找到 " + result.getContent().size() + " 本图书");
return result;
}
// 多字段排序
public Page<Book> findBooksWithMultipleSort(int page, int size) {
System.out.println("多字段排序分页查询");
Sort sort = Sort.of(
Sort.Order.desc("publishedDate"),
Sort.Order.asc("title")
);
Pageable pageable = Pageable.from(page, size, sort);
Page<Book> result = bookRepository.findAll(pageable);
return result;
}
// 按分类分页
public Page<Book> findBooksByCategory(String category, int page, int size) {
System.out.println("按分类分页: " + category);
Pageable pageable = Pageable.from(page, size);
Page<Book> result = bookRepository.findByCategory(category, pageable);
System.out.println(" 分类 '" + category + "' 总数: " + result.getTotalSize());
return result;
}
// 价格范围分页
public Page<Book> findBooksByPriceRange(BigDecimal minPrice, BigDecimal maxPrice,
int page, int size) {
System.out.println("价格范围分页: " + minPrice + " - " + maxPrice);
Sort sort = Sort.of(Sort.Order.asc("price"));
Pageable pageable = Pageable.from(page, size, sort);
Page<Book> result = bookRepository.findByPriceBetween(minPrice, maxPrice, pageable);
System.out.println(" 找到 " + result.getTotalSize() + " 本符合价格范围的图书");
return result;
}
// 搜索分页
public Page<Book> searchBooks(String keyword, String category, int page, int size) {
System.out.println("搜索图书: keyword=" + keyword + ", category=" + category);
Sort sort = Sort.of(Sort.Order.desc("publishedDate"));
Pageable pageable = Pageable.from(page, size, sort);
Page<Book> result = bookRepository
.findByTitleContainingAndCategoryOrderByPublishedDateDesc(
keyword,
category,
pageable
);
return result;
}
// 分页导航
public Map<String, Object> getPageInfo(Page<Book> page) {
Map<String, Object> info = new HashMap<>();
info.put("currentPage", page.getPageNumber());
info.put("pageSize", page.getSize());
info.put("totalPages", page.getTotalPages());
info.put("totalElements", page.getTotalSize());
info.put("hasNext", page.getPageNumber() < page.getTotalPages() - 1);
info.put("hasPrevious", page.getPageNumber() > 0);
info.put("isFirst", page.getPageNumber() == 0);
info.put("isLast", page.getPageNumber() == page.getTotalPages() - 1);
info.put("content", page.getContent());
return info;
}
}
// 分页控制器
@Controller("/api/books")
public class BookController {
private final BookService bookService;
public BookController(BookService bookService) {
this.bookService = bookService;
}
// 基础分页
@Get
public Page<Book> list(
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "20") int size) {
return bookService.findBooks(page, size);
}
// 带排序的分页
@Get("/sorted")
public Page<Book> listSorted(
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "20") int size,
@QueryValue(defaultValue = "title") String sort,
@QueryValue(defaultValue = "ASC") String direction) {
return bookService.findBooksWithSort(page, size, sort, direction);
}
// 按分类分页
@Get("/category/{category}")
public Page<Book> listByCategory(
String category,
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "20") int size) {
return bookService.findBooksByCategory(category, page, size);
}
// 价格范围分页
@Get("/price-range")
public Page<Book> listByPriceRange(
@QueryValue BigDecimal minPrice,
@QueryValue BigDecimal maxPrice,
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "20") int size) {
return bookService.findBooksByPriceRange(minPrice, maxPrice, page, size);
}
// 搜索分页
@Get("/search")
public Page<Book> search(
@QueryValue String keyword,
@QueryValue String category,
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "20") int size) {
return bookService.searchBooks(keyword, category, page, size);
}
// 分页信息
@Get("/page-info")
public Map<String, Object> getPageInfo(
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "20") int size) {
Page<Book> bookPage = bookService.findBooks(page, size);
return bookService.getPageInfo(bookPage);
}
}
// 分页响应示例
// GET /api/books?page=0&size=10
// {
// "content": [...],
// "pageable": {
// "number": 0,
// "size": 10,
// "sort": {
// "sorted": false
// }
// },
// "totalPages": 15,
// "totalSize": 150,
// "size": 10,
// "number": 0
// }
---
b.Sort排序
a.排序配置
通过Sort对象配置查询结果的排序方式,支持单字段和多字段排序,可指定升序或降序。排序配置可独立使用或与分页结合。
b.排序配置示例
---
@Singleton
public class SortingService {
private final BookRepository bookRepository;
public SortingService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
// 单字段排序
public List<Book> findBooksSortedByTitle() {
System.out.println("按标题排序查询");
Sort sort = Sort.of(Sort.Order.asc("title"));
Pageable pageable = Pageable.from(0, 100, sort);
return bookRepository.findAll(pageable).getContent();
}
// 多字段排序
public List<Book> findBooksSortedByMultipleFields() {
System.out.println("多字段排序查询");
Sort sort = Sort.of(
Sort.Order.desc("publishedDate"), // 首先按出版日期降序
Sort.Order.asc("title"), // 然后按标题升序
Sort.Order.desc("price") // 最后按价格降序
);
Pageable pageable = Pageable.from(0, 100, sort);
return bookRepository.findAll(pageable).getContent();
}
// 忽略大小写排序
public List<Book> findBooksSortedIgnoreCase() {
System.out.println("忽略大小写排序");
Sort sort = Sort.of(
Sort.Order.asc("title").ignoreCase()
);
Pageable pageable = Pageable.from(0, 100, sort);
return bookRepository.findAll(pageable).getContent();
}
// 空值处理排序
public List<Book> findBooksSortedWithNullHandling() {
System.out.println("空值处理排序");
Sort sort = Sort.of(
Sort.Order.desc("publishedDate")
.nullsLast() // 空值放在最后
);
Pageable pageable = Pageable.from(0, 100, sort);
return bookRepository.findAll(pageable).getContent();
}
// 动态排序
public List<Book> findBooksWithDynamicSort(String sortField, String direction) {
System.out.println("动态排序: " + sortField + " " + direction);
Sort.Order order;
if ("ASC".equalsIgnoreCase(direction)) {
order = Sort.Order.asc(sortField);
} else {
order = Sort.Order.desc(sortField);
}
Sort sort = Sort.of(order);
Pageable pageable = Pageable.from(0, 100, sort);
return bookRepository.findAll(pageable).getContent();
}
// 条件排序
public List<Book> findBooksWithConditionalSort(String category, String sortBy) {
System.out.println("条件排序: category=" + category + ", sortBy=" + sortBy);
Sort sort = switch (sortBy) {
case "price-asc" -> Sort.of(Sort.Order.asc("price"));
case "price-desc" -> Sort.of(Sort.Order.desc("price"));
case "date-new" -> Sort.of(Sort.Order.desc("publishedDate"));
case "date-old" -> Sort.of(Sort.Order.asc("publishedDate"));
case "title" -> Sort.of(Sort.Order.asc("title"));
default -> Sort.of(Sort.Order.desc("publishedDate"));
};
Pageable pageable = Pageable.from(0, 100, sort);
return bookRepository.findByCategory(category, pageable).getContent();
}
// 复合排序示例
public List<Book> findBooksWithComplexSort() {
System.out.println("复合排序");
// 按分类升序,同分类内按价格降序,同价格按标题升序
Sort sort = Sort.of(
Sort.Order.asc("category"),
Sort.Order.desc("price"),
Sort.Order.asc("title").ignoreCase()
);
Pageable pageable = Pageable.from(0, 100, sort);
return bookRepository.findAll(pageable).getContent();
}
}
// 排序控制器
@Controller("/api/books/sort")
public class SortingController {
private final SortingService sortingService;
public SortingController(SortingService sortingService) {
this.sortingService = sortingService;
}
@Get("/title")
public List<Book> sortByTitle() {
return sortingService.findBooksSortedByTitle();
}
@Get("/multiple")
public List<Book> sortByMultiple() {
return sortingService.findBooksSortedByMultipleFields();
}
@Get("/dynamic")
public List<Book> sortDynamic(
@QueryValue String field,
@QueryValue String direction) {
return sortingService.findBooksWithDynamicSort(field, direction);
}
@Get("/conditional")
public List<Book> sortConditional(
@QueryValue String category,
@QueryValue String sortBy) {
return sortingService.findBooksWithConditionalSort(category, sortBy);
}
}
---
03.自定义Repository
a.接口扩展
a.添加自定义方法
通过接口继承和实现类扩展Repository功能,添加复杂查询逻辑。可以混合使用派生查询、@Query注解和自定义实现方法。
b.接口扩展示例
---
// 自定义Repository接口
public interface CustomUserRepository {
List<User> findUsersWithCustomLogic(String criteria);
Map<String, Long> getUserStatistics();
void bulkUpdateUserStatus(List<Long> userIds, String status);
}
// 标准Repository接口
@Repository
public interface UserRepository extends JpaRepository<User, Long>, CustomUserRepository {
// 标准查询方法
Optional<User> findByUsername(String username);
List<User> findByActive(boolean active);
// 同时继承CustomUserRepository的自定义方法
}
// 自定义Repository实现
@Singleton
public class CustomUserRepositoryImpl implements CustomUserRepository {
private final EntityManager entityManager;
public CustomUserRepositoryImpl(EntityManager entityManager) {
this.entityManager = entityManager;
System.out.println("CustomUserRepositoryImpl初始化");
}
@Override
@Transactional
public List<User> findUsersWithCustomLogic(String criteria) {
System.out.println("自定义查询逻辑: " + criteria);
// 使用EntityManager执行复杂查询
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
// 构建复杂查询条件
List<Predicate> predicates = new ArrayList<>();
if (criteria.contains("active")) {
predicates.add(cb.equal(root.get("active"), true));
}
if (criteria.contains("verified")) {
predicates.add(cb.equal(root.get("verified"), true));
}
if (criteria.contains("recent")) {
LocalDateTime cutoff = LocalDateTime.now().minusDays(30);
predicates.add(cb.greaterThan(root.get("createdAt"), cutoff));
}
query.where(predicates.toArray(new Predicate[0]));
query.orderBy(cb.desc(root.get("createdAt")));
List<User> results = entityManager.createQuery(query).getResultList();
System.out.println(" 找到 " + results.size() + " 个用户");
return results;
}
@Override
public Map<String, Long> getUserStatistics() {
System.out.println("获取用户统计信息");
Map<String, Long> stats = new HashMap<>();
// 总用户数
Long totalUsers = entityManager.createQuery(
"SELECT COUNT(u) FROM User u",
Long.class
).getSingleResult();
stats.put("totalUsers", totalUsers);
// 活跃用户数
Long activeUsers = entityManager.createQuery(
"SELECT COUNT(u) FROM User u WHERE u.active = true",
Long.class
).getSingleResult();
stats.put("activeUsers", activeUsers);
// 已验证用户数
Long verifiedUsers = entityManager.createQuery(
"SELECT COUNT(u) FROM User u WHERE u.verified = true",
Long.class
).getSingleResult();
stats.put("verifiedUsers", verifiedUsers);
// 本月新用户数
LocalDateTime monthStart = LocalDateTime.now().withDayOfMonth(1).withHour(0).withMinute(0);
Long newUsersThisMonth = entityManager.createQuery(
"SELECT COUNT(u) FROM User u WHERE u.createdAt >= :monthStart",
Long.class
).setParameter("monthStart", monthStart).getSingleResult();
stats.put("newUsersThisMonth", newUsersThisMonth);
System.out.println(" 统计结果: " + stats);
return stats;
}
@Override
@Transactional
public void bulkUpdateUserStatus(List<Long> userIds, String status) {
System.out.println("批量更新用户状态: " + userIds.size() + " 个用户");
int updated = entityManager.createQuery(
"UPDATE User u SET u.status = :status, u.updatedAt = :now " +
"WHERE u.id IN :ids"
)
.setParameter("status", status)
.setParameter("now", LocalDateTime.now())
.setParameter("ids", userIds)
.executeUpdate();
System.out.println(" 更新了 " + updated + " 个用户");
}
}
// 使用自定义Repository
@Singleton
public class UserManagementService {
private final UserRepository userRepository;
public UserManagementService(UserRepository userRepository) {
this.userRepository = userRepository;
System.out.println("UserManagementService初始化");
}
// 使用标准方法
public Optional<User> findByUsername(String username) {
return userRepository.findByUsername(username);
}
// 使用自定义方法
public List<User> findActiveRecentUsers() {
return userRepository.findUsersWithCustomLogic("active,recent");
}
// 获取统计信息
public Map<String, Long> getStatistics() {
return userRepository.getUserStatistics();
}
// 批量更新
public void suspendUsers(List<Long> userIds) {
userRepository.bulkUpdateUserStatus(userIds, "SUSPENDED");
}
}
---
b.Repository组合
a.多Repository协作
在服务层组合多个Repository实现复杂业务逻辑,协调不同实体的数据访问。通过事务管理保证数据一致性,实现跨Repository的业务操作。
b.Repository组合示例
---
// 多个Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
List<User> findByIdIn(List<Long> ids);
}
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByUserId(Long userId);
List<Order> findByStatus(String status);
}
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByIdIn(List<Long> ids);
}
@Repository
public interface WalletRepository extends JpaRepository<Wallet, Long> {
Optional<Wallet> findByUserId(Long userId);
}
// 钱包实体
@Entity
@Table(name = "wallets")
public class Wallet {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id", unique = true)
private Long userId;
@Column(precision = 10, scale = 2)
private BigDecimal balance;
@Version
private Long version; // 乐观锁
// Getter和Setter省略
}
// 组合多Repository的服务
@Singleton
public class OrderManagementService {
private final OrderRepository orderRepository;
private final UserRepository userRepository;
private final ProductRepository productRepository;
private final WalletRepository walletRepository;
public OrderManagementService(
OrderRepository orderRepository,
UserRepository userRepository,
ProductRepository productRepository,
WalletRepository walletRepository) {
this.orderRepository = orderRepository;
this.userRepository = userRepository;
this.productRepository = productRepository;
this.walletRepository = walletRepository;
System.out.println("OrderManagementService初始化");
}
// 创建订单 - 涉及多个Repository
@Transactional
public Order createOrder(Long userId, List<OrderItemRequest> items) {
System.out.println("创建订单: 用户ID=" + userId);
// 1. 验证用户存在
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("用户不存在"));
// 2. 获取产品信息
List<Long> productIds = items.stream()
.map(OrderItemRequest::getProductId)
.collect(Collectors.toList());
List<Product> products = productRepository.findByIdIn(productIds);
if (products.size() != productIds.size()) {
throw new IllegalArgumentException("部分产品不存在");
}
// 3. 计算总价
BigDecimal total = BigDecimal.ZERO;
for (OrderItemRequest item : items) {
Product product = products.stream()
.filter(p -> p.getId().equals(item.getProductId()))
.findFirst()
.orElseThrow();
BigDecimal itemTotal = product.getPrice()
.multiply(BigDecimal.valueOf(item.getQuantity()));
total = total.add(itemTotal);
}
// 4. 创建订单
Order order = new Order();
order.setOrderNumber("ORD-" + System.currentTimeMillis());
order.setUserId(userId);
order.setStatus("PENDING");
order.setTotal(total);
Order savedOrder = orderRepository.save(order);
System.out.println(" 订单已创建: " + savedOrder.getOrderNumber());
return savedOrder;
}
// 支付订单 - 涉及订单和钱包
@Transactional
public void payOrder(Long orderId) {
System.out.println("支付订单: " + orderId);
// 1. 获取订单
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new IllegalArgumentException("订单不存在"));
if (!"PENDING".equals(order.getStatus())) {
throw new IllegalStateException("订单状态不正确");
}
// 2. 获取用户钱包
Wallet wallet = walletRepository.findByUserId(order.getUserId())
.orElseThrow(() -> new IllegalArgumentException("钱包不存在"));
// 3. 检查余额
if (wallet.getBalance().compareTo(order.getTotal()) < 0) {
throw new IllegalStateException("余额不足");
}
// 4. 扣除余额
wallet.setBalance(wallet.getBalance().subtract(order.getTotal()));
walletRepository.update(wallet);
// 5. 更新订单状态
order.setStatus("PAID");
orderRepository.update(order);
System.out.println(" 订单支付成功");
}
// 获取用户订单摘要
@Transactional(readOnly = true)
public UserOrderSummary getUserOrderSummary(Long userId) {
System.out.println("获取用户订单摘要: " + userId);
// 1. 获取用户信息
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("用户不存在"));
// 2. 获取用户所有订单
List<Order> orders = orderRepository.findByUserId(userId);
// 3. 获取钱包余额
Wallet wallet = walletRepository.findByUserId(userId)
.orElse(null);
// 4. 统计订单信息
long totalOrders = orders.size();
long paidOrders = orders.stream()
.filter(o -> "PAID".equals(o.getStatus()))
.count();
BigDecimal totalSpent = orders.stream()
.filter(o -> "PAID".equals(o.getStatus()))
.map(Order::getTotal)
.reduce(BigDecimal.ZERO, BigDecimal::add);
UserOrderSummary summary = new UserOrderSummary(
user.getUsername(),
totalOrders,
paidOrders,
totalSpent,
wallet != null ? wallet.getBalance() : BigDecimal.ZERO
);
System.out.println(" 摘要: " + summary);
return summary;
}
// 批量处理待支付订单
@Transactional
public Map<String, Integer> processPendingOrders() {
System.out.println("批量处理待支付订单");
List<Order> pendingOrders = orderRepository.findByStatus("PENDING");
System.out.println(" 待支付订单: " + pendingOrders.size());
int processed = 0;
int failed = 0;
for (Order order : pendingOrders) {
try {
payOrder(order.getId());
processed++;
} catch (Exception e) {
System.err.println(" 订单支付失败: " + order.getId() + " - " + e.getMessage());
failed++;
}
}
Map<String, Integer> result = new HashMap<>();
result.put("processed", processed);
result.put("failed", failed);
System.out.println(" 处理完成: 成功=" + processed + ", 失败=" + failed);
return result;
}
}
// 订单项请求
public class OrderItemRequest {
private Long productId;
private Integer quantity;
// Getter和Setter省略
}
// 用户订单摘要
public class UserOrderSummary {
private String username;
private long totalOrders;
private long paidOrders;
private BigDecimal totalSpent;
private BigDecimal walletBalance;
public UserOrderSummary(String username, long totalOrders, long paidOrders,
BigDecimal totalSpent, BigDecimal walletBalance) {
this.username = username;
this.totalOrders = totalOrders;
this.paidOrders = paidOrders;
this.totalSpent = totalSpent;
this.walletBalance = walletBalance;
}
@Override
public String toString() {
return "UserOrderSummary{username='" + username + "', totalOrders=" + totalOrders +
", paidOrders=" + paidOrders + ", totalSpent=" + totalSpent +
", walletBalance=" + walletBalance + "}";
}
// Getter省略
}
---
5.3 JPA集成
01.实体映射
a.@Entity注解
a.实体类定义
使用@Entity标记JPA实体类,@Table指定表名,@Column配置列属性。实体类通过注解映射到数据库表,实现对象关系映射(ORM)。
b.实体映射示例
---
import jakarta.persistence.*;
import java.time.LocalDateTime;
// 基础实体映射
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "employee_number", unique = true, nullable = false, length = 20)
private String employeeNumber;
@Column(name = "first_name", nullable = false, length = 50)
private String firstName;
@Column(name = "last_name", nullable = false, length = 50)
private String lastName;
@Column(nullable = false, unique = true, length = 100)
private String email;
@Column(length = 20)
private String phone;
@Column(length = 50)
private String department;
@Column(length = 50)
private String position;
@Column(precision = 10, scale = 2)
private BigDecimal salary;
@Column(name = "hire_date", nullable = false)
private LocalDate hireDate;
@Column(name = "is_active", nullable = false)
private Boolean active = true;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// 生命周期回调
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
System.out.println("准备保存员工: " + employeeNumber);
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
System.out.println("准备更新员工: " + employeeNumber);
}
@PostPersist
protected void afterCreate() {
System.out.println("员工已保存: " + id);
}
@PostUpdate
protected void afterUpdate() {
System.out.println("员工已更新: " + id);
}
@PostLoad
protected void afterLoad() {
System.out.println("员工已加载: " + employeeNumber);
}
// 构造函数
public Employee() {}
public Employee(String employeeNumber, String firstName, String lastName, String email) {
this.employeeNumber = employeeNumber;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.hireDate = LocalDate.now();
}
// Getter和Setter省略
}
// 枚举类型映射
@Entity
@Table(name = "tasks")
public class Task {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
@Column(columnDefinition = "TEXT")
private String description;
// 枚举映射为字符串
@Enumerated(EnumType.STRING)
@Column(length = 20)
private TaskStatus status;
// 枚举映射为整数
@Enumerated(EnumType.ORDINAL)
@Column
private TaskPriority priority;
@Column(name = "due_date")
private LocalDate dueDate;
// Getter和Setter省略
}
// 任务状态枚举
public enum TaskStatus {
TODO,
IN_PROGRESS,
REVIEW,
DONE,
CANCELLED
}
// 任务优先级枚举
public enum TaskPriority {
LOW, // 0
MEDIUM, // 1
HIGH, // 2
URGENT // 3
}
// 大对象(LOB)映射
@Entity
@Table(name = "documents")
public class Document {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
// 大文本对象
@Lob
@Column(columnDefinition = "TEXT")
private String content;
// 二进制大对象
@Lob
@Column(columnDefinition = "BLOB")
private byte[] fileData;
@Column(name = "file_name")
private String fileName;
@Column(name = "file_type")
private String fileType;
// Getter和Setter省略
}
// 嵌入式对象
@Embeddable
public class Address {
@Column(length = 100)
private String street;
@Column(length = 50)
private String city;
@Column(length = 50)
private String state;
@Column(name = "zip_code", length = 10)
private String zipCode;
@Column(length = 50)
private String country;
// 构造函数
public Address() {}
public Address(String street, String city, String state, String zipCode, String country) {
this.street = street;
this.city = city;
this.state = state;
this.zipCode = zipCode;
this.country = country;
}
// Getter和Setter省略
}
// 使用嵌入式对象的实体
@Entity
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false, unique = true)
private String email;
// 嵌入地址对象
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "street", column = @Column(name = "billing_street")),
@AttributeOverride(name = "city", column = @Column(name = "billing_city")),
@AttributeOverride(name = "state", column = @Column(name = "billing_state")),
@AttributeOverride(name = "zipCode", column = @Column(name = "billing_zip")),
@AttributeOverride(name = "country", column = @Column(name = "billing_country"))
})
private Address billingAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "street", column = @Column(name = "shipping_street")),
@AttributeOverride(name = "city", column = @Column(name = "shipping_city")),
@AttributeOverride(name = "state", column = @Column(name = "shipping_state")),
@AttributeOverride(name = "zipCode", column = @Column(name = "shipping_zip")),
@AttributeOverride(name = "country", column = @Column(name = "shipping_country"))
})
private Address shippingAddress;
// Getter和Setter省略
}
// Repository使用示例
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
Optional<Employee> findByEmployeeNumber(String employeeNumber);
List<Employee> findByDepartment(String department);
List<Employee> findByActiveTrue();
List<Employee> findByHireDateBetween(LocalDate start, LocalDate end);
}
@Repository
public interface TaskRepository extends JpaRepository<Task, Long> {
List<Task> findByStatus(TaskStatus status);
List<Task> findByPriority(TaskPriority priority);
List<Task> findByDueDateBefore(LocalDate date);
}
@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
Optional<Customer> findByEmail(String email);
}
// 服务层使用
@Singleton
public class EmployeeService {
private final EmployeeRepository employeeRepository;
public EmployeeService(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
@Transactional
public Employee createEmployee(String empNumber, String firstName,
String lastName, String email) {
System.out.println("创建员工: " + empNumber);
Employee employee = new Employee(empNumber, firstName, lastName, email);
employee.setDepartment("Engineering");
employee.setPosition("Software Engineer");
employee.setSalary(new BigDecimal("80000.00"));
Employee saved = employeeRepository.save(employee);
System.out.println("员工已创建: " + saved.getId());
return saved;
}
public List<Employee> findByDepartment(String department) {
System.out.println("查询部门员工: " + department);
return employeeRepository.findByDepartment(department);
}
}
---
b.关系映射
a.一对一、一对多、多对多
使用@OneToOne、@OneToMany、@ManyToOne、@ManyToMany注解定义实体间关系。支持级联操作、懒加载、fetch策略等配置,实现复杂的对象关系映射。
b.关系映射示例
---
// === 一对一关系 ===
// 用户实体
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String email;
// 一对一关系 - 用户详情
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private UserProfile profile;
// 构造函数
public User() {}
public User(String username, String email) {
this.username = username;
this.email = email;
}
// Getter和Setter省略
}
// 用户详情实体
@Entity
@Table(name = "user_profiles")
public class UserProfile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", unique = true, nullable = false)
private User user;
@Column(name = "full_name")
private String fullName;
@Column(name = "phone_number")
private String phoneNumber;
@Column
private LocalDate birthDate;
@Column(columnDefinition = "TEXT")
private String bio;
// Getter和Setter省略
}
// === 一对多 / 多对一关系 ===
// 部门实体
@Entity
@Table(name = "departments")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String name;
@Column
private String description;
// 一对多关系 - 部门有多个员工
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Employee> employees = new ArrayList<>();
// 便捷方法
public void addEmployee(Employee employee) {
employees.add(employee);
employee.setDepartment(this);
}
public void removeEmployee(Employee employee) {
employees.remove(employee);
employee.setDepartment(null);
}
// Getter和Setter省略
}
// 员工实体(扩展)
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
// 多对一关系 - 员工属于一个部门
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
// Getter和Setter省略
}
// === 多对多关系 ===
// 学生实体
@Entity
@Table(name = "students")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(unique = true)
private String studentNumber;
// 多对多关系 - 学生选修多门课程
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "student_courses",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
// 便捷方法
public void enrollCourse(Course course) {
courses.add(course);
course.getStudents().add(this);
}
public void dropCourse(Course course) {
courses.remove(course);
course.getStudents().remove(this);
}
// Getter和Setter省略
}
// 课程实体
@Entity
@Table(name = "courses")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(unique = true)
private String courseCode;
@Column
private Integer credits;
// 多对多关系 - 课程有多个学生
@ManyToMany(mappedBy = "courses", fetch = FetchType.LAZY)
private Set<Student> students = new HashSet<>();
// Getter和Setter省略
}
// === 带属性的多对多关系 ===
// 订单实体
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "order_number")
private String orderNumber;
// 一对多关系 - 订单有多个订单项
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> orderItems = new ArrayList<>();
// 便捷方法
public void addOrderItem(Product product, Integer quantity, BigDecimal price) {
OrderItem orderItem = new OrderItem(this, product, quantity, price);
orderItems.add(orderItem);
}
// Getter和Setter省略
}
// 产品实体
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(precision = 10, scale = 2)
private BigDecimal price;
// 一对多关系 - 产品在多个订单项中
@OneToMany(mappedBy = "product")
private List<OrderItem> orderItems = new ArrayList<>();
// Getter和Setter省略
}
// 订单项实体 - 关联实体
@Entity
@Table(name = "order_items")
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 多对一关系 - 订单
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id", nullable = false)
private Order order;
// 多对一关系 - 产品
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id", nullable = false)
private Product product;
@Column(nullable = false)
private Integer quantity;
@Column(precision = 10, scale = 2, nullable = false)
private BigDecimal price;
// 构造函数
public OrderItem() {}
public OrderItem(Order order, Product product, Integer quantity, BigDecimal price) {
this.order = order;
this.product = product;
this.quantity = quantity;
this.price = price;
}
// Getter和Setter省略
}
// Repository
@Repository
public interface DepartmentRepository extends JpaRepository<Department, Long> {
Optional<Department> findByName(String name);
@Query("SELECT d FROM Department d LEFT JOIN FETCH d.employees WHERE d.id = :id")
Optional<Department> findByIdWithEmployees(@Param("id") Long id);
}
@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
Optional<Student> findByStudentNumber(String studentNumber);
@Query("SELECT s FROM Student s LEFT JOIN FETCH s.courses WHERE s.id = :id")
Optional<Student> findByIdWithCourses(@Param("id") Long id);
}
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("SELECT o FROM Order o LEFT JOIN FETCH o.orderItems WHERE o.id = :id")
Optional<Order> findByIdWithItems(@Param("id") Long id);
}
// 服务层使用
@Singleton
public class RelationshipService {
private final DepartmentRepository departmentRepository;
private final StudentRepository studentRepository;
private final OrderRepository orderRepository;
private final ProductRepository productRepository;
public RelationshipService(DepartmentRepository departmentRepository,
StudentRepository studentRepository,
OrderRepository orderRepository,
ProductRepository productRepository) {
this.departmentRepository = departmentRepository;
this.studentRepository = studentRepository;
this.orderRepository = orderRepository;
this.productRepository = productRepository;
}
// 一对多示例
@Transactional
public void demonstrateOneToMany() {
System.out.println("\n=== 一对多关系演示 ===");
// 创建部门
Department department = new Department();
department.setName("Engineering");
department.setDescription("Engineering Department");
// 创建员工
Employee emp1 = new Employee();
emp1.setName("John Doe");
Employee emp2 = new Employee();
emp2.setName("Jane Smith");
// 建立关系
department.addEmployee(emp1);
department.addEmployee(emp2);
// 保存(级联保存员工)
departmentRepository.save(department);
System.out.println("部门及员工已保存");
}
// 多对多示例
@Transactional
public void demonstrateManyToMany() {
System.out.println("\n=== 多对多关系演示 ===");
// 创建学生
Student student = new Student();
student.setName("Alice Johnson");
student.setStudentNumber("S001");
// 创建课程
Course course1 = new Course();
course1.setName("Java Programming");
course1.setCourseCode("CS101");
Course course2 = new Course();
course2.setName("Database Systems");
course2.setCourseCode("CS201");
// 学生选课
student.enrollCourse(course1);
student.enrollCourse(course2);
// 保存
studentRepository.save(student);
System.out.println("学生及选课已保存");
}
// 带属性的多对多示例
@Transactional
public void demonstrateOrderItems() {
System.out.println("\n=== 订单项关系演示 ===");
// 创建订单
Order order = new Order();
order.setOrderNumber("ORD-001");
// 获取产品
Product product1 = productRepository.findById(1L).orElseThrow();
Product product2 = productRepository.findById(2L).orElseThrow();
// 添加订单项
order.addOrderItem(product1, 2, product1.getPrice());
order.addOrderItem(product2, 1, product2.getPrice());
// 保存
orderRepository.save(order);
System.out.println("订单及订单项已保存");
}
}
---
02.EntityManager
a.实体管理
a.持久化上下文
EntityManager管理实体的生命周期,包括托管、游离、删除状态。提供persist、merge、remove、find等方法操作实体,管理实体与数据库的同步。
b.EntityManager使用示例
---
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.*;
@Singleton
public class EntityManagerService {
private final EntityManager entityManager;
public EntityManagerService(EntityManager entityManager) {
this.entityManager = entityManager;
System.out.println("EntityManagerService初始化");
}
// 持久化新实体
@Transactional
public Employee persistEmployee(Employee employee) {
System.out.println("持久化员工: " + employee.getName());
// persist将新实体添加到持久化上下文
entityManager.persist(employee);
System.out.println(" 员工已添加到持久化上下文");
// flush将更改同步到数据库
entityManager.flush();
System.out.println(" 更改已刷新到数据库, ID: " + employee.getId());
return employee;
}
// 查找实体
@Transactional(readOnly = true)
public Employee findEmployee(Long id) {
System.out.println("查找员工: " + id);
// find立即从数据库加载
Employee employee = entityManager.find(Employee.class, id);
if (employee != null) {
System.out.println(" 找到员工: " + employee.getName());
System.out.println(" 实体状态: 托管(Managed)");
} else {
System.out.println(" 员工不存在");
}
return employee;
}
// 获取实体引用
@Transactional(readOnly = true)
public Employee getEmployeeReference(Long id) {
System.out.println("获取员工引用: " + id);
// getReference返回代理,不立即访问数据库
Employee employee = entityManager.getReference(Employee.class, id);
System.out.println(" 获得代理对象");
// 访问属性时才真正加载
System.out.println(" 员工姓名: " + employee.getName());
return employee;
}
// 合并游离实体
@Transactional
public Employee mergeEmployee(Employee detachedEmployee) {
System.out.println("合并游离实体: " + detachedEmployee.getId());
// merge将游离实体的状态合并到托管实体
Employee managedEmployee = entityManager.merge(detachedEmployee);
System.out.println(" 实体已合并,状态: 托管(Managed)");
return managedEmployee;
}
// 刷新实体
@Transactional
public Employee refreshEmployee(Long id) {
System.out.println("刷新实体: " + id);
Employee employee = entityManager.find(Employee.class, id);
if (employee != null) {
// refresh从数据库重新加载实体状态
entityManager.refresh(employee);
System.out.println(" 实体已从数据库刷新");
}
return employee;
}
// 分离实体
@Transactional
public Employee detachEmployee(Long id) {
System.out.println("分离实体: " + id);
Employee employee = entityManager.find(Employee.class, id);
if (employee != null) {
// detach将实体从持久化上下文移除
entityManager.detach(employee);
System.out.println(" 实体已分离,状态: 游离(Detached)");
// 对游离实体的修改不会同步到数据库
employee.setName("Modified Name");
System.out.println(" 修改游离实体(不会保存到数据库)");
}
return employee;
}
// 删除实体
@Transactional
public void removeEmployee(Long id) {
System.out.println("删除实体: " + id);
Employee employee = entityManager.find(Employee.class, id);
if (employee != null) {
// remove标记实体为删除状态
entityManager.remove(employee);
System.out.println(" 实体已标记删除");
// flush执行实际的DELETE语句
entityManager.flush();
System.out.println(" DELETE语句已执行");
}
}
// 检查实体是否被管理
@Transactional
public void checkEntityState(Long id) {
System.out.println("\n=== 检查实体状态 ===");
Employee employee = entityManager.find(Employee.class, id);
System.out.println("1. find后: " + (entityManager.contains(employee) ? "托管" : "游离"));
entityManager.detach(employee);
System.out.println("2. detach后: " + (entityManager.contains(employee) ? "托管" : "游离"));
Employee merged = entityManager.merge(employee);
System.out.println("3. merge后: " + (entityManager.contains(merged) ? "托管" : "游离"));
}
// 清空持久化上下文
@Transactional
public void clearPersistenceContext() {
System.out.println("清空持久化上下文");
// clear分离所有托管实体
entityManager.clear();
System.out.println(" 所有实体已分离");
}
// 手动刷新
@Transactional
public void manualFlush() {
System.out.println("手动刷新演示");
Employee employee = new Employee();
employee.setName("Test Employee");
entityManager.persist(employee);
System.out.println(" 实体已persist (未刷新到数据库)");
// 手动刷新
entityManager.flush();
System.out.println(" 已刷新到数据库, ID: " + employee.getId());
}
// 实体生命周期演示
@Transactional
public void demonstrateEntityLifecycle() {
System.out.println("\n=== 实体生命周期演示 ===");
// 1. 新建(New)状态
Employee employee = new Employee();
employee.setName("Lifecycle Demo");
System.out.println("1. 新建状态: contains=" + entityManager.contains(employee));
// 2. 托管(Managed)状态
entityManager.persist(employee);
System.out.println("2. persist后: contains=" + entityManager.contains(employee));
// 3. 修改托管实体
employee.setName("Modified Name");
System.out.println("3. 修改托管实体(自动同步)");
// 4. 刷新到数据库
entityManager.flush();
System.out.println("4. flush后: ID=" + employee.getId());
// 5. 分离(Detached)状态
entityManager.detach(employee);
System.out.println("5. detach后: contains=" + entityManager.contains(employee));
// 6. 重新托管
Employee managed = entityManager.merge(employee);
System.out.println("6. merge后: contains=" + entityManager.contains(managed));
// 7. 删除(Removed)状态
entityManager.remove(managed);
System.out.println("7. remove后: 标记为删除");
}
}
---
b.JPQL查询
a.对象查询语言
JPQL是面向对象的查询语言,使用实体和属性而非表和列。支持SELECT、UPDATE、DELETE操作,提供丰富的查询功能如聚合、分组、排序、子查询等。
b.JPQL查询示例
---
@Singleton
public class JPQLQueryService {
private final EntityManager entityManager;
public JPQLQueryService(EntityManager entityManager) {
this.entityManager = entityManager;
}
// 基础查询
@Transactional(readOnly = true)
public List<Employee> findAllEmployees() {
System.out.println("JPQL: 查询所有员工");
String jpql = "SELECT e FROM Employee e";
TypedQuery<Employee> query = entityManager.createQuery(jpql, Employee.class);
List<Employee> results = query.getResultList();
System.out.println(" 找到 " + results.size() + " 个员工");
return results;
}
// 条件查询
@Transactional(readOnly = true)
public List<Employee> findEmployeesByDepartment(String department) {
System.out.println("JPQL: 按部门查询");
String jpql = "SELECT e FROM Employee e WHERE e.department = :dept";
TypedQuery<Employee> query = entityManager.createQuery(jpql, Employee.class);
query.setParameter("dept", department);
return query.getResultList();
}
// 多条件查询
@Transactional(readOnly = true)
public List<Employee> findEmployeesWithCriteria(String department, BigDecimal minSalary) {
System.out.println("JPQL: 多条件查询");
String jpql = "SELECT e FROM Employee e " +
"WHERE e.department = :dept AND e.salary >= :minSalary " +
"ORDER BY e.salary DESC";
TypedQuery<Employee> query = entityManager.createQuery(jpql, Employee.class);
query.setParameter("dept", department);
query.setParameter("minSalary", minSalary);
return query.getResultList();
}
// 聚合查询
@Transactional(readOnly = true)
public Map<String, Object> getEmployeeStatistics() {
System.out.println("JPQL: 聚合查询");
// 统计员工总数
String countJpql = "SELECT COUNT(e) FROM Employee e WHERE e.active = true";
Long count = entityManager.createQuery(countJpql, Long.class).getSingleResult();
// 计算平均工资
String avgJpql = "SELECT AVG(e.salary) FROM Employee e WHERE e.active = true";
Double avgSalary = entityManager.createQuery(avgJpql, Double.class).getSingleResult();
// 最高工资
String maxJpql = "SELECT MAX(e.salary) FROM Employee e";
BigDecimal maxSalary = entityManager.createQuery(maxJpql, BigDecimal.class).getSingleResult();
// 最低工资
String minJpql = "SELECT MIN(e.salary) FROM Employee e WHERE e.active = true";
BigDecimal minSalary = entityManager.createQuery(minJpql, BigDecimal.class).getSingleResult();
Map<String, Object> stats = new HashMap<>();
stats.put("totalEmployees", count);
stats.put("averageSalary", avgSalary);
stats.put("maxSalary", maxSalary);
stats.put("minSalary", minSalary);
System.out.println(" 统计结果: " + stats);
return stats;
}
// 分组查询
@Transactional(readOnly = true)
public List<Object[]> getEmployeeCountByDepartment() {
System.out.println("JPQL: 分组查询");
String jpql = "SELECT e.department, COUNT(e), AVG(e.salary) " +
"FROM Employee e " +
"WHERE e.active = true " +
"GROUP BY e.department " +
"HAVING COUNT(e) > 0 " +
"ORDER BY COUNT(e) DESC";
List<Object[]> results = entityManager.createQuery(jpql).getResultList();
System.out.println(" 部门统计:");
for (Object[] row : results) {
System.out.println(" " + row[0] + ": " + row[1] + " 人, 平均工资: " + row[2]);
}
return results;
}
// 连接查询
@Transactional(readOnly = true)
public List<Employee> findEmployeesWithDepartment() {
System.out.println("JPQL: 连接查询");
String jpql = "SELECT e FROM Employee e " +
"JOIN FETCH e.department d " +
"WHERE d.name = :deptName";
TypedQuery<Employee> query = entityManager.createQuery(jpql, Employee.class);
query.setParameter("deptName", "Engineering");
return query.getResultList();
}
// 子查询
@Transactional(readOnly = true)
public List<Employee> findHighEarners() {
System.out.println("JPQL: 子查询");
String jpql = "SELECT e FROM Employee e " +
"WHERE e.salary > (SELECT AVG(e2.salary) FROM Employee e2)";
TypedQuery<Employee> query = entityManager.createQuery(jpql, Employee.class);
return query.getResultList();
}
// 更新查询
@Transactional
public int updateEmployeeSalary(String department, BigDecimal increment) {
System.out.println("JPQL: 批量更新");
String jpql = "UPDATE Employee e " +
"SET e.salary = e.salary + :increment " +
"WHERE e.department = :dept";
int updated = entityManager.createQuery(jpql)
.setParameter("increment", increment)
.setParameter("dept", department)
.executeUpdate();
System.out.println(" 更新了 " + updated + " 个员工");
return updated;
}
// 删除查询
@Transactional
public int deleteInactiveEmployees(LocalDate before) {
System.out.println("JPQL: 批量删除");
String jpql = "DELETE FROM Employee e " +
"WHERE e.active = false AND e.hireDate < :date";
int deleted = entityManager.createQuery(jpql)
.setParameter("date", before)
.executeUpdate();
System.out.println(" 删除了 " + deleted + " 个员工");
return deleted;
}
// 分页查询
@Transactional(readOnly = true)
public List<Employee> findEmployeesWithPagination(int page, int size) {
System.out.println("JPQL: 分页查询");
String jpql = "SELECT e FROM Employee e ORDER BY e.id";
TypedQuery<Employee> query = entityManager.createQuery(jpql, Employee.class);
query.setFirstResult(page * size);
query.setMaxResults(size);
List<Employee> results = query.getResultList();
System.out.println(" 第 " + page + " 页, 共 " + results.size() + " 条");
return results;
}
// 命名查询
@Transactional(readOnly = true)
public List<Employee> useNamedQuery(String department) {
System.out.println("JPQL: 使用命名查询");
TypedQuery<Employee> query = entityManager.createNamedQuery(
"Employee.findByDepartment",
Employee.class
);
query.setParameter("dept", department);
return query.getResultList();
}
}
// 在Employee实体中定义命名查询
@Entity
@Table(name = "employees")
@NamedQueries({
@NamedQuery(
name = "Employee.findByDepartment",
query = "SELECT e FROM Employee e WHERE e.department = :dept"
),
@NamedQuery(
name = "Employee.findActive",
query = "SELECT e FROM Employee e WHERE e.active = true ORDER BY e.name"
)
})
public class Employee {
// ... 实体定义
}
---
03.Criteria API
a.类型安全查询
a.Criteria构建器
Criteria API提供类型安全的查询构建方式,通过Java代码而非字符串构建查询。编译时检查类型错误,支持动态查询条件组合,适合复杂查询场景。
b.Criteria查询示例
---
@Singleton
public class CriteriaQueryService {
private final EntityManager entityManager;
public CriteriaQueryService(EntityManager entityManager) {
this.entityManager = entityManager;
}
// 基础Criteria查询
@Transactional(readOnly = true)
public List<Employee> findAllWithCriteria() {
System.out.println("Criteria: 查询所有员工");
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
cq.select(root);
TypedQuery<Employee> query = entityManager.createQuery(cq);
return query.getResultList();
}
// 条件查询
@Transactional(readOnly = true)
public List<Employee> findByDepartmentWithCriteria(String department) {
System.out.println("Criteria: 按部门查询");
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
// 构建WHERE条件
Predicate predicate = cb.equal(root.get("department"), department);
cq.where(predicate);
return entityManager.createQuery(cq).getResultList();
}
// 多条件组合
@Transactional(readOnly = true)
public List<Employee> findWithMultipleConditions(
String department, BigDecimal minSalary, Boolean active) {
System.out.println("Criteria: 多条件查询");
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
List<Predicate> predicates = new ArrayList<>();
// 添加部门条件
if (department != null) {
predicates.add(cb.equal(root.get("department"), department));
}
// 添加工资条件
if (minSalary != null) {
predicates.add(cb.greaterThanOrEqualTo(root.get("salary"), minSalary));
}
// 添加状态条件
if (active != null) {
predicates.add(cb.equal(root.get("active"), active));
}
// 组合所有条件(AND)
cq.where(predicates.toArray(new Predicate[0]));
// 排序
cq.orderBy(cb.desc(root.get("salary")));
return entityManager.createQuery(cq).getResultList();
}
// OR条件
@Transactional(readOnly = true)
public List<Employee> findWithOrConditions(String dept1, String dept2) {
System.out.println("Criteria: OR条件查询");
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
// OR条件
Predicate or = cb.or(
cb.equal(root.get("department"), dept1),
cb.equal(root.get("department"), dept2)
);
cq.where(or);
return entityManager.createQuery(cq).getResultList();
}
// LIKE查询
@Transactional(readOnly = true)
public List<Employee> searchByName(String keyword) {
System.out.println("Criteria: LIKE查询");
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
// LIKE条件
Predicate like = cb.like(
cb.lower(root.get("name")),
"%" + keyword.toLowerCase() + "%"
);
cq.where(like);
return entityManager.createQuery(cq).getResultList();
}
// 范围查询
@Transactional(readOnly = true)
public List<Employee> findBySalaryRange(BigDecimal min, BigDecimal max) {
System.out.println("Criteria: 范围查询");
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
// BETWEEN条件
Predicate between = cb.between(root.get("salary"), min, max);
cq.where(between);
return entityManager.createQuery(cq).getResultList();
}
// 聚合查询
@Transactional(readOnly = true)
public Long countEmployeesByDepartment(String department) {
System.out.println("Criteria: 统计查询");
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
Root<Employee> root = cq.from(Employee.class);
// COUNT聚合
cq.select(cb.count(root));
cq.where(cb.equal(root.get("department"), department));
return entityManager.createQuery(cq).getSingleResult();
}
// 分组查询
@Transactional(readOnly = true)
public List<Object[]> getAverageSalaryByDepartment() {
System.out.println("Criteria: 分组查询");
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> cq = cb.createQuery(Object[].class);
Root<Employee> root = cq.from(Employee.class);
// 选择部门和平均工资
cq.multiselect(
root.get("department"),
cb.avg(root.get("salary"))
);
// 按部门分组
cq.groupBy(root.get("department"));
// HAVING条件
cq.having(cb.gt(cb.count(root), 0));
return entityManager.createQuery(cq).getResultList();
}
// 连接查询
@Transactional(readOnly = true)
public List<Employee> findEmployeesWithDepartmentCriteria() {
System.out.println("Criteria: 连接查询");
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
// JOIN FETCH
root.fetch("department", JoinType.LEFT);
cq.select(root);
return entityManager.createQuery(cq).getResultList();
}
// 排序
@Transactional(readOnly = true)
public List<Employee> findWithSort(boolean ascending) {
System.out.println("Criteria: 排序查询");
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
cq.select(root);
// 动态排序
if (ascending) {
cq.orderBy(cb.asc(root.get("salary")));
} else {
cq.orderBy(cb.desc(root.get("salary")));
}
return entityManager.createQuery(cq).getResultList();
}
// 分页
@Transactional(readOnly = true)
public List<Employee> findWithPagination(int page, int size) {
System.out.println("Criteria: 分页查询");
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
cq.select(root);
cq.orderBy(cb.asc(root.get("id")));
TypedQuery<Employee> query = entityManager.createQuery(cq);
query.setFirstResult(page * size);
query.setMaxResults(size);
return query.getResultList();
}
// 动态查询构建器
@Transactional(readOnly = true)
public List<Employee> dynamicSearch(EmployeeSearchCriteria criteria) {
System.out.println("Criteria: 动态搜索");
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
List<Predicate> predicates = new ArrayList<>();
// 姓名搜索
if (criteria.getName() != null && !criteria.getName().isEmpty()) {
predicates.add(cb.like(
cb.lower(root.get("name")),
"%" + criteria.getName().toLowerCase() + "%"
));
}
// 部门筛选
if (criteria.getDepartment() != null) {
predicates.add(cb.equal(root.get("department"), criteria.getDepartment()));
}
// 工资范围
if (criteria.getMinSalary() != null) {
predicates.add(cb.greaterThanOrEqualTo(
root.get("salary"),
criteria.getMinSalary()
));
}
if (criteria.getMaxSalary() != null) {
predicates.add(cb.lessThanOrEqualTo(
root.get("salary"),
criteria.getMaxSalary()
));
}
// 入职日期范围
if (criteria.getHireDateFrom() != null) {
predicates.add(cb.greaterThanOrEqualTo(
root.get("hireDate"),
criteria.getHireDateFrom()
));
}
if (criteria.getHireDateTo() != null) {
predicates.add(cb.lessThanOrEqualTo(
root.get("hireDate"),
criteria.getHireDateTo()
));
}
// 状态筛选
if (criteria.getActive() != null) {
predicates.add(cb.equal(root.get("active"), criteria.getActive()));
}
// 组合所有条件
cq.where(predicates.toArray(new Predicate[0]));
// 排序
if (criteria.getSortBy() != null) {
if (criteria.isAscending()) {
cq.orderBy(cb.asc(root.get(criteria.getSortBy())));
} else {
cq.orderBy(cb.desc(root.get(criteria.getSortBy())));
}
}
TypedQuery<Employee> query = entityManager.createQuery(cq);
// 分页
if (criteria.getPage() != null && criteria.getSize() != null) {
query.setFirstResult(criteria.getPage() * criteria.getSize());
query.setMaxResults(criteria.getSize());
}
return query.getResultList();
}
}
// 搜索条件对象
public class EmployeeSearchCriteria {
private String name;
private String department;
private BigDecimal minSalary;
private BigDecimal maxSalary;
private LocalDate hireDateFrom;
private LocalDate hireDateTo;
private Boolean active;
private String sortBy;
private boolean ascending = true;
private Integer page;
private Integer size;
// Getter和Setter省略
}
---
b.动态查询构建
a.条件组合器
使用Specification模式或Predicate组合实现灵活的动态查询构建。根据运行时条件动态添加查询子句,避免硬编码大量查询方法。
b.Specification模式示例
---
import io.micronaut.data.jpa.repository.criteria.Specification;
// Specification工具类
public class EmployeeSpecifications {
public static Specification<Employee> hasName(String name) {
return (root, query, cb) -> {
if (name == null || name.isEmpty()) {
return cb.conjunction();
}
return cb.like(cb.lower(root.get("name")),
"%" + name.toLowerCase() + "%");
};
}
public static Specification<Employee> hasDepartment(String department) {
return (root, query, cb) -> {
if (department == null) {
return cb.conjunction();
}
return cb.equal(root.get("department"), department);
};
}
public static Specification<Employee> salaryBetween(BigDecimal min, BigDecimal max) {
return (root, query, cb) -> {
if (min == null && max == null) {
return cb.conjunction();
}
if (min != null && max != null) {
return cb.between(root.get("salary"), min, max);
}
if (min != null) {
return cb.greaterThanOrEqualTo(root.get("salary"), min);
}
return cb.lessThanOrEqualTo(root.get("salary"), max);
};
}
public static Specification<Employee> isActive(Boolean active) {
return (root, query, cb) -> {
if (active == null) {
return cb.conjunction();
}
return cb.equal(root.get("active"), active);
};
}
public static Specification<Employee> hiredAfter(LocalDate date) {
return (root, query, cb) -> {
if (date == null) {
return cb.conjunction();
}
return cb.greaterThanOrEqualTo(root.get("hireDate"), date);
};
}
}
// 使用Specification
@Singleton
public class EmployeeSpecificationService {
private final EmployeeRepository employeeRepository;
public EmployeeSpecificationService(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
public List<Employee> searchEmployees(String name, String department,
BigDecimal minSalary, BigDecimal maxSalary) {
System.out.println("Specification搜索");
// 组合多个Specification
Specification<Employee> spec = Specification
.where(EmployeeSpecifications.hasName(name))
.and(EmployeeSpecifications.hasDepartment(department))
.and(EmployeeSpecifications.salaryBetween(minSalary, maxSalary))
.and(EmployeeSpecifications.isActive(true));
return employeeRepository.findAll(spec);
}
public List<Employee> complexSearch(EmployeeSearchCriteria criteria) {
System.out.println("复杂Specification搜索");
Specification<Employee> spec = Specification.where(null);
if (criteria.getName() != null) {
spec = spec.and(EmployeeSpecifications.hasName(criteria.getName()));
}
if (criteria.getDepartment() != null) {
spec = spec.and(EmployeeSpecifications.hasDepartment(criteria.getDepartment()));
}
if (criteria.getMinSalary() != null || criteria.getMaxSalary() != null) {
spec = spec.and(EmployeeSpecifications.salaryBetween(
criteria.getMinSalary(),
criteria.getMaxSalary()
));
}
if (criteria.getHireDateFrom() != null) {
spec = spec.and(EmployeeSpecifications.hiredAfter(criteria.getHireDateFrom()));
}
if (criteria.getActive() != null) {
spec = spec.and(EmployeeSpecifications.isActive(criteria.getActive()));
}
return employeeRepository.findAll(spec);
}
}
---
5.4 JDBC支持
01.JDBC配置
a.数据源配置
a.连接池设置
配置JDBC数据源和连接池,包括URL、驱动、用户名、密码、连接池大小等参数。Micronaut Data JDBC提供轻量级的数据访问方式,无需JPA的额外开销。
b.JDBC配置示例
---
// build.gradle - JDBC依赖
dependencies {
annotationProcessor("io.micronaut.data:micronaut-data-processor")
implementation("io.micronaut.data:micronaut-data-jdbc")
implementation("io.micronaut.sql:micronaut-jdbc-hikari")
runtimeOnly("mysql:mysql-connector-java")
}
// application.yml - JDBC配置
datasources:
default:
url: jdbc:mysql://localhost:3306/mydb
driverClassName: com.mysql.cj.jdbc.Driver
username: root
password: ${DB_PASSWORD}
dialect: MYSQL
# HikariCP连接池配置
maximum-pool-size: 10
minimum-idle: 2
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
// JDBC实体
import io.micronaut.data.annotation.*;
import io.micronaut.data.model.naming.NamingStrategies;
@MappedEntity(value = "products", namingStrategy = NamingStrategies.UnderScoreSeparated.class)
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
private BigDecimal price;
private Integer stock;
private String category;
@DateCreated
private LocalDateTime createdAt;
@DateUpdated
private LocalDateTime updatedAt;
// 构造函数
public Product() {}
public Product(String name, BigDecimal price, Integer stock) {
this.name = name;
this.price = price;
this.stock = stock;
}
// Getter和Setter省略
}
// JDBC Repository
import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.query.builder.sql.Dialect;
@JdbcRepository(dialect = Dialect.MYSQL)
public interface ProductRepository extends CrudRepository<Product, Long> {
List<Product> findByCategory(String category);
List<Product> findByPriceBetween(BigDecimal min, BigDecimal max);
Optional<Product> findByName(String name);
@Query("SELECT * FROM products WHERE stock < :threshold")
List<Product> findLowStock(int threshold);
}
// JDBC服务
@Singleton
public class ProductJdbcService {
private final ProductRepository productRepository;
public ProductJdbcService(ProductRepository productRepository) {
this.productRepository = productRepository;
System.out.println("ProductJdbcService初始化");
}
@Transactional
public Product createProduct(String name, BigDecimal price, Integer stock) {
System.out.println("JDBC创建产品: " + name);
Product product = new Product(name, price, stock);
product.setCategory("Electronics");
Product saved = productRepository.save(product);
System.out.println(" 产品已保存, ID: " + saved.getId());
return saved;
}
public List<Product> findByCategory(String category) {
System.out.println("JDBC查询分类: " + category);
return productRepository.findByCategory(category);
}
public List<Product> findLowStockProducts() {
System.out.println("JDBC查询低库存产品");
return productRepository.findLowStock(10);
}
}
---
b.原生SQL
a.@Query原生SQL
使用@Query注解编写原生SQL语句,直接操作数据库。支持命名参数、位置参数,适合需要数据库特定功能或优化查询的场景。
b.原生SQL示例
---
@JdbcRepository(dialect = Dialect.MYSQL)
public interface NativeSQLRepository extends CrudRepository<Product, Long> {
// 原生SQL查询
@Query(value = "SELECT * FROM products WHERE price > :price ORDER BY price DESC",
nativeQuery = true)
List<Product> findExpensiveProducts(BigDecimal price);
// 原生SQL聚合
@Query(value = "SELECT category, COUNT(*), AVG(price) FROM products GROUP BY category",
nativeQuery = true)
List<Map<String, Object>> getCategoryStatistics();
// 原生SQL更新
@Query(value = "UPDATE products SET stock = stock + :amount WHERE id = :id",
nativeQuery = true)
int increaseStock(Long id, int amount);
// 原生SQL删除
@Query(value = "DELETE FROM products WHERE stock = 0 AND created_at < :date",
nativeQuery = true)
int deleteZeroStockProducts(LocalDateTime date);
// MySQL特定功能 - 全文搜索
@Query(value = "SELECT * FROM products WHERE MATCH(name, description) " +
"AGAINST(:keyword IN NATURAL LANGUAGE MODE)",
nativeQuery = true)
List<Product> fullTextSearch(String keyword);
}
@Singleton
public class NativeSQLService {
private final NativeSQLRepository repository;
public NativeSQLService(NativeSQLRepository repository) {
this.repository = repository;
}
public void demonstrateNativeSQL() {
System.out.println("=== 原生SQL演示 ===");
// 查询贵重产品
List<Product> expensive = repository.findExpensiveProducts(
new BigDecimal("1000")
);
System.out.println("贵重产品数: " + expensive.size());
// 分类统计
List<Map<String, Object>> stats = repository.getCategoryStatistics();
stats.forEach(stat ->
System.out.println(" " + stat.get("category") + ": " +
stat.get("COUNT(*)") + " 个, 平均价格: " +
stat.get("AVG(price)"))
);
// 增加库存
int updated = repository.increaseStock(1L, 50);
System.out.println("更新库存: " + updated + " 条记录");
}
}
---
02.批量操作
a.批量插入
a.saveAll批量保存
使用saveAll方法批量保存实体,JDBC实现通过批处理优化多条INSERT语句的执行,提升大量数据插入的性能。
b.批量插入示例
---
@Singleton
public class BatchInsertService {
private final ProductRepository productRepository;
public BatchInsertService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Transactional
public List<Product> batchInsert(int count) {
System.out.println("批量插入: " + count + " 个产品");
List<Product> products = new ArrayList<>();
for (int i = 0; i < count; i++) {
Product product = new Product();
product.setName("Product " + i);
product.setPrice(BigDecimal.valueOf(100 + i));
product.setStock(100);
product.setCategory("Category" + (i % 5));
products.add(product);
}
long start = System.currentTimeMillis();
Iterable<Product> saved = productRepository.saveAll(products);
long duration = System.currentTimeMillis() - start;
List<Product> result = new ArrayList<>();
saved.forEach(result::add);
System.out.println(" 批量插入完成: " + duration + "ms");
System.out.println(" 平均: " + (duration * 1.0 / count) + "ms/条");
return result;
}
@Transactional
public void batchUpdate(List<Long> ids, String newCategory) {
System.out.println("批量更新: " + ids.size() + " 个产品");
List<Product> products = new ArrayList<>();
for (Long id : ids) {
Product product = productRepository.findById(id).orElse(null);
if (product != null) {
product.setCategory(newCategory);
products.add(product);
}
}
productRepository.updateAll(products);
System.out.println(" 批量更新完成");
}
}
---
b.批量更新和删除
a.批量操作优化
使用批量更新和删除方法减少数据库交互次数,通过单条SQL语句处理多条记录,显著提升大批量数据操作的性能。
b.批量操作示例
---
@JdbcRepository(dialect = Dialect.MYSQL)
public interface BatchOperationRepository extends CrudRepository<Product, Long> {
// 批量更新
@Query("UPDATE products SET stock = stock - :amount WHERE id IN (:ids)")
int decreaseStockBatch(List<Long> ids, int amount);
@Query("UPDATE products SET category = :category WHERE id IN (:ids)")
int updateCategoryBatch(List<Long> ids, String category);
// 批量删除
@Query("DELETE FROM products WHERE id IN (:ids)")
int deleteBatch(List<Long> ids);
void deleteAll(Iterable<Product> products);
}
@Singleton
public class BatchOperationService {
private final BatchOperationRepository repository;
public BatchOperationService(BatchOperationRepository repository) {
this.repository = repository;
}
@Transactional
public void batchDecreaseStock(List<Long> productIds, int amount) {
System.out.println("批量减少库存: " + productIds.size() + " 个产品");
int updated = repository.decreaseStockBatch(productIds, amount);
System.out.println(" 更新了 " + updated + " 条记录");
}
@Transactional
public void batchDelete(List<Long> ids) {
System.out.println("批量删除: " + ids.size() + " 个产品");
int deleted = repository.deleteBatch(ids);
System.out.println(" 删除了 " + deleted + " 条记录");
}
}
---
03.事务管理
a.@Transactional
a.声明式事务
使用@Transactional注解声明事务边界,支持事务传播、隔离级别、只读事务等配置。框架自动管理事务的开启、提交、回滚。
b.事务管理示例
---
@Singleton
public class TransactionService {
private final ProductRepository productRepository;
private final OrderRepository orderRepository;
public TransactionService(ProductRepository productRepository,
OrderRepository orderRepository) {
this.productRepository = productRepository;
this.orderRepository = orderRepository;
}
// 基础事务
@Transactional
public void createOrder(Long productId, int quantity) {
System.out.println("创建订单(事务)");
// 减少库存
Product product = productRepository.findById(productId)
.orElseThrow(() -> new IllegalArgumentException("产品不存在"));
if (product.getStock() < quantity) {
throw new IllegalStateException("库存不足");
}
product.setStock(product.getStock() - quantity);
productRepository.update(product);
// 创建订单
Order order = new Order();
order.setProductId(productId);
order.setQuantity(quantity);
order.setTotal(product.getPrice().multiply(BigDecimal.valueOf(quantity)));
orderRepository.save(order);
System.out.println(" 订单创建成功(事务提交)");
}
// 只读事务
@Transactional(readOnly = true)
public List<Product> findAllProducts() {
System.out.println("查询所有产品(只读事务)");
return productRepository.findAll();
}
// 事务回滚
@Transactional
public void failingOperation() {
System.out.println("失败操作(事务回滚)");
Product product = new Product("Test", BigDecimal.TEN, 100);
productRepository.save(product);
// 抛出异常,触发回滚
throw new RuntimeException("模拟异常");
}
// 嵌套事务
@Transactional
public void outerTransaction() {
System.out.println("外层事务");
Product product = new Product("Outer", BigDecimal.TEN, 100);
productRepository.save(product);
try {
innerTransaction();
} catch (Exception e) {
System.out.println(" 内层事务失败,但外层继续");
}
System.out.println(" 外层事务完成");
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerTransaction() {
System.out.println(" 内层事务(独立事务)");
Product product = new Product("Inner", BigDecimal.TEN, 100);
productRepository.save(product);
throw new RuntimeException("内层失败");
}
}
---
b.编程式事务
a.TransactionOperations
使用TransactionOperations接口编程式控制事务,提供更灵活的事务管理方式。适合需要动态控制事务边界或复杂事务逻辑的场景。
b.编程式事务示例
---
import io.micronaut.transaction.TransactionOperations;
import io.micronaut.transaction.TransactionStatus;
@Singleton
public class ProgrammaticTransactionService {
private final TransactionOperations<Connection> transactionManager;
private final ProductRepository productRepository;
public ProgrammaticTransactionService(
TransactionOperations<Connection> transactionManager,
ProductRepository productRepository) {
this.transactionManager = transactionManager;
this.productRepository = productRepository;
}
public void executeInTransaction() {
System.out.println("编程式事务");
transactionManager.executeWrite(status -> {
System.out.println(" 事务已开始");
Product product = new Product("Test", BigDecimal.TEN, 100);
productRepository.save(product);
System.out.println(" 产品已保存");
return product;
});
System.out.println(" 事务已提交");
}
public void conditionalTransaction(boolean shouldCommit) {
System.out.println("条件事务: shouldCommit=" + shouldCommit);
transactionManager.executeWrite(status -> {
Product product = new Product("Conditional", BigDecimal.TEN, 100);
productRepository.save(product);
if (!shouldCommit) {
System.out.println(" 手动回滚");
status.setRollbackOnly();
}
return product;
});
}
public <T> T executeWithRetry(Supplier<T> operation, int maxRetries) {
System.out.println("带重试的事务");
int attempt = 0;
while (attempt < maxRetries) {
try {
return transactionManager.executeWrite(status -> {
System.out.println(" 尝试 " + (attempt + 1));
return operation.get();
});
} catch (Exception e) {
attempt++;
if (attempt >= maxRetries) {
throw e;
}
System.out.println(" 失败,重试...");
}
}
throw new RuntimeException("重试失败");
}
}
---
04.性能优化
a.连接池调优
a.HikariCP配置
调整连接池参数优化数据库连接管理,包括最大连接数、最小空闲连接、连接超时、空闲超时等。合理配置连接池提升应用性能和资源利用率。
b.连接池优化示例
---
// application.yml - HikariCP优化配置
datasources:
default:
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
driverClassName: com.mysql.cj.jdbc.Driver
username: root
password: ${DB_PASSWORD}
# 连接池大小
maximum-pool-size: 20 # 最大连接数
minimum-idle: 5 # 最小空闲连接
# 超时设置
connection-timeout: 30000 # 连接超时(30秒)
idle-timeout: 600000 # 空闲超时(10分钟)
max-lifetime: 1800000 # 连接最大生命周期(30分钟)
# 性能优化
auto-commit: true
connection-test-query: SELECT 1
leak-detection-threshold: 60000 # 连接泄漏检测(60秒)
# 连接池名称
pool-name: HikariPool-Default
// 连接池监控
@Singleton
public class ConnectionPoolMonitor {
private final DataSource dataSource;
public ConnectionPoolMonitor(DataSource dataSource) {
this.dataSource = dataSource;
}
public Map<String, Object> getPoolStatistics() {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikari = (HikariDataSource) dataSource;
HikariPoolMXBean pool = hikari.getHikariPoolMXBean();
Map<String, Object> stats = new HashMap<>();
stats.put("activeConnections", pool.getActiveConnections());
stats.put("idleConnections", pool.getIdleConnections());
stats.put("totalConnections", pool.getTotalConnections());
stats.put("threadsAwaitingConnection", pool.getThreadsAwaitingConnection());
System.out.println("连接池统计:");
System.out.println(" 活跃连接: " + pool.getActiveConnections());
System.out.println(" 空闲连接: " + pool.getIdleConnections());
System.out.println(" 总连接数: " + pool.getTotalConnections());
return stats;
}
return Collections.emptyMap();
}
}
---
b.查询优化
a.索引和查询计划
通过创建索引、优化查询语句、使用EXPLAIN分析查询计划等方式提升查询性能。避免全表扫描,合理使用索引和查询条件。
b.查询优化示例
---
@Singleton
public class QueryOptimizationService {
private final ProductRepository productRepository;
private final EntityManager entityManager;
public QueryOptimizationService(ProductRepository productRepository,
EntityManager entityManager) {
this.productRepository = productRepository;
this.entityManager = entityManager;
}
// 批量查询优化
public List<Product> findProductsBatch(List<Long> ids) {
System.out.println("批量查询优化: " + ids.size() + " 个ID");
// 使用IN查询,避免多次单独查询
String jpql = "SELECT p FROM Product p WHERE p.id IN :ids";
return entityManager.createQuery(jpql, Product.class)
.setParameter("ids", ids)
.getResultList();
}
// 投影查询优化
@Query("SELECT p.id, p.name, p.price FROM Product p WHERE p.category = :category")
public List<Object[]> findProductProjection(String category) {
// 只查询需要的字段,减少数据传输
System.out.println("投影查询: " + category);
return Collections.emptyList();
}
// 分页查询优化
public Page<Product> findWithOptimizedPagination(int page, int size) {
System.out.println("优化分页查询");
// 使用覆盖索引
Sort sort = Sort.of(Sort.Order.asc("id"));
Pageable pageable = Pageable.from(page, size, sort);
return productRepository.findAll(pageable);
}
// 避免N+1查询
@Query("SELECT p FROM Product p LEFT JOIN FETCH p.category WHERE p.id IN :ids")
public List<Product> findWithCategory(List<Long> ids) {
// 使用JOIN FETCH一次性加载关联数据
System.out.println("避免N+1查询");
return Collections.emptyList();
}
}
---
5.5 缓存策略
01.缓存配置
a.@Cacheable注解
a.方法级缓存
使用@Cacheable注解在方法级别启用缓存,减少数据库访问次数。首次调用执行数据库查询并缓存结果,后续调用直接返回缓存数据,显著提升查询性能。
b.缓存配置示例
---
// build.gradle - 缓存依赖
dependencies {
implementation("io.micronaut.cache:micronaut-cache-caffeine")
implementation("io.micronaut.cache:micronaut-cache-redis")
}
// application.yml - 缓存配置
micronaut:
caches:
# Caffeine本地缓存
users:
maximum-size: 1000
expire-after-write: 10m
expire-after-access: 5m
record-stats: true
products:
maximum-size: 5000
expire-after-write: 30m
initial-capacity: 100
# Redis分布式缓存
sessions:
redis:
enabled: true
expire-after-write: 1h
redis:
uri: redis://localhost:6379
caches:
sessions:
expire-after-write: 3600s
// 缓存实体
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String username;
@Column
private String email;
@Column
private LocalDateTime lastModified;
// 构造函数和Getter/Setter省略
}
// 使用@Cacheable的Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
List<User> findByEmail(String email);
}
// 缓存服务
import io.micronaut.cache.annotation.Cacheable;
import io.micronaut.cache.annotation.CacheInvalidate;
import io.micronaut.cache.annotation.CachePut;
@Singleton
public class UserCacheService {
private final UserRepository userRepository;
public UserCacheService(UserRepository userRepository) {
this.userRepository = userRepository;
System.out.println("UserCacheService初始化");
}
// 方法结果缓存
@Cacheable("users")
public Optional<User> findById(Long id) {
System.out.println("从数据库查询用户: " + id);
return userRepository.findById(id);
}
// 使用复合键缓存
@Cacheable(value = "users", parameters = {"username"})
public Optional<User> findByUsername(String username) {
System.out.println("从数据库查询用户名: " + username);
return userRepository.findByUsername(username);
}
// 缓存列表结果
@Cacheable(value = "users", parameters = {"email"})
public List<User> findByEmail(String email) {
System.out.println("从数据库查询邮箱: " + email);
return userRepository.findByEmail(email);
}
// 创建用户(不缓存)
public User createUser(String username, String email) {
System.out.println("创建用户: " + username);
User user = new User();
user.setUsername(username);
user.setEmail(email);
user.setLastModified(LocalDateTime.now());
return userRepository.save(user);
}
// 缓存统计
public void logCacheStatistics() {
System.out.println("\n=== 缓存统计 ===");
System.out.println("缓存有效降低数据库访问次数");
}
}
// 控制器使用缓存
@Controller("/api/cached-users")
public class CachedUserController {
private final UserCacheService userService;
public CachedUserController(UserCacheService userService) {
this.userService = userService;
}
@Get("/{id}")
public Optional<User> get(Long id) {
// 第一次调用查询数据库并缓存
// 后续调用直接从缓存返回
return userService.findById(id);
}
@Get("/username/{username}")
public Optional<User> getByUsername(String username) {
return userService.findByUsername(username);
}
@Get("/email/{email}")
public List<User> getByEmail(String email) {
return userService.findByEmail(email);
}
@Post
public User create(@Body CreateUserRequest request) {
return userService.createUser(
request.getUsername(),
request.getEmail()
);
}
}
// 测试缓存效果
@Singleton
public class CacheTestService {
private final UserCacheService userService;
public CacheTestService(UserCacheService userService) {
this.userService = userService;
}
public void testCachePerformance() {
System.out.println("=== 缓存性能测试 ===\n");
Long userId = 1L;
// 第一次调用 - 查询数据库
System.out.println("第一次调用:");
long start1 = System.currentTimeMillis();
userService.findById(userId);
long duration1 = System.currentTimeMillis() - start1;
System.out.println("耗时: " + duration1 + "ms\n");
// 第二次调用 - 从缓存获取
System.out.println("第二次调用(缓存):");
long start2 = System.currentTimeMillis();
userService.findById(userId);
long duration2 = System.currentTimeMillis() - start2;
System.out.println("耗时: " + duration2 + "ms\n");
// 性能提升
double improvement = ((duration1 - duration2) * 100.0) / duration1;
System.out.println("性能提升: " + String.format("%.2f", stats.hitRate() * 100));
System.out.println(" 加载耗时: " + stats.averageLoadPenalty() + "ns");
System.out.println(" 淘汰次数: " + stats.evictionCount());
}
}
}
}
---
b.分布式缓存
a.Redis集成
使用Redis实现分布式缓存,支持集群部署的多实例间缓存共享。提供缓存发布订阅、缓存失效通知等功能,适合分布式系统场景。
b.Redis缓存示例
---
// Redis缓存配置
// application.yml
redis:
uri: redis://localhost:6379
caches:
users:
enabled: true
expire-after-write: 1800s
charset: UTF-8
value-serializer: io.micronaut.jackson.serialize.JacksonObjectSerializer
sessions:
enabled: true
expire-after-write: 3600s
// Redis缓存服务
@Singleton
public class RedisCacheService {
private final UserRepository userRepository;
public RedisCacheService(UserRepository userRepository) {
this.userRepository = userRepository;
System.out.println("RedisCacheService初始化");
}
// Redis缓存查询
@Cacheable(value = "users", cacheResolver = RedisCache.class)
public Optional<User> findUserById(Long id) {
System.out.println("Redis查询用户: " + id);
return userRepository.findById(id);
}
// Redis缓存更新
@CachePut(value = "users", parameters = {"id"})
public User updateUser(Long id, String email) {
System.out.println("Redis更新用户: " + id);
User user = userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("用户不存在"));
user.setEmail(email);
user.setLastModified(LocalDateTime.now());
return userRepository.update(user);
}
// Redis缓存失效
@CacheInvalidate(value = "users", parameters = {"id"})
public void deleteUser(Long id) {
System.out.println("Redis删除用户: " + id);
userRepository.deleteById(id);
}
}
// Redis发布订阅
import io.lettuce.core.pubsub.RedisPubSubAdapter;
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
@Singleton
public class RedisPubSubService {
private final StatefulRedisPubSubConnection<String, String> connection;
@Inject
public RedisPubSubService(RedisClient redisClient) {
this.connection = redisClient.connectPubSub();
setupSubscription();
}
private void setupSubscription() {
connection.addListener(new RedisPubSubAdapter<String, String>() {
@Override
public void message(String channel, String message) {
System.out.println("收到缓存失效消息: " + channel + " -> " + message);
handleCacheInvalidation(channel, message);
}
});
// 订阅缓存失效通道
connection.sync().subscribe("cache:invalidate");
}
private void handleCacheInvalidation(String channel, String message) {
// 处理缓存失效消息
System.out.println(" 处理缓存失效: " + message);
}
// 发布缓存失效消息
public void publishInvalidation(String cacheKey) {
System.out.println("发布缓存失效: " + cacheKey);
connection.sync().publish("cache:invalidate", cacheKey);
}
}
// Redis集群缓存
@Singleton
public class RedisClusterCacheService {
// 使用一致性哈希分布缓存
@Cacheable(value = "users")
public Optional<User> findUserInCluster(Long id) {
System.out.println("Redis集群查询: " + id);
// Redis Cluster自动处理数据分片
return Optional.empty();
}
// 批量操作Redis缓存
public Map<Long, User> batchGetUsers(List<Long> ids) {
System.out.println("批量查询Redis: " + ids.size() + " 个用户");
Map<Long, User> result = new HashMap<>();
// 使用Pipeline批量查询
// 实际实现需要Redis Pipeline API
return result;
}
}
---
5.6 数据库迁移
01.Flyway集成
a.迁移脚本
a.版本化SQL脚本
Flyway通过版本化SQL脚本管理数据库schema变更,自动追踪已执行的迁移,确保数据库结构一致性。支持可重复脚本、回调钩子等高级功能。
b.Flyway配置示例
---
// build.gradle - Flyway依赖
dependencies {
implementation("io.micronaut.flyway:micronaut-flyway")
runtimeOnly("org.flywaydb:flyway-mysql")
}
// application.yml - Flyway配置
datasources:
default:
url: jdbc:mysql://localhost:3306/mydb
driverClassName: com.mysql.cj.jdbc.Driver
username: root
password: ${DB_PASSWORD}
flyway:
datasources:
default:
enabled: true
# 迁移脚本位置
locations: classpath:db/migration
# 基线版本
baseline-version: 1
baseline-on-migrate: true
# 验证迁移脚本
validate-on-migrate: true
# 清理数据库(生产环境禁用)
clean-disabled: true
# 占位符替换
placeholders:
table_prefix: app_
// 迁移脚本目录结构
// src/main/resources/db/migration/
// ├── V1__Create_users_table.sql
// ├── V2__Add_email_index.sql
// ├── V3__Create_products_table.sql
// ├── V4__Add_created_at_columns.sql
// └── R__Create_views.sql
// V1__Create_users_table.sql - 创建用户表
CREATE TABLE ${table_prefix}users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_username (username),
INDEX idx_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 插入初始管理员用户
INSERT INTO ${table_prefix}users (username, email, password_hash, active)
VALUES ('admin', '[email protected]', '$2a$10$...', TRUE);
// V2__Add_email_index.sql - 添加邮箱索引
CREATE INDEX idx_users_email_active ON ${table_prefix}users(email, active);
-- 添加唯一约束
ALTER TABLE ${table_prefix}users
ADD CONSTRAINT uk_users_username UNIQUE (username);
// V3__Create_products_table.sql - 创建产品表
CREATE TABLE ${table_prefix}products (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
price DECIMAL(10, 2) NOT NULL,
stock INT NOT NULL DEFAULT 0,
category VARCHAR(50),
active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_category (category),
INDEX idx_price (price),
INDEX idx_active (active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 创建产品分类表
CREATE TABLE ${table_prefix}categories (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
description VARCHAR(255),
parent_id BIGINT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (parent_id) REFERENCES ${table_prefix}categories(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
// V4__Add_created_at_columns.sql - 添加时间戳列
ALTER TABLE ${table_prefix}products
ADD COLUMN deleted_at TIMESTAMP NULL,
ADD INDEX idx_deleted_at (deleted_at);
-- 添加软删除标记
ALTER TABLE ${table_prefix}users
ADD COLUMN deleted_at TIMESTAMP NULL;
// R__Create_views.sql - 可重复脚本(视图)
-- 活跃用户视图
CREATE OR REPLACE VIEW v_active_users AS
SELECT id, username, email, created_at
FROM ${table_prefix}users
WHERE active = TRUE AND deleted_at IS NULL;
-- 产品统计视图
CREATE OR REPLACE VIEW v_product_stats AS
SELECT
category,
COUNT(*) as product_count,
AVG(price) as avg_price,
SUM(stock) as total_stock
FROM ${table_prefix}products
WHERE active = TRUE AND deleted_at IS NULL
GROUP BY category;
// Flyway监听器
import io.micronaut.flyway.event.FlywayMigrationFinishedEvent;
import io.micronaut.runtime.event.annotation.EventListener;
@Singleton
public class FlywayEventListener {
@EventListener
public void onMigrationFinished(FlywayMigrationFinishedEvent event) {
System.out.println("=== Flyway迁移完成 ===");
System.out.println("数据源: " + event.getDataSourceName());
System.out.println("迁移结果: " + event.getMigrationResult());
System.out.println("执行时间: " + event.getMigrationResult().totalMigrationTime + "ms");
System.out.println("成功迁移数: " + event.getMigrationResult().migrationsExecuted);
}
}
// 编程式Flyway控制
@Singleton
public class FlywayService {
private final Flyway flyway;
public FlywayService(Flyway flyway) {
this.flyway = flyway;
}
// 获取迁移信息
public void getMigrationInfo() {
System.out.println("=== 迁移信息 ===");
MigrationInfoService info = flyway.info();
for (MigrationInfo migration : info.all()) {
System.out.println("版本: " + migration.getVersion());
System.out.println(" 描述: " + migration.getDescription());
System.out.println(" 类型: " + migration.getType());
System.out.println(" 状态: " + migration.getState());
System.out.println(" 执行时间: " + migration.getInstalledOn());
System.out.println(" 执行耗时: " + migration.getExecutionTime() + "ms\n");
}
}
// 验证迁移
public boolean validateMigrations() {
System.out.println("验证迁移脚本...");
try {
flyway.validate();
System.out.println(" 验证通过");
return true;
} catch (Exception e) {
System.err.println(" 验证失败: " + e.getMessage());
return false;
}
}
// 修复迁移(生产环境慎用)
public void repairMigrations() {
System.out.println("修复迁移记录...");
flyway.repair();
System.out.println(" 修复完成");
}
}
// 迁移测试
@MicronautTest
public class FlywayMigrationTest {
@Inject
FlywayService flywayService;
@Test
public void testMigrationsApplied() {
System.out.println("测试迁移是否正确应用");
// 验证迁移
assertTrue(flywayService.validateMigrations());
// 检查迁移信息
flywayService.getMigrationInfo();
}
}
---
b.版本控制
a.迁移版本管理
使用版本号(V1, V2, V3...)和时间戳管理迁移脚本执行顺序,Flyway自动追踪已执行版本。支持基线迁移、跳过版本等灵活配置。
b.版本管理示例
---
// 版本号命名规范
// V{版本号}__{描述}.sql
// V1__Initial_schema.sql
// V2__Add_user_roles.sql
// V2.1__Fix_user_constraints.sql
// 时间戳命名
// V{YYYYMMDDHHmmss}__{描述}.sql
// V20240101120000__Create_orders_table.sql
// V20240102093000__Add_payment_status.sql
// V5__Create_orders_table.sql - 订单表
CREATE TABLE ${table_prefix}orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_number VARCHAR(50) NOT NULL UNIQUE,
user_id BIGINT NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
payment_method VARCHAR(20),
payment_status VARCHAR(20) DEFAULT 'UNPAID',
shipping_address TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
paid_at TIMESTAMP NULL,
shipped_at TIMESTAMP NULL,
delivered_at TIMESTAMP NULL,
INDEX idx_user_id (user_id),
INDEX idx_order_number (order_number),
INDEX idx_status (status),
INDEX idx_created_at (created_at),
FOREIGN KEY (user_id) REFERENCES ${table_prefix}users(id) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 订单项表
CREATE TABLE ${table_prefix}order_items (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL,
subtotal DECIMAL(10, 2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_order_id (order_id),
INDEX idx_product_id (product_id),
FOREIGN KEY (order_id) REFERENCES ${table_prefix}orders(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES ${table_prefix}products(id) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
// V6__Add_audit_columns.sql - 添加审计列
-- 为主要表添加审计字段
ALTER TABLE ${table_prefix}users
ADD COLUMN created_by VARCHAR(50),
ADD COLUMN updated_by VARCHAR(50);
ALTER TABLE ${table_prefix}products
ADD COLUMN created_by VARCHAR(50),
ADD COLUMN updated_by VARCHAR(50);
ALTER TABLE ${table_prefix}orders
ADD COLUMN created_by VARCHAR(50),
ADD COLUMN updated_by VARCHAR(50),
ADD COLUMN cancelled_at TIMESTAMP NULL,
ADD COLUMN cancelled_by VARCHAR(50);
// V7__Create_audit_log_table.sql - 审计日志表
CREATE TABLE ${table_prefix}audit_logs (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
table_name VARCHAR(50) NOT NULL,
record_id BIGINT NOT NULL,
action VARCHAR(20) NOT NULL,
old_values JSON,
new_values JSON,
changed_by VARCHAR(50),
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ip_address VARCHAR(45),
user_agent TEXT,
INDEX idx_table_record (table_name, record_id),
INDEX idx_changed_at (changed_at),
INDEX idx_changed_by (changed_by)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
// V8__Add_full_text_search.sql - 全文搜索索引
-- 为产品表添加全文索引
ALTER TABLE ${table_prefix}products
ADD FULLTEXT INDEX ft_name_description (name, description);
-- 创建搜索历史表
CREATE TABLE ${table_prefix}search_history (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT,
search_query VARCHAR(255) NOT NULL,
results_count INT,
search_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_search_time (search_time),
FOREIGN KEY (user_id) REFERENCES ${table_prefix}users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
// 迁移版本管理服务
@Singleton
public class MigrationVersionService {
private final Flyway flyway;
public MigrationVersionService(Flyway flyway) {
this.flyway = flyway;
}
// 获取当前版本
public String getCurrentVersion() {
MigrationInfo current = flyway.info().current();
if (current != null) {
String version = current.getVersion().toString();
System.out.println("当前数据库版本: " + version);
return version;
}
return "未迁移";
}
// 获取所有待执行的迁移
public List<String> getPendingMigrations() {
System.out.println("检查待执行的迁移...");
List<String> pending = new ArrayList<>();
for (MigrationInfo info : flyway.info().pending()) {
String migration = info.getVersion() + " - " + info.getDescription();
pending.add(migration);
System.out.println(" 待执行: " + migration);
}
return pending;
}
// 获取已应用的迁移
public List<String> getAppliedMigrations() {
System.out.println("已应用的迁移:");
List<String> applied = new ArrayList<>();
for (MigrationInfo info : flyway.info().applied()) {
String migration = info.getVersion() + " - " + info.getDescription();
applied.add(migration);
System.out.println(" " + migration +
" (执行于: " + info.getInstalledOn() + ")");
}
return applied;
}
}
---
02.Liquibase集成
a.变更集管理
a.XML/YAML变更日志
Liquibase使用XML、YAML或JSON格式定义数据库变更集,提供比Flyway更丰富的抽象和数据库无关特性。支持回滚、标签、上下文等高级功能。
b.Liquibase配置示例
---
// build.gradle - Liquibase依赖
dependencies {
implementation("io.micronaut.liquibase:micronaut-liquibase")
}
// application.yml - Liquibase配置
datasources:
default:
url: jdbc:mysql://localhost:3306/mydb
driverClassName: com.mysql.cj.jdbc.Driver
username: root
password: ${DB_PASSWORD}
liquibase:
datasources:
default:
enabled: true
# 变更日志文件
change-log: classpath:db/changelog/db.changelog-master.xml
# 执行上下文
contexts: dev,test
# 标签过滤
labels: feature-a,feature-b
# 回滚失败时不停止
drop-first: false
// 变更日志目录结构
// src/main/resources/db/changelog/
// ├── db.changelog-master.xml
// ├── changes/
// │ ├── v1.0-initial-schema.xml
// │ ├── v1.1-add-indexes.xml
// │ └── v2.0-add-orders.xml
// └── data/
// └── initial-data.xml
// db.changelog-master.xml - 主变更日志
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd">
<!-- 包含所有变更集文件 -->
<include file="classpath:db/changelog/changes/v1.0-initial-schema.xml"/>
<include file="classpath:db/changelog/changes/v1.1-add-indexes.xml"/>
<include file="classpath:db/changelog/changes/v2.0-add-orders.xml"/>
<include file="classpath:db/changelog/data/initial-data.xml"/>
</databaseChangeLog>
// v1.0-initial-schema.xml - 初始schema
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd">
<!-- 创建用户表 -->
<changeSet id="1" author="developer">
<createTable tableName="users">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="username" type="VARCHAR(50)">
<constraints unique="true" nullable="false"/>
</column>
<column name="email" type="VARCHAR(100)">
<constraints unique="true" nullable="false"/>
</column>
<column name="password_hash" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="active" type="BOOLEAN" defaultValueBoolean="true"/>
<column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP"/>
<column name="updated_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP"/>
</createTable>
<!-- 回滚操作 -->
<rollback>
<dropTable tableName="users"/>
</rollback>
</changeSet>
<!-- 创建产品表 -->
<changeSet id="2" author="developer">
<createTable tableName="products">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true"/>
</column>
<column name="name" type="VARCHAR(100)">
<constraints nullable="false"/>
</column>
<column name="description" type="TEXT"/>
<column name="price" type="DECIMAL(10,2)">
<constraints nullable="false"/>
</column>
<column name="stock" type="INT" defaultValueNumeric="0"/>
<column name="category" type="VARCHAR(50)"/>
<column name="active" type="BOOLEAN" defaultValueBoolean="true"/>
<column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP"/>
<column name="updated_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP"/>
</createTable>
<rollback>
<dropTable tableName="products"/>
</rollback>
</changeSet>
</databaseChangeLog>
// v1.1-add-indexes.xml - 添加索引
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd">
<!-- 为用户表添加索引 -->
<changeSet id="3" author="developer">
<createIndex tableName="users" indexName="idx_username">
<column name="username"/>
</createIndex>
<createIndex tableName="users" indexName="idx_email">
<column name="email"/>
</createIndex>
<createIndex tableName="users" indexName="idx_active_created">
<column name="active"/>
<column name="created_at"/>
</createIndex>
<rollback>
<dropIndex tableName="users" indexName="idx_username"/>
<dropIndex tableName="users" indexName="idx_email"/>
<dropIndex tableName="users" indexName="idx_active_created"/>
</rollback>
</changeSet>
<!-- 为产品表添加索引 -->
<changeSet id="4" author="developer">
<createIndex tableName="products" indexName="idx_category">
<column name="category"/>
</createIndex>
<createIndex tableName="products" indexName="idx_price">
<column name="price"/>
</createIndex>
<rollback>
<dropIndex tableName="products" indexName="idx_category"/>
<dropIndex tableName="products" indexName="idx_price"/>
</rollback>
</changeSet>
</databaseChangeLog>
// YAML格式变更日志
// db.changelog-master.yml
databaseChangeLog:
- include:
file: db/changelog/changes/v1.0-initial-schema.yml
- include:
file: db/changelog/changes/v2.0-add-orders.yml
// v2.0-add-orders.yml
databaseChangeLog:
- changeSet:
id: 5
author: developer
changes:
- createTable:
tableName: orders
columns:
- column:
name: id
type: BIGINT
autoIncrement: true
constraints:
primaryKey: true
nullable: false
- column:
name: order_number
type: VARCHAR(50)
constraints:
unique: true
nullable: false
- column:
name: user_id
type: BIGINT
constraints:
nullable: false
foreignKeyName: fk_orders_user
references: users(id)
- column:
name: total_amount
type: DECIMAL(10,2)
constraints:
nullable: false
- column:
name: status
type: VARCHAR(20)
defaultValue: PENDING
- column:
name: created_at
type: TIMESTAMP
defaultValueComputed: CURRENT_TIMESTAMP
rollback:
- dropTable:
tableName: orders
// Liquibase服务
@Singleton
public class LiquibaseService {
private final Liquibase liquibase;
public LiquibaseService(Liquibase liquibase) {
this.liquibase = liquibase;
}
// 获取变更集状态
public void getChangeSetStatus() {
System.out.println("=== Liquibase变更集状态 ===");
try {
List<ChangeSet> changeSets = liquibase.listUnrunChangeSets(
new Contexts(),
new LabelExpression()
);
System.out.println("未执行的变更集数: " + changeSets.size());
for (ChangeSet changeSet : changeSets) {
System.out.println("ID: " + changeSet.getId());
System.out.println(" 作者: " + changeSet.getAuthor());
System.out.println(" 文件: " + changeSet.getFilePath());
System.out.println(" 描述: " + changeSet.getDescription());
}
} catch (Exception e) {
System.err.println("获取变更集状态失败: " + e.getMessage());
}
}
// 执行更新到指定标签
public void updateToTag(String tag) {
System.out.println("更新数据库到标签: " + tag);
try {
liquibase.update(tag, new Contexts());
System.out.println(" 更新完成");
} catch (Exception e) {
System.err.println("更新失败: " + e.getMessage());
}
}
// 回滚到指定标签
public void rollbackToTag(String tag) {
System.out.println("回滚数据库到标签: " + tag);
try {
liquibase.rollback(tag, new Contexts());
System.out.println(" 回滚完成");
} catch (Exception e) {
System.err.println("回滚失败: " + e.getMessage());
}
}
// 生成回滚SQL
public String generateRollbackSQL(int count) {
System.out.println("生成回滚SQL (最近 " + count + " 个变更集)");
try {
StringWriter writer = new StringWriter();
liquibase.rollback(count, null, new Contexts(), writer);
return writer.toString();
} catch (Exception e) {
System.err.println("生成回滚SQL失败: " + e.getMessage());
return "";
}
}
}
---
b.回滚策略
a.变更回滚
Liquibase支持定义回滚操作,可以回滚到特定版本或标签。提供回滚预览、回滚SQL生成等功能,确保数据库变更可逆。
b.回滚策略示例
---
// 定义回滚操作的变更集
// v3.0-add-user-roles.xml
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd">
<!-- 创建角色表 -->
<changeSet id="6" author="developer">
<createTable tableName="roles">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true"/>
</column>
<column name="name" type="VARCHAR(50)">
<constraints unique="true" nullable="false"/>
</column>
<column name="description" type="VARCHAR(255)"/>
</createTable>
<!-- 明确的回滚操作 -->
<rollback>
<dropTable tableName="roles"/>
</rollback>
</changeSet>
<!-- 创建用户角色关联表 -->
<changeSet id="7" author="developer">
<createTable tableName="user_roles">
<column name="user_id" type="BIGINT">
<constraints nullable="false"
foreignKeyName="fk_user_roles_user"
references="users(id)"/>
</column>
<column name="role_id" type="BIGINT">
<constraints nullable="false"
foreignKeyName="fk_user_roles_role"
references="roles(id)"/>
</column>
</createTable>
<addPrimaryKey tableName="user_roles"
columnNames="user_id, role_id"
constraintName="pk_user_roles"/>
<!-- 回滚操作 -->
<rollback>
<dropTable tableName="user_roles"/>
</rollback>
</changeSet>
<!-- 插入初始角色数据 -->
<changeSet id="8" author="developer">
<insert tableName="roles">
<column name="name" value="ADMIN"/>
<column name="description" value="管理员角色"/>
</insert>
<insert tableName="roles">
<column name="name" value="USER"/>
<column name="description" value="普通用户角色"/>
</insert>
<!-- 回滚:删除插入的数据 -->
<rollback>
<delete tableName="roles">
<where>name IN ('ADMIN', 'USER')</where>
</delete>
</rollback>
</changeSet>
<!-- 添加标签用于回滚 -->
<changeSet id="9" author="developer">
<tagDatabase tag="version-1.0"/>
</changeSet>
</databaseChangeLog>
// 使用自定义SQL的回滚
<changeSet id="10" author="developer">
<sql>
ALTER TABLE products ADD COLUMN discount DECIMAL(5,2) DEFAULT 0.00;
UPDATE products SET discount = 0.10 WHERE category = 'Electronics';
</sql>
<!-- 自定义SQL回滚 -->
<rollback>
<sql>
ALTER TABLE products DROP COLUMN discount;
</sql>
</rollback>
</changeSet>
// 复杂回滚操作
<changeSet id="11" author="developer">
<!-- 修改列类型 -->
<modifyDataType tableName="products"
columnName="price"
newDataType="DECIMAL(12,2)"/>
<!-- 自动生成回滚 -->
<rollback changeSetId="11" changeSetAuthor="developer"/>
</changeSet>
// 回滚管理服务
@Singleton
public class RollbackManagementService {
private final Liquibase liquibase;
public RollbackManagementService(Liquibase liquibase) {
this.liquibase = liquibase;
}
// 回滚指定数量的变更集
public void rollbackCount(int count) {
System.out.println("回滚最近 " + count + " 个变更集");
try {
liquibase.rollback(count, null, new Contexts());
System.out.println(" 回滚完成");
} catch (Exception e) {
System.err.println("回滚失败: " + e.getMessage());
}
}
// 回滚到指定日期
public void rollbackToDate(Date date) {
System.out.println("回滚到日期: " + date);
try {
liquibase.rollback(date, new Contexts());
System.out.println(" 回滚完成");
} catch (Exception e) {
System.err.println("回滚失败: " + e.getMessage());
}
}
// 回滚到指定标签
public void rollbackToTag(String tag) {
System.out.println("回滚到标签: " + tag);
try {
liquibase.rollback(tag, new Contexts());
System.out.println(" 回滚完成");
} catch (Exception e) {
System.err.println("回滚失败: " + e.getMessage());
}
}
// 预览回滚SQL
public String previewRollback(int count) {
System.out.println("预览回滚SQL (最近 " + count + " 个变更集)");
try {
StringWriter writer = new StringWriter();
liquibase.rollback(count, null, new Contexts(), writer);
String rollbackSQL = writer.toString();
System.out.println("\n回滚SQL:\n" + rollbackSQL);
return rollbackSQL;
} catch (Exception e) {
System.err.println("预览回滚失败: " + e.getMessage());
return "";
}
}
// 测试回滚(先更新再回滚)
public void testRollback(String tag) {
System.out.println("测试回滚到标签: " + tag);
try {
// 1. 记录当前状态
System.out.println(" 1. 记录当前状态");
String currentTag = "test-checkpoint-" + System.currentTimeMillis();
liquibase.tag(currentTag);
// 2. 执行更新
System.out.println(" 2. 执行更新到标签: " + tag);
liquibase.update(tag, new Contexts());
// 3. 回滚到原状态
System.out.println(" 3. 回滚到原状态");
liquibase.rollback(currentTag, new Contexts());
System.out.println(" 测试回滚完成");
} catch (Exception e) {
System.err.println("测试回滚失败: " + e.getMessage());
}
}
}
---
03.Schema管理
a.自动生成schema
a.JPA自动DDL
通过JPA的hbm2ddl.auto配置自动生成数据库schema,适合开发环境快速迭代。支持create、update、validate等模式,生产环境建议使用validate或关闭。
b.自动DDL示例
---
// application.yml - JPA自动DDL配置
jpa:
default:
properties:
hibernate:
# DDL自动生成策略
hbm2ddl:
auto: update # create | create-drop | update | validate | none
# SQL输出
show_sql: true
format_sql: true
use_sql_comments: true
# Schema命名策略
physical_naming_strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
implicit_naming_strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
// 实体定义自动生成表
@Entity
@Table(name = "customers",
indexes = {
@Index(name = "idx_email", columnList = "email"),
@Index(name = "idx_phone", columnList = "phone")
},
uniqueConstraints = {
@UniqueConstraint(name = "uk_customer_email", columnNames = {"email"})
})
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(nullable = false, unique = true, length = 100)
private String email;
@Column(length = 20)
private String phone;
@Embedded
private Address address;
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
@Column(name = "created_at", nullable = false, updatable = false)
@CreationTimestamp
private LocalDateTime createdAt;
@Column(name = "updated_at")
@UpdateTimestamp
private LocalDateTime updatedAt;
// Getter和Setter省略
}
@Embeddable
public class Address {
@Column(length = 255)
private String street;
@Column(length = 50)
private String city;
@Column(length = 20)
private String zipCode;
@Column(length = 50)
private String country;
// Getter和Setter省略
}
// DDL生成服务
@Singleton
public class SchemaGenerationService {
private final EntityManagerFactory entityManagerFactory;
public SchemaGenerationService(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
}
// 生成DDL脚本
public void generateDDLScript() {
System.out.println("=== 生成DDL脚本 ===");
Map<String, Object> properties = new HashMap<>();
properties.put("javax.persistence.schema-generation.scripts.action", "create");
properties.put("javax.persistence.schema-generation.scripts.create-target",
"target/generated-schema.sql");
// 触发schema生成
Persistence.generateSchema("default", properties);
System.out.println("DDL脚本已生成到: target/generated-schema.sql");
}
// 验证schema
public void validateSchema() {
System.out.println("验证数据库Schema...");
SessionFactory sessionFactory =
entityManagerFactory.unwrap(SessionFactory.class);
// Hibernate会自动验证schema与实体映射的一致性
System.out.println(" Schema验证完成");
}
}
// 环境特定配置
// application-dev.yml - 开发环境
jpa:
default:
properties:
hibernate:
hbm2ddl:
auto: update # 开发环境自动更新
show_sql: true
// application-prod.yml - 生产环境
jpa:
default:
properties:
hibernate:
hbm2ddl:
auto: validate # 生产环境只验证
show_sql: false
// 初始化数据
@Singleton
public class DataInitializer {
@EventListener
public void onStartup(ServerStartupEvent event) {
System.out.println("=== 初始化数据 ===");
// 初始化逻辑
}
}
---
b.Schema版本化
a.迁移工具整合
结合Flyway或Liquibase实现schema版本化管理,在生产环境使用迁移工具,开发环境可选择自动DDL。确保数据库变更可追踪、可回滚、可复现。
b.整合示例
---
// 混合使用JPA和Flyway
// application.yml
jpa:
default:
properties:
hibernate:
# 生产环境使用Flyway,验证schema
hbm2ddl:
auto: validate
show_sql: false
flyway:
datasources:
default:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true
// Schema同步服务
@Singleton
public class SchemaSyncService {
private final EntityManagerFactory entityManagerFactory;
private final Flyway flyway;
public SchemaSyncService(EntityManagerFactory entityManagerFactory,
Flyway flyway) {
this.entityManagerFactory = entityManagerFactory;
this.flyway = flyway;
}
// 生成迁移脚本
public void generateMigrationScript() {
System.out.println("=== 生成迁移脚本 ===");
// 1. 生成当前实体的DDL
String ddl = generateEntityDDL();
// 2. 与现有schema对比
String diff = compareWithCurrentSchema(ddl);
// 3. 生成Flyway迁移脚本
if (!diff.isEmpty()) {
String version = getNextMigrationVersion();
String filename = String.format("V%s__Generated_migration.sql", version);
System.out.println("生成迁移文件: " + filename);
System.out.println("变更内容:\n" + diff);
// 写入迁移文件
saveMigrationScript(filename, diff);
} else {
System.out.println("Schema无变更");
}
}
private String generateEntityDDL() {
// 生成DDL逻辑
return "";
}
private String compareWithCurrentSchema(String ddl) {
// 对比逻辑
return "";
}
private String getNextMigrationVersion() {
MigrationInfo current = flyway.info().current();
if (current != null) {
String version = current.getVersion().toString();
int nextVersion = Integer.parseInt(version) + 1;
return String.valueOf(nextVersion);
}
return "1";
}
private void saveMigrationScript(String filename, String content) {
// 保存文件逻辑
}
}
// CI/CD集成
@Singleton
public class MigrationCIService {
// 验证迁移脚本
public boolean validateMigrations() {
System.out.println("CI: 验证迁移脚本");
// 1. 检查迁移文件命名
// 2. 验证SQL语法
// 3. 检查是否有未提交的迁移
// 4. 验证回滚脚本
return true;
}
// 在测试数据库运行迁移
public boolean testMigrations() {
System.out.println("CI: 测试迁移脚本");
// 1. 创建临时测试数据库
// 2. 运行所有迁移
// 3. 测试回滚
// 4. 清理测试数据库
return true;
}
}
---
5.7 R2DBC响应式数据访问
01.R2DBC概述
a.核心概念
R2DBC(Reactive Relational Database Connectivity)是响应式关系数据库连接规范,提供完全非阻塞的数据库访问能力。Micronaut Data R2DBC支持响应式流式查询,适用于高并发场景,充分利用系统资源。
b.主要优势
a.非阻塞I/O
所有数据库操作都是异步非阻塞的,不会占用线程等待数据库响应,提升系统吞吐量。
b.背压支持
支持响应式流背压机制,消费者可以控制数据生产速率,避免内存溢出。
c.流式处理
支持大数据集的流式处理,逐条消费数据,内存占用低。
02.依赖配置
a.添加依赖
a.功能说明
添加Micronaut Data R2DBC和数据库驱动依赖。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.data:micronaut-data-r2dbc")
implementation("io.micronaut.r2dbc:micronaut-r2dbc-core")
// MySQL驱动
runtimeOnly("dev.miku:r2dbc-mysql")
// PostgreSQL驱动
// runtimeOnly("org.postgresql:r2dbc-postgresql")
// H2驱动(测试用)
// runtimeOnly("io.r2dbc:r2dbc-h2")
annotationProcessor("io.micronaut.data:micronaut-data-processor")
}
---
b.数据源配置
a.功能说明
配置R2DBC数据源连接信息。
b.配置示例
---
// application.yml
r2dbc:
datasources:
default:
url: r2dbc:mysql://localhost:3306/testdb
username: root
password: secret
driver: mysql
options:
# 连接池配置
initial-size: 5
max-size: 20
max-idle-time: 30m
max-acquire-time: 3s
max-create-connection-time: 3s
---
03.Repository定义
a.响应式Repository
a.功能说明
继承ReactiveStreamsCrudRepository或CoroutineCrudRepository,定义响应式数据访问接口。
b.代码示例
---
import io.micronaut.data.annotation.*;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.r2dbc.annotation.R2dbcRepository;
import io.micronaut.data.repository.reactive.ReactiveStreamsCrudRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@R2dbcRepository(dialect = Dialect.MYSQL)
public interface ProductRepository extends ReactiveStreamsCrudRepository<Product, Long> {
// 返回Mono<Product>
Mono<Product> findByName(String name);
// 返��Flux<Product>
Flux<Product> findByCategory(String category);
// 自定义查询
@Query("SELECT * FROM product WHERE price > :minPrice ORDER BY price")
Flux<Product> findExpensiveProducts(Double minPrice);
// 统计查询
Mono<Long> countByCategory(String category);
// 存在性检查
Mono<Boolean> existsByName(String name);
// 删除操作
Mono<Long> deleteByCategory(String category);
}
---
b.实体类定义
a.功能说明
使用@MappedEntity定义实体类,映射到数据库表。
b.代码示例
---
import io.micronaut.data.annotation.*;
import io.micronaut.serde.annotation.Serdeable;
@Serdeable
@MappedEntity("product")
public class Product {
@Id
@GeneratedValue(GeneratedValue.Type.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "category")
private String category;
@Column(name = "price")
private Double price;
@Column(name = "stock")
private Integer stock;
@DateCreated
private LocalDateTime createdAt;
@DateUpdated
private LocalDateTime updatedAt;
// Constructors, Getters and Setters
public Product() {}
public Product(String name, String category, Double price, Integer stock) {
this.name = name;
this.category = category;
this.price = price;
this.stock = stock;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Integer getStock() {
return stock;
}
public void setStock(Integer stock) {
this.stock = stock;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}
---
04.响应式Controller
a.基本CRUD操作
a.功能说明
在Controller中使用响应式Repository,返回Mono或Flux。
b.代码示例
---
import io.micronaut.http.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Controller("/products")
public class ProductController {
private final ProductRepository productRepository;
public ProductController(ProductRepository productRepository) {
this.productRepository = productRepository;
}
// 查询所有产品
@Get
public Flux<Product> listAll() {
return productRepository.findAll();
}
// 根据ID查询
@Get("/{id}")
public Mono<Product> findById(Long id) {
return productRepository.findById(id);
}
// 创建产品
@Post
public Mono<Product> create(@Body Product product) {
return productRepository.save(product);
}
// 更新产品
@Put("/{id}")
public Mono<Product> update(Long id, @Body Product product) {
return productRepository.findById(id)
.flatMap(existing -> {
existing.setName(product.getName());
existing.setPrice(product.getPrice());
existing.setStock(product.getStock());
return productRepository.update(existing);
});
}
// 删除产品
@Delete("/{id}")
public Mono<Void> delete(Long id) {
return productRepository.deleteById(id).then();
}
}
---
b.复杂查询操作
a.功能说明
实现分页、排序、条件查询等复杂操作。
b.代码示例
---
import io.micronaut.data.model.Pageable;
import io.micronaut.data.model.Page;
@Controller("/products")
public class ProductController {
private final ProductRepository productRepository;
public ProductController(ProductRepository productRepository) {
this.productRepository = productRepository;
}
// 按分类查询
@Get("/category/{category}")
public Flux<Product> findByCategory(String category) {
return productRepository.findByCategory(category);
}
// 查询高价产品
@Get("/expensive")
public Flux<Product> findExpensive(@QueryValue Double minPrice) {
return productRepository.findExpensiveProducts(minPrice);
}
// 统计分类产品数量
@Get("/category/{category}/count")
public Mono<Long> countByCategory(String category) {
return productRepository.countByCategory(category);
}
// 检查产品是否存在
@Get("/exists/{name}")
public Mono<Boolean> exists(String name) {
return productRepository.existsByName(name);
}
// 批量删除
@Delete("/category/{category}")
public Mono<Long> deleteByCategory(String category) {
return productRepository.deleteByCategory(category);
}
}
---
05.响应式事务
a.声明式事务
a.功能说明
使用@TransactionalEventListener注解管理响应式事务。
b.代码示例
---
import io.micronaut.transaction.annotation.Transactional;
import reactor.core.publisher.Mono;
@Singleton
public class OrderService {
private final OrderRepository orderRepository;
private final ProductRepository productRepository;
public OrderService(OrderRepository orderRepository,
ProductRepository productRepository) {
this.orderRepository = orderRepository;
this.productRepository = productRepository;
}
@Transactional
public Mono<Order> createOrder(Order order) {
return productRepository.findById(order.getProductId())
.flatMap(product -> {
if (product.getStock() < order.getQuantity()) {
return Mono.error(new RuntimeException("库存不足"));
}
// 扣减库存
product.setStock(product.getStock() - order.getQuantity());
return productRepository.update(product)
.then(orderRepository.save(order));
});
}
@Transactional
public Mono<Void> cancelOrder(Long orderId) {
return orderRepository.findById(orderId)
.flatMap(order ->
productRepository.findById(order.getProductId())
.flatMap(product -> {
// 恢复库存
product.setStock(product.getStock() + order.getQuantity());
return productRepository.update(product);
})
.then(orderRepository.deleteById(orderId))
)
.then();
}
}
---
b.编程式事务
a.功能说明
使用ReactiveTransactionOperations手动管理事务。
b.代码示例
---
import io.micronaut.transaction.reactive.ReactiveTransactionOperations;
import reactor.core.publisher.Mono;
@Singleton
public class PaymentService {
private final ReactiveTransactionOperations<Connection> transactionManager;
private final OrderRepository orderRepository;
private final PaymentRepository paymentRepository;
public PaymentService(
ReactiveTransactionOperations<Connection> transactionManager,
OrderRepository orderRepository,
PaymentRepository paymentRepository
) {
this.transactionManager = transactionManager;
this.orderRepository = orderRepository;
this.paymentRepository = paymentRepository;
}
public Mono<Payment> processPayment(Long orderId, Payment payment) {
return transactionManager.withTransaction(status ->
orderRepository.findById(orderId)
.flatMap(order -> {
payment.setOrderId(orderId);
payment.setAmount(order.getTotalAmount());
return paymentRepository.save(payment)
.flatMap(savedPayment -> {
order.setStatus("PAID");
return orderRepository.update(order)
.thenReturn(savedPayment);
});
})
);
}
}
---
06.流式处理
a.大数据集处理
a.功能说明
使用Flux流式处理大量数据,避免一次性加载到内存。
b.代码示例
---
import reactor.core.publisher.Flux;
import java.time.Duration;
@Singleton
public class ReportService {
private final ProductRepository productRepository;
public ReportService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
// 流式导出产品数据
public Flux<String> exportProducts() {
return productRepository.findAll()
.map(product -> String.format("%d,%s,%.2f,%d",
product.getId(),
product.getName(),
product.getPrice(),
product.getStock()
))
.delayElements(Duration.ofMillis(10)); // 控制速率
}
// 批量处理
public Mono<Long> updatePrices(Double multiplier) {
return productRepository.findAll()
.buffer(100) // 每批100条
.flatMap(batch ->
Flux.fromIterable(batch)
.flatMap(product -> {
product.setPrice(product.getPrice() * multiplier);
return productRepository.update(product);
})
.collectList()
)
.count();
}
}
---
b.背压控制
a.功能说明
使用背压操作符控制数据流速率。
b.代码示例
---
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;
@Controller("/stream")
public class StreamController {
private final ProductRepository productRepository;
public StreamController(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Get(value = "/products", produces = "text/event-stream")
public Flux<Product> streamProducts() {
return productRepository.findAll()
.onBackpressureBuffer(1000) // 缓冲区大小
.publishOn(Schedulers.boundedElastic())
.delayElements(Duration.ofMillis(100)); // 限流
}
@Get(value = "/products/drop", produces = "text/event-stream")
public Flux<Product> streamWithDrop() {
return productRepository.findAll()
.onBackpressureDrop() // 丢弃超出处理能力的数据
.publishOn(Schedulers.boundedElastic());
}
@Get(value = "/products/latest", produces = "text/event-stream")
public Flux<Product> streamLatest() {
return productRepository.findAll()
.onBackpressureLatest() // 只保留最新数据
.publishOn(Schedulers.boundedElastic());
}
}
---
07.性能优化
a.连接池配置
a.功能说明
优化R2DBC连接池参数,提升并发性能。
b.配置示例
---
// application.yml
r2dbc:
datasources:
default:
url: r2dbc:mysql://localhost:3306/testdb
username: root
password: secret
options:
# 初始连接数
initial-size: 10
# 最大连接数
max-size: 50
# 最大空闲时间
max-idle-time: 30m
# 获取连接超时
max-acquire-time: 3s
# 创建连接超时
max-create-connection-time: 3s
# 连接验证查询
validation-query: SELECT 1
# 连接验证深度
validation-depth: LOCAL
---
b.批量操作
a.功能说明
使用批量操作减少数据库往返次数。
b.代码示例
---
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Singleton
public class BatchService {
private final ProductRepository productRepository;
public BatchService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
// 批量插入
public Mono<Long> batchInsert(List<Product> products) {
return Flux.fromIterable(products)
.buffer(100) // 每批100条
.flatMap(batch ->
productRepository.saveAll(batch).collectList()
)
.count();
}
// 批量更新
public Mono<Long> batchUpdate(List<Product> products) {
return Flux.fromIterable(products)
.buffer(100)
.flatMap(batch ->
productRepository.updateAll(batch).collectList()
)
.count();
}
// 批量删除
public Mono<Void> batchDelete(List<Long> ids) {
return Flux.fromIterable(ids)
.buffer(100)
.flatMap(batch ->
productRepository.deleteAll(
productRepository.findAllById(batch)
)
)
.then();
}
}
---
c.查询优化
a.功能说明
使用索引、避免N+1查询等优化技巧。
b.代码示例
---
import io.micronaut.data.annotation.Join;
@R2dbcRepository(dialect = Dialect.MYSQL)
public interface OrderRepository extends ReactiveStreamsCrudRepository<Order, Long> {
// 使用JOIN避免N+1查询
@Join(value = "product", type = Join.Type.FETCH)
Flux<Order> findAll();
@Join(value = "product", type = Join.Type.FETCH)
Mono<Order> findById(Long id);
// 使用索引字段查询
@Query("SELECT * FROM orders WHERE user_id = :userId AND status = :status")
Flux<Order> findByUserIdAndStatus(Long userId, String status);
// 只查询需要的字段
@Query("SELECT id, total_amount, status FROM orders WHERE user_id = :userId")
Flux<OrderSummary> findSummaryByUserId(Long userId);
}
// DTO投影
@Introspected
public class OrderSummary {
private Long id;
private Double totalAmount;
private String status;
// Getters and Setters
}
---
6 高级特性
6.1 反应式编程
01.反应式编程概述
a.定义与特点
Micronaut原生支持反应式编程模型,基于Reactive Streams规范,提供非阻塞异步处理能力。通过响应式流处理,应用可以高效处理大量并发请求,充分利用系统资源,避免线程阻塞导致的性能瓶颈。
b.核心优势
a.资源利用率
采用事件驱动模型,少量线程即可处理大量并发连接,显著降低内存占用和上下文切换开销。
b.背压支持
实现Reactive Streams背压机制,生产者根据消费者处理能力动态调整数据发送速率,防止系统过载。
c.组合能力
提供丰富的操作符,支持流的转换、过滤、合并等操作,简化复杂异步逻辑的实现。
02.Reactor集成
a.依赖配置
a.功能说明
Micronaut默认集成Project Reactor,提供Mono和Flux两种核心响应式类型,分别表示0-1个元素和0-N个元素的异步序列。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.reactor:micronaut-reactor")
implementation("io.projectreactor:reactor-core")
}
---
b.基础使用
a.Mono示例
a.功能说明
Mono表示包含0或1个元素的异步序列,适用于单个结果的异步操作,如数据库查询单条记录、HTTP请求等场景。
b.代码示例
---
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import reactor.core.publisher.Mono;
import java.time.Duration;
@Controller("/reactive")
public class ReactiveController {
// 返回单个用户信息
@Get("/user/{id}")
public Mono<User> getUser(Long id) {
return Mono.fromCallable(() -> {
// 模拟数据库查询
Thread.sleep(100);
return new User(id, "User" + id, "user" + id + "@example.com");
})
.subscribeOn(Schedulers.boundedElastic())
.timeout(Duration.ofSeconds(5));
}
// 延迟响应示例
@Get("/delayed")
public Mono<String> delayedResponse() {
return Mono.just("Response")
.delayElement(Duration.ofSeconds(2))
.map(s -> s + " after 2 seconds");
}
}
---
b.Flux示例
a.功能说明
Flux表示包含0到N个元素的异步序列,适用于流式数据处理,如分页查询、实时数据推送、文件流处理等场景。
b.代码示例
---
import reactor.core.publisher.Flux;
import java.time.Duration;
@Controller("/stream")
public class StreamController {
// 流式返回用户列表
@Get("/users")
public Flux<User> streamUsers() {
return Flux.range(1, 10)
.delayElements(Duration.ofMillis(100))
.map(i -> new User(
Long.valueOf(i),
"User" + i,
"user" + i + "@example.com"
));
}
// 服务器推送事件(SSE)
@Get(value = "/events", produces = MediaType.TEXT_EVENT_STREAM)
public Flux<ServerSentEvent<String>> streamEvents() {
return Flux.interval(Duration.ofSeconds(1))
.map(seq -> ServerSentEvent.<String>builder()
.id(String.valueOf(seq))
.data("Event " + seq)
.build());
}
// 无限流示例
@Get(value = "/infinite", produces = MediaType.TEXT_EVENT_STREAM)
public Flux<Long> infiniteStream() {
return Flux.interval(Duration.ofMillis(500))
.take(Duration.ofSeconds(10));
}
}
---
03.RxJava集成
a.依赖配置
a.功能说明
Micronaut同时支持RxJava 2和RxJava 3,提供Single、Maybe、Observable、Flowable等响应式类型,与Reactor互补。
b.配置示例
---
// build.gradle - RxJava 3
dependencies {
implementation("io.micronaut.rxjava3:micronaut-rxjava3")
implementation("io.reactivex.rxjava3:rxjava:3.1.5")
}
---
b.RxJava使用
a.Single和Maybe
a.功能说明
Single表示恰好一个元素的异步序列,Maybe表示0或1个元素,适用于可能为空的查询结果。
b.代码示例
---
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.schedulers.Schedulers;
@Controller("/rx")
public class RxController {
// Single示例:必定返回结果
@Get("/count")
public Single<Long> getCount() {
return Single.fromCallable(() -> {
// 模拟计数查询
Thread.sleep(50);
return 100L;
}).subscribeOn(Schedulers.io());
}
// Maybe示例:可能为空
@Get("/find/{id}")
public Maybe<User> findUser(Long id) {
return Maybe.fromCallable(() -> {
if (id > 100) {
return null; // 未找到
}
return new User(id, "User" + id, "user" + id + "@example.com");
}).subscribeOn(Schedulers.io());
}
}
---
b.Observable和Flowable
a.功能说明
Observable用于简单的多元素流,Flowable支持背压控制,适用于高吞吐量场景。
b.代码示例
---
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.BackpressureStrategy;
@Controller("/rx-stream")
public class RxStreamController {
// Observable示例
@Get("/items")
public Observable<Item> streamItems() {
return Observable.range(1, 20)
.map(i -> new Item(i, "Item " + i))
.delay(100, TimeUnit.MILLISECONDS);
}
// Flowable示例:支持背压
@Get(value = "/data", produces = MediaType.TEXT_EVENT_STREAM)
public Flowable<String> streamData() {
return Flowable.create(emitter -> {
for (int i = 0; i < 1000; i++) {
if (emitter.isCancelled()) {
break;
}
emitter.onNext("Data " + i);
Thread.sleep(10);
}
emitter.onComplete();
}, BackpressureStrategy.BUFFER)
.subscribeOn(Schedulers.io());
}
}
---
04.响应式HTTP客户端
a.声明式客户端
a.功能说明
Micronaut的HTTP客户端原生支持响应式类型,可直接返回Mono、Flux、Single等,实现非阻塞的远程调用。
b.代码示例
---
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;
@Client("https://api.example.com")
public interface ApiClient {
// 响应式单个结果
@Get("/user/{id}")
Mono<User> getUser(Long id);
// 响应式列表
@Get("/users")
Flux<User> listUsers();
// RxJava类型
@Get("/count")
Single<Long> getCount();
}
// 使用示例
@Controller("/api")
public class ApiController {
private final ApiClient apiClient;
public ApiController(ApiClient apiClient) {
this.apiClient = apiClient;
}
@Get("/proxy/user/{id}")
public Mono<User> proxyGetUser(Long id) {
return apiClient.getUser(id)
.timeout(Duration.ofSeconds(3))
.onErrorResume(e -> Mono.just(
new User(id, "Fallback", "[email protected]")
));
}
@Get("/proxy/users")
public Flux<User> proxyListUsers() {
return apiClient.listUsers()
.take(10)
.filter(user -> user.getId() > 5);
}
}
---
b.组合多个请求
a.功能说明
响应式编程的核心优势在于轻松组合多个异步操作,实现并行请求、依赖请求等复杂场景。
b.代码示例
---
@Controller("/combined")
public class CombinedController {
private final ApiClient apiClient;
private final OrderClient orderClient;
public CombinedController(ApiClient apiClient, OrderClient orderClient) {
this.apiClient = apiClient;
this.orderClient = orderClient;
}
// 并行请求多个服务
@Get("/dashboard/{userId}")
public Mono<Dashboard> getDashboard(Long userId) {
Mono<User> userMono = apiClient.getUser(userId);
Mono<List<Order>> ordersMono = orderClient.getUserOrders(userId)
.collectList();
Mono<Long> countMono = orderClient.getOrderCount(userId);
return Mono.zip(userMono, ordersMono, countMono)
.map(tuple -> new Dashboard(
tuple.getT1(),
tuple.getT2(),
tuple.getT3()
));
}
// 依赖请求:先获取用户,再获取订单
@Get("/user-orders/{userId}")
public Flux<Order> getUserOrders(Long userId) {
return apiClient.getUser(userId)
.flatMapMany(user -> orderClient.getUserOrders(user.getId()));
}
// 错误处理和重试
@Get("/resilient/{userId}")
public Mono<User> getResilientUser(Long userId) {
return apiClient.getUser(userId)
.retry(3)
.timeout(Duration.ofSeconds(5))
.onErrorResume(TimeoutException.class, e ->
Mono.just(new User(userId, "Timeout", "[email protected]"))
)
.onErrorResume(e ->
Mono.just(new User(userId, "Error", "[email protected]"))
);
}
}
---
05.响应式数据访问
a.R2DBC集成
a.功能说明
R2DBC(Reactive Relational Database Connectivity)是响应式关系数据库驱动规范,Micronaut Data支持R2DBC实现完全非阻塞的数据库访问。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.data:micronaut-data-r2dbc")
implementation("io.r2dbc:r2dbc-postgresql:0.8.13.RELEASE")
runtimeOnly("org.postgresql:postgresql")
}
// application.yml
r2dbc:
datasources:
default:
url: r2dbc:postgresql://localhost:5432/mydb
username: user
password: pass
max-size: 10
---
c.Repository定义
---
import io.micronaut.data.annotation.Repository;
import io.micronaut.data.repository.reactive.ReactorCrudRepository;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;
@Repository
public interface UserRepository extends ReactorCrudRepository<User, Long> {
// 响应式查询方法
Mono<User> findByEmail(String email);
Flux<User> findByNameContaining(String name);
Mono<Long> countByAgeGreaterThan(Integer age);
// 自定义查询
@Query("SELECT * FROM users WHERE created_at > :date")
Flux<User> findRecentUsers(LocalDateTime date);
}
// 使用示例
@Controller("/db-users")
public class DbUserController {
private final UserRepository userRepository;
public DbUserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Get("/{id}")
public Mono<User> getUser(Long id) {
return userRepository.findById(id);
}
@Get("/search")
public Flux<User> searchUsers(String name) {
return userRepository.findByNameContaining(name);
}
@Post
public Mono<User> createUser(@Body User user) {
return userRepository.save(user);
}
@Delete("/{id}")
public Mono<Void> deleteUser(Long id) {
return userRepository.deleteById(id);
}
}
---
b.MongoDB响应式驱动
a.功能说明
Micronaut支持MongoDB响应式驱动,提供完全异步的文档数据库访问能力。
b.代码示例
---
// build.gradle
dependencies {
implementation("io.micronaut.mongodb:micronaut-mongo-reactive")
}
// application.yml
mongodb:
uri: mongodb://localhost:27017/mydb
// Repository定义
import io.micronaut.data.mongodb.annotation.MongoRepository;
import io.micronaut.data.repository.reactive.ReactorCrudRepository;
@MongoRepository
public interface ProductRepository extends ReactorCrudRepository<Product, String> {
Flux<Product> findByCategory(String category);
Mono<Product> findByName(String name);
Flux<Product> findByPriceBetween(Double min, Double max);
}
// 使用示例
@Controller("/products")
public class ProductController {
private final ProductRepository productRepository;
public ProductController(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Get
public Flux<Product> listProducts() {
return productRepository.findAll();
}
@Get("/category/{category}")
public Flux<Product> getByCategory(String category) {
return productRepository.findByCategory(category);
}
@Post
public Mono<Product> createProduct(@Body Product product) {
return productRepository.save(product);
}
}
---
6.2 消息驱动
01.消息驱动架构
a.核心概念
消息驱动架构通过异步消息传递实现服务间解耦,提升系统的可扩展性和容错能力。Micronaut提供统一的消息抽象层,支持多种消息中间件,包括Kafka、RabbitMQ、NATS等,简化消息驱动应用的开发。
b.主要优势
a.异步解耦
生产者和消费者通过消息队列解耦,无需直接依赖,提升系统灵活性和可维护性。
b.削峰填谷
消息队列缓冲突发流量,消费者按自身处理能力消费消息,避免系统过载。
c.可靠传递
消息持久化和确认机制保证消息不丢失,支持事务和重试策略。
02.Kafka集成
a.依赖配置
a.功能说明
Micronaut Kafka模块提供声明式的生产者和消费者注解,简化Kafka客户端的使用,支持响应式流处理。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.kafka:micronaut-kafka")
}
// application.yml
kafka:
bootstrap:
servers: localhost:9092
producers:
default:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
consumers:
default:
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
group-id: micronaut-group
auto-offset-reset: earliest
---
b.消息生产者
a.同步生产者
a.功能说明
使用@KafkaClient注解定义生产者接口,Micronaut自动生成实现类,支持同步和异步发送消息。
b.代码示例
---
import io.micronaut.configuration.kafka.annotation.KafkaClient;
import io.micronaut.configuration.kafka.annotation.KafkaKey;
import io.micronaut.configuration.kafka.annotation.Topic;
@KafkaClient
public interface OrderProducer {
// 发送消息到指定主题
@Topic("orders")
void sendOrder(@KafkaKey String orderId, Order order);
// 发送带分区键的消息
@Topic("orders")
void sendOrderWithKey(@KafkaKey String key, Order order);
}
// 使用示例
@Controller("/orders")
public class OrderController {
private final OrderProducer orderProducer;
public OrderController(OrderProducer orderProducer) {
this.orderProducer = orderProducer;
}
@Post
public HttpResponse<String> createOrder(@Body Order order) {
String orderId = UUID.randomUUID().toString();
order.setId(orderId);
orderProducer.sendOrder(orderId, order);
return HttpResponse.ok("Order created: " + orderId);
}
}
---
b.响应式生产者
a.功能说明
生产者方法可以返回响应式类型,实现完全非阻塞的消息发送,适用于高吞吐量场景。
b.代码示例
---
import reactor.core.publisher.Mono;
import org.apache.kafka.clients.producer.RecordMetadata;
@KafkaClient
public interface ReactiveOrderProducer {
// 返回Mono,异步发送
@Topic("orders")
Mono<RecordMetadata> sendOrder(@KafkaKey String orderId, Order order);
// 批量发送
@Topic("orders")
Flux<RecordMetadata> sendOrders(Flux<Order> orders);
}
// 使用示例
@Controller("/reactive-orders")
public class ReactiveOrderController {
private final ReactiveOrderProducer orderProducer;
public ReactiveOrderController(ReactiveOrderProducer orderProducer) {
this.orderProducer = orderProducer;
}
@Post
public Mono<HttpResponse<String>> createOrder(@Body Order order) {
String orderId = UUID.randomUUID().toString();
order.setId(orderId);
return orderProducer.sendOrder(orderId, order)
.map(metadata -> HttpResponse.ok(
"Order sent to partition " + metadata.partition() +
" at offset " + metadata.offset()
))
.onErrorResume(e -> Mono.just(
HttpResponse.serverError("Failed to send order: " + e.getMessage())
));
}
@Post("/batch")
public Mono<HttpResponse<String>> createOrders(@Body List<Order> orders) {
return orderProducer.sendOrders(Flux.fromIterable(orders))
.collectList()
.map(results -> HttpResponse.ok(
"Sent " + results.size() + " orders"
));
}
}
---
c.消息消费者
a.基础消费者
a.功能说明
使用@KafkaListener注解定义消费者,自动订阅主题并处理消息,支持多种消息确认模式。
b.代码示例
---
import io.micronaut.configuration.kafka.annotation.KafkaListener;
import io.micronaut.configuration.kafka.annotation.OffsetReset;
import io.micronaut.configuration.kafka.annotation.Topic;
import io.micronaut.messaging.annotation.MessageBody;
import io.micronaut.messaging.annotation.MessageHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@KafkaListener(
groupId = "order-processor",
offsetReset = OffsetReset.EARLIEST
)
public class OrderConsumer {
private static final Logger LOG = LoggerFactory.getLogger(OrderConsumer.class);
// 消费单条消息
@Topic("orders")
public void processOrder(@MessageBody Order order,
@MessageHeader("kafka_key") String key,
@MessageHeader("kafka_offset") long offset) {
LOG.info("Processing order {} at offset {}", order.getId(), offset);
// 处理订单逻辑
processOrderLogic(order);
}
private void processOrderLogic(Order order) {
// 业务处理
LOG.info("Order processed: {}", order);
}
}
---
b.批量消费者
a.功能说明
消费者可以批量接收消息,提高处理效率,适用于批处理场景。
b.代码示例
---
import io.micronaut.configuration.kafka.annotation.KafkaListener;
import io.micronaut.configuration.kafka.annotation.Topic;
import java.util.List;
@KafkaListener(
groupId = "batch-processor",
batch = true
)
public class BatchOrderConsumer {
private static final Logger LOG = LoggerFactory.getLogger(BatchOrderConsumer.class);
// 批量消费消息
@Topic("orders")
public void processBatch(List<Order> orders) {
LOG.info("Processing batch of {} orders", orders.size());
orders.forEach(order -> {
// 批量处理逻辑
LOG.info("Processing order: {}", order.getId());
});
LOG.info("Batch processing completed");
}
}
---
c.响应式消费者
a.功能说明
消费者方法可以返回响应式类型,实现背压控制和流式处理。
b.代码示例
---
import reactor.core.publisher.Mono;
@KafkaListener(groupId = "reactive-processor")
public class ReactiveOrderConsumer {
private static final Logger LOG = LoggerFactory.getLogger(ReactiveOrderConsumer.class);
@Topic("orders")
public Mono<Void> processOrder(@MessageBody Order order) {
return Mono.fromRunnable(() -> {
LOG.info("Reactive processing order: {}", order.getId());
})
.then(saveToDatabase(order))
.then(sendNotification(order))
.doOnError(e -> LOG.error("Error processing order", e))
.onErrorResume(e -> Mono.empty());
}
private Mono<Void> saveToDatabase(Order order) {
return Mono.fromRunnable(() -> {
// 保存到数据库
LOG.info("Saved order to database: {}", order.getId());
});
}
private Mono<Void> sendNotification(Order order) {
return Mono.fromRunnable(() -> {
// 发送通知
LOG.info("Notification sent for order: {}", order.getId());
});
}
}
---
03.RabbitMQ集成
a.依赖配置
a.功能说明
Micronaut RabbitMQ模块提供AMQP协议支持,实现可靠的消息传递和灵活的路由机制。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.rabbitmq:micronaut-rabbitmq")
}
// application.yml
rabbitmq:
uri: amqp://guest:guest@localhost:5672
exchanges:
- name: orders-exchange
type: topic
queues:
- name: orders-queue
durable: true
bindings:
- exchange: orders-exchange
queue: orders-queue
routing-key: order.*
---
b.消息生产者
a.功能说明
使用@RabbitClient注解定义生产者,支持交换机、路由键等AMQP特性。
b.代码示例
---
import io.micronaut.rabbitmq.annotation.RabbitClient;
import io.micronaut.rabbitmq.annotation.RabbitProperty;
@RabbitClient("orders-exchange")
public interface RabbitOrderProducer {
// 发送到指定路由键
void send(@RabbitProperty("routingKey") String routingKey, Order order);
// 发送带优先级的消息
void sendWithPriority(
@RabbitProperty("routingKey") String routingKey,
@RabbitProperty("priority") int priority,
Order order
);
}
// 使用示例
@Controller("/rabbit-orders")
public class RabbitOrderController {
private final RabbitOrderProducer producer;
public RabbitOrderController(RabbitOrderProducer producer) {
this.producer = producer;
}
@Post
public HttpResponse<String> createOrder(@Body Order order) {
producer.send("order.created", order);
return HttpResponse.ok("Order sent to RabbitMQ");
}
@Post("/urgent")
public HttpResponse<String> createUrgentOrder(@Body Order order) {
producer.sendWithPriority("order.urgent", 10, order);
return HttpResponse.ok("Urgent order sent");
}
}
---
c.消息消费者
a.功能说明
使用@RabbitListener注解定义消费者,支持消息确认、拒绝、重新入队等操作。
b.代码示例
---
import io.micronaut.rabbitmq.annotation.Queue;
import io.micronaut.rabbitmq.annotation.RabbitListener;
import com.rabbitmq.client.Channel;
import io.micronaut.messaging.Acknowledgement;
@RabbitListener
public class RabbitOrderConsumer {
private static final Logger LOG = LoggerFactory.getLogger(RabbitOrderConsumer.class);
// 自动确认模式
@Queue("orders-queue")
public void receive(Order order) {
LOG.info("Received order: {}", order.getId());
processOrder(order);
}
// 手动确认模式
@Queue(value = "orders-queue", acknowledge = "MANUAL")
public void receiveManual(Order order, Acknowledgement acknowledgement) {
try {
LOG.info("Processing order: {}", order.getId());
processOrder(order);
acknowledgement.ack();
} catch (Exception e) {
LOG.error("Error processing order", e);
acknowledgement.nack(true); // 重新入队
}
}
private void processOrder(Order order) {
// 业务处理逻辑
LOG.info("Order processed successfully: {}", order.getId());
}
}
---
04.NATS集成
a.依赖配置
a.功能说明
NATS是轻量级高性能消息系统,Micronaut提供NATS集成,适用于微服务间的快速通信。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.nats:micronaut-nats")
}
// application.yml
nats:
addresses:
- nats://localhost:4222
max-reconnect: 5
---
b.发布订阅模式
a.功能说明
NATS支持发布订阅模式,实现一对多的消息分发。
b.代码示例
---
import io.micronaut.nats.annotation.NatsClient;
import io.micronaut.nats.annotation.Subject;
// 生产者
@NatsClient
public interface EventPublisher {
@Subject("events.user")
void publishUserEvent(UserEvent event);
@Subject("events.order")
void publishOrderEvent(OrderEvent event);
}
// 消费者
import io.micronaut.nats.annotation.NatsListener;
@NatsListener
public class EventSubscriber {
private static final Logger LOG = LoggerFactory.getLogger(EventSubscriber.class);
@Subject("events.user")
public void onUserEvent(UserEvent event) {
LOG.info("Received user event: {}", event);
}
@Subject("events.order")
public void onOrderEvent(OrderEvent event) {
LOG.info("Received order event: {}", event);
}
// 通配符订阅
@Subject("events.*")
public void onAnyEvent(String subject, byte[] data) {
LOG.info("Received event on subject: {}", subject);
}
}
---
c.请求响应模式
a.功能说明
NATS支持请求响应模式,实现同步RPC调用。
b.代码示例
---
// 客户端
@NatsClient
public interface UserService {
@Subject("user.get")
User getUser(Long userId);
@Subject("user.create")
User createUser(User user);
}
// 服务端
@NatsListener
public class UserServiceImpl {
private static final Logger LOG = LoggerFactory.getLogger(UserServiceImpl.class);
@Subject("user.get")
public User getUser(Long userId) {
LOG.info("Getting user: {}", userId);
return new User(userId, "User" + userId, "[email protected]");
}
@Subject("user.create")
public User createUser(User user) {
LOG.info("Creating user: {}", user.getName());
user.setId(System.currentTimeMillis());
return user;
}
}
// 使用示例
@Controller("/nats-users")
public class NatsUserController {
private final UserService userService;
public NatsUserController(UserService userService) {
this.userService = userService;
}
@Get("/{id}")
public User getUser(Long id) {
return userService.getUser(id);
}
@Post
public User createUser(@Body User user) {
return userService.createUser(user);
}
}
---
05.消息序列化
a.JSON序列化
a.功能说明
Micronaut默认使用Jackson进行JSON序列化,支持自定义序列化配置。
b.配置示例
---
// application.yml
jackson:
serialization:
indent-output: true
deserialization:
fail-on-unknown-properties: false
// 自定义序列化器
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Replaces;
import jakarta.inject.Singleton;
@Factory
public class JacksonConfiguration {
@Singleton
@Replaces(ObjectMapper.class)
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.findAndRegisterModules();
return mapper;
}
}
---
b.自定义序列化器
a.功能说明
可以为Kafka、RabbitMQ等消息系统配置自定义序列化器,支持Avro、Protobuf等格式。
b.代码示例
---
// Kafka自定义序列化器
import org.apache.kafka.common.serialization.Serializer;
import org.apache.kafka.common.serialization.Deserializer;
public class OrderSerializer implements Serializer<Order> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public byte[] serialize(String topic, Order order) {
try {
return objectMapper.writeValueAsBytes(order);
} catch (Exception e) {
throw new RuntimeException("Failed to serialize order", e);
}
}
}
public class OrderDeserializer implements Deserializer<Order> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public Order deserialize(String topic, byte[] data) {
try {
return objectMapper.readValue(data, Order.class);
} catch (Exception e) {
throw new RuntimeException("Failed to deserialize order", e);
}
}
}
// 配置使用
// application.yml
kafka:
producers:
default:
value-serializer: com.example.OrderSerializer
consumers:
default:
value-deserializer: com.example.OrderDeserializer
---
6.3 分布式追踪
01.分布式追踪概述
a.核心概念
分布式追踪通过记录请求在微服务系统中的完整调用链路,帮助开发者理解系统行为、定位性能瓶颈和排查故障。Micronaut集成OpenTelemetry和Zipkin等主流追踪系统,提供自动化的链路追踪能力,无需侵入业务代码。
b.主要优势
a.全链路可视化
追踪系统记录请求从入口到出口的完整路径,包括服务调用关系、耗时分布和错误信息,提供直观的调用链视图。
b.性能分析
通过分析每个服务节点的响应时间,快速识别性能瓶颈,优化关键路径,提升系统整体性能。
c.故障定位
当系统出现异常时,追踪数据帮助快速定位问题发生的具体服务和调用环节,缩短故障排查时间。
02.OpenTelemetry集成
a.依赖配置
a.功能说明
OpenTelemetry是云原生计算基金会的可观测性标准,Micronaut提供原生支持,自动采集追踪、指标和日志数据。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.tracing:micronaut-tracing-opentelemetry")
implementation("io.opentelemetry:opentelemetry-exporter-zipkin")
}
// application.yml
tracing:
enabled: true
zipkin:
enabled: true
url: http://localhost:9411
sampler:
probability: 0.1
otel:
service:
name: order-service
traces:
exporter: zipkin
---
b.自动追踪
a.HTTP请求追踪
a.功能说明
Micronaut自动为所有HTTP请求创建追踪Span,记录请求方法、路径、状态码和响应时间,无需手动埋点。
b.代码示例
---
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import jakarta.inject.Inject;
@Controller("/orders")
public class OrderController {
@Inject
@Client("http://inventory-service")
private HttpClient inventoryClient;
// 自动追踪HTTP请求
@Get("/{id}")
public Order getOrder(Long id) {
// 调用下游服务,自动传播追踪上下文
String inventory = inventoryClient.toBlocking()
.retrieve("/inventory/" + id);
return new Order(id, "Order-" + id, inventory);
}
}
---
b.数据库操作追踪
a.功能说明
集成JDBC和R2DBC驱动,自动追踪数据库查询操作,记录SQL语句、执行时间和影响行数。
b.代码示例
---
import io.micronaut.data.annotation.Repository;
import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.repository.CrudRepository;
@JdbcRepository(dialect = Dialect.MYSQL)
public interface OrderRepository extends CrudRepository<Order, Long> {
// 数据库查询自动追踪
List<Order> findByStatus(String status);
// 复杂查询也会自动追踪
@Query("SELECT * FROM orders WHERE user_id = :userId AND status = :status")
List<Order> findUserOrders(Long userId, String status);
}
// 使用示例
@Controller("/orders")
public class OrderController {
private final OrderRepository orderRepository;
public OrderController(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Get("/pending")
public List<Order> getPendingOrders() {
// 数据库操作自动记录到追踪链路
return orderRepository.findByStatus("PENDING");
}
}
---
c.自定义追踪
a.手动创建Span
a.功能说明
在关键业务逻辑中手动创建Span,记录自定义操作的执行时间和上下文信息。
b.代码示例
---
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@Singleton
public class OrderService {
@Inject
private Tracer tracer;
public Order processOrder(Order order) {
// 创建自定义Span
Span span = tracer.spanBuilder("processOrder")
.setAttribute("order.id", order.getId())
.setAttribute("order.amount", order.getAmount())
.startSpan();
try (Scope scope = span.makeCurrent()) {
// 业务逻辑
validateOrder(order);
calculateTotal(order);
saveOrder(order);
span.addEvent("Order processed successfully");
return order;
} catch (Exception e) {
span.recordException(e);
span.setAttribute("error", true);
throw e;
} finally {
span.end();
}
}
private void validateOrder(Order order) {
Span span = tracer.spanBuilder("validateOrder").startSpan();
try (Scope scope = span.makeCurrent()) {
// 验证逻辑
if (order.getAmount() <= 0) {
throw new IllegalArgumentException("Invalid amount");
}
} finally {
span.end();
}
}
private void calculateTotal(Order order) {
// 计算逻辑
}
private void saveOrder(Order order) {
// 保存逻辑
}
}
---
b.添加标签和事件
a.功能说明
为Span添加自定义标签和事件,记录业务相关的元数据和关键操作节点。
b.代码示例
---
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributeKey;
@Singleton
public class PaymentService {
@Inject
private Tracer tracer;
public PaymentResult processPayment(Payment payment) {
Span span = Span.current();
// 添加业务标签
span.setAttribute("payment.method", payment.getMethod());
span.setAttribute("payment.amount", payment.getAmount());
span.setAttribute("user.id", payment.getUserId());
// 记录关键事件
span.addEvent("Payment validation started");
if (validatePayment(payment)) {
span.addEvent("Payment validated",
Attributes.of(
AttributeKey.stringKey("validation.result"), "success"
));
// 调用支付网关
span.addEvent("Calling payment gateway");
PaymentResult result = callPaymentGateway(payment);
span.setAttribute("payment.status", result.getStatus());
span.addEvent("Payment completed");
return result;
} else {
span.addEvent("Payment validation failed");
span.setAttribute("error", true);
throw new PaymentException("Validation failed");
}
}
private boolean validatePayment(Payment payment) {
return payment.getAmount() > 0 && payment.getMethod() != null;
}
private PaymentResult callPaymentGateway(Payment payment) {
return new PaymentResult("SUCCESS", payment.getAmount());
}
}
---
03.Zipkin集成
a.依赖配置
a.功能说明
Zipkin是广泛使用的分布式追踪系统,Micronaut提供开箱即用的集成支持。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.tracing:micronaut-tracing-zipkin")
}
// application.yml
tracing:
zipkin:
enabled: true
url: http://localhost:9411
sampler:
probability: 1.0
http:
path: /api/v2/spans
---
b.追踪上报
a.HTTP上报
a.功能说明
配置Zipkin服务端地址后,Micronaut自动通过HTTP POST请求上报追踪数据。
b.配置示例
---
// application.yml
tracing:
zipkin:
enabled: true
url: http://zipkin-server:9411
http:
path: /api/v2/spans
connect-timeout: 1s
read-timeout: 10s
sampler:
probability: 0.1
micronaut:
application:
name: order-service
---
b.消息队列上报
a.功能说明
支持通过Kafka上报追踪数据,避免直接依赖Zipkin服务。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.tracing:micronaut-tracing-zipkin")
implementation("io.micronaut.kafka:micronaut-kafka")
}
// application.yml
tracing:
zipkin:
enabled: true
kafka:
enabled: true
topic: zipkin
kafka:
bootstrap:
servers: localhost:9092
---
04.追踪最佳实践
a.采样策略
a.功能说明
合理配置采样率,平衡追踪覆盖率和系统性能开销。
b.配置示例
---
// application.yml
tracing:
zipkin:
sampler:
# 固定概率采样:10%的请求
probability: 0.1
# 自定义采样器
import io.micronaut.context.annotation.Factory;
import io.micronaut.tracing.brave.sampler.TraceSampler;
import brave.sampler.Sampler;
import jakarta.inject.Singleton;
@Factory
public class SamplerConfiguration {
@Singleton
public Sampler customSampler() {
// 速率限制采样:每秒最多100个追踪
return Sampler.create(0.1f);
}
}
---
b.敏感信息处理
a.功能说明
追踪数据中避免记录敏感信息,或在上报前进行脱敏处理。
b.代码示例
---
import io.micronaut.tracing.annotation.NewSpan;
import io.micronaut.tracing.annotation.SpanTag;
@Singleton
public class UserService {
// 使用@NewSpan创建追踪
@NewSpan("getUserInfo")
public User getUserInfo(
@SpanTag("user.id") Long userId,
String password // 不添加@SpanTag,避免记录密码
) {
// 业务逻辑
return userRepository.findById(userId);
}
// 脱敏处理
@NewSpan("processPayment")
public void processPayment(
@SpanTag("card.last4") String cardLast4, // 只记录后4位
String fullCardNumber // 完整卡号不记录
) {
// 支付处理
}
}
---
c.性能优化
a.异步上报
a.功能说明
追踪数据采用异步方式上报,避免阻塞业务请求。
b.配置示例
---
// application.yml
tracing:
zipkin:
enabled: true
url: http://localhost:9411
http:
# 异步发送配置
message-timeout: 1s
close-timeout: 1s
# 批量发送
max-requests: 64
queue-size: 1000
---
b.本地缓存
a.功能说明
当追踪系统不可用时,追踪数据暂存本地,避免数据丢失。
b.配置示例
---
// application.yml
tracing:
zipkin:
enabled: true
url: http://localhost:9411
http:
# 连接失败时的重试策略
connect-timeout: 1s
read-timeout: 10s
# 本地队列配置
sender:
type: http
queue-size: 10000
---
6.4 服务发现
01.服务发现概述
a.核心概念
服务发现是微服务架构的关键组件,负责动态管理服务实例的注册和查询。Micronaut支持多种服务发现机制,包括Consul、Eureka、Kubernetes等,提供自动注册、健康检查和负载均衡能力,简化微服务间的通信。
b.主要优势
a.动态服务管理
服务实例启动时自动注册到注册中心,停止时自动注销,客户端无需维护静态服务列表,支持服务的弹性伸缩。
b.健康检查
注册中心定期检查服务实例的健康状态,自动剔除不健康的实例,确保客户端只调用可用的服务。
c.负载均衡
客户端从注册中心获取多个服务实例,通过负载均衡算法分发请求,提升系统的可用性和性能。
02.Consul集成
a.依赖配置
a.功能说明
Consul是HashiCorp开发的服务网格解决方案,提供服务发现、健康检查、KV存储等功能,Micronaut提供原生支持。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.discovery:micronaut-discovery-client")
}
// application.yml
consul:
client:
registration:
enabled: true
defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
micronaut:
application:
name: order-service
---
b.服务注册
a.自动注册
a.功能说明
Micronaut应用启动时自动向Consul注册服务实例,包括服务名称、IP地址、端口和元数据。
b.配置示例
---
// application.yml
consul:
client:
registration:
enabled: true
# 服务注册配置
prefer-ip-address: true
ip-address: ${HOSTNAME:localhost}
port: ${SERVER_PORT:8080}
# 健康检查配置
check:
enabled: true
interval: 10s
http: true
path: /health
# 服务标签
tags:
- version=1.0
- environment=production
defaultZone: localhost:8500
micronaut:
application:
name: order-service
---
b.手动注册
a.功能说明
通过编程方式手动控制服务注册和注销,适用于特殊场景。
b.代码示例
---
import io.micronaut.discovery.ServiceInstance;
import io.micronaut.discovery.consul.ConsulClient;
import io.micronaut.discovery.consul.client.v1.CatalogEntry;
import io.micronaut.discovery.consul.client.v1.NewServiceEntry;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@Singleton
public class ServiceRegistration {
@Inject
private ConsulClient consulClient;
public void registerService() {
NewServiceEntry entry = new NewServiceEntry(
"order-service",
"order-service-1",
"localhost",
8080
);
entry.setTags(Arrays.asList("version=1.0", "region=us-east"));
// 配置健康检查
NewServiceEntry.Check check = new NewServiceEntry.Check();
check.setHttp("http://localhost:8080/health");
check.setInterval("10s");
entry.setCheck(check);
consulClient.register(entry);
}
public void deregisterService(String serviceId) {
consulClient.deregister(serviceId);
}
}
---
c.服务发现
a.声明式客户端
a.功能说明
使用@Client注解声明式调用远程服务,Micronaut自动从Consul查询服务实例并进行负载均衡。
b.代码示例
---
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
// 通过服务名称调用
@Client("inventory-service")
public interface InventoryClient {
@Get("/inventory/{productId}")
Inventory getInventory(Long productId);
@Get("/inventory/check/{productId}")
boolean checkStock(Long productId);
}
// 使用示例
@Controller("/orders")
public class OrderController {
private final InventoryClient inventoryClient;
public OrderController(InventoryClient inventoryClient) {
this.inventoryClient = inventoryClient;
}
@Post
public HttpResponse<Order> createOrder(@Body Order order) {
// 自动服务发现和负载均衡
boolean inStock = inventoryClient.checkStock(order.getProductId());
if (inStock) {
Inventory inventory = inventoryClient.getInventory(order.getProductId());
order.setInventoryInfo(inventory);
return HttpResponse.ok(order);
} else {
return HttpResponse.badRequest();
}
}
}
---
b.编程式发现
a.功能说明
通过DiscoveryClient API编程式查询服务实例,获取更多控制权。
b.代码示例
---
import io.micronaut.discovery.DiscoveryClient;
import io.micronaut.discovery.ServiceInstance;
import io.micronaut.http.client.HttpClient;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import reactor.core.publisher.Flux;
@Singleton
public class ServiceDiscoveryExample {
@Inject
private DiscoveryClient discoveryClient;
public void discoverServices() {
// 获取所有服务名称
Flux<String> serviceIds = Flux.from(discoveryClient.getServiceIds());
serviceIds.subscribe(serviceId -> {
System.out.println("Found service: " + serviceId);
});
// 获取特定服务的所有实例
Flux<ServiceInstance> instances = Flux.from(
discoveryClient.getInstances("order-service")
);
instances.subscribe(instance -> {
System.out.println("Service instance: " +
instance.getId() + " at " +
instance.getHost() + ":" +
instance.getPort());
});
}
public String callService(String serviceName, String path) {
// 获取服务实例
ServiceInstance instance = Flux.from(
discoveryClient.getInstances(serviceName)
).blockFirst();
if (instance != null) {
String url = "http://" + instance.getHost() + ":" +
instance.getPort() + path;
HttpClient client = HttpClient.create(instance.getURI().toURL());
return client.toBlocking().retrieve(path);
}
throw new RuntimeException("Service not found: " + serviceName);
}
}
---
03.Eureka集成
a.依赖配置
a.功能说明
Eureka是Netflix开源的服务注册中心,广泛应用于Spring Cloud生态,Micronaut提供兼容支持。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.discovery:micronaut-discovery-client")
}
// application.yml
eureka:
client:
registration:
enabled: true
defaultZone: "${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}/eureka"
micronaut:
application:
name: payment-service
---
b.服务注册配置
a.功能说明
配置Eureka客户端的注册参数,包括实例ID、租约续期时间等。
b.配置示例
---
// application.yml
eureka:
client:
registration:
enabled: true
prefer-ip-address: true
# 实例配置
instance-id: ${micronaut.application.name}:${random.value}
lease-renewal-interval: 30s
lease-expiration-duration: 90s
# 元数据
metadata:
version: 1.0.0
region: us-west
defaultZone: http://localhost:8761/eureka
instance:
hostname: ${HOSTNAME:localhost}
port: ${SERVER_PORT:8080}
---
c.服务调用
a.功能说明
通过服务名称调用Eureka注册的服务,支持客户端负载均衡。
b.代码示例
---
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.client.annotation.Client;
@Client("payment-service")
public interface PaymentClient {
@Post("/payments")
PaymentResult processPayment(@Body Payment payment);
@Get("/payments/{id}")
Payment getPayment(Long id);
}
// 使用示例
@Controller("/checkout")
public class CheckoutController {
private final PaymentClient paymentClient;
public CheckoutController(PaymentClient paymentClient) {
this.paymentClient = paymentClient;
}
@Post
public HttpResponse<CheckoutResult> checkout(@Body CheckoutRequest request) {
Payment payment = new Payment(
request.getAmount(),
request.getPaymentMethod()
);
// 调用支付服务
PaymentResult result = paymentClient.processPayment(payment);
if ("SUCCESS".equals(result.getStatus())) {
return HttpResponse.ok(new CheckoutResult(true, result.getTransactionId()));
} else {
return HttpResponse.badRequest(new CheckoutResult(false, null));
}
}
}
---
04.Kubernetes服务发现
a.依赖配置
a.功能说明
在Kubernetes环境中,Micronaut可以直接使用Kubernetes API进行服务发现,无需额外的注册中心。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.kubernetes:micronaut-kubernetes-discovery-client")
}
// application.yml
kubernetes:
client:
discovery:
enabled: true
mode: service
namespace: default
micronaut:
application:
name: user-service
---
b.服务发现配置
a.���能说明
配置Kubernetes服务发现的命名空间、标签选择器等参数。
b.配置示例
---
// application.yml
kubernetes:
client:
discovery:
enabled: true
mode: service
namespace: production
# 标签选择器
labels:
app: microservices
tier: backend
# 包含的服务
includes:
- order-service
- inventory-service
# 排除的服务
excludes:
- legacy-service
---
c.服务调用
a.功能说明
通过Kubernetes Service名称调用服务,Micronaut自动解析为Pod IP地址。
b.代码示例
---
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
// 使用Kubernetes Service名称
@Client("http://user-service")
public interface UserClient {
@Get("/users/{id}")
User getUser(Long id);
@Get("/users/search")
List<User> searchUsers(String query);
}
// 使用示例
@Controller("/profile")
public class ProfileController {
private final UserClient userClient;
public ProfileController(UserClient userClient) {
this.userClient = userClient;
}
@Get("/{userId}")
public User getUserProfile(Long userId) {
// Kubernetes自动解析服务名称
return userClient.getUser(userId);
}
}
---
05.负载均衡策略
a.内置策略
a.轮询策略
a.功能说明
默认的负载均衡策略,按顺序轮流调用服务实例。
b.配置示例
---
// application.yml
micronaut:
http:
client:
load-balancer:
# 轮询策略(默认)
strategy: round-robin
---
b.随机策略
a.功能说明
随机选择服务实例,适用于实例性能相近的场景。
b.配置示例
---
// application.yml
micronaut:
http:
client:
load-balancer:
strategy: random
---
b.自定义策略
a.功能说明
实现LoadBalancer接口,自定义负载均衡算法,如加权轮询、最少连接等。
b.代码示例
---
import io.micronaut.context.annotation.Requires;
import io.micronaut.discovery.ServiceInstance;
import io.micronaut.http.client.loadbalance.DiscoveryClientLoadBalancer;
import io.micronaut.http.client.loadbalance.ServiceInstanceList;
import jakarta.inject.Singleton;
import reactor.core.publisher.Flux;
@Singleton
@Requires(property = "micronaut.http.client.load-balancer.strategy", value = "weighted")
public class WeightedLoadBalancer extends DiscoveryClientLoadBalancer {
public WeightedLoadBalancer(ServiceInstanceList serviceInstanceList) {
super(serviceInstanceList);
}
@Override
public Flux<ServiceInstance> select(Object discriminator) {
return Flux.from(serviceInstanceList.getInstances())
.collectList()
.flatMapMany(instances -> {
if (instances.isEmpty()) {
return Flux.empty();
}
// 根据权重选择实例
ServiceInstance selected = selectByWeight(instances);
return Flux.just(selected);
});
}
private ServiceInstance selectByWeight(List<ServiceInstance> instances) {
int totalWeight = instances.stream()
.mapToInt(i -> getWeight(i))
.sum();
int random = new Random().nextInt(totalWeight);
int currentWeight = 0;
for (ServiceInstance instance : instances) {
currentWeight += getWeight(instance);
if (random < currentWeight) {
return instance;
}
}
return instances.get(0);
}
private int getWeight(ServiceInstance instance) {
return instance.getMetadata()
.get("weight")
.map(Integer::parseInt)
.orElse(1);
}
}
---
6.5 配置中心集成
01.配置中心概述
a.核心概念
配置中心提供集中化的配置管理,支持配置的动态更新、版本控制和环境隔离。Micronaut支持多种配置中心,包括Consul、Spring Cloud Config、AWS Parameter Store等,实现配置与代码的分离,简化多环境部署和配置管理。
b.主要优势
a.集中管理
所有服务的配置集中存储在配置中心,统一管理和维护,避免配置分散在各个服务中,降低管理复杂度。
b.动态更新
配置变更后无需重启服务即可生效,支持热更新,提升系统的灵活性和可维护性。
c.环境隔离
不同环境使用不同的配置,如开发、测试、生产环境,通过命名空间或标签实现配置隔离。
02.Consul配置中心
a.依赖配置
a.功能说明
Consul的KV存储可以作为配置中心,Micronaut自动从Consul加载配置,支持配置的动态刷新。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.discovery:micronaut-discovery-client")
}
// bootstrap.yml
micronaut:
application:
name: order-service
config-client:
enabled: true
consul:
client:
config:
enabled: true
format: YAML
path: /config
defaultZone: localhost:8500
---
b.配置存储
a.功能说明
在Consul KV存储中按照约定的路径存储配置,Micronaut自动加载对应的配置。
b.配置示例
---
# Consul KV路径结构
/config/application/data # 所有应用共享配置
/config/order-service/data # order-service专属配置
/config/order-service,prod/data # order-service生产环境配置
# 配置内容示例(YAML格式)
# /config/order-service/data
datasources:
default:
url: jdbc:mysql://localhost:3306/orders
username: root
password: secret
driver-class-name: com.mysql.cj.jdbc.Driver
kafka:
bootstrap:
servers: localhost:9092
# 使用配置
import io.micronaut.context.annotation.Value;
import jakarta.inject.Singleton;
@Singleton
public class DatabaseConfig {
@Value("${datasources.default.url}")
private String dbUrl;
@Value("${datasources.default.username}")
private String dbUsername;
public void printConfig() {
System.out.println("Database URL: " + dbUrl);
System.out.println("Database User: " + dbUsername);
}
}
---
c.动态刷新
a.功能说明
配置变更后,Micronaut自动检测并刷新配置,使用@Refreshable注解的Bean会重新创建。
b.代码示例
---
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.runtime.context.scope.Refreshable;
@Refreshable
@ConfigurationProperties("app")
public class AppConfig {
private String name;
private int maxConnections;
private boolean debugMode;
// Getters and Setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMaxConnections() {
return maxConnections;
}
public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}
public boolean isDebugMode() {
return debugMode;
}
public void setDebugMode(boolean debugMode) {
this.debugMode = debugMode;
}
}
// 使用配置
@Controller("/config")
public class ConfigController {
private final AppConfig appConfig;
public ConfigController(AppConfig appConfig) {
this.appConfig = appConfig;
}
@Get("/current")
public Map<String, Object> getCurrentConfig() {
return Map.of(
"name", appConfig.getName(),
"maxConnections", appConfig.getMaxConnections(),
"debugMode", appConfig.isDebugMode()
);
}
}
---
03.Spring Cloud Config集成
a.依赖配置
a.功能说明
Spring Cloud Config是广泛使用的配置中心,Micronaut提供兼容支持,可以复用现有的Config Server。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.spring:micronaut-spring-cloud-config-client")
}
// bootstrap.yml
micronaut:
application:
name: payment-service
config-client:
enabled: true
spring:
cloud:
config:
enabled: true
uri: http://localhost:8888
# 配置文件名称
name: ${micronaut.application.name}
# 环境
profile: production
# 分支
label: master
# 失败快速启动
fail-fast: true
# 重试配置
retry:
max-attempts: 6
initial-interval: 1000
---
b.配置文件
a.功能说明
在Config Server的Git仓库中存储配置文件,按照应用名称和环境组织。
b.配置示例
---
# Git仓库结构
config-repo/
├── application.yml # 所有应用共享
├── payment-service.yml # payment-service默认配置
├── payment-service-dev.yml # 开发环境
├── payment-service-prod.yml # 生产环境
# payment-service-prod.yml
server:
port: 8080
datasources:
default:
url: jdbc:postgresql://prod-db:5432/payments
username: ${DB_USER}
password: ${DB_PASSWORD}
security:
jwt:
secret: ${JWT_SECRET}
expiration: 3600
# 使用配置
import io.micronaut.context.annotation.ConfigurationProperties;
@ConfigurationProperties("security.jwt")
public class JwtConfig {
private String secret;
private int expiration;
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public int getExpiration() {
return expiration;
}
public void setExpiration(int expiration) {
this.expiration = expiration;
}
}
---
c.配置刷新
a.功能说明
通过RefreshEvent触发配置刷新,或使用/refresh端点手动刷新。
b.代码示例
---
import io.micronaut.context.event.ApplicationEventPublisher;
import io.micronaut.runtime.context.scope.refresh.RefreshEvent;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import jakarta.inject.Inject;
@Controller("/admin")
public class AdminController {
@Inject
private ApplicationEventPublisher<RefreshEvent> eventPublisher;
// 手动触发配置刷新
@Post("/refresh")
public String refreshConfig() {
eventPublisher.publishEvent(new RefreshEvent());
return "Configuration refreshed";
}
}
// 监听配置刷新事件
import io.micronaut.context.event.ApplicationEventListener;
import io.micronaut.runtime.context.scope.refresh.RefreshEvent;
import jakarta.inject.Singleton;
@Singleton
public class ConfigRefreshListener implements ApplicationEventListener<RefreshEvent> {
@Override
public void onApplicationEvent(RefreshEvent event) {
System.out.println("Configuration refreshed at: " + System.currentTimeMillis());
// 执行刷新后的逻辑
}
}
---
04.AWS Parameter Store集成
a.依赖配置
a.功能说明
AWS Systems Manager Parameter Store提供安全的配置和密钥管理,Micronaut支持从Parameter Store加载配置。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.aws:micronaut-aws-parameter-store")
}
// bootstrap.yml
micronaut:
application:
name: user-service
config-client:
enabled: true
aws:
client:
system-manager:
parameterstore:
enabled: true
# 参数路径前缀
path: /config/user-service
# 是否使用安全字符串
use-secure-strings: true
region: us-east-1
---
b.参数存储
a.功能说明
在AWS Parameter Store中按照层级路径存储参数,支持普通字符串和加密字符串。
b.配置示例
---
# AWS Parameter Store参数结构
/config/user-service/database/url
/config/user-service/database/username
/config/user-service/database/password # SecureString类型
# 使用AWS CLI创建参数
aws ssm put-parameter \
--name "/config/user-service/database/url" \
--value "jdbc:mysql://prod-db:3306/users" \
--type "String"
aws ssm put-parameter \
--name "/config/user-service/database/password" \
--value "secret-password" \
--type "SecureString"
# 在代码中使用
import io.micronaut.context.annotation.Value;
import jakarta.inject.Singleton;
@Singleton
public class DatabaseService {
@Value("${database.url}")
private String dbUrl;
@Value("${database.username}")
private String dbUsername;
@Value("${database.password}")
private String dbPassword;
public void connect() {
System.out.println("Connecting to: " + dbUrl);
// 使用加密的密码连接数据库
}
}
---
c.动态配置
a.功能说明
Parameter Store支持配置的版本管理和动态更新,Micronaut可以定期轮询获取最新配置。
b.配置示例
---
// bootstrap.yml
aws:
client:
system-manager:
parameterstore:
enabled: true
path: /config/user-service
# 启用自动刷新
watch: true
# 刷新间隔
watch-delay: 60s
# 使用@Refreshable注解
import io.micronaut.runtime.context.scope.Refreshable;
import io.micronaut.context.annotation.ConfigurationProperties;
@Refreshable
@ConfigurationProperties("feature")
public class FeatureConfig {
private boolean newFeatureEnabled;
private int maxRetries;
public boolean isNewFeatureEnabled() {
return newFeatureEnabled;
}
public void setNewFeatureEnabled(boolean newFeatureEnabled) {
this.newFeatureEnabled = newFeatureEnabled;
}
public int getMaxRetries() {
return maxRetries;
}
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
}
---
05.配置最佳实践
a.敏感信息管理
a.功能说明
敏感信息如密码、密钥应使用加密存储,避免明文配置。
b.代码示例
---
# 使用环境变量
datasources:
default:
url: jdbc:mysql://localhost:3306/db
username: ${DB_USER}
password: ${DB_PASSWORD}
# 使用Consul加密
# 在Consul中存储加密后的值
consul kv put /config/order-service/db-password "encrypted:AES:base64value"
# 使用AWS Secrets Manager
// build.gradle
dependencies {
implementation("io.micronaut.aws:micronaut-aws-secretsmanager")
}
// bootstrap.yml
aws:
client:
secrets-manager:
enabled: true
region: us-east-1
# 在代码中使用
import io.micronaut.context.annotation.Value;
@Singleton
public class SecretService {
@Value("${/secret/database/password}")
private String dbPassword;
public void useSecret() {
// 自动从Secrets Manager获取密钥
System.out.println("Using secret: " + dbPassword);
}
}
---
b.配置优先级
a.功能说明
Micronaut支持多种配置源,按照优先级合并配置,高优先级覆盖低优先级。
b.配置示例
---
# 配置优先级(从高到低)
# 1. 命令行参数
java -jar app.jar --server.port=9090
# 2. 系统属性
java -Dserver.port=9090 -jar app.jar
# 3. 环境变量
export SERVER_PORT=9090
# 4. 配置中心(Consul/Config Server)
# 5. application.yml
# 6. application-{env}.yml
# 配置合并示例
import io.micronaut.context.env.Environment;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@Singleton
public class ConfigService {
@Inject
private Environment environment;
public void printConfigSources() {
environment.getPropertySources().forEach(source -> {
System.out.println("Config source: " + source.getName());
});
}
public String getProperty(String key) {
return environment.getProperty(key, String.class).orElse("default");
}
}
---
c.配置验证
a.功能说明
使用Bean Validation验证配置的有效性,启动时检查配置错误。
b.代码示例
---
import io.micronaut.context.annotation.ConfigurationProperties;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
@ConfigurationProperties("app")
public class ValidatedConfig {
@NotBlank(message = "Application name is required")
private String name;
@Min(value = 1, message = "Max connections must be at least 1")
private int maxConnections;
@Pattern(regexp = "^(dev|test|prod)$", message = "Invalid environment")
private String environment;
// Getters and Setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMaxConnections() {
return maxConnections;
}
public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}
public String getEnvironment() {
return environment;
}
public void setEnvironment(String environment) {
this.environment = environment;
}
}
// 配置验证失败时,应用启动会抛出异常
// application.yml
app:
name: "" # 验证失败:name不能为空
max-connections: 0 # 验证失败:必须至少为1
environment: "invalid" # 验证失败:必须是dev/test/prod
---
6.6 安全认证
01.安全认证概述
a.核心概念
Micronaut Security提供全面的安全认证和授权功能,支持JWT、OAuth 2.0、LDAP等多种认证方式,内置CSRF防护、会话管理等安全特性,简化应用的安全实现。
b.主要优势
a.多种认证方式
支持基于Token的认证、基于Session的认证、OAuth 2.0社交登录等多种认证机制,满足不同场景需求。
b.声明式安全
使用注解声明式配置安全规则,如@Secured、@RolesAllowed等,代码简洁清晰,易于维护。
c.细粒度授权
支持基于角色和权限的访问控制,可以精确控制到方法级别的访问权限。
02.JWT认证
a.依赖配置
a.功能说明
JWT是无状态的Token认证方式,适用于微服务和移动应用,Micronaut提供完整的JWT支持。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.security:micronaut-security-jwt")
}
// application.yml
micronaut:
security:
enabled: true
token:
jwt:
enabled: true
signatures:
secret:
generator:
secret: "${JWT_SECRET:pleaseChangeThisSecretKey}"
jws-algorithm: HS256
generator:
access-token:
expiration: 3600
---
b.用户认证
a.功能说明
实现AuthenticationProvider接口,自定义用户认证逻辑,验证用户名和密码。
b.代码示例
---
import io.micronaut.http.HttpRequest;
import io.micronaut.security.authentication.*;
import io.reactivex.rxjava3.core.Flowable;
import jakarta.inject.Singleton;
import org.reactivestreams.Publisher;
@Singleton
public class CustomAuthenticationProvider implements AuthenticationProvider<HttpRequest<?>> {
@Override
public Publisher<AuthenticationResponse> authenticate(
HttpRequest<?> httpRequest,
AuthenticationRequest<?, ?> authenticationRequest
) {
String username = authenticationRequest.getIdentity().toString();
String password = authenticationRequest.getSecret().toString();
// 验证用户名和密码
if ("admin".equals(username) && "password".equals(password)) {
return Flowable.just(
AuthenticationResponse.success(
username,
List.of("ROLE_ADMIN", "ROLE_USER")
)
);
} else if ("user".equals(username) && "password".equals(password)) {
return Flowable.just(
AuthenticationResponse.success(
username,
List.of("ROLE_USER")
)
);
}
return Flowable.just(
AuthenticationResponse.failure(
AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH
)
);
}
}
---
c.登录端点
a.功能说明
创建登录端点,接收用户凭证并返回JWT Token。
b.代码示例
---
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.rules.SecurityRule;
@Controller("/auth")
@Secured(SecurityRule.IS_ANONYMOUS)
public class AuthController {
// 登录端点由Micronaut Security自动提供
// POST /login
// {
// "username": "admin",
// "password": "password"
// }
// 返回:
// {
// "username": "admin",
// "roles": ["ROLE_ADMIN", "ROLE_USER"],
// "access_token": "eyJhbGciOiJIUzI1NiJ9...",
// "token_type": "Bearer",
// "expires_in": 3600
// }
}
// 受保护的端点
import io.micronaut.security.authentication.Authentication;
@Controller("/api")
public class SecureController {
@Get("/profile")
@Secured(SecurityRule.IS_AUTHENTICATED)
public Map<String, Object> getProfile(Authentication authentication) {
return Map.of(
"username", authentication.getName(),
"roles", authentication.getRoles()
);
}
@Get("/admin")
@Secured("ROLE_ADMIN")
public String adminOnly() {
return "Admin access granted";
}
@Get("/user")
@Secured({"ROLE_USER", "ROLE_ADMIN"})
public String userAccess() {
return "User access granted";
}
}
---
d.Token刷新
a.功能说明
实现Token刷新机制,延长用户会话时间,无需重新登录。
b.代码示例
---
// application.yml
micronaut:
security:
token:
jwt:
generator:
refresh-token:
enabled: true
secret: "${JWT_REFRESH_SECRET:anotherSecretKey}"
// 刷新Token端点
import io.micronaut.http.annotation.Post;
import io.micronaut.http.annotation.Body;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.rules.SecurityRule;
import io.micronaut.security.token.jwt.generator.JwtTokenGenerator;
import io.micronaut.security.token.jwt.validator.JwtTokenValidator;
@Controller("/auth")
public class TokenController {
private final JwtTokenGenerator tokenGenerator;
public TokenController(JwtTokenGenerator tokenGenerator) {
this.tokenGenerator = tokenGenerator;
}
@Post("/refresh")
@Secured(SecurityRule.IS_ANONYMOUS)
public Map<String, Object> refresh(@Body Map<String, String> request) {
String refreshToken = request.get("refresh_token");
// 验证refresh token并生成新的access token
// 实际实现需要验证refresh token的有效性
Optional<String> newToken = tokenGenerator.generateToken(
Map.of("username", "user", "roles", List.of("ROLE_USER"))
);
if (newToken.isPresent()) {
return Map.of(
"access_token", newToken.get(),
"token_type", "Bearer",
"expires_in", 3600
);
}
throw new RuntimeException("Invalid refresh token");
}
}
---
03.OAuth 2.0集成
a.依赖配置
a.功能说明
Micronaut支持OAuth 2.0授权码流程,可以集成Google、GitHub、Facebook等第三方登录。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.security:micronaut-security-oauth2")
}
// application.yml
micronaut:
security:
oauth2:
enabled: true
clients:
google:
client-id: ${GOOGLE_CLIENT_ID}
client-secret: ${GOOGLE_CLIENT_SECRET}
scopes:
- email
- profile
github:
client-id: ${GITHUB_CLIENT_ID}
client-secret: ${GITHUB_CLIENT_SECRET}
scopes:
- user:email
- read:user
---
b.OAuth登录
a.功能说明
配置OAuth 2.0登录端点,用户通过第三方平台完成认证。
b.代码示例
---
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.rules.SecurityRule;
import io.micronaut.security.authentication.Authentication;
@Controller
public class OAuthController {
// OAuth登录端点由Micronaut自动提供
// GET /oauth/login/google - 跳转到Google登录
// GET /oauth/login/github - 跳转到GitHub登录
// OAuth回调端点
// GET /oauth/callback/google
// GET /oauth/callback/github
@Get("/")
@Secured(SecurityRule.IS_ANONYMOUS)
public String home() {
return """
<html>
<body>
<h1>Login</h1>
<a href="/oauth/login/google">Login with Google</a><br>
<a href="/oauth/login/github">Login with GitHub</a>
</body>
</html>
""";
}
@Get("/dashboard")
@Secured(SecurityRule.IS_AUTHENTICATED)
public Map<String, Object> dashboard(Authentication authentication) {
return Map.of(
"message", "Welcome to dashboard",
"user", authentication.getName(),
"attributes", authentication.getAttributes()
);
}
}
---
c.自定义用户映射
a.功能说明
OAuth认证成功后,将第三方用户信息映射到应用的用户模型。
b.代码示例
---
import io.micronaut.context.annotation.Replaces;
import io.micronaut.security.oauth2.endpoint.authorization.state.State;
import io.micronaut.security.oauth2.endpoint.token.response.OauthAuthenticationMapper;
import io.micronaut.security.oauth2.endpoint.token.response.TokenResponse;
import io.micronaut.security.authentication.AuthenticationResponse;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
@Singleton
@Named("google")
public class GoogleUserMapper implements OauthAuthenticationMapper {
@Override
public Publisher<AuthenticationResponse> createAuthenticationResponse(
TokenResponse tokenResponse,
State state
) {
// 从tokenResponse获取用户信息
Map<String, Object> claims = tokenResponse.getClaims();
String email = (String) claims.get("email");
String name = (String) claims.get("name");
// 创建应用用户
return Mono.just(
AuthenticationResponse.success(
email,
List.of("ROLE_USER"),
Map.of(
"name", name,
"email", email,
"provider", "google"
)
)
);
}
}
---
04.基于角色的访问控制
a.方法级安全
a.功能说明
使用@Secured注解保护方法,限制只有特定角色的用户才能访问。
b.代码示例
---
import io.micronaut.http.annotation.*;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.rules.SecurityRule;
import jakarta.annotation.security.RolesAllowed;
@Controller("/orders")
public class OrderController {
// 允许所有认证用户访问
@Get
@Secured(SecurityRule.IS_AUTHENTICATED)
public List<Order> listOrders() {
return orderService.findAll();
}
// 只允许ADMIN角色访问
@Delete("/{id}")
@Secured("ROLE_ADMIN")
public HttpResponse<?> deleteOrder(Long id) {
orderService.delete(id);
return HttpResponse.noContent();
}
// 允许多个角色访问
@Put("/{id}")
@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
public Order updateOrder(Long id, @Body Order order) {
return orderService.update(id, order);
}
// 使用JSR-250注解
@Post
@RolesAllowed({"ADMIN", "USER"})
public Order createOrder(@Body Order order) {
return orderService.create(order);
}
// 允许匿名访问
@Get("/public")
@Secured(SecurityRule.IS_ANONYMOUS)
public String publicEndpoint() {
return "Public access";
}
}
---
b.表达式安全
a.功能说明
使用SpEL表��式实现复杂的访问控制逻辑。
b.代码示例
---
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.authentication.Authentication;
@Controller("/documents")
public class DocumentController {
// 自定义安全规则
@Get("/{id}")
@Secured("#{authentication.name == #id or hasRole('ADMIN')}")
public Document getDocument(Long id, Authentication authentication) {
return documentService.findById(id);
}
// 基于方法参数的安全检查
@Put("/{id}")
public Document updateDocument(
Long id,
@Body Document document,
Authentication authentication
) {
// 检查用户是否有权限修改文档
Document existing = documentService.findById(id);
if (!existing.getOwnerId().equals(authentication.getName()) &&
!authentication.getRoles().contains("ROLE_ADMIN")) {
throw new ForbiddenException("Access denied");
}
return documentService.update(id, document);
}
}
---
05.安全最佳实践
a.密码加密
a.功能说明
使用BCrypt等强加密算法存储密码,避免明文存储。
b.代码示例
---
import io.micronaut.security.authentication.providers.PasswordEncoder;
import jakarta.inject.Singleton;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Singleton
public class UserService {
private final PasswordEncoder passwordEncoder;
public UserService(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
public User registerUser(String username, String rawPassword) {
// 加密密码
String encodedPassword = passwordEncoder.encode(rawPassword);
User user = new User();
user.setUsername(username);
user.setPassword(encodedPassword);
return userRepository.save(user);
}
public boolean verifyPassword(String rawPassword, String encodedPassword) {
return passwordEncoder.matches(rawPassword, encodedPassword);
}
}
---
b.CSRF防护
a.功能说明
启用CSRF防护,防止跨站请求伪造攻击。
b.配置示例
---
// application.yml
micronaut:
security:
csrf:
enabled: true
# CSRF Token生成策略
token-generator-class: io.micronaut.security.token.jwt.generator.DefaultJwtTokenGenerator
// 在表单中包含CSRF Token
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.security.csrf.CsrfToken;
@Controller("/form")
public class FormController {
@Get
public String showForm(CsrfToken csrfToken) {
return """
<form method="POST" action="/submit">
<input type="hidden" name="_csrf" value="%s">
<input type="text" name="data">
<button type="submit">Submit</button>
</form>
""".formatted(csrfToken.getToken());
}
}
---
c.安全头配置
a.功能说明
配置HTTP安全头,增强应用的安全性。
b.配置示例
---
// application.yml
micronaut:
security:
enabled: true
# 安全头配置
intercept-url-map:
- pattern: /**
http-method: GET
access:
- isAnonymous()
# X-Frame-Options
x-frame-options:
enabled: true
mode: DENY
# X-Content-Type-Options
x-content-type-options:
enabled: true
# X-XSS-Protection
x-xss-protection:
enabled: true
mode: block
---
6.7 GraphQL支持
01.GraphQL概述
a.核心概念
GraphQL是一种API查询语言,客户端可以精确指定需要的数据字段,避免过度获取或获取不足的问题。Micronaut提供GraphQL集成,支持Schema定义、查询解析和订阅功能,简化GraphQL API的开发。
b.主要优势
a.精确数据获取
客户端通过查询语句明确指定需要的字段,服务端只返回请求的数据,减少网络传输和数据处理开销。
b.强类型系统
GraphQL Schema定义了完整的类型系统,提供自动化的文档生成和类型验证,提升API的可维护性。
c.单一端点
所有查询通过单一HTTP端点处理,简化API设计和客户端集成,支持批量查询和嵌套查询。
02.GraphQL集成
a.依赖配置
a.功能说明
Micronaut支持GraphQL Java实现,提供注解驱动的Schema定义和查询解析。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.graphql:micronaut-graphql")
}
// application.yml
graphql:
enabled: true
path: /graphql
graphiql:
enabled: true
path: /graphiql
---
b.Schema定义
a.功能说明
使用GraphQL SDL定义Schema,描述API的类型、查询和变更操作。
b.配置示例
---
// src/main/resources/schema.graphqls
type Query {
# 查询单个用户
user(id: ID!): User
# 查询所有用户
users: [User!]!
# 查询订单
order(id: ID!): Order
# 查询用户的订单列表
userOrders(userId: ID!): [Order!]!
}
type Mutation {
# 创建用户
createUser(input: CreateUserInput!): User!
# 更新用户
updateUser(id: ID!, input: UpdateUserInput!): User!
# 删除用户
deleteUser(id: ID!): Boolean!
# 创建订单
createOrder(input: CreateOrderInput!): Order!
}
type User {
id: ID!
username: String!
email: String!
orders: [Order!]!
createdAt: String!
}
type Order {
id: ID!
userId: ID!
user: User!
productName: String!
quantity: Int!
totalAmount: Float!
status: OrderStatus!
createdAt: String!
}
enum OrderStatus {
PENDING
CONFIRMED
SHIPPED
DELIVERED
CANCELLED
}
input CreateUserInput {
username: String!
email: String!
password: String!
}
input UpdateUserInput {
username: String
email: String
}
input CreateOrderInput {
userId: ID!
productName: String!
quantity: Int!
totalAmount: Float!
}
---
03.查询解析器
a.Query实现
a.功能说明
实现GraphQL查询解析器,处理客户端的查询请求。
b.代码示例
---
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import io.micronaut.graphql.annotations.GraphQLQuery;
import jakarta.inject.Singleton;
@Singleton
public class UserQueryResolver {
private final UserService userService;
public UserQueryResolver(UserService userService) {
this.userService = userService;
}
@GraphQLQuery
public User user(Long id) {
return userService.findById(id);
}
@GraphQLQuery
public List<User> users() {
return userService.findAll();
}
@GraphQLQuery
public List<Order> userOrders(Long userId) {
return orderService.findByUserId(userId);
}
}
@Singleton
public class OrderQueryResolver {
private final OrderService orderService;
public OrderQueryResolver(OrderService orderService) {
this.orderService = orderService;
}
@GraphQLQuery
public Order order(Long id) {
return orderService.findById(id);
}
}
---
b.Mutation实现
a.功能说明
实现GraphQL变更解析器,处理数据的创建、更新和删除操作。
b.代码示例
---
import io.micronaut.graphql.annotations.GraphQLMutation;
import jakarta.inject.Singleton;
@Singleton
public class UserMutationResolver {
private final UserService userService;
public UserMutationResolver(UserService userService) {
this.userService = userService;
}
@GraphQLMutation
public User createUser(CreateUserInput input) {
User user = new User();
user.setUsername(input.getUsername());
user.setEmail(input.getEmail());
user.setPassword(input.getPassword());
return userService.create(user);
}
@GraphQLMutation
public User updateUser(Long id, UpdateUserInput input) {
User user = userService.findById(id);
if (input.getUsername() != null) {
user.setUsername(input.getUsername());
}
if (input.getEmail() != null) {
user.setEmail(input.getEmail());
}
return userService.update(user);
}
@GraphQLMutation
public Boolean deleteUser(Long id) {
userService.delete(id);
return true;
}
}
@Singleton
public class OrderMutationResolver {
private final OrderService orderService;
public OrderMutationResolver(OrderService orderService) {
this.orderService = orderService;
}
@GraphQLMutation
public Order createOrder(CreateOrderInput input) {
Order order = new Order();
order.setUserId(input.getUserId());
order.setProductName(input.getProductName());
order.setQuantity(input.getQuantity());
order.setTotalAmount(input.getTotalAmount());
order.setStatus(OrderStatus.PENDING);
return orderService.create(order);
}
}
---
c.字���解析器
a.功能说明
实现嵌套字段的解析器,处理对象关联查询。
b.代码示例
---
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import io.micronaut.graphql.annotations.GraphQLDataFetcher;
import jakarta.inject.Singleton;
@Singleton
public class UserFieldResolver {
private final OrderService orderService;
public UserFieldResolver(OrderService orderService) {
this.orderService = orderService;
}
// 解析User.orders字段
@GraphQLDataFetcher("User.orders")
public List<Order> orders(DataFetchingEnvironment env) {
User user = env.getSource();
return orderService.findByUserId(user.getId());
}
}
@Singleton
public class OrderFieldResolver {
private final UserService userService;
public OrderFieldResolver(UserService userService) {
this.userService = userService;
}
// 解析Order.user字段
@GraphQLDataFetcher("Order.user")
public User user(DataFetchingEnvironment env) {
Order order = env.getSource();
return userService.findById(order.getUserId());
}
}
---
04.GraphQL查询示例
a.基础查询
a.功能说明
客户端发送GraphQL查询,精确指定需要的字段。
b.查询示例
---
# 查询单个用户
query {
user(id: "1") {
id
username
email
}
}
# 查询用户及其订单
query {
user(id: "1") {
id
username
email
orders {
id
productName
quantity
totalAmount
status
}
}
}
# 查询多个资源
query {
users {
id
username
}
order(id: "100") {
id
productName
user {
username
}
}
}
---
b.变更操作
a.功能说明
使用Mutation执行数据的创建、更新和删除操作。
b.查询示例
---
# 创建用户
mutation {
createUser(input: {
username: "john_doe"
email: "[email protected]"
password: "secret123"
}) {
id
username
email
createdAt
}
}
# 更新用户
mutation {
updateUser(id: "1", input: {
username: "john_updated"
email: "[email protected]"
}) {
id
username
email
}
}
# 创建订单
mutation {
createOrder(input: {
userId: "1"
productName: "Laptop"
quantity: 1
totalAmount: 999.99
}) {
id
productName
status
user {
username
}
}
}
---
c.查询变量
a.功能说明
使用变量参数化查询,提高查询的复用性和安全性。
b.查询示例
---
# 查询定义
query GetUser($userId: ID!) {
user(id: $userId) {
id
username
email
orders {
id
productName
totalAmount
}
}
}
# 变量
{
"userId": "1"
}
# 创建用户Mutation
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
username
email
}
}
# 变量
{
"input": {
"username": "jane_doe",
"email": "[email protected]",
"password": "password123"
}
}
---
05.高级特性
a.DataLoader
a.功能说明
使用DataLoader解决N+1查询问题,批量加载关联数据,提升查询性能。
b.代码示例
---
import org.dataloader.DataLoader;
import org.dataloader.DataLoaderRegistry;
import io.micronaut.context.annotation.Factory;
import jakarta.inject.Singleton;
@Factory
public class DataLoaderFactory {
@Singleton
public DataLoaderRegistry dataLoaderRegistry(UserService userService) {
DataLoaderRegistry registry = new DataLoaderRegistry();
// 用户DataLoader
DataLoader<Long, User> userLoader = DataLoader.newDataLoader(
userIds -> CompletableFuture.supplyAsync(() ->
userService.findByIds(userIds)
)
);
registry.register("userLoader", userLoader);
return registry;
}
}
// 在字段解析器中使用
@Singleton
public class OrderFieldResolver {
@GraphQLDataFetcher("Order.user")
public CompletableFuture<User> user(DataFetchingEnvironment env) {
Order order = env.getSource();
DataLoader<Long, User> userLoader = env.getDataLoader("userLoader");
return userLoader.load(order.getUserId());
}
}
---
b.订阅支持
a.功能说明
实现GraphQL订阅,支持实时数据推送。
b.代码示例
---
// Schema定义
type Subscription {
orderCreated: Order!
orderStatusChanged(orderId: ID!): Order!
}
// 订阅解析器
import io.micronaut.graphql.annotations.GraphQLSubscription;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
@Singleton
public class OrderSubscriptionResolver {
private final Flux<Order> orderStream;
public OrderSubscriptionResolver() {
this.orderStream = Flux.create(emitter -> {
// 订阅订单创建事件
});
}
@GraphQLSubscription
public Publisher<Order> orderCreated() {
return orderStream;
}
@GraphQLSubscription
public Publisher<Order> orderStatusChanged(Long orderId) {
return orderStream.filter(order ->
order.getId().equals(orderId)
);
}
}
// 客户端订阅
subscription {
orderCreated {
id
productName
status
user {
username
}
}
}
---
c.错误处理
a.功能说明
自定义GraphQL错误处理,提供友好的错误信息。
b.代码示例
---
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import graphql.schema.DataFetchingEnvironment;
import io.micronaut.graphql.errors.GraphQLErrorHandler;
import jakarta.inject.Singleton;
@Singleton
public class CustomErrorHandler implements GraphQLErrorHandler {
@Override
public GraphQLError handleException(
DataFetchingEnvironment environment,
Throwable exception
) {
if (exception instanceof NotFoundException) {
return GraphqlErrorBuilder.newError(environment)
.message("Resource not found: " + exception.getMessage())
.errorType(ErrorType.NOT_FOUND)
.build();
}
if (exception instanceof ValidationException) {
return GraphqlErrorBuilder.newError(environment)
.message("Validation failed: " + exception.getMessage())
.errorType(ErrorType.VALIDATION_ERROR)
.build();
}
return GraphqlErrorBuilder.newError(environment)
.message("Internal server error")
.errorType(ErrorType.INTERNAL_ERROR)
.build();
}
}
---
6.8 GraalVM原生镜像
01.GraalVM概述
a.核心概念
GraalVM原生镜像将Java应用编译为本地可执行文件,实现毫秒级启动时间和极低的内存占用。Micronaut专为GraalVM优化,通过编译时依赖注入和反射元数据生成,简化原生镜像的构建过程。
b.主要优势
a.快速启动
原生镜像启动时间从秒级降低到毫秒级,适用于Serverless和容器化场景,显著提升应用的响应速度。
b.低内存占用
原生镜像的内存占用比JVM应用减少50%-80%,降低基础设施成本,提升资源利用率。
c.即时性能
无需JVM预热,应用启动即达到峰值性能,避免传统JVM的冷启动问题。
02.环境准备
a.GraalVM安装
a.功能说明
安装GraalVM和Native Image工具,配置环境变量。
b.安装步骤
---
# 下载GraalVM
# https://www.graalvm.org/downloads/
# macOS/Linux安装
export GRAALVM_HOME=/path/to/graalvm
export PATH=$GRAALVM_HOME/bin:$PATH
# 安装Native Image
gu install native-image
# 验证安装
java -version
native-image --version
# Windows安装
# 1. 下载GraalVM Windows版本
# 2. 解压到目录,如 C:\graalvm
# 3. 设置环境变量
# GRAALVM_HOME=C:\graalvm
# PATH=%GRAALVM_HOME%\bin;%PATH%
# 4. 安装Native Image
gu.cmd install native-image
---
b.项目配置
a.功能说明
配置Gradle或Maven项目,添加GraalVM Native Image插件。
b.配置示例
---
// build.gradle
plugins {
id("io.micronaut.application") version "4.0.0"
id("io.micronaut.graalvm") version "4.0.0"
}
graalvmNative {
binaries {
main {
imageName = "micronaut-app"
mainClass = "com.example.Application"
buildArgs.add("--verbose")
buildArgs.add("-H:+ReportExceptionStackTraces")
}
}
}
// Maven配置
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.9.28</version>
<configuration>
<imageName>micronaut-app</imageName>
<mainClass>com.example.Application</mainClass>
<buildArgs>
<buildArg>--verbose</buildArg>
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
</buildArgs>
</configuration>
</plugin>
---
03.构建原生镜像
a.本地构建
a.功能说明
使用Gradle或Maven命令构建原生镜像。
b.构建命令
---
# Gradle构建
./gradlew nativeCompile
# Maven构建
./mvnw package -Dpackaging=native-image
# 构建输出
# build/native/nativeCompile/micronaut-app
# 运行原生镜像
./build/native/nativeCompile/micronaut-app
# 查看启动时间和内存占用
time ./build/native/nativeCompile/micronaut-app
# 输出示例
# Startup completed in 23ms
# Memory: 15MB
---
b.Docker构建
a.功能说明
使用Docker多阶段构建创建原生镜像容器。
b.Dockerfile示例
---
# Dockerfile
FROM ghcr.io/graalvm/graalvm-ce:ol8-java17-22.3.0 AS builder
WORKDIR /app
COPY . .
# 安装Native Image
RUN gu install native-image
# 构建原生镜像
RUN ./gradlew nativeCompile
# 运行阶段
FROM oraclelinux:8-slim
WORKDIR /app
# 复制原生镜像
COPY --from=builder /app/build/native/nativeCompile/micronaut-app .
# 暴露端口
EXPOSE 8080
# 运行应用
ENTRYPOINT ["./micronaut-app"]
# 构建Docker镜像
docker build -t micronaut-native:latest .
# 运行容器
docker run -p 8080:8080 micronaut-native:latest
# 查看容器资源占用
docker stats
---
c.CI/CD集成
a.功能说明
在CI/CD流水线中自动构建原生镜像。
b.GitHub Actions示例
---
# .github/workflows/native-image.yml
name: Build Native Image
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup GraalVM
uses: graalvm/setup-graalvm@v1
with:
version: '22.3.0'
java-version: '17'
components: 'native-image'
- name: Build Native Image
run: ./gradlew nativeCompile
- name: Test Native Image
run: |
./build/native/nativeCompile/micronaut-app &
sleep 5
curl http://localhost:8080/health
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: native-image
path: build/native/nativeCompile/micronaut-app
---
04.反射配置
a.自动配置
a.功能说明
Micronaut自动生成反射配置,无需手动配置大部分反射使用。
b.配置示例
---
// Micronaut自动处理的反射
// - @Controller、@Service等注解的类
// - @ConfigurationProperties配置类
// - @Introspected注解的类
// 需要手动配置的反射
// - 第三方库的反射使用
// - 动态类加载
// - JNI调用
// src/main/resources/META-INF/native-image/reflect-config.json
[
{
"name": "com.example.DynamicClass",
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredMethods": true,
"allPublicMethods": true,
"allDeclaredFields": true,
"allPublicFields": true
}
]
---
b.手动配置
a.功能说明
为第三方库或动态加载的类添加反射配置。
b.代码示例
---
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.ReflectiveAccess;
// 使用@Introspected注解
@Introspected
public class User {
private Long id;
private String username;
private String email;
// Getters and Setters
}
// 使用@ReflectiveAccess注解
@ReflectiveAccess
public class DynamicService {
public void dynamicMethod() {
// 动态调用的方法
}
}
// 编程式注册反射
import io.micronaut.context.annotation.Factory;
import io.micronaut.core.annotation.TypeHint;
import jakarta.inject.Singleton;
@Factory
public class ReflectionConfiguration {
@Singleton
@TypeHint(
value = {ThirdPartyClass.class},
accessType = {TypeHint.AccessType.ALL_DECLARED_CONSTRUCTORS,
TypeHint.AccessType.ALL_DECLARED_METHODS}
)
public ThirdPartyClass thirdPartyBean() {
return new ThirdPartyClass();
}
}
---
05.性能优化
a.构建优化
a.功能说明
配置Native Image构建参数,优化镜像大小和性能。
b.配置示例
---
// build.gradle
graalvmNative {
binaries {
main {
buildArgs.addAll([
// 优化级别
"-O3",
// 启用G1 GC
"--gc=G1",
// 静态链接
"--static",
// 压缩
"-H:+CompressedReferences",
// 移除未使用代码
"-H:+RemoveUnusedSymbols",
// 优化字符串
"-H:+OptimizeStringConcat",
// 内存配置
"-J-Xmx8g"
])
}
}
}
---
b.运行时优化
a.功能说明
配置原生镜像的运行时参数,优化内存和性能。
b.配置示例
---
# 运行时参数
./micronaut-app \
-Xmx128m \
-Xms128m \
-XX:MaxDirectMemorySize=64m
# 环境变量配置
export MICRONAUT_ENVIRONMENTS=prod
export JAVA_OPTS="-Xmx128m -Xms128m"
# Docker运行配置
docker run \
-p 8080:8080 \
-m 256m \
--cpus 0.5 \
-e MICRONAUT_ENVIRONMENTS=prod \
micronaut-native:latest
---
c.监控和诊断
a.功能说明
启用原生镜像的监控和诊断功能。
b.配置示例
---
// build.gradle
graalvmNative {
binaries {
main {
buildArgs.addAll([
// 启用监控
"--enable-monitoring=heapdump,jfr,jvmstat",
// 启用诊断
"-H:+AllowVMInspection",
// 生成调试信息
"-H:+SourceLevelDebug",
// 生成性能报告
"-H:+DashboardAll",
"-H:DashboardDump=dashboard.bgv"
])
}
}
}
# 运行时启用JFR
./micronaut-app -XX:StartFlightRecording=filename=recording.jfr
# 生成堆转储
kill -SIGUSR1 <pid>
---
06.常见问题
a.类初始化问题
a.功能说明
配置类的初始化时机,解决构建时初始化错误。
b.配置示例
---
// src/main/resources/META-INF/native-image/native-image.properties
Args = --initialize-at-build-time=com.example.StaticClass \
--initialize-at-run-time=com.example.DynamicClass
// 或在build.gradle中配置
graalvmNative {
binaries {
main {
buildArgs.add("--initialize-at-build-time=com.example.StaticClass")
buildArgs.add("--initialize-at-run-time=com.example.DynamicClass")
}
}
}
---
b.资源文件访问
a.功能说明
配置资源文件的访问,确保原生镜像能正确加载资源。
b.配置示例
---
// src/main/resources/META-INF/native-image/resource-config.json
{
"resources": {
"includes": [
{"pattern": "application.yml"},
{"pattern": "application-*.yml"},
{"pattern": "logback.xml"},
{"pattern": "META-INF/.*"},
{"pattern": "templates/.*\\.html"}
]
}
}
// 或使用@ResourceHint注解
import io.micronaut.core.annotation.ResourceHint;
@ResourceHint(
patterns = {
"application.yml",
"templates/*.html"
}
)
public class Application {
public static void main(String[] args) {
Micronaut.run(Application.class, args);
}
}
---
c.第三方库兼容性
a.功能说明
处理第三方库的GraalVM兼容性问题。
b.解决方案
---
# 检查库的GraalVM兼���性
# https://www.graalvm.org/native-image/libraries-and-frameworks/
# 常见不兼容库的替代方案
# - CGLIB → Micronaut AOP
# - Javassist → ByteBuddy
# - ASM动态生成 → 编译时生成
# 添加第三方库的配置
// build.gradle
dependencies {
// 使用GraalVM兼容的库版本
implementation("org.postgresql:postgresql:42.5.0")
// 添加GraalVM元数据
implementation("org.graalvm.buildtools:graalvm-reachability-metadata:0.9.28")
}
# 使用Tracing Agent生成配置
java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image \
-jar build/libs/app.jar
# 运行测试生成配置
./gradlew test -Pagent
---
7 实战应用
7.1 项目创建与配置
01.项目创建
a.使用Micronaut Launch
a.功能说明
Micronaut Launch是官方的项目生成器,提供Web界面和CLI工具,快速创建项目脚手架。
b.使用步骤
---
# Web界面创建
# 访问 https://micronaut.io/launch/
# 1. 选择应用类型:Application, CLI, Function等
# 2. 选择Java版本:17, 21
# 3. 选择构建工具:Gradle或Maven
# 4. 选择语言:Java, Kotlin, Groovy
# 5. 添加特性:Data JPA, Security JWT, Kafka等
# 6. 下载项目压缩包
# CLI工具创建
# 安装Micronaut CLI
sdk install micronaut
# 创建项目
mn create-app com.example.demo \
--features=data-jpa,security-jwt,kafka \
--build=gradle \
--lang=java \
--jdk=17
# 创建微服务项目
mn create-app com.example.order-service \
--features=discovery-consul,tracing-zipkin,graalvm \
--build=gradle
# 创建函数项目
mn create-function com.example.lambda \
--features=aws-lambda \
--build=gradle
---
b.项目结构
a.功能说明
Micronaut项目遵循标准的Maven/Gradle项目结构,���码组织清晰。
b.目录结构
---
order-service/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/
│ │ │ ├── Application.java # 应用入口
│ │ │ ├── controller/ # 控制器
│ │ │ │ └── OrderController.java
│ │ │ ├── service/ # 业务逻辑
│ │ │ │ └── OrderService.java
│ │ │ ├── repository/ # 数据访问
│ │ │ │ └── OrderRepository.java
│ │ │ ├── domain/ # 领域模型
│ │ │ │ └── Order.java
│ │ │ └── config/ # 配置类
│ │ │ └── DatabaseConfig.java
│ │ └── resources/
│ │ ├── application.yml # 主配置文件
│ │ ├── application-dev.yml # 开发环境配置
│ │ ├── application-prod.yml # 生产环境配置
│ │ └── logback.xml # 日志配置
│ └── test/
│ ├── java/
│ │ └── com/example/
│ │ └── OrderControllerTest.java
│ └── resources/
│ └── application-test.yml
├── build.gradle # Gradle构建文件
├── settings.gradle
├── gradle.properties
└── Dockerfile # Docker配置
---
02.基础配置
a.应用配置
a.功能说明
配置应用的基本信息,包括名称、端口、环境等。
b.配置示例
---
// application.yml
micronaut:
application:
name: order-service
server:
port: 8080
context-path: /api
# 环境配置
environments:
default: dev
# 服务器配置
netty:
default:
allocator:
max-order: 3
# 日志配置
logger:
levels:
com.example: DEBUG
io.micronaut: INFO
---
b.数据源配置
a.功能说明
配置数据库连接,支持多数据源和连接池。
b.配置示例
---
// application.yml
datasources:
default:
url: jdbc:mysql://localhost:3306/orders?serverTimezone=UTC
driverClassName: com.mysql.cj.jdbc.Driver
username: root
password: ${DB_PASSWORD:secret}
dialect: MYSQL
# 连接池配置
maximum-pool-size: 10
minimum-idle: 2
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
# JPA配置
jpa:
default:
properties:
hibernate:
hbm2ddl:
auto: update
show_sql: true
format_sql: true
# 使用配置
import io.micronaut.data.annotation.Repository;
import io.micronaut.data.jpa.repository.JpaRepository;
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByStatus(String status);
}
---
c.环境配置
a.功能说明
为不同环境配置不同的参数,如开发、测试、生产环境。
b.配置示例
---
// application-dev.yml
micronaut:
server:
port: 8080
datasources:
default:
url: jdbc:mysql://localhost:3306/orders_dev
logger:
levels:
com.example: DEBUG
// application-prod.yml
micronaut:
server:
port: 80
datasources:
default:
url: jdbc:mysql://prod-db:3306/orders
maximum-pool-size: 50
logger:
levels:
com.example: WARN
# 启动时指定环境
java -jar app.jar -Dmicronaut.environments=prod
# 或使用环境变量
export MICRONAUT_ENVIRONMENTS=prod
java -jar app.jar
---
03.依赖管理
a.核心依赖
a.功能说明
添加Micronaut核心依赖和常用模块。
b.配置示例
---
// build.gradle
plugins {
id("io.micronaut.application") version "4.0.0"
}
version = "1.0.0"
group = "com.example"
repositories {
mavenCentral()
}
dependencies {
// Micronaut核心
implementation("io.micronaut:micronaut-runtime")
implementation("io.micronaut:micronaut-http-server-netty")
implementation("io.micronaut:micronaut-validation")
// 数据访问
implementation("io.micronaut.data:micronaut-data-jpa")
implementation("io.micronaut.sql:micronaut-jdbc-hikari")
runtimeOnly("mysql:mysql-connector-java")
// 安全
implementation("io.micronaut.security:micronaut-security-jwt")
// 服务发现
implementation("io.micronaut.discovery:micronaut-discovery-client")
// 日志
runtimeOnly("ch.qos.logback:logback-classic")
// 测试
testImplementation("io.micronaut.test:micronaut-test-junit5")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}
application {
mainClass.set("com.example.Application")
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
---
b.版本管理
a.功能说明
使用BOM统一管理依赖版本,避免版本冲突。
b.配置示例
---
// build.gradle
dependencies {
// 使用Micronaut BOM
implementation(platform("io.micronaut.platform:micronaut-platform:4.0.0"))
// 无需指定版本
implementation("io.micronaut:micronaut-runtime")
implementation("io.micronaut.data:micronaut-data-jpa")
implementation("io.micronaut.security:micronaut-security-jwt")
}
// gradle.properties
micronautVersion=4.0.0
---
04.应用入口
a.主类定义
a.功能说明
定义应用的主类和启动方法。
b.代码示例
---
package com.example;
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String[] args) {
Micronaut.run(Application.class, args);
}
}
// 自定义启动配置
import io.micronaut.context.ApplicationContext;
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String[] args) {
Micronaut.build(args)
.mainClass(Application.class)
.banner(false) // 禁用启动横幅
.start();
}
}
---
b.启动监听器
a.功能说明
监听应用启动和关闭事件,执行初始化和清理逻辑。
b.代码示例
---
import io.micronaut.context.event.ApplicationEventListener;
import io.micronaut.context.event.StartupEvent;
import io.micronaut.context.event.ShutdownEvent;
import jakarta.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class ApplicationStartupListener implements ApplicationEventListener<StartupEvent> {
private static final Logger LOG = LoggerFactory.getLogger(ApplicationStartupListener.class);
@Override
public void onApplicationEvent(StartupEvent event) {
LOG.info("Application started successfully");
LOG.info("Server running on port: {}", event.getSource().getEnvironment().getProperty("micronaut.server.port", Integer.class).orElse(8080));
// 执行初始化逻辑
initializeCache();
loadConfiguration();
}
private void initializeCache() {
LOG.info("Initializing cache...");
}
private void loadConfiguration() {
LOG.info("Loading configuration...");
}
}
@Singleton
public class ApplicationShutdownListener implements ApplicationEventListener<ShutdownEvent> {
private static final Logger LOG = LoggerFactory.getLogger(ApplicationShutdownListener.class);
@Override
public void onApplicationEvent(ShutdownEvent event) {
LOG.info("Application shutting down...");
// 执行清理逻辑
closeConnections();
flushCache();
}
private void closeConnections() {
LOG.info("Closing database connections...");
}
private void flushCache() {
LOG.info("Flushing cache...");
}
}
---
05.开发工具
a.热重载
a.功能说明
配置自动重载,提升开发效率。
b.配置示例
---
// build.gradle
dependencies {
developmentOnly("io.micronaut:micronaut-runtime")
}
// 启用自动重载
micronaut {
runtime("netty")
processing {
incremental(true)
annotations("com.example.*")
}
}
# 运行开发模式
./gradlew run --continuous
# 或使用IDE插件
# IntelliJ IDEA: Micronaut插件
# VS Code: Micronaut扩展
---
b.API文档
a.功能说明
集成OpenAPI/Swagger,自动生成API文档。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.openapi:micronaut-openapi")
annotationProcessor("io.micronaut.openapi:micronaut-openapi")
}
// application.yml
micronaut:
router:
static-resources:
swagger:
paths: classpath:META-INF/swagger
mapping: /swagger/**
swagger-ui:
paths: classpath:META-INF/swagger/views/swagger-ui
mapping: /swagger-ui/**
# 访问Swagger UI
# http://localhost:8080/swagger-ui/
// 在Controller中添加文档注解
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@Controller("/orders")
@Tag(name = "Order Management")
public class OrderController {
@Get("/{id}")
@Operation(summary = "Get order by ID", description = "Returns a single order")
public Order getOrder(Long id) {
return orderService.findById(id);
}
}
---
7.2 RESTful API开发
01.控制器开发
a.基础控制器
a.功能说明
使用@Controller注解定义RESTful API端点,处理HTTP请求。
b.代码示例
---
import io.micronaut.http.annotation.*;
import io.micronaut.http.HttpResponse;
import jakarta.inject.Inject;
@Controller("/api/orders")
public class OrderController {
@Inject
private OrderService orderService;
@Get
public List<Order> listOrders() {
return orderService.findAll();
}
@Get("/{id}")
public Order getOrder(Long id) {
return orderService.findById(id)
.orElseThrow(() -> new NotFoundException("Order not found"));
}
@Post
public HttpResponse<Order> createOrder(@Body Order order) {
Order created = orderService.create(order);
return HttpResponse.created(created);
}
@Put("/{id}")
public Order updateOrder(Long id, @Body Order order) {
return orderService.update(id, order);
}
@Delete("/{id}")
public HttpResponse<?> deleteOrder(Long id) {
orderService.delete(id);
return HttpResponse.noContent();
}
}
---
b.参数绑定
a.功能说明
自动绑定路径参数、查询参数、请求体等。
b.代码示例
---
@Controller("/api/products")
public class ProductController {
// 路径参数
@Get("/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.findById(id);
}
// 查询参数
@Get("/search")
public List<Product> searchProducts(
@QueryValue String keyword,
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "10") int size
) {
return productService.search(keyword, page, size);
}
// 请求头
@Get("/user-products")
public List<Product> getUserProducts(
@Header("Authorization") String token
) {
String userId = extractUserId(token);
return productService.findByUserId(userId);
}
// Cookie
@Get("/preferences")
public Map<String, Object> getPreferences(
@CookieValue("session-id") String sessionId
) {
return preferenceService.getBySession(sessionId);
}
}
---
02.数据验证
a.Bean Validation
a.功能说明
使用JSR-380注解验证请求数据。
b.代码示例
---
import jakarta.validation.constraints.*;
public class CreateOrderRequest {
@NotBlank(message = "Product name is required")
@Size(min = 3, max = 100)
private String productName;
@NotNull
@Min(value = 1, message = "Quantity must be at least 1")
@Max(value = 1000)
private Integer quantity;
@NotNull
@DecimalMin(value = "0.01")
private BigDecimal price;
@Email(message = "Invalid email format")
private String customerEmail;
@Pattern(regexp = "^\\+?[1-9]\\d{1,14}$", message = "Invalid phone number")
private String phoneNumber;
// Getters and Setters
}
@Controller("/api/orders")
public class OrderController {
@Post
public HttpResponse<Order> createOrder(@Valid @Body CreateOrderRequest request) {
Order order = orderService.create(request);
return HttpResponse.created(order);
}
}
---
b.自定义验证器
a.功能说明
实现自定义验证逻辑。
b.代码示例
---
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = OrderStatusValidator.class)
public @interface ValidOrderStatus {
String message() default "Invalid order status";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class OrderStatusValidator implements ConstraintValidator<ValidOrderStatus, String> {
private static final Set<String> VALID_STATUSES = Set.of(
"PENDING", "CONFIRMED", "SHIPPED", "DELIVERED", "CANCELLED"
);
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && VALID_STATUSES.contains(value.toUpperCase());
}
}
// 使用自定义验证器
public class UpdateOrderRequest {
@ValidOrderStatus
private String status;
// Getters and Setters
}
---
03.异常处理
a.全局异常处理
a.功能说明
使用@Error注解处理全局异常。
b.代码示例
---
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Error;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.hateoas.JsonError;
@Controller
public class GlobalExceptionHandler {
@Error(global = true, exception = NotFoundException.class)
public HttpResponse<JsonError> handleNotFound(HttpRequest request, NotFoundException e) {
JsonError error = new JsonError(e.getMessage())
.link("self", request.getUri());
return HttpResponse.notFound(error);
}
@Error(global = true, exception = ValidationException.class)
public HttpResponse<JsonError> handleValidation(HttpRequest request, ValidationException e) {
JsonError error = new JsonError("Validation failed: " + e.getMessage());
return HttpResponse.badRequest(error);
}
@Error(global = true)
public HttpResponse<JsonError> handleGeneric(HttpRequest request, Throwable e) {
JsonError error = new JsonError("Internal server error");
return HttpResponse.serverError(error);
}
}
---
b.自定义异常
a.功能说明
定义业务异常类型。
b.代码示例
---
public class NotFoundException extends RuntimeException {
public NotFoundException(String message) {
super(message);
}
}
public class BusinessException extends RuntimeException {
private final String errorCode;
public BusinessException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
}
// 异常处理器
@Error(exception = BusinessException.class)
public HttpResponse<Map<String, Object>> handleBusinessException(
HttpRequest request,
BusinessException e
) {
Map<String, Object> error = Map.of(
"code", e.getErrorCode(),
"message", e.getMessage(),
"path", request.getPath()
);
return HttpResponse.badRequest(error);
}
---
04.响应处理
a.JSON响应
a.功能说明
自动序列化对象为JSON响应。
b.代码示例
---
@Controller("/api/users")
public class UserController {
@Get("/{id}")
public User getUser(Long id) {
// 自动序列化为JSON
return userService.findById(id);
}
@Get
public HttpResponse<List<User>> listUsers() {
List<User> users = userService.findAll();
return HttpResponse.ok(users)
.header("X-Total-Count", String.valueOf(users.size()));
}
// 自定义响应状态
@Post
public HttpResponse<User> createUser(@Body User user) {
User created = userService.create(user);
return HttpResponse.created(created)
.header("Location", "/api/users/" + created.getId());
}
}
---
b.分页响应
a.功能说明
实现分页查询和响应。
b.代码示例
---
import io.micronaut.data.model.Page;
import io.micronaut.data.model.Pageable;
@Controller("/api/orders")
public class OrderController {
@Get
public HttpResponse<Page<Order>> listOrders(
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "20") int size,
@QueryValue(defaultValue = "id,desc") String sort
) {
Pageable pageable = Pageable.from(page, size);
Page<Order> orders = orderService.findAll(pageable);
return HttpResponse.ok(orders)
.header("X-Total-Count", String.valueOf(orders.getTotalSize()))
.header("X-Total-Pages", String.valueOf(orders.getTotalPages()));
}
}
// 自定义分页响应
public class PageResponse<T> {
private List<T> content;
private int page;
private int size;
private long total;
private int totalPages;
// Constructor, Getters and Setters
}
@Get("/custom-page")
public PageResponse<Order> customPage(Pageable pageable) {
Page<Order> page = orderService.findAll(pageable);
return new PageResponse<>(
page.getContent(),
page.getPageNumber(),
page.getSize(),
page.getTotalSize(),
page.getTotalPages()
);
}
---
05.API版本控制
a.URL版本控制
a.功能说明
通过URL路径实现API版本控制。
b.代码示例
---
@Controller("/api/v1/orders")
public class OrderControllerV1 {
@Get("/{id}")
public OrderV1 getOrder(Long id) {
return orderService.findByIdV1(id);
}
}
@Controller("/api/v2/orders")
public class OrderControllerV2 {
@Get("/{id}")
public OrderV2 getOrder(Long id) {
return orderService.findByIdV2(id);
}
}
---
b.Header版本控制
a.功能说明
通过请求头实现API版本控制。
b.代码示例
---
@Controller("/api/orders")
public class OrderController {
@Get(value = "/{id}", produces = "application/vnd.api.v1+json")
public OrderV1 getOrderV1(Long id) {
return orderService.findByIdV1(id);
}
@Get(value = "/{id}", produces = "application/vnd.api.v2+json")
public OrderV2 getOrderV2(Long id) {
return orderService.findByIdV2(id);
}
}
// 客户端请求
// GET /api/orders/1
// Accept: application/vnd.api.v1+json
---
7.3 微服务架构实践
01.服务拆分
a.拆分原则
a.功能说明
按照业务领域和职责边界拆分微服务,遵循单一职责原则。
b.拆分示例
---
# 电商系统微服务拆分
order-service/ # 订单服务
- 订单创建
- 订单查询
- 订单状态管理
inventory-service/ # 库存服务
- 库存查询
- 库存扣减
- 库存补充
payment-service/ # 支付服务
- 支付处理
- 退款处理
- 支付查询
user-service/ # 用户服务
- 用户注册
- 用户认证
- 用户信息管理
notification-service/ # 通知服务
- 邮件通知
- 短信通知
- 推送通知
---
b.服务通信
a.同步通信
a.功能说明
使用HTTP客户端进行服务间同步调用。
b.代码示例
---
// 订单服务调用库存服务
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
@Client("inventory-service")
public interface InventoryClient {
@Get("/inventory/{productId}")
Inventory getInventory(Long productId);
@Post("/inventory/reserve")
boolean reserveInventory(@Body ReserveRequest request);
}
@Service
public class OrderService {
@Inject
private InventoryClient inventoryClient;
@Inject
private PaymentClient paymentClient;
public Order createOrder(CreateOrderRequest request) {
// 1. 检查库存
Inventory inventory = inventoryClient.getInventory(request.getProductId());
if (inventory.getQuantity() < request.getQuantity()) {
throw new BusinessException("INSUFFICIENT_INVENTORY", "库存不足");
}
// 2. 预留库存
boolean reserved = inventoryClient.reserveInventory(
new ReserveRequest(request.getProductId(), request.getQuantity())
);
if (!reserved) {
throw new BusinessException("RESERVE_FAILED", "库存预留失败");
}
// 3. 创建订单
Order order = new Order();
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
order.setStatus("PENDING");
return orderRepository.save(order);
}
}
---
b.异步通信
a.功能说明
使用消息队列实现服务间异步通信。
b.代码示例
---
// 订单服务发布订单创建事件
import io.micronaut.configuration.kafka.annotation.KafkaClient;
import io.micronaut.configuration.kafka.annotation.Topic;
@KafkaClient
public interface OrderEventProducer {
@Topic("order-events")
void publishOrderCreated(OrderCreatedEvent event);
}
@Service
public class OrderService {
@Inject
private OrderEventProducer eventProducer;
public Order createOrder(CreateOrderRequest request) {
Order order = orderRepository.save(new Order(request));
// 发布事件
OrderCreatedEvent event = new OrderCreatedEvent(
order.getId(),
order.getUserId(),
order.getTotalAmount()
);
eventProducer.publishOrderCreated(event);
return order;
}
}
// 通知服务消费订单事件
import io.micronaut.configuration.kafka.annotation.KafkaListener;
import io.micronaut.configuration.kafka.annotation.Topic;
@KafkaListener(groupId = "notification-service")
public class OrderEventConsumer {
@Inject
private NotificationService notificationService;
@Topic("order-events")
public void onOrderCreated(OrderCreatedEvent event) {
// 发送订单确认邮件
notificationService.sendOrderConfirmation(
event.getUserId(),
event.getOrderId()
);
}
}
---
02.服务编排
a.Saga模式
a.功能说明
使用Saga模式管理分布式事务,确保数据一致性。
b.代码示例
---
@Service
public class OrderSagaOrchestrator {
@Inject
private InventoryClient inventoryClient;
@Inject
private PaymentClient paymentClient;
@Inject
private OrderRepository orderRepository;
public Order executeOrderSaga(CreateOrderRequest request) {
Order order = null;
boolean inventoryReserved = false;
boolean paymentProcessed = false;
try {
// Step 1: 创建订单
order = new Order(request);
order.setStatus("PENDING");
order = orderRepository.save(order);
// Step 2: 预留库存
inventoryReserved = inventoryClient.reserveInventory(
new ReserveRequest(order.getProductId(), order.getQuantity())
);
if (!inventoryReserved) {
throw new SagaException("库存预留失败");
}
// Step 3: 处理支付
PaymentResult paymentResult = paymentClient.processPayment(
new PaymentRequest(order.getId(), order.getTotalAmount())
);
if (!"SUCCESS".equals(paymentResult.getStatus())) {
throw new SagaException("支付失败");
}
paymentProcessed = true;
// Step 4: 确认订单
order.setStatus("CONFIRMED");
order = orderRepository.update(order);
return order;
} catch (Exception e) {
// 补偿操作
if (paymentProcessed) {
paymentClient.refund(order.getId());
}
if (inventoryReserved) {
inventoryClient.releaseInventory(
new ReleaseRequest(order.getProductId(), order.getQuantity())
);
}
if (order != null) {
order.setStatus("CANCELLED");
orderRepository.update(order);
}
throw new BusinessException("ORDER_FAILED", "订单创建失败: " + e.getMessage());
}
}
}
---
b.事件驱动编排
a.功能说明
使用事件驱动方式编排服务流程。
b.代码示例
---
// 订单服务
@Service
public class OrderService {
@Inject
private OrderEventProducer eventProducer;
public Order createOrder(CreateOrderRequest request) {
Order order = orderRepository.save(new Order(request));
// 发布订单创建事件
eventProducer.publishOrderCreated(new OrderCreatedEvent(order));
return order;
}
@KafkaListener(groupId = "order-service")
@Topic("payment-events")
public void onPaymentCompleted(PaymentCompletedEvent event) {
Order order = orderRepository.findById(event.getOrderId()).orElseThrow();
order.setStatus("PAID");
orderRepository.update(order);
// 发布订单已支付事件
eventProducer.publishOrderPaid(new OrderPaidEvent(order));
}
}
// 库存服务
@KafkaListener(groupId = "inventory-service")
public class InventoryEventConsumer {
@Topic("order-events")
public void onOrderCreated(OrderCreatedEvent event) {
// 预留库存
boolean reserved = inventoryService.reserve(
event.getProductId(),
event.getQuantity()
);
if (reserved) {
eventProducer.publishInventoryReserved(
new InventoryReservedEvent(event.getOrderId())
);
} else {
eventProducer.publishInventoryReserveFailed(
new InventoryReserveFailedEvent(event.getOrderId())
);
}
}
@Topic("order-events")
public void onOrderPaid(OrderPaidEvent event) {
// 扣减库存
inventoryService.deduct(event.getProductId(), event.getQuantity());
}
}
---
03.服务治理
a.熔断器
a.功能说明
使用Resilience4j实现熔断器模式,防止故障扩散。
b.代码示例
---
// build.gradle
dependencies {
implementation("io.micronaut.resilience4j:micronaut-resilience4j")
}
// application.yml
resilience4j:
circuitbreaker:
instances:
inventory-service:
failure-rate-threshold: 50
wait-duration-in-open-state: 10s
sliding-window-size: 10
// 使用熔断器
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
@Service
public class OrderService {
@Inject
private InventoryClient inventoryClient;
@CircuitBreaker(name = "inventory-service", fallbackMethod = "getInventoryFallback")
public Inventory getInventory(Long productId) {
return inventoryClient.getInventory(productId);
}
public Inventory getInventoryFallback(Long productId, Exception e) {
// 降级逻辑:返回默认库存信息
return new Inventory(productId, 0, "服务暂时不可用");
}
}
---
b.限流
a.功能说明
实现API限流,保护服务不被过载。
b.代码示例
---
// application.yml
resilience4j:
ratelimiter:
instances:
order-api:
limit-for-period: 100
limit-refresh-period: 1s
timeout-duration: 0s
// 使用限流器
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
@Controller("/api/orders")
public class OrderController {
@Post
@RateLimiter(name = "order-api")
public HttpResponse<Order> createOrder(@Body CreateOrderRequest request) {
Order order = orderService.createOrder(request);
return HttpResponse.created(order);
}
}
---
04.配置管理
a.集中配置
a.功能说明
使用配置中心管理所有微服务的配置。
b.配置示例
---
// bootstrap.yml
micronaut:
application:
name: order-service
config-client:
enabled: true
consul:
client:
config:
enabled: true
format: YAML
defaultZone: localhost:8500
# Consul配置结构
/config/application/data # 所有服务共享
/config/order-service/data # 订单服务专属
/config/order-service,prod/data # 生产环境配置
---
b.配置刷新
a.功能说明
支持配置动态刷新,无需重启服务。
b.代码示例
---
import io.micronaut.runtime.context.scope.Refreshable;
import io.micronaut.context.annotation.ConfigurationProperties;
@Refreshable
@ConfigurationProperties("order")
public class OrderConfig {
private int maxOrdersPerDay;
private BigDecimal minOrderAmount;
// Getters and Setters
}
@Service
public class OrderService {
@Inject
private OrderConfig orderConfig;
public void validateOrder(Order order) {
if (order.getTotalAmount().compareTo(orderConfig.getMinOrderAmount()) < 0) {
throw new ValidationException("订单金额低于最小限制");
}
}
}
---
05.监控和追踪
a.分布式追踪
a.功能说明
使用Zipkin追踪跨服务调用链路。
b.配置示例
---
// application.yml
tracing:
zipkin:
enabled: true
url: http://zipkin:9411
sampler:
probability: 0.1
# 自动追踪HTTP调用
@Client("inventory-service")
public interface InventoryClient {
@Get("/inventory/{id}")
Inventory getInventory(Long id); // 自动追踪
}
---
b.健康检查
a.功能说明
实现健康检查端点,监控服务状态。
b.代码示例
---
// application.yml
endpoints:
health:
enabled: true
sensitive: false
// 自定义健康检查
import io.micronaut.management.health.indicator.HealthIndicator;
import io.micronaut.management.health.indicator.HealthResult;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
@Singleton
public class DatabaseHealthIndicator implements HealthIndicator {
@Inject
private DataSource dataSource;
@Override
public Publisher<HealthResult> getResult() {
return Mono.fromCallable(() -> {
try (Connection conn = dataSource.getConnection()) {
boolean isValid = conn.isValid(5);
return isValid ?
HealthResult.builder("database").status(HealthStatus.UP).build() :
HealthResult.builder("database").status(HealthStatus.DOWN).build();
} catch (Exception e) {
return HealthResult.builder("database")
.status(HealthStatus.DOWN)
.exception(e)
.build();
}
});
}
}
---
7.4 性能调优
01.JVM调优
a.内存配置
a.功能说明
优化JVM堆内存和垃圾回收器配置,提升应用性能。
b.配置示例
---
# 启动参数
java -Xms512m -Xmx2g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+ParallelRefProcEnabled \
-jar app.jar
# Docker配置
docker run -m 2g \
-e JAVA_OPTS="-Xms512m -Xmx2g -XX:+UseG1GC" \
micronaut-app
# Kubernetes配置
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"
---
b.GC调优
a.功能说明
选择合适的垃圾回收器并优化GC参数。
b.配置示例
---
# G1 GC配置(推荐)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m
-XX:InitiatingHeapOccupancyPercent=45
# ZGC配置(低延迟)
-XX:+UseZGC
-XX:ZCollectionInterval=5
-XX:ZAllocationSpikeTolerance=2
# GC日志
-Xlog:gc*:file=gc.log:time,uptime,level,tags
---
02.数据库优化
a.连接池配置
a.功能说明
优化数据库连接池参数,提升数据库访问性能。
b.配置示例
---
// application.yml
datasources:
default:
url: jdbc:mysql://localhost:3306/db
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
leak-detection-threshold: 60000
---
b.查询优化
a.功能说明
优化数据库查询,使用索引和批量操作。
b.代码示例
---
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// 使用索引字段查询
@Query("SELECT o FROM Order o WHERE o.userId = :userId AND o.status = :status")
List<Order> findByUserIdAndStatus(Long userId, String status);
// 批量查询
@Query("SELECT o FROM Order o WHERE o.id IN :ids")
List<Order> findByIds(List<Long> ids);
// 分页查询
Page<Order> findByStatus(String status, Pageable pageable);
// 使用DTO投影
@Query("SELECT new com.example.dto.OrderSummary(o.id, o.totalAmount, o.status) " +
"FROM Order o WHERE o.userId = :userId")
List<OrderSummary> findOrderSummaries(Long userId);
}
// 批量插入
@Service
public class OrderService {
@Transactional
public void batchInsert(List<Order> orders) {
int batchSize = 100;
for (int i = 0; i < orders.size(); i++) {
orderRepository.save(orders.get(i));
if (i % batchSize == 0) {
entityManager.flush();
entityManager.clear();
}
}
}
}
---
03.缓存优化
a.本地缓存
a.功能说明
使用Caffeine实现本地缓存,减少数据库访问。
b.代码示例
---
// build.gradle
dependencies {
implementation("io.micronaut.cache:micronaut-cache-caffeine")
}
// application.yml
micronaut:
caches:
products:
maximum-size: 1000
expire-after-write: 10m
users:
maximum-size: 500
expire-after-access: 30m
// 使用缓存
import io.micronaut.cache.annotation.Cacheable;
import io.micronaut.cache.annotation.CacheInvalidate;
@Service
public class ProductService {
@Cacheable("products")
public Product findById(Long id) {
return productRepository.findById(id).orElseThrow();
}
@CacheInvalidate("products")
public Product update(Long id, Product product) {
return productRepository.update(product);
}
}
---
b.分布式缓存
a.功能说明
使用Redis实现分布式缓存。
b.代码示例
---
// build.gradle
dependencies {
implementation("io.micronaut.redis:micronaut-redis-lettuce")
}
// application.yml
redis:
uri: redis://localhost:6379
caches:
orders:
expire-after-write: 1h
// 使用Redis缓存
@Service
public class OrderService {
@Inject
private RedisCommands<String, String> redis;
public Order findById(Long id) {
String cacheKey = "order:" + id;
String cached = redis.get(cacheKey);
if (cached != null) {
return objectMapper.readValue(cached, Order.class);
}
Order order = orderRepository.findById(id).orElseThrow();
redis.setex(cacheKey, 3600, objectMapper.writeValueAsString(order));
return order;
}
}
---
04.HTTP优化
a.连接池配置
a.功能说明
优化HTTP客户端连接池配置。
b.配置示例
---
// application.yml
micronaut:
http:
client:
pool:
enabled: true
max-connections: 50
max-pending-acquires: 100
read-timeout: 10s
connect-timeout: 5s
---
b.响应压缩
a.功能说明
启用HTTP响应压缩,减少网络传输。
b.配置示例
---
// application.yml
micronaut:
server:
netty:
compression:
enabled: true
mime-types:
- application/json
- text/html
threshold: 1024
---
05.异步处理
a.响应式编程
a.功能说明
使用响应式编程提升并发处理能力。
b.代码示例
---
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;
@Controller("/api/orders")
public class OrderController {
@Get("/{id}")
public Mono<Order> getOrder(Long id) {
return Mono.fromCallable(() -> orderService.findById(id));
}
@Get
public Flux<Order> listOrders() {
return Flux.fromIterable(orderService.findAll());
}
@Post
public Mono<HttpResponse<Order>> createOrder(@Body Order order) {
return Mono.fromCallable(() -> orderService.create(order))
.map(HttpResponse::created);
}
}
---
b.线程池配置
a.功能说明
配置异步任务线程池。
b.配置示例
---
// application.yml
micronaut:
executors:
io:
type: fixed
n-threads: 75
scheduled:
type: scheduled
core-pool-size: 2
// 使用异步执行
import io.micronaut.scheduling.annotation.Async;
@Service
public class NotificationService {
@Async
public void sendEmail(String to, String subject, String body) {
// 异步发送邮件
}
@Async("io")
public void processLargeFile(String filePath) {
// 使用IO线程池处理文件
}
}
---
7.5 监控与日志
01.日志配置
a.Logback配置
a.功能说明
配置Logback日志框架,实现日志分级和输出。
b.配置示例
---
// src/main/resources/logback.xml
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/application-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.example" level="DEBUG"/>
<logger name="io.micronaut" level="INFO"/>
<logger name="io.netty" level="WARN"/>
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
// 在代码中使用
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class OrderService {
private static final Logger LOG = LoggerFactory.getLogger(OrderService.class);
public Order createOrder(Order order) {
LOG.debug("Creating order: {}", order);
try {
Order created = orderRepository.save(order);
LOG.info("Order created successfully: {}", created.getId());
return created;
} catch (Exception e) {
LOG.error("Failed to create order", e);
throw e;
}
}
}
---
b.结构化日志
a.功能说明
使用JSON格式输出结构化日志,便于日志分析。
b.配置示例
---
// build.gradle
dependencies {
implementation("net.logstash.logback:logstash-logback-encoder:7.3")
}
// logback.xml
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>traceId</includeMdcKeyName>
<includeMdcKeyName>spanId</includeMdcKeyName>
</encoder>
</appender>
// 使用MDC添加上下文信息
import org.slf4j.MDC;
@Service
public class OrderService {
public Order createOrder(Order order) {
MDC.put("orderId", order.getId().toString());
MDC.put("userId", order.getUserId().toString());
try {
LOG.info("Processing order");
return orderRepository.save(order);
} finally {
MDC.clear();
}
}
}
---
02.指标监控
a.Micrometer集成
a.功能说明
使用Micrometer收集应用指标。
b.配置示例
---
// build.gradle
dependencies {
implementation("io.micronaut.micrometer:micronaut-micrometer-core")
implementation("io.micronaut.micrometer:micronaut-micrometer-registry-prometheus")
}
// application.yml
micronaut:
metrics:
enabled: true
export:
prometheus:
enabled: true
step: PT1M
descriptions: true
endpoints:
prometheus:
sensitive: false
// 访问指标端点
// http://localhost:8080/prometheus
// 自定义指标
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Timer;
@Service
public class OrderService {
private final Counter orderCounter;
private final Timer orderTimer;
public OrderService(MeterRegistry registry) {
this.orderCounter = registry.counter("orders.created");
this.orderTimer = registry.timer("orders.processing.time");
}
public Order createOrder(Order order) {
return orderTimer.record(() -> {
Order created = orderRepository.save(order);
orderCounter.increment();
return created;
});
}
}
---
b.健康检查
a.功能说明
实现健康检查端点,监控服务状态。
b.代码示例
---
// application.yml
endpoints:
health:
enabled: true
sensitive: false
details-visible: ANONYMOUS
// 自定义健康检查
import io.micronaut.management.health.indicator.HealthIndicator;
import io.micronaut.management.health.indicator.HealthResult;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
@Singleton
public class CustomHealthIndicator implements HealthIndicator {
@Inject
private OrderService orderService;
@Override
public Publisher<HealthResult> getResult() {
return Mono.fromCallable(() -> {
try {
// 检查服务是否正常
long count = orderService.count();
return HealthResult.builder("order-service")
.status(HealthStatus.UP)
.details(Map.of("totalOrders", count))
.build();
} catch (Exception e) {
return HealthResult.builder("order-service")
.status(HealthStatus.DOWN)
.exception(e)
.build();
}
});
}
}
// 访问健康检查
// http://localhost:8080/health
---
03.APM集成
a.Prometheus监控
a.功能说明
集成Prometheus进行指标采集和监控。
b.配置示例
---
// prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'micronaut-app'
static_configs:
- targets: ['localhost:8080']
metrics_path: '/prometheus'
# 启动Prometheus
docker run -d \
-p 9090:9090 \
-v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \
prom/prometheus
# 访问Prometheus UI
# http://localhost:9090
---
b.Grafana可视化
a.功能说明
使用Grafana创建监控仪表板。
b.配置示例
---
# 启动Grafana
docker run -d \
-p 3000:3000 \
grafana/grafana
# 配置数据源
# 1. 访问 http://localhost:3000
# 2. 添加Prometheus数据源
# 3. URL: http://prometheus:9090
# 导入Micronaut仪表板
# Dashboard ID: 11955
# 常用查询
# - JVM内存使用: jvm_memory_used_bytes
# - HTTP请求数: http_server_requests_total
# - 响应时间: http_server_requests_seconds
# - 数据库连接: hikaricp_connections_active
---
04.日志聚合
a.ELK Stack
a.功能说明
使用Elasticsearch、Logstash、Kibana进行日志聚合和分析。
b.配置示例
---
// logback.xml - Logstash输出
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>logstash:5000</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
// docker-compose.yml
version: '3'
services:
elasticsearch:
image: elasticsearch:8.5.0
ports:
- "9200:9200"
environment:
- discovery.type=single-node
logstash:
image: logstash:8.5.0
ports:
- "5000:5000"
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
kibana:
image: kibana:8.5.0
ports:
- "5601:5601"
depends_on:
- elasticsearch
// logstash.conf
input {
tcp {
port => 5000
codec => json
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "micronaut-logs-%{+YYYY.MM.dd}"
}
}
---
b.日志查询
a.功能说明
在Kibana中查询和分析日志。
b.查询示例
---
# Kibana查询语法
# 查询错误日志
level: ERROR
# 查询特定服务
logger_name: "com.example.OrderService"
# 查询特定时间范围
@timestamp: [2024-01-01 TO 2024-01-31]
# 组合查询
level: ERROR AND logger_name: "com.example.*"
# 聚合查询
# 按日志级别统计
# 按服务统计错误数
---
05.告警配置
a.Prometheus告警
a.功能说明
配置Prometheus告警规则。
b.配置示例
---
// alert.rules.yml
groups:
- name: micronaut_alerts
interval: 30s
rules:
- alert: HighErrorRate
expr: rate(http_server_requests_total{status=~"5.."}[5m]) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate detected"
description: "Error rate is {{ $value }} requests/sec"
- alert: HighMemoryUsage
expr: jvm_memory_used_bytes / jvm_memory_max_bytes > 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "High memory usage"
description: "Memory usage is {{ $value | humanizePercentage }}"
- alert: ServiceDown
expr: up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Service is down"
---
b.Alertmanager配置
a.功能说明
配置告警通知渠道。
b.配置示例
---
// alertmanager.yml
global:
resolve_timeout: 5m
route:
group_by: ['alertname']
group_wait: 10s
group_interval: 10s
repeat_interval: 1h
receiver: 'email'
receivers:
- name: 'email'
email_configs:
- to: '[email protected]'
from: '[email protected]'
smarthost: 'smtp.example.com:587'
auth_username: 'alertmanager'
auth_password: 'password'
- name: 'slack'
slack_configs:
- api_url: 'https://hooks.slack.com/services/xxx'
channel: '#alerts'
---
7.6 容器化部署
01.Docker镜像构建
a.Dockerfile编写
a.功能说明
编写Dockerfile构建应用镜像。
b.配置示例
---
# 多阶段构建Dockerfile
FROM gradle:8.5-jdk17 AS builder
WORKDIR /app
COPY . .
RUN gradle clean build --no-daemon
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
# 构建镜像
docker build -t micronaut-app:1.0.0 .
# 运行容器
docker run -d \
-p 8080:8080 \
--name micronaut-app \
micronaut-app:1.0.0
---
b.镜像优化
a.功能说明
优化Docker镜像大小和构建速度。
b.配置示例
---
# 使用.dockerignore
.git
.gradle
build
*.md
.idea
# 分层构建
FROM gradle:8.5-jdk17 AS builder
WORKDIR /app
# 先复制依赖文件
COPY build.gradle settings.gradle ./
COPY gradle ./gradle
RUN gradle dependencies --no-daemon
# 再复制源代码
COPY src ./src
RUN gradle build --no-daemon
# 使用Alpine基础镜像
FROM eclipse-temurin:17-jre-alpine
# 添加非root用户
RUN addgroup -S micronaut && adduser -S micronaut -G micronaut
WORKDIR /app
COPY --from=builder /app/build/libs/app.jar app.jar
RUN chown -R micronaut:micronaut /app
USER micronaut
EXPOSE 8080
ENTRYPOINT ["java", \
"-XX:+UseContainerSupport", \
"-XX:MaxRAMPercentage=75.0", \
"-jar", "app.jar"]
---
02.Docker Compose
a.多服务编排
a.功能说明
使用Docker Compose编排多个服务。
b.配置示例
---
# docker-compose.yml
version: '3.8'
services:
order-service:
build: ./order-service
ports:
- "8080:8080"
environment:
- MICRONAUT_ENVIRONMENTS=prod
- DB_HOST=mysql
- REDIS_HOST=redis
depends_on:
- mysql
- redis
networks:
- app-network
inventory-service:
build: ./inventory-service
ports:
- "8081:8080"
environment:
- MICRONAUT_ENVIRONMENTS=prod
- DB_HOST=mysql
depends_on:
- mysql
networks:
- app-network
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=secret
- MYSQL_DATABASE=orders
volumes:
- mysql-data:/var/lib/mysql
networks:
- app-network
redis:
image: redis:7-alpine
networks:
- app-network
volumes:
mysql-data:
networks:
app-network:
driver: bridge
# 启动所有服务
docker-compose up -d
# 查看日志
docker-compose logs -f order-service
# 停止服务
docker-compose down
---
b.健康检查
a.功能说明
配置容器健康检查。
b.配置示例
---
# docker-compose.yml
services:
order-service:
build: ./order-service
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Dockerfile
FROM eclipse-temurin:17-jre-alpine
# 安装curl用于健康检查
RUN apk add --no-cache curl
WORKDIR /app
COPY app.jar app.jar
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]
---
03.Kubernetes部署
a.Deployment配置
a.功能说明
创建Kubernetes Deployment部署应用。
b.配置示例
---
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
labels:
app: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: micronaut-app:1.0.0
ports:
- containerPort: 8080
env:
- name: MICRONAUT_ENVIRONMENTS
value: "prod"
- name: DB_HOST
value: "mysql-service"
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 20
periodSeconds: 5
# 部署应用
kubectl apply -f deployment.yaml
# 查看部署状态
kubectl get deployments
kubectl get pods
# 查看日志
kubectl logs -f deployment/order-service
---
b.Service配置
a.功能说明
创建Kubernetes Service暴露应用。
b.配置示例
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
type: LoadBalancer
selector:
app: order-service
ports:
- protocol: TCP
port: 80
targetPort: 8080
# 部署Service
kubectl apply -f service.yaml
# 查看Service
kubectl get services
# 获取外部IP
kubectl get service order-service
---
c.ConfigMap和Secret
a.功能说明
使用ConfigMap和Secret管理配置和敏感信息。
b.配置示例
---
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: order-config
data:
application.yml: |
micronaut:
application:
name: order-service
server:
port: 8080
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: order-secret
type: Opaque
data:
db-password: c2VjcmV0 # base64编码
# 在Deployment中使用
spec:
containers:
- name: order-service
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: order-secret
key: db-password
volumeMounts:
- name: config
mountPath: /app/config
volumes:
- name: config
configMap:
name: order-config
# 创建ConfigMap和Secret
kubectl apply -f configmap.yaml
kubectl apply -f secret.yaml
---
04.Helm部署
a.Chart创建
a.功能说明
使用Helm简化Kubernetes应用部署。
b.配置示例
---
# 创建Helm Chart
helm create micronaut-app
# Chart.yaml
apiVersion: v2
name: micronaut-app
version: 1.0.0
appVersion: "1.0.0"
# values.yaml
replicaCount: 3
image:
repository: micronaut-app
tag: "1.0.0"
pullPolicy: IfNotPresent
service:
type: LoadBalancer
port: 80
targetPort: 8080
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
env:
- name: MICRONAUT_ENVIRONMENTS
value: "prod"
# 安装Chart
helm install order-service ./micronaut-app
# 升级
helm upgrade order-service ./micronaut-app
# 回滚
helm rollback order-service 1
# 卸载
helm uninstall order-service
---
b.多环境配置
a.功能说明
为不同环境配置不同的values文件。
b.配置示例
---
# values-dev.yaml
replicaCount: 1
image:
tag: "dev"
env:
- name: MICRONAUT_ENVIRONMENTS
value: "dev"
# values-prod.yaml
replicaCount: 5
image:
tag: "1.0.0"
env:
- name: MICRONAUT_ENVIRONMENTS
value: "prod"
resources:
requests:
memory: "1Gi"
cpu: "1000m"
# 部署到不同环境
helm install order-service-dev ./micronaut-app -f values-dev.yaml
helm install order-service-prod ./micronaut-app -f values-prod.yaml
---
05.CI/CD集成
a.GitHub Actions
a.功能说明
使用GitHub Actions自动化构建和部署。
b.配置示例
---
# .github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
run: ./gradlew build
- name: Build Docker image
run: docker build -t micronaut-app:${{ github.sha }} .
- name: Push to Registry
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker tag micronaut-app:${{ github.sha }} myregistry/micronaut-app:${{ github.sha }}
docker push myregistry/micronaut-app:${{ github.sha }}
- name: Deploy to Kubernetes
uses: azure/k8s-deploy@v4
with:
manifests: |
k8s/deployment.yaml
k8s/service.yaml
images: |
myregistry/micronaut-app:${{ github.sha }}
---
b.GitLab CI
a.功能说明
使用GitLab CI/CD进行自动化部署。
b.配置示例
---
# .gitlab-ci.yml
stages:
- build
- test
- deploy
variables:
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
build:
stage: build
image: gradle:8.5-jdk17
script:
- gradle clean build
artifacts:
paths:
- build/libs/*.jar
docker:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker build -t $DOCKER_IMAGE .
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker push $DOCKER_IMAGE
deploy:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/order-service order-service=$DOCKER_IMAGE
- kubectl rollout status deployment/order-service
only:
- main
---
7.7 常见问题排查
01.启动问题
a.端口占用
a.问题描述
应用启动失败,提示端口已被占用。
b.解决方案
---
# 查看端口占用
# Linux/macOS
lsof -i :8080
netstat -an | grep 8080
# Windows
netstat -ano | findstr :8080
# 终止占用进程
kill -9 <PID>
# 或修改应用端口
// application.yml
micronaut:
server:
port: 8081
# 或使用随机端口
micronaut:
server:
port: -1
---
b.依赖冲突
a.问题描述
应用启动时出现ClassNotFoundException或NoSuchMethodError。
b.解决方案
---
# 查看依赖树
./gradlew dependencies
# 排除冲突依赖
// build.gradle
dependencies {
implementation("io.micronaut:micronaut-runtime") {
exclude group: 'org.slf4j', module: 'slf4j-api'
}
}
# 强制使用特定版本
configurations.all {
resolutionStrategy {
force 'org.slf4j:slf4j-api:2.0.0'
}
}
# 清理缓存重新构建
./gradlew clean build --refresh-dependencies
---
c.Bean注入失败
a.问题描述
启动时提示No bean of type found。
b.解决方案
---
# 检查Bean定义
@Singleton // 确保有注解
public class OrderService {
// ...
}
# 检查包扫描路径
@Application
@ComponentScan("com.example") // 指定扫描路径
public class Application {
// ...
}
# 检查依赖注入
@Controller("/orders")
public class OrderController {
@Inject // 使用@Inject注解
private OrderService orderService;
// 或使用构造器注入(推荐)
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
}
# 查看Bean定义
./gradlew run --debug
---
02.数据库问题
a.连接失败
a.问题描述
应用无法连接到数据库。
b.解决方案
---
# 检查数据库配置
// application.yml
datasources:
default:
url: jdbc:mysql://localhost:3306/db?serverTimezone=UTC
username: root
password: secret
driverClassName: com.mysql.cj.jdbc.Driver
# 测试数据库连接
mysql -h localhost -u root -p
# 检查防火墙
telnet localhost 3306
# 检查数据库驱动
// build.gradle
dependencies {
runtimeOnly("mysql:mysql-connector-java:8.0.33")
}
# 启用SQL日志
// application.yml
jpa:
default:
properties:
hibernate:
show_sql: true
format_sql: true
logger:
levels:
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
---
b.事务问题
a.问题描述
数据未正确提交或回滚。
b.解决方案
---
# 确保使用@Transactional注解
@Service
public class OrderService {
@Transactional
public Order createOrder(Order order) {
return orderRepository.save(order);
}
@Transactional(readOnly = true)
public Order findById(Long id) {
return orderRepository.findById(id).orElseThrow();
}
}
# 检查事务传播
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void independentOperation() {
// 独立事务
}
# 异常回滚配置
@Transactional(rollbackFor = Exception.class)
public void riskyOperation() throws Exception {
// 所有��常都回滚
}
# 手动事务管理
@Inject
private TransactionOperations<Connection> transactionManager;
public void manualTransaction() {
transactionManager.executeWrite(status -> {
// 事务操作
return null;
});
}
---
03.性能问题
a.响应慢
a.问题描述
API响应时间过长。
b.解决方案
---
# 启用请求日志
// application.yml
logger:
levels:
io.micronaut.http.server: DEBUG
# 添加性能监控
import io.micrometer.core.instrument.Timer;
@Controller("/orders")
public class OrderController {
@Inject
private MeterRegistry registry;
@Get("/{id}")
public Order getOrder(Long id) {
Timer.Sample sample = Timer.start(registry);
try {
return orderService.findById(id);
} finally {
sample.stop(registry.timer("order.get.time"));
}
}
}
# 检查数据库查询
// 启用慢查询日志
jpa:
default:
properties:
hibernate:
generate_statistics: true
# 添加数据库索引
@Entity
@Table(indexes = {
@Index(name = "idx_user_id", columnList = "user_id"),
@Index(name = "idx_status", columnList = "status")
})
public class Order {
// ...
}
# 使用缓存
@Cacheable("orders")
public Order findById(Long id) {
return orderRepository.findById(id).orElseThrow();
}
---
b.内存泄漏
a.问题描述
应用内存持续增长,最终OOM。
b.解决方案
---
# 生成堆转储
jmap -dump:live,format=b,file=heap.bin <pid>
# 分析堆转储
# 使用Eclipse MAT或VisualVM
# 启用GC日志
java -Xlog:gc*:file=gc.log:time,uptime,level,tags \
-jar app.jar
# 检查资源泄漏
@Service
public class FileService {
public void processFile(String path) {
// 使用try-with-resources
try (InputStream is = new FileInputStream(path)) {
// 处理文件
} catch (IOException e) {
LOG.error("Failed to process file", e);
}
}
}
# 检查线程池
@Inject
@Named("io")
private ExecutorService executor;
@PreDestroy
public void cleanup() {
executor.shutdown();
}
---
04.网络问题
a.服务调用失败
a.问题描述
微服务间调用失败或超时。
b.解决方案
---
# 检查服务发现
@Client("inventory-service")
public interface InventoryClient {
@Get("/inventory/{id}")
Inventory getInventory(Long id);
}
# 配置超时
// application.yml
micronaut:
http:
client:
read-timeout: 30s
connect-timeout: 10s
# 添加重试
// build.gradle
dependencies {
implementation("io.micronaut.retry:micronaut-retry")
}
@Retryable(attempts = "3", delay = "1s")
public Inventory getInventory(Long id) {
return inventoryClient.getInventory(id);
}
# 添加熔断器
@CircuitBreaker(name = "inventory-service")
public Inventory getInventory(Long id) {
return inventoryClient.getInventory(id);
}
# 检查网络连通性
curl http://inventory-service:8080/health
ping inventory-service
---
b.负载均衡问题
a.问题描述
请求未均匀分配到服务实例。
b.解决方案
---
# 检查服务注册
curl http://consul:8500/v1/catalog/service/order-service
# 配置负载均衡策略
// application.yml
micronaut:
http:
client:
load-balancer:
strategy: round-robin # 或 random
# 健康检查配置
consul:
client:
registration:
check:
enabled: true
interval: 10s
http: true
path: /health
# 查看负载均衡日志
logger:
levels:
io.micronaut.http.client.loadbalance: DEBUG
---
05.配置问题
a.配置不生效
a.问题描述
修改配置后应用行为未改变。
b.解决方案
---
# 检查配置文件位置
src/main/resources/application.yml
src/main/resources/application-{env}.yml
# 检查环境变量
export MICRONAUT_ENVIRONMENTS=prod
java -jar app.jar
# 或使用系统属性
java -Dmicronaut.environments=prod -jar app.jar
# 查看生效的配置
@Inject
private Environment environment;
public void printConfig() {
environment.getPropertySources().forEach(source -> {
LOG.info("Config source: {}", source.getName());
});
}
# 配置优先级
# 1. 命令行参数
# 2. 系统属性
# 3. 环境变量
# 4. 配置中心
# 5. application.yml
---
b.配置验证失败
a.问题描述
启动时提示配置验证错误。
b.解决方案
---
# 检查配置类
@ConfigurationProperties("app")
public class AppConfig {
@NotBlank
private String name;
@Min(1)
private int maxConnections;
// Getters and Setters
}
# 提供正确的配置
// application.yml
app:
name: "order-service"
max-connections: 10
# 禁用验证(不推荐)
@ConfigurationProperties(value = "app", validate = false)
public class AppConfig {
// ...
}
---
06.日志问题
a.日志未输出
a.问题描述
应用日志未正常输出。
b.解决方案
---
# 检查logback配置
// src/main/resources/logback.xml
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
# 检查日志级别
// application.yml
logger:
levels:
com.example: DEBUG
io.micronaut: INFO
# 使用正确的Logger
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger LOG = LoggerFactory.getLogger(OrderService.class);
# 检查依赖
// build.gradle
dependencies {
runtimeOnly("ch.qos.logback:logback-classic")
}
---
b.日志文件过大
a.问题描述
日志文件占用大量磁盘空间。
b.解决方案
---
# 配置日志滚动
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/application-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
# 调整日志级别
logger:
levels:
root: WARN
com.example: INFO
# 使用异步日志
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE"/>
<queueSize>512</queueSize>
</appender>
---