micronaut
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMicronaut Guide
Micronaut 指南
Applies to: Micronaut 4.x, Java 21+, Microservices, Serverless, CLI Applications, IoT
适用于:Micronaut 4.x、Java 21+、微服务、Serverless、CLI应用、IoT
Core Principles
核心原则
- Compile-Time DI: All dependency injection resolved at compile time -- no reflection at runtime
- GraalVM Native: First-class native image support for fast startup and low memory
- Reactive by Default: Built-in reactive programming with non-blocking I/O
- Cloud-Native: Service discovery, distributed tracing, config management out of the box
- Fast Startup: Sub-100ms startup makes it ideal for serverless and containers
- Standard Library: Use annotations, not Spring-specific ones
jakarta.inject
- 编译时DI:所有依赖注入在编译阶段完成解析,运行时无反射操作
- GraalVM Native:原生镜像第一等支持,启动速度快、内存占用低
- 默认响应式:内置响应式编程能力,支持非阻塞I/O
- 云原生:开箱支持服务发现、分布式链路追踪、配置管理
- 快速启动:启动耗时低于100ms,非常适合Serverless和容器场景
- 标准库规范:使用注解,而非Spring专属注解
jakarta.inject
Guardrails
护栏规范
Dependency Injection
依赖注入
- Use constructor injection exclusively (compile-time verified)
- Annotate beans with ,
@Singleton, or@Prototype@RequestScope - Use for third-party objects that cannot be annotated
@Factory - Use for conditional bean loading
@Requires - Never use or Spring DI annotations
@Autowired - Prefer interface-based injection for testability
- Avoid field injection (not compile-time verifiable)
- 仅使用构造函数注入(编译时可校验)
- 为Bean标注、
@Singleton或@Prototype注解@RequestScope - 对无法添加注解的第三方对象使用创建
@Factory - 使用实现条件化Bean加载
@Requires - 禁止使用或Spring DI相关注解
@Autowired - 优先使用基于接口的注入提升可测试性
- 避免使用字段注入(无法在编译时校验)
HTTP Controllers
HTTP控制器
- Annotate controllers with
@Controller("/path") - Use for JDBC/blocking operations
@ExecuteOn(TaskExecutors.BLOCKING) - Apply at class level for request validation
@Validated - Return for status code control, direct type for 200 OK
HttpResponse<T> - Use for POST endpoints
@Status(HttpStatus.CREATED) - Keep controllers thin -- delegate business logic to services
- Add OpenAPI annotations (,
@Operation) for all endpoints@ApiResponse
- 为控制器标注注解
@Controller("/path") - 对JDBC/阻塞操作添加注解
@ExecuteOn(TaskExecutors.BLOCKING) - 在类级别添加注解实现请求参数校验
@Validated - 需自定义状态码时返回,200 OK场景可直接返回对应类型
HttpResponse<T> - 为POST接口添加注解
@Status(HttpStatus.CREATED) - 保持控制器逻辑精简,将业务逻辑下沉到Service层
- 为所有接口添加OpenAPI注解(、
@Operation)@ApiResponse
Data Access
数据访问
- Use or
@JdbcRepositorywith dialect specification@MongoRepository - Extend for pagination support
PageableRepository<Entity, ID> - Use for custom queries, prefer derived query methods when possible
@Query - Apply at service layer, not repository layer
@Transactional - Use for read-only operations
@Transactional(readOnly = true) - Use Flyway or Liquibase for schema migrations (never )
schema-generate: CREATE - Always include rollback scripts for migrations
- 使用指定方言的或
@JdbcRepository@MongoRepository - 继承实现分页能力
PageableRepository<Entity, ID> - 自定义查询使用注解,优先使用派生查询方法
@Query - 在Service层而非Repository层添加注解
@Transactional - 对只读操作使用注解
@Transactional(readOnly = true) - 使用Flyway或Liquibase做Schema迁移(禁止使用)
schema-generate: CREATE - 所有迁移脚本必须配套回滚脚本
DTOs and Serialization
DTO与序列化
- Use Java records for DTOs (immutable by default)
- Annotate all DTOs with (Micronaut serialization)
@Serdeable - Apply Jakarta validation annotations (,
@NotBlank,@Email)@Size - Never expose domain entities directly in API responses
- Use MapStruct with for entity-DTO mapping
componentModel = "jsr330" - Create separate request and response DTOs
- DTO使用Java record实现(默认不可变)
- 所有DTO标注注解(Micronaut序列化专用)
@Serdeable - 添加Jakarta校验注解(、
@NotBlank、@Email等)@Size - 禁止在API响应中直接暴露领域实体
- 使用的MapStruct实现实体与DTO的映射
componentModel = "jsr330 - 请求与响应DTO分开定义
Error Handling
错误处理
- Implement for each exception type
ExceptionHandler<E, HttpResponse<T>> - Return RFC 7807 Problem Details format for error responses
- Use on exception handlers
@Requires(classes = {...}) - Log warnings/errors in exception handlers
- Never expose internal details (stack traces, SQL) in error responses
- 每类异常单独实现
ExceptionHandler<E, HttpResponse<T>> - 错误响应返回RFC 7807 Problem Details格式
- 异常处理器添加注解
@Requires(classes = {...}) - 异常处理器中记录警告/错误日志
- 禁止在错误响应中暴露内部细节(栈轨迹、SQL等)
Configuration
配置
- Use with environment-specific overrides (
application.yml)application-dev.yml - Reference secrets via environment variables:
${ENV_VAR:default} - Never hardcode secrets, API keys, or passwords
- Use for type-safe configuration beans
@ConfigurationProperties - Enable health checks and Prometheus metrics for production
- 使用并配套环境专属配置覆盖(如
application.yml)application-dev.yml - 通过环境变量引用密钥:
${ENV_VAR:default} - 禁止硬编码密钥、API Key或密码
- 使用实现类型安全的配置Bean
@ConfigurationProperties - 生产环境开启健康检查与Prometheus指标
Testing
测试
- Use for integration tests (starts embedded server)
@MicronautTest - Use for mocking dependencies in integration tests
@MockBean - Use for unit tests
@ExtendWith(MockitoExtension.class) - Use Testcontainers for database integration tests
- Implement for dynamic test configuration
TestPropertyProvider - Test names describe behavior:
shouldCreateUserWhenEmailIsUnique - Coverage target: >80% for business logic, >60% overall
- 集成测试使用注解(会启动嵌入式服务)
@MicronautTest - 集成测试中使用模拟依赖
@MockBean - 单元测试使用注解
@ExtendWith(MockitoExtension.class) - 数据库集成测试使用Testcontainers
- 实现实现动态测试配置
TestPropertyProvider - 测试用例名称要描述行为,例如
shouldCreateUserWhenEmailIsUnique - 覆盖率目标:业务逻辑>80%,整体>60%
Performance
性能
- Use only for blocking operations
@ExecuteOn(TaskExecutors.BLOCKING) - Use reactive types (,
Mono) for non-blocking I/O when appropriateFlux - Enable AOT optimizations in for production
build.gradle - Use connection pooling (HikariCP) with tuned pool sizes
- Paginate all list endpoints (never return unbounded collections)
- 仅在阻塞操作上使用
@ExecuteOn(TaskExecutors.BLOCKING) - 非阻塞I/O场景按需使用响应式类型(、
Mono)Flux - 生产环境在中开启AOT优化
build.gradle - 使用连接池(HikariCP)并调整合适的连接池大小
- 所有列表接口支持分页(禁止返回无边界集合)
Project Structure
项目结构
myapp/
├── src/main/
│ ├── java/com/example/
│ │ ├── Application.java # Entry point with @OpenAPIDefinition
│ │ ├── config/ # @Factory, @ConfigurationProperties
│ │ ├── controller/ # @Controller classes
│ │ ├── service/ # Business logic interfaces
│ │ │ └── impl/ # Service implementations
│ │ ├── repository/ # @JdbcRepository interfaces
│ │ ├── domain/ # @MappedEntity classes
│ │ ├── dto/ # @Serdeable records
│ │ ├── mapper/ # MapStruct @Mapper interfaces
│ │ └── exception/ # Custom exceptions + handlers
│ └── resources/
│ ├── application.yml # Main config (with -dev.yml, -prod.yml overrides)
│ ├── db/migration/ # Flyway SQL files
│ └── logback.xml
├── src/test/java/com/example/ # Unit + integration tests
├── build.gradle # Micronaut Gradle plugin
└── settings.gradle- separates interface from implementation for testability
service/impl/ - uses
dto/records for API request/response types@Serdeable - bridges domain entities to DTOs via MapStruct
mapper/ - bundles custom exceptions with their
exception/implementationsExceptionHandler
myapp/
├── src/main/
│ ├── java/com/example/
│ │ ├── Application.java # Entry point with @OpenAPIDefinition
│ │ ├── config/ # @Factory, @ConfigurationProperties
│ │ ├── controller/ # @Controller classes
│ │ ├── service/ # Business logic interfaces
│ │ │ └── impl/ # Service implementations
│ │ ├── repository/ # @JdbcRepository interfaces
│ │ ├── domain/ # @MappedEntity classes
│ │ ├── dto/ # @Serdeable records
│ │ ├── mapper/ # MapStruct @Mapper interfaces
│ │ └── exception/ # Custom exceptions + handlers
│ └── resources/
│ ├── application.yml # Main config (with -dev.yml, -prod.yml overrides)
│ ├── db/migration/ # Flyway SQL files
│ └── logback.xml
├── src/test/java/com/example/ # Unit + integration tests
├── build.gradle # Micronaut Gradle plugin
└── settings.gradle- 将接口与实现分离,提升可测试性
service/impl/ - 使用
dto/record作为API请求/响应类型@Serdeable - 通过MapStruct实现领域实体与DTO的转换
mapper/ - 将自定义异常与对应的
exception/实现放在一起ExceptionHandler
Compile-Time DI Patterns
编译时DI模式
Singleton Bean
单例Bean
java
@Singleton
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final UserMapper userMapper;
// Constructor injection -- resolved at compile time
}java
@Singleton
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final UserMapper userMapper;
// Constructor injection -- resolved at compile time
}Factory for Third-Party Objects
第三方对象工厂
java
@Factory
public class SecurityBeans {
@Singleton
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
}java
@Factory
public class SecurityBeans {
@Singleton
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
}Conditional Beans
条件Bean
java
@Singleton
@Requires(env = "production")
public class ProductionEmailService implements EmailService { }
@Singleton
@Requires(env = "dev")
public class MockEmailService implements EmailService { }java
@Singleton
@Requires(env = "production")
public class ProductionEmailService implements EmailService { }
@Singleton
@Requires(env = "dev")
public class MockEmailService implements EmailService { }Controller Pattern
控制器模式
java
@Controller("/api/users")
@Validated
@RequiredArgsConstructor
@ExecuteOn(TaskExecutors.BLOCKING)
@Secured(SecurityRule.IS_AUTHENTICATED)
@Tag(name = "Users", description = "User management endpoints")
public class UserController {
private final UserService userService;
@Post
@Status(HttpStatus.CREATED)
@Operation(summary = "Create a new user")
@ApiResponse(responseCode = "201", description = "User created")
@ApiResponse(responseCode = "409", description = "Email already exists")
public HttpResponse<UserResponse> createUser(@Body @Valid UserRequest request) {
UserResponse user = userService.createUser(request);
return HttpResponse.created(user)
.headers(h -> h.location(URI.create("/api/users/" + user.id())));
}
@Get("/{id}")
@Operation(summary = "Get user by ID")
public UserResponse getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
@Get
@Operation(summary = "List users with pagination")
public PageResponse<UserResponse> getAllUsers(
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "20") int size) {
return userService.getAllUsers(page, size);
}
@Put("/{id}")
public UserResponse updateUser(@PathVariable Long id, @Body @Valid UserRequest request) {
return userService.updateUser(id, request);
}
@Delete("/{id}")
@Status(HttpStatus.NO_CONTENT)
@Secured({"ROLE_ADMIN"})
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}
}java
@Controller("/api/users")
@Validated
@RequiredArgsConstructor
@ExecuteOn(TaskExecutors.BLOCKING)
@Secured(SecurityRule.IS_AUTHENTICATED)
@Tag(name = "Users", description = "User management endpoints")
public class UserController {
private final UserService userService;
@Post
@Status(HttpStatus.CREATED)
@Operation(summary = "Create a new user")
@ApiResponse(responseCode = "201", description = "User created")
@ApiResponse(responseCode = "409", description = "Email already exists")
public HttpResponse<UserResponse> createUser(@Body @Valid UserRequest request) {
UserResponse user = userService.createUser(request);
return HttpResponse.created(user)
.headers(h -> h.location(URI.create("/api/users/" + user.id())));
}
@Get("/{id}")
@Operation(summary = "Get user by ID")
public UserResponse getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
@Get
@Operation(summary = "List users with pagination")
public PageResponse<UserResponse> getAllUsers(
@QueryValue(defaultValue = "0") int page,
@QueryValue(defaultValue = "20") int size) {
return userService.getAllUsers(page, size);
}
@Put("/{id}")
public UserResponse updateUser(@PathVariable Long id, @Body @Valid UserRequest request) {
return userService.updateUser(id, request);
}
@Delete("/{id}")
@Status(HttpStatus.NO_CONTENT)
@Secured({"ROLE_ADMIN"})
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}
}Data Access Patterns
数据访问模式
Domain Entity
领域实体
java
@Serdeable
@MappedEntity("users")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
@Id
@GeneratedValue(GeneratedValue.Type.AUTO)
private Long id;
@Column("email")
private String email;
@Column("password")
private String password;
@DateCreated
@Column("created_at")
private Instant createdAt;
@DateUpdated
@Column("updated_at")
private Instant updatedAt;
}java
@Serdeable
@MappedEntity("users")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
@Id
@GeneratedValue(GeneratedValue.Type.AUTO)
private Long id;
@Column("email")
private String email;
@Column("password")
private String password;
@DateCreated
@Column("created_at")
private Instant createdAt;
@DateUpdated
@Column("updated_at")
private Instant updatedAt;
}Repository
Repository
java
@JdbcRepository(dialect = Dialect.POSTGRES)
public interface UserRepository extends PageableRepository<User, Long> {
Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
List<User> findByActiveTrue();
Page<User> findByRole(String role, Pageable pageable);
@Query("UPDATE User u SET u.active = :active WHERE u.id = :id")
void updateActiveStatus(Long id, boolean active);
}java
@JdbcRepository(dialect = Dialect.POSTGRES)
public interface UserRepository extends PageableRepository<User, Long> {
Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
List<User> findByActiveTrue();
Page<User> findByRole(String role, Pageable pageable);
@Query("UPDATE User u SET u.active = :active WHERE u.id = :id")
void updateActiveStatus(Long id, boolean active);
}DTO Records
DTO记录类
java
@Serdeable
public record UserRequest(
@NotBlank @Email @Size(max = 255) String email,
@NotBlank @Size(min = 8, max = 100) String password,
@NotBlank @Size(max = 100) String firstName,
@NotBlank @Size(max = 100) String lastName
) {}
@Serdeable
public record UserResponse(
Long id, String email, String firstName, String lastName,
Boolean active, Instant createdAt, Instant updatedAt
) {}
@Serdeable
public record PageResponse<T>(
List<T> content, int page, int size,
long totalElements, int totalPages, boolean first, boolean last
) {
public static <T> PageResponse<T> of(List<T> content, int page, int size, long total) {
int totalPages = (int) Math.ceil((double) total / size);
return new PageResponse<>(content, page, size, total, totalPages,
page == 0, page >= totalPages - 1);
}
}java
@Serdeable
public record UserRequest(
@NotBlank @Email @Size(max = 255) String email,
@NotBlank @Size(min = 8, max = 100) String password,
@NotBlank @Size(max = 100) String firstName,
@NotBlank @Size(max = 100) String lastName
) {}
@Serdeable
public record UserResponse(
Long id, String email, String firstName, String lastName,
Boolean active, Instant createdAt, Instant updatedAt
) {}
@Serdeable
public record PageResponse<T>(
List<T> content, int page, int size,
long totalElements, int totalPages, boolean first, boolean last
) {
public static <T> PageResponse<T> of(List<T> content, int page, int size, long total) {
int totalPages = (int) Math.ceil((double) total / size);
return new PageResponse<>(content, page, size, total, totalPages,
page == 0, page >= totalPages - 1);
}
}Configuration (application.yml)
配置(application.yml)
yaml
micronaut:
application:
name: myapp
server:
port: 8080
security:
authentication: bearer
token:
jwt:
signatures:
secret:
generator:
secret: ${JWT_SECRET}
jws-algorithm: HS256
intercept-url-map:
- pattern: /health/**
access: [isAnonymous()]
- pattern: /api/**
access: [isAuthenticated()]
datasources:
default:
url: jdbc:postgresql://localhost:5432/myapp
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}
flyway:
datasources:
default:
enabled: true
locations: classpath:db/migration
endpoints:
health: { enabled: true, sensitive: false }
prometheus: { enabled: true, sensitive: false }yaml
micronaut:
application:
name: myapp
server:
port: 8080
security:
authentication: bearer
token:
jwt:
signatures:
secret:
generator:
secret: ${JWT_SECRET}
jws-algorithm: HS256
intercept-url-map:
- pattern: /health/**
access: [isAnonymous()]
- pattern: /api/**
access: [isAuthenticated()]
datasources:
default:
url: jdbc:postgresql://localhost:5432/myapp
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}
flyway:
datasources:
default:
enabled: true
locations: classpath:db/migration
endpoints:
health: { enabled: true, sensitive: false }
prometheus: { enabled: true, sensitive: false }Error Handling Pattern
错误处理模式
Custom Exception
自定义异常
java
public class ResourceNotFoundException extends RuntimeException {
private final String resourceName;
private final String fieldName;
private final Object fieldValue;
public ResourceNotFoundException(String resource, String field, Object value) {
super(String.format("%s not found with %s: '%s'", resource, field, value));
this.resourceName = resource;
this.fieldName = field;
this.fieldValue = value;
}
// getters
}java
public class ResourceNotFoundException extends RuntimeException {
private final String resourceName;
private final String fieldName;
private final Object fieldValue;
public ResourceNotFoundException(String resource, String field, Object value) {
super(String.format("%s not found with %s: '%s'", resource, field, value));
this.resourceName = resource;
this.fieldName = field;
this.fieldValue = value;
}
// getters
}Exception Handler
异常处理器
java
@Singleton
@Produces
@Requires(classes = {ResourceNotFoundException.class, ExceptionHandler.class})
public class ResourceNotFoundExceptionHandler
implements ExceptionHandler<ResourceNotFoundException, HttpResponse<ErrorResponse>> {
@Override
public HttpResponse<ErrorResponse> handle(HttpRequest request, ResourceNotFoundException ex) {
ErrorResponse error = ErrorResponse.of(404, "Not Found", ex.getMessage(), request.getPath());
return HttpResponse.notFound(error);
}
}java
@Singleton
@Produces
@Requires(classes = {ResourceNotFoundException.class, ExceptionHandler.class})
public class ResourceNotFoundExceptionHandler
implements ExceptionHandler<ResourceNotFoundException, HttpResponse<ErrorResponse>> {
@Override
public HttpResponse<ErrorResponse> handle(HttpRequest request, ResourceNotFoundException ex) {
ErrorResponse error = ErrorResponse.of(404, "Not Found", ex.getMessage(), request.getPath());
return HttpResponse.notFound(error);
}
}Testing Patterns
测试模式
Unit Test (Mockito)
单元测试(Mockito)
java
@ExtendWith(MockitoExtension.class)
@DisplayName("UserService")
class UserServiceTest {
@Mock private UserRepository userRepository;
@Mock private UserMapper userMapper;
private UserServiceImpl userService;
@BeforeEach
void setUp() {
userService = new UserServiceImpl(userRepository, userMapper);
}
@Test
@DisplayName("should create user when email is unique")
void shouldCreateUserWhenEmailIsUnique() {
when(userRepository.existsByEmail("test@example.com")).thenReturn(false);
when(userMapper.toEntity(any())).thenReturn(new User());
when(userRepository.save(any())).thenReturn(new User());
when(userMapper.toResponse(any())).thenReturn(expectedResponse);
UserResponse result = userService.createUser(request);
assertThat(result).isNotNull();
verify(userRepository).save(any());
}
}java
@ExtendWith(MockitoExtension.class)
@DisplayName("UserService")
class UserServiceTest {
@Mock private UserRepository userRepository;
@Mock private UserMapper userMapper;
private UserServiceImpl userService;
@BeforeEach
void setUp() {
userService = new UserServiceImpl(userRepository, userMapper);
}
@Test
@DisplayName("should create user when email is unique")
void shouldCreateUserWhenEmailIsUnique() {
when(userRepository.existsByEmail("test@example.com")).thenReturn(false);
when(userMapper.toEntity(any())).thenReturn(new User());
when(userRepository.save(any())).thenReturn(new User());
when(userMapper.toResponse(any())).thenReturn(expectedResponse);
UserResponse result = userService.createUser(request);
assertThat(result).isNotNull();
verify(userRepository).save(any());
}
}Integration Test (MicronautTest + Testcontainers)
集成测试(MicronautTest + Testcontainers)
java
@MicronautTest
@Testcontainers
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class IntegrationTest implements TestPropertyProvider {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine");
@Inject @Client("/") HttpClient client;
@Override
public Map<String, String> getProperties() {
postgres.start();
return Map.of(
"datasources.default.url", postgres.getJdbcUrl(),
"datasources.default.username", postgres.getUsername(),
"datasources.default.password", postgres.getPassword()
);
}
@Test
void healthEndpoint_shouldReturnUp() {
var response = client.toBlocking().exchange(HttpRequest.GET("/health"), String.class);
assertThat(response.status().getCode()).isEqualTo(200);
}
}java
@MicronautTest
@Testcontainers
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class IntegrationTest implements TestPropertyProvider {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine");
@Inject @Client("/") HttpClient client;
@Override
public Map<String, String> getProperties() {
postgres.start();
return Map.of(
"datasources.default.url", postgres.getJdbcUrl(),
"datasources.default.username", postgres.getUsername(),
"datasources.default.password", postgres.getPassword()
);
}
@Test
void healthEndpoint_shouldReturnUp() {
var response = client.toBlocking().exchange(HttpRequest.GET("/health"), String.class);
assertThat(response.status().getCode()).isEqualTo(200);
}
}Build & Run Commands
构建与运行命令
bash
./gradlew build # Build
MICRONAUT_ENVIRONMENTS=dev ./gradlew run # Run (dev profile)
./gradlew test # Run tests
./gradlew test jacocoTestReport # Tests + coverage report
./gradlew nativeCompile # GraalVM native image
./gradlew dockerBuild # Docker image
./gradlew dockerBuildNative # Native Docker image
./gradlew clean build # Clean build
./gradlew check # Lint (checkstyle/spotbugs)bash
./gradlew build # Build
MICRONAUT_ENVIRONMENTS=dev ./gradlew run # Run (dev profile)
./gradlew test # Run tests
./gradlew test jacocoTestReport # Tests + coverage report
./gradlew nativeCompile # GraalVM native image
./gradlew dockerBuild # Docker image
./gradlew dockerBuildNative # Native Docker image
./gradlew clean build # Clean build
./gradlew check # Lint (checkstyle/spotbugs)Do's and Don'ts
最佳实践与禁忌
Do
推荐做法
- Use constructor injection (compile-time verified)
- Use for all DTOs
@Serdeable - Use for blocking operations
@ExecuteOn(TaskExecutors.BLOCKING) - Use at the service layer
@Transactional - Use validation annotations on DTOs
- Use native compilation for production deployments
- Use Micronaut Data for repositories
- Enable health checks and metrics
- 使用构造函数注入(编译时可校验)
- 所有DTO添加注解
@Serdeable - 阻塞操作添加注解
@ExecuteOn(TaskExecutors.BLOCKING) - Service层添加注解
@Transactional - DTO上添加校验注解
- 生产部署使用原生编译
- Repository层使用Micronaut Data
- 开启健康检查与指标监控
Don't
禁止做法
- Don't use reflection-based libraries without GraalVM configuration
- Don't use or Spring-specific annotations
@Autowired - Don't block in reactive streams
- Don't expose internal details in error responses
- Don't skip DTO validation
- Don't use mutable DTOs (prefer records)
- Don't ignore compile-time warnings from annotation processors
- 没有配置GraalVM的情况下使用基于反射的库
- 使用或Spring专属注解
@Autowired - 在响应式流中执行阻塞操作
- 错误响应中暴露内部细节
- 跳过DTO校验
- 使用可变DTO(优先使用record)
- 忽略注解处理器的编译时警告
Advanced Topics
进阶主题
For detailed patterns and examples, see:
- references/patterns.md -- GraalVM native, messaging, security, advanced testing, deployment patterns
了解更多详细模式与示例请查看:
- references/patterns.md -- GraalVM原生镜像、消息队列、安全、高级测试、部署模式