关于 java-coding-standards
面向构建或审查 Spring Boot 和 Quarkus 服务团队的 Java 17+ 编码规范技能。它根据构建文件识别框架,并结合通用规范提供相应指导。
主要功能
- 01Spring Boot 与 Quarkus 规范
- 02根据构建文件识别框架
- 03命名与包结构指导
- 04不可变性与类型安全实践
- 05测试和异常处理指导
适用场景
- 01审查 Java 服务代码
- 02组织 Spring Boot 项目结构
- 03应用 Quarkus 专属规范
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ midudev/ autoskills --skill java-coding-standards安装后在 Claude Code 中输入/java-coding-standards确认已加载。
详细介绍
Java 编码规范
适用于 Spring Boot 和 Quarkus 服务中可读、可维护的 Java(17+)代码的规范。
适用场景
- 在 Spring Boot 或 Quarkus 项目中编写或审查 Java 代码
- 规范命名、不可变性或异常处理约定
- 使用记录类、密封类或模式匹配(Java 17+)
- 审查 Optional、流或泛型的使用
- 组织包和项目布局
- [QUARKUS]:使用 CDI 作用域、Panache 实体或响应式管道
工作原理
框架检测
应用规范前,先从构建文件中确定框架:
- 构建文件包含
quarkus→ 应用 [QUARKUS] 约定 - 构建文件包含
spring-boot→ 应用 [SPRING] 约定 - 两者均未检测到 → 仅应用通用约定
核心原则
- 清晰优先于巧妙
- 默认采用不可变设计;尽量减少共享可变状态
- 通过有意义的异常快速失败
- 保持命名和包结构一致
- [QUARKUS]:优先采用构建时处理而非运行时处理;尽可能避免运行时反射
示例
以下各节展示 Spring Boot、Quarkus 和通用 Java 的具体示例, 涵盖命名、不可变性、依赖注入、响应式代码、异常、 项目布局、日志记录、配置和测试。
命名
// PASS: Classes/Records: PascalCase
public class MarketService {}
public record Money(BigDecimal amount, Currency currency) {}
// PASS: Methods/fields: camelCase
private final MarketRepository marketRepository;
public Market findBySlug(String slug) {}
// PASS: Constants: UPPER_SNAKE_CASE
private static final int MAX_PAGE_SIZE = 100;
// PASS: [QUARKUS] JAX-RS resources named as *Resource, not *Controller
public class MarketResource {}
// PASS: [SPRING] REST controllers named as *Controller
public class MarketController {}
不可变性
// PASS: Favor records and final fields
public record MarketDto(Long id, String name, MarketStatus status) {}
public class Market {
private final Long id;
private final String name;
// getters only, no setters
}
// PASS: [QUARKUS] Panache active-record entities use public fields (Quarkus convention)
@Entity
public class Market extends PanacheEntity {
public String name;
public MarketStatus status;
// Panache generates accessors at build time; public fields are idiomatic here
}
// PASS: [QUARKUS] Panache MongoDB entities
@MongoEntity(collection = "markets")
public class Market extends PanacheMongoEntity {
public String name;
public MarketStatus status;
}
Optional 的使用
// PASS: Return Optional from find* methods
// [SPRING]
Optional<Market> market = marketRepository.findBySlug(slug);
// [QUARKUS] Panache
Optional<Market> market = Market.find("slug", slug).firstResultOptional();
// PASS: Map/flatMap instead of get()
return market
.map(MarketResponse::from)
.orElseThrow(() -> new EntityNotFoundException("Market not found"));
流的最佳实践
// PASS: Use streams for transformations, keep pipelines short
List<String> names = markets.stream()
.map(Market::name)
.filter(Objects::nonNull)
.toList();
// FAIL: Avoid complex nested streams; prefer loops for clarity
依赖注入
// PASS: [SPRING] Constructor injection (preferred over @Autowired on fields)
@Service
public class MarketService {
private final MarketRepository marketRepository;
public MarketService(MarketRepository marketRepository) {
this.marketRepository = marketRepository;
}
}
// PASS: [QUARKUS] Constructor injection
@ApplicationScoped
public class MarketService {
private final MarketRepository marketRepository;
@Inject
public MarketService(MarketRepository marketRepository) {
this.marketRepository = marketRepository;
}
}
// PASS: [QUARKUS] Package-private field injection (acceptable in Quarkus — avoids proxy issues)
@ApplicationScoped
public class MarketService {
@Inject
MarketRepository marketRepository;
}
// FAIL: [SPRING] Field injection with @Autowired
@Autowired
private MarketRepository marketRepository; // use constructor injection
// FAIL: [QUARKUS] @Singleton when interception or lazy init is needed
@Singleton // non-proxyable — use @ApplicationScoped instead
public class MarketService {}
响应式模式 [QUARKUS]
// PASS: Return Uni/Multi from reactive endpoints
@GET
@Path("/{slug}")
public Uni<Market> findBySlug(@PathParam("slug") String slug) {
return Market.find("slug", slug)
.<Market>firstResult()
.onItem().ifNull().failWith(() -> new MarketNotFoundException(slug));
}
// PASS: Non-blocking pipeline composition
public Uni<OrderConfirmation> placeOrder(OrderRequest req) {
return validateOrder(req)
.chain(valid -> persistOrder(valid))
.chain(order -> notifyFulfillment(order));
}
// FAIL: Blocking call inside a Uni/Multi pipeline
public Uni<Market> find(String slug) {
Market m = Market.find("slug", slug).firstResult(); // BLOCKING — breaks event loop
return Uni.createFrom().item(m);
}
// FAIL: Subscribing more than once to a shared Uni
Uni<Market> shared = fetchMarket(slug);
shared.subscribe().with(m -> log(m));
shared.subscribe().with(m -> cache(m)); // double subscribe — use Uni.memoize()
异常
- 域错误使用非受检异常;为技术异常添加上下文后再包装
- 创建特定于领域的异常(例如,
MarketNotFoundException) - 避免使用宽泛的
catch (Exception ex),除非要在集中处理时重新抛出或记录日志
throw new MarketNotFoundException(slug);
集中式异常处理
// [SPRING]
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MarketNotFoundException.class)
public ResponseEntity<ErrorResponse> handle(MarketNotFoundException ex) {
return ResponseEntity.status(404).body(ErrorResponse.from(ex));
}
}
// [QUARKUS] Option A: ExceptionMapper
@Provider
public class MarketNotFoundMapper implements ExceptionMapper<MarketNotFoundException> {
@Override
public Response toResponse(MarketNotFoundException ex) {
return Response.status(404).entity(ErrorResponse.from(ex)).build();
}
}
// [QUARKUS] Option B: @ServerExceptionMapper (RESTEasy Reactive)
@ServerExceptionMapper
public RestResponse<ErrorResponse> handle(MarketNotFoundException ex) {
return RestResponse.status(Status.NOT_FOUND, ErrorResponse.from(ex));
}
泛型与类型安全
- 避免使用原始类型;声明泛型参数
- 可复用的工具优先使用有界泛型
public <T extends Identifiable> Map<Long, T> indexById(Collection<T> items) { ... }
项目结构
[SPRING] Maven/Gradle
src/main/java/com/example/app/
config/
controller/
service/
repository/
domain/
dto/
util/
src/main/resources/
application.yml
src/test/java/... (mirrors main)
[QUARKUS] Maven/Gradle
src/main/java/com/example/app/
config/ # @ConfigMapping, @ConfigProperty beans, Producers
resource/ # JAX-RS resources (not "controller")
service/
repository/ # PanacheRepository implementations (if not using active record)
domain/ # JPA/Panache entities, MongoDB entities
dto/
util/
mapper/ # MapStruct mappers (if used)
src/main/resources/
application.properties # Quarkus convention (YAML supported with quarkus-config-yaml)
import.sql # Hibernate auto-import for dev/test
src/test/java/... (mirrors main)
格式与风格
- 始终一致地使用 2 或 4 个空格(遵循项目标准)
- 每个文件仅包含一个顶层公共类型
- 方法应简短且专注;提取辅助方法
- 成员顺序:常量、字段、构造函数、公共方法、受保护方法、私有方法
应避免的代码异味
- 参数列表过长 → 使用 DTO/构建器
- 嵌套过深 → 尽早返回
- 魔法数字 → 使用命名常量
- 静态可变状态 → 优先使用依赖注入
- 静默的 catch 代码块 → 记录日志并采取措施,或重新抛出
- [QUARKUS]:本应使用
@ApplicationScoped却使用@Singleton— 会破坏代理和拦截功能 - [QUARKUS]:混用
quarkus-resteasy-reactive和quarkus-resteasy(经典版)— 选择一种技术栈 - [QUARKUS]:在同一有界上下文中混用 Panache 活动记录模式和仓库模式 — 选择一种
日志记录
// [SPRING] SLF4J
private static final Logger log = LoggerFactory.getLogger(MarketService.class);
log.info("fetch_market slug={}", slug);
log.error("failed_fetch_market slug={}", slug, ex);
// [QUARKUS] JBoss Logging (default, zero-cost at build time)
private static final Logger log = Logger.getLogger(MarketService.class);
log.infof("fetch_market slug=%s", slug);
log.errorf(ex, "failed_fetch_market slug=%s", slug);
// [QUARKUS] Alternative: simplified logging with @Inject
@Inject
Logger log; // CDI-injected, scoped to declaring class
空值处理
- 仅在不可避免时接受
@Nullable;否则使用@NonNull - 对输入使用 Bean Validation(
@NotNull、@NotBlank) - [QUARKUS]:对
@BeanParam、@RestForm和请求体参数应用@Valid
配置
// [SPRING] @ConfigurationProperties
@ConfigurationProperties(prefix = "market")
public record MarketProperties(int maxPageSize, Duration cacheTtl) {}
// [QUARKUS] @ConfigMapping (type-safe, build-time validated)
@ConfigMapping(prefix = "market")
public interface MarketConfig {
int maxPageSize();
Duration cacheTtl();
}
// [QUARKUS] Simple values with @ConfigProperty
@ConfigProperty(name = "market.max-page-size", defaultValue = "100")
int maxPageSize;
测试要求
通用
- 使用 JUnit 5 + AssertJ 进行流畅断言
- 使用 Mockito 创建模拟;尽可能避免部分模拟
- 优先编写确定性测试;不要使用隐藏的休眠
[SPRING]
- 控制器切片使用
@WebMvcTest,仓库切片使用@DataJpaTest @SpringBootTest仅用于完整集成测试- 使用
@MockBean替换 Spring 上下文中的 Bean
[QUARKUS]
- 单元测试使用纯 JUnit 5 + Mockito(不使用
@QuarkusTest) @QuarkusTest仅用于 CDI 集成测试- 在集成测试中使用
@InjectMock替换 CDI Bean - 使用 Dev Services 管理数据库/Kafka/Redis——如果 Dev Services 已足够,就避免手动配置 Testcontainers
- 使用
@QuarkusTestResource管理自定义外部服务的生命周期
// [SPRING] Controller test
@WebMvcTest(MarketController.class)
class MarketControllerTest {
@Autowired MockMvc mockMvc;
@MockBean MarketService marketService;
}
// [QUARKUS] Integration test
@QuarkusTest
class MarketResourceTest {
@InjectMock
MarketService marketService;
@Test
void should_return_404_when_market_not_found() {
given().when().get("/markets/unknown").then().statusCode(404);
}
}
// [QUARKUS] Unit test (no CDI, no @QuarkusTest)
@ExtendWith(MockitoExtension.class)
class MarketServiceTest {
@Mock MarketRepository marketRepository;
@InjectMocks MarketService marketService;
}
请记住:编写有明确意图、类型明确且可观测的代码。除非证明确有必要,否则应优先考虑可维护性,而非微优化。
评论
支持 Markdown