单元1 · Micronaut 快速入门
特性、创建项目、Hello World
- Micronaut:编译期 DI 与 AOP,启动快、内存低、云原生。
- 创建:mn create-app 或 Launch 网站生成项目。
- 路由:@Controller + @Get 定义 REST 接口。
实训1.1 Micronaut 特性
说明 Micronaut 的核心特性(编译期 DI、快速启动、低内存)。
Micronaut 编译期完成依赖注入与 AOP,启动快、内存低、云原生友好。
// Micronaut 核心优势
// 1. 编译期 DI:依赖注入在编译时完成,无反射开销
// 2. 快速启动:启动时间毫秒级
// 3. 低内存:适合 Serverless 与微服务
// 4. 多语言:Java/Kotlin/Groovy 支持
public class MicronautIntro {
// 以上为要点
}
实训1.2 创建 Micronaut 项目
使用 Micronaut Launch 创建项目。
mn 命令行工具或 Micronaut Launch 网站生成项目骨架。
# 使用 CLI 创建
mn create-app com.helpme.demo --build=maven --lang=java
# 指定特性创建
mn create-app com.helpme.demo -f http-client -f data-jdbc
# 运行应用
cd demo
./mvnw run
实训1.3 第一个控制器
编写 HelloController 返回欢迎信息。
@Controller + @Get 注解定义路由,Micronaut 自动注册。
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
@Controller("/hello")
public class HelloController {
@Get
public String hello() {
return "Hello Micronaut";
}
@Get("/{name}")
public String helloName(String name) {
return "Hello, " + name;
}
}
单元2 · 依赖注入
@Singleton、@Inject、构造器注入
- DI:@Singleton + 构造器注入,编译期完成无反射。
- 作用域:Singleton 单例、Prototype 原型、RequestScope 每请求。
实训2.1 定义 Bean
定义 GreetingService 为 @Singleton Bean。
@Singleton 默认单例;Micronaut 编译期生成 Bean 元数据。
import jakarta.inject.Singleton;
@Singleton
public class GreetingService {
public String greet(String name) {
return "Hello, " + name;
}
}
实训2.2 构造器注入
在控制器中构造器注入 GreetingService。
构造器注入是 Micronaut 推荐方式,无反射、易于测试。
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
@Controller("/greet")
public class GreetController {
private final GreetingService greetingService;
public GreetController(GreetingService greetingService) {
this.greetingService = greetingService;
}
@Get("/{name}")
public String greet(String name) {
return greetingService.greet(name);
}
}
实训2.3 作用域
对比 @Singleton、@Prototype、@RequestScope 作用域。
Singleton 单例;Prototype 每次注入新实例;RequestScope 每请求。
import io.micronaut.runtime.http.scope.RequestScope;
import jakarta.inject.Singleton;
import jakarta.inject.Prototype;
@Singleton
public class AppService { }
@Prototype
public class ProtoService { }
@RequestScope
public class RequestState { }
单元3 · REST 控制器
@Controller、@Get/@Post、参数绑定
- 参数:@PathVariable/@QueryValue/@DefaultValue。
- JSON:@Body 绑定请求体,@Consumes/@Produces。
- 响应:HttpResponse.ok/created/notFound。
实训3.1 路径与查询参数
编写接收路径参数与查询参数的接口。
@PathVariable 路径参数、@QueryValue 查询参数、@DefaultValue 默认值。
import io.micronaut.http.annotation.*;
@Controller("/api/users")
public class UserController {
@Get("/{id}")
public String getUser(@PathVariable Long id,
@QueryValue(defaultValue = "false") boolean verbose) {
return "用户 " + id + ", verbose=" + verbose;
}
}
实训3.2 POST JSON 请求
接收 JSON 请求体创建用户。
@Body User user 绑定请求体;@Consumes/@Produces 声明媒体类型。
import io.micronaut.http.annotation.*;
import io.micronaut.http.MediaType;
@Controller("/api/users")
public class UserController {
@Post(consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON)
public User create(@Body User user) {
user.setId(1L);
return user;
}
}
实训3.3 响应与状态码
使用 HttpResponse 返回自定义状态码。
HttpResponse.created(obj) 返回 201,response.notFound() 返回 404。
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.*;
@Controller("/api/users")
public class UserController {
@Get("/{id}")
public HttpResponse<User> get(@PathVariable Long id) {
if (id > 100) {
return HttpResponse.notFound();
}
User u = new User();
u.setId(id);
u.setName("用户" + id);
return HttpResponse.ok(u);
}
}
单元4 · 配置管理
application.yml、@ConfigurationProperties、环境
- 配置:application.yml + @Value。
- 映射类:@ConfigurationProperties 类型安全绑定。
- 环境:application-dev/prod.yml + MICRONAUT_ENVIRONMENTS。
实训4.1 读取配置
在 application.yml 配置属性并注入使用。
@Value 注入单值;@ConfigurationProperties 映射配置类。
# application.yml
app:
name: Helpme
version: 1.0.0
import io.micronaut.context.annotation.Value;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
@Controller("/config")
public class ConfigController {
@Value("${app.name}")
String appName;
@Value("${app.version:0.0.1}")
String version;
@Get
public String info() {
return appName + ":" + version;
}
}
实训4.2 配置属性类
使用 @ConfigurationProperties 映射配置为类。
@ConfigurationProperties("app") 绑定 app.* 配置到字段。
import io.micronaut.context.annotation.ConfigurationProperties;
@ConfigurationProperties("app")
public class AppConfig {
private String name;
private int port;
private boolean debug;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
public boolean isDebug() { return debug; }
public void setDebug(boolean debug) { this.debug = debug; }
}
实训4.3 环境切换
使用不同环境配置切换 dev/prod。
application-dev.yml / application-prod.yml,MICRONAUT_ENVIRONMENTS 指定。
# 环境文件
# application-dev.yml
app:
name: Helpme-Dev
# application-prod.yml
app:
name: Helpme-Prod
# 启动时指定环境
MICRONAUT_ENVIRONMENTS=prod ./mvnw run
单元5 · 数据访问
Data JDBC、@MappedEntity、Repository
- Data JDBC:@MappedEntity 映射 + CrudRepository。
- 分页:Pageable + Page 分页查询。
实训5.1 实体映射
定义 User 实体使用 Data JDBC 映射。
@MappedEntity 映射表;@Id @GeneratedValue 主键自增。
import io.micronaut.data.annotation.GeneratedValue;
import io.micronaut.data.annotation.Id;
import io.micronaut.data.annotation.MappedEntity;
@MappedEntity("users")
public class User {
@Id
@GeneratedValue(GeneratedValue.Type.AUTO)
private Long id;
private String name;
private int age;
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 int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
实训5.2 Repository 接口
定义 UserRepository 继承 CrudRepository。
CrudRepository 提供 save/findById/deleteAll 等内置方法。
import io.micronaut.data.annotation.Repository;
import io.micronaut.data.repository.CrudRepository;
import java.util.List;
import java.util.Optional;
@Repository
public interface UserRepository extends CrudRepository<User, Long> {
List<User> findByName(String name);
Optional<User> findFirstByAgeGreaterThan(int age);
long countByAgeGreaterThan(int age);
}
实训5.3 分页查询
实现分页查询用户列表。
Pageable 分页参数;Page 返回分页结果与总数。
import io.micronaut.data.annotation.Repository;
import io.micronaut.data.model.Page;
import io.micronaut.data.model.Pageable;
import io.micronaut.data.repository.CrudRepository;
@Repository
public interface UserRepository extends CrudRepository<User, Long> {
Page<User> findAll(Pageable pageable);
}
// 使用
Page<User> page = userRepository.findAll(Pageable.from(0, 10));
System.out.println("总数:" + page.getTotalSize());
System.out.println("当前页:" + page.getContent());
单元6 · HTTP 客户端
@Client、声明式客户端
- 声明式客户端:@Client + 接口方法自动生成实现。
- 响应式:返回 Single/Uni 异步调用。
实训6.1 声明式客户端
编写声明式 HTTP 客户端调用外部 API。
@Client 注解 + 接口方法定义,Micronaut 自动生成实现。
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
@Client("https://api.helpme.com")
public interface HelpmeApiClient {
@Get("/users/{id}")
User getUser(Long id);
}
实训6.2 客户端错误处理
处理客户端调用异常与错误响应。
@Error 或 HttpClientResponseException 捕获错误;fallback 兜底。
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.client.exceptions.HttpClientResponseException;
@Client("https://api.helpme.com")
public interface HelpmeApiClient {
@Get("/users/{id}")
User getUser(Long id);
default User getUserSafe(Long id) {
try {
return getUser(id);
} catch (HttpClientResponseException e) {
System.out.println("调用失败:" + e.getStatus());
return null;
}
}
}
实训6.3 响应式客户端
使用响应式客户端返回 Uni 异步结果。
返回类型改为 Uni
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import io.reactivex.Single;
@Client("https://api.helpme.com")
public interface RxHelpmeApiClient {
@Get("/users/{id}")
Single<User> getUserRx(Long id);
}
单元7 · AOP 与切面
@Around、自定义注解、拦截器
- AOP:@Around + MethodInterceptor 环绕切面。
- @Introduction:接口引入默认实现。
实训7.1 @Around 环绕切面
编写 @Around 切面记录方法执行时间。
@Around 环绕通知,MethodInvocationContext 提供方法调用信息。
import io.micronaut.aop.Around;
import io.micronaut.aop.InterceptorBean;
import io.micronaut.aop.MethodInterceptor;
import io.micronaut.aop.MethodInvocationContext;
import jakarta.inject.Singleton;
@Around
public @interface Timed { }
@Singleton
@InterceptorBean(Timed.class)
public class TimedInterceptor implements MethodInterceptor<Object, Object> {
@Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
long start = System.currentTimeMillis();
Object result = context.proceed();
long cost = System.currentTimeMillis() - start;
System.out.println(context.getMethodName() + " 耗时 " + cost + "ms");
return result;
}
}
实训7.2 使用自定义注解
在业务方法上使用 @Timed 注解。
在方法或类上标注自定义切面注解,Micronaut 编译期织入。
import jakarta.inject.Singleton;
@Singleton
public class OrderService {
@Timed
public String createOrder(String no) {
try { Thread.sleep(50); } catch (InterruptedException ignored) {}
return "订单 " + no;
}
}
实训7.3 引入与适配
使用 @Introduction 实现接口默认实现(适配器模式)。
@Introduction 将接口实现委托给指定类,常用于解耦。
import io.micronaut.aop.Introduction;
import io.micronaut.context.annotation.Bean;
@Introduction
public @interface Notify { }
@Bean
public class NotifyAdvice implements NotifySender {
@Override
public void send(String msg) {
System.out.println("发送通知:" + msg);
}
}
public interface NotifySender {
void send(String msg);
}
单元8 · 消息与事件
事件总线、Kafka 集成
- 事件:ApplicationEventPublisher + @EventListener。
- Kafka:@KafkaListener + @Topic 消费消息。
实训8.1 事件发布
使用 ApplicationEventPublisher 发布事件。
publishEvent 发布领域事件,解耦业务模块。
import io.micronaut.context.event.ApplicationEventPublisher;
import jakarta.inject.Singleton;
@Singleton
public class OrderService {
private final ApplicationEventPublisher eventPublisher;
public OrderService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void createOrder(String no) {
System.out.println("创建订单:" + no);
eventPublisher.publishEvent(new OrderCreatedEvent(no));
}
}
public class OrderCreatedEvent {
public final String orderNo;
public OrderCreatedEvent(String orderNo) { this.orderNo = orderNo; }
}
实训8.2 事件监听
监听 OrderCreatedEvent 事件。
@EventListener 监听事件,方法参数为事件类型。
import io.micronaut.context.event.EventListener;
import jakarta.inject.Singleton;
@Singleton
public class OrderEventListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
System.out.println("监听事件:订单 " + event.orderNo + " 已创建");
}
}
实训8.3 Kafka 消费者
使用 Micronaut Kafka 注解消费消息。
@KafkaListener + @Topic 订阅 topic,方法接收消息。
import io.micronaut.configuration.kafka.annotation.KafkaListener;
import io.micronaut.configuration.kafka.annotation.Topic;
@KafkaListener(groupId = "order-group")
public class OrderConsumer {
@Topic("orders")
public void consume(String order) {
System.out.println("消费订单:" + order);
}
}
单元9 · 调度任务
@Scheduled、Quartz
- 调度:@Scheduled fixedDelay/cron/fixedRate。
实训9.1 @Scheduled 定时任务
创建每 10 秒执行一次的定时任务。
@Scheduled(fixedDelay = "10s") 固定延迟;cron 支持 cron 表达式。
import io.micronaut.scheduling.annotation.Scheduled;
import jakarta.inject.Singleton;
import java.time.LocalTime;
@Singleton
public class SchedulerBean {
@Scheduled(fixedDelay = "10s")
public void everyTenSeconds() {
System.out.println("定时任务:" + LocalTime.now());
}
}
实训9.2 Cron 定时任务
使用 cron 表达式在每天 8 点执行任务。
@Scheduled(cron = "0 0 8 * * ?") 按 cron 触发。
import io.micronaut.scheduling.annotation.Scheduled;
import jakarta.inject.Singleton;
@Singleton
public class DailyJob {
@Scheduled(cron = "0 0 8 * * ?")
public void dailyReport() {
System.out.println("生成每日报告");
}
}
实训9.3 固定延迟与固定频率
区分 fixedDelay 与 initialDelay 的语义。
fixedDelay 上次结束后计时;initialDelay 首次执行延迟。
import io.micronaut.scheduling.annotation.Scheduled;
import jakarta.inject.Singleton;
@Singleton
public class JobBean {
// 首次延迟 5 秒,之后每 10 秒执行一次(上次完成后计时)
@Scheduled(fixedDelay = "10s", initialDelay = "5s")
public void jobA() {
System.out.println("JobA");
}
// 每 30 秒固定频率执行(不等待上次完成)
@Scheduled(fixedRate = "30s")
public void jobB() {
System.out.println("JobB");
}
}
单元10 · 安全与认证
Security、JWT、RBAC
- 安全:micronaut-security-jwt + @Secured。
- 角色:ROLE_ADMIN 等角色控制。
- 用户:Authentication 参数获取登录信息。
实训10.1 开启安全
引入 Micronaut Security 依赖并启用 JWT 认证。
micronaut-security-jwt 依赖 + application.yml 配置。
<dependency>
<groupId>io.micronaut.security</groupId>
<artifactId>micronaut-security-jwt</artifactId>
</dependency>
# application.yml
micronaut:
security:
enabled: true
token:
jwt:
signatures:
secret:
generator:
secret: "CHANGE_ME_SECRET_KEY_AT_LEAST_32_CHARS"
实训10.2 角色权限
使用 @Secured 注解控制接口访问角色。
@Secured(SecurityRule.IS_AUTHENTICATED) 需登录;@Secured("ROLE_ADMIN") 限角色。
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.rules.SecurityRule;
@Controller("/admin")
@Secured(SecurityRule.IS_AUTHENTICATED)
public class AdminController {
@Get
@Secured("ROLE_ADMIN")
public String adminOnly() {
return "管理员可见";
}
}
实训10.3 获取登录用户
在控制器中获取当前登录用户信息。
Authentication 参数自动注入,获取用户名与角色。
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.authentication.Authentication;
import io.micronaut.security.rules.SecurityRule;
@Controller("/me")
@Secured(SecurityRule.IS_AUTHENTICATED)
public class MeController {
@Get
public String me(Authentication authentication) {
String username = authentication.getName();
Object roles = authentication.getAttributes().get("roles");
return "当前用户:" + username + ", 角色:" + roles;
}
}
单元11 · 测试
MicronautTest、Mockito
- 测试:@MicronautTest + HttpClient。
- Mock:@MockBean 隔离依赖。
- 属性:@TestPropertySource 覆盖配置。
实训11.1 控制器测试
编写 @MicronautTest 测试 REST 接口。
@MicronautTest 启动应用上下文;HttpClient 发起请求断言。
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import jakarta.inject.Inject;
import static org.junit.jupiter.api.Assertions.*;
@MicronautTest
public class HelloControllerTest {
@Inject
@Client("/")
HttpClient client;
@Test
public void testHello() {
String body = client.toBlocking().retrieve("/hello");
assertEquals("Hello Micronaut", body);
}
}
实训11.2 服务测试与 Mock
使用 @MockBean 替换依赖进行隔离测试。
@MockBean 生成 Mockito mock,when/thenReturn 控制行为。
import io.micronaut.test.annotation.MockBean;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import jakarta.inject.Inject;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
@MicronautTest
public class GreetServiceTest {
@Inject
GreetingService greetingService;
@MockBean(GreetingService.class)
GreetingService mockService() {
return mock(GreetingService.class);
}
@Test
public void testGreet() {
when(greetingService.greet("Tom")).thenReturn("Mocked Tom");
assertEquals("Mocked Tom", greetingService.greet("Tom"));
}
}
实训11.3 嵌入式服务器测试
使用 @TestPropertySource 配置测试环境属性。
@TestPropertySource(properties) 覆盖配置,隔离测试环境。
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import io.micronaut.test.annotation.TestPropertySource;
import org.junit.jupiter.api.Test;
@MicronautTest
@TestPropertySource(properties = {
"app.name=TestApp",
"app.debug=true"
})
public class ConfigTest {
@jakarta.inject.Inject
AppConfig appConfig;
@Test
public void testConfig() {
org.junit.jupiter.api.Assertions.assertEquals("TestApp", appConfig.getName());
}
}
单元12 · 综合项目实训
综合运用所学知识完成项目
- 综合应用:实体 + 仓储 + 控制器 + 事件解耦。
实训12.1 图书管理 API - 实体与仓储
定义 Book 实体与 BookRepository。
综合运用 @MappedEntity 与 Repository 接口。
import io.micronaut.data.annotation.GeneratedValue;
import io.micronaut.data.annotation.Id;
import io.micronaut.data.annotation.MappedEntity;
import io.micronaut.data.annotation.Repository;
import io.micronaut.data.repository.CrudRepository;
import java.util.List;
@MappedEntity("books")
public class Book {
@Id @GeneratedValue(GeneratedValue.Type.AUTO)
private Long id;
private String title;
private String author;
private boolean available;
// getter/setter 省略
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public boolean isAvailable() { return available; }
public void setAvailable(boolean available) { this.available = available; }
}
@Repository
public interface BookRepository extends CrudRepository<Book, Long> {
List<Book> findByAvailableTrue();
List<Book> findByAuthor(String author);
}
实训12.2 图书管理 API - 控制器
实现图书增删改查与借还接口。
综合运用控制器注解与仓储方法。
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.*;
import jakarta.inject.Inject;
import java.util.List;
import java.util.Optional;
@Controller("/api/books")
public class BookController {
@Inject
BookRepository bookRepository;
@Get
public List<Book> list(@QueryValue(defaultValue = "false") boolean availableOnly) {
return availableOnly ? bookRepository.findByAvailableTrue() : bookRepository.findAll();
}
@Post
public HttpResponse<Book> create(@Body Book book) {
return HttpResponse.created(bookRepository.save(book));
}
@Get("/{id}")
public HttpResponse<Book> get(@PathVariable Long id) {
Optional<Book> book = bookRepository.findById(id);
return book.map(HttpResponse::ok).orElseGet(HttpResponse::notFound);
}
@Delete("/{id}")
public HttpResponse<?> delete(@PathVariable Long id) {
bookRepository.deleteById(id);
return HttpResponse.noContent();
}
}
实训12.3 图书管理 API - 事件通知
借书成功时发布事件并异步记录日志。
综合运用事件发布与监听解耦。
import io.micronaut.context.event.ApplicationEventPublisher;
import io.micronaut.context.event.EventListener;
import io.micronaut.http.annotation.*;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
// 事件
public class BookBorrowedEvent {
public final Long bookId;
public final String borrower;
public BookBorrowedEvent(Long bookId, String borrower) {
this.bookId = bookId;
this.borrower = borrower;
}
}
// 借书接口
@Controller("/api/books")
public class BorrowController {
@Inject
ApplicationEventPublisher eventPublisher;
@Post("/{id}/borrow")
public String borrow(@PathVariable Long id, @Body String borrower) {
eventPublisher.publishEvent(new BookBorrowedEvent(id, borrower));
return "借书成功";
}
}
// 事件监听
@Singleton
public class BorrowListener {
@EventListener
public void onBorrow(BookBorrowedEvent event) {
System.out.println("日志:图书 " + event.bookId + " 被 " + event.borrower + " 借走");
}
}