Skip to main content Skills Marketplace Discover and explore AI skills built by the community.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Copy promptShow prompt details A direct command skips the review prompt. Inspect the source before running it.
npx skills add https://github.com/doccker/cc-use-exp --skill java-devThe command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Download Zip Downloading... More from this repository Related occupations SOC
Based on SOC occupation classification
name java-dev description Java 开发规范,包含命名约定、异常处理、Spring Boot 最佳实践等 version v3.0 paths ["**/*.java","**/pom.xml","**/build.gradle","**/build.gradle.kts"]
Java 开发规范
参考来源: Google Java Style Guide、阿里巴巴 Java 开发手册
工具链
mvn clean compile
mvn test
mvn verify
./gradlew build
./gradlew test
命名约定
类型 规则 示例 包名 全小写,域名反转 com.example.project类名 大驼峰,名词/名词短语 UserService, HttpClient方法名 小驼峰,动词开头 findById, isValid
常量 全大写下划线分隔 MAX_RETRY_COUNT
布尔返回值 is/has/can 前缀 isActive(), hasPermission()
类成员顺序 public class Example {
public static final String CONSTANT = "value" ;
private static Logger logger = LoggerFactory.getLogger(Example.class);
private Long id;
public Example () { }
public static Example create () { return new Example (); }
public void doSomething () { }
private void helperMethod () { }
}
DTO/VO 类规范 规则 说明 ❌ 禁止手写 getter/setter DTO、VO、Request、Response 类一律使用 Lombok ✅ 使用 @Data 普通 DTO ✅ 使用 @Value 不可变 DTO ✅ 使用 @Builder 字段较多时配合使用 ⚠️ Entity 类慎用 @Data JPA Entity 的 equals/hashCode 会影响 Hibernate 代理
public class UserDTO {
private Long id;
private String name;
public Long getId () { return id; }
public void setId (Long id) { this .id = id; }
}
@Data
public class UserDTO {
private Long id;
private String name;
}
批量查询规范 规则 说明 ❌ 禁止 IN 子句超过 500 个参数 SQL 解析开销大,执行计划不稳定 ✅ 超过时分批查询 每批 500,合并结果 ✅ 封装通用工具方法 避免每处手写分批逻辑
List<User> users = userRepository.findByIdIn(allIds);
public static <T, R> List<R> batchQuery (List<T> params, int batchSize,
Function<List<T>, List<R>> queryFn) {
List<R> result = new ArrayList <>();
for (int i = 0 ; i < params.size(); i += batchSize) {
List<T> batch = params.subList(i, Math.min(i + batchSize, params.size()));
result.addAll(queryFn.apply(batch));
}
return result;
}
List<User> users = batchQuery(allIds, 500 , ids -> userRepository.findByIdIn(ids));
N+1 查询防范 规则 说明 ❌ 禁止循环内调用 Repository/Mapper stream/forEach/for 内每次迭代触发一次查询 ✅ 循环外批量查询,结果转 Map 查询次数从 N 降为 1(或 distinct 数)
records.forEach(record -> {
long count = deviceRepo.countByDeviceId(record.getDeviceId());
record.setDeviceCount(count);
});
List<String> deviceIds = records.stream()
.map(Record::getDeviceId).distinct().collect(Collectors.toList());
Map<String, Long> countMap = deviceRepo.countByDeviceIdIn(deviceIds).stream()
.collect(Collectors.toMap(CountDTO::getDeviceId, CountDTO::getCount));
records.forEach(r -> r.setDeviceCount(countMap.getOrDefault(r.getDeviceId(), 0L )));
场景 循环内(❌) 循环外(✅) count repo.countByXxx(id)repo.countByXxxIn(ids) → Map<id, count>findById repo.findById(id)repo.findByIdIn(ids) → Map<id, entity>exists repo.existsByXxx(id)repo.findXxxIn(ids) → Set<id> + set.contains()
并发安全规范 规则 说明 ❌ 禁止 read-modify-write 先读余额再写回,并发下丢失更新 ❌ 禁止 check-then-act 无兜底 先检查再操作,并发下条件失效 ✅ 使用原子更新 SQL UPDATE SET balance = balance + :delta WHERE id = :id✅ 或使用乐观锁 @Version 字段 + 重试机制✅ 唯一索引兜底 防重复插入的最后防线
PointsAccount account = accountRepo.findById(id);
account.setBalance(account.getBalance() + points);
accountRepo.save(account);
@Modifying
@Query("UPDATE PointsAccount SET balance = balance + :points WHERE id = :id")
int addBalance (@Param("id") Long id, @Param("points") int points) ;
@Version
private Long version;
if (!rewardRepo.existsByTenantIdAndPeriod(tenantId, period)) {
rewardRepo.save(new RankingReward (...));
}
try {
rewardRepo.save(new RankingReward (...));
} catch (DataIntegrityViolationException e) {
log.warn("重复结算已被唯一索引拦截: tenantId={}, period={}" , tenantId, period);
}
异常处理
try {
user = userRepository.findById(id);
} catch (DataAccessException e) {
throw new ServiceException ("Failed to find user: " + id, e);
}
try (InputStream is = new FileInputStream (file)) {
}
catch (Exception e) { e.printStackTrace(); }
空值处理
public Optional<User> findById (Long id) {
return userRepository.findById(id);
}
public void updateUser (User user) {
Objects.requireNonNull(user, "user must not be null" );
}
String name = Optional.ofNullable(user)
.map(User::getName)
.orElse("Unknown" );
并发编程
ExecutorService executor = Executors.newFixedThreadPool(10 );
Future<Result> future = executor.submit(() -> doWork());
CompletableFuture<User> future = CompletableFuture
.supplyAsync(() -> findUser(id))
.thenApply(user -> enrichUser(user));
new Thread (() -> doWork()).start();
测试规范 (JUnit 5) class UserServiceTest {
@Test
@DisplayName("根据 ID 查找用户 - 用户存在时返回用户")
void findById_whenUserExists_returnsUser () {
when (userRepository.findById(1L )).thenReturn(Optional.of(expected));
Optional<User> result = userService.findById(1L );
assertThat(result).isPresent();
assertThat(result.get().getName()).isEqualTo("test" );
}
}
Spring Boot 规范
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
}
主题 一句话规则 注入方式 用 @RequiredArgsConstructor 构造函数注入,不用 @Autowired 字段注入 Auth 降级 optional-auth 路径遇无效/过期 token 降级为匿名,不返回 401/403 循环依赖 Spring Boot 3.x 默认禁止构造器循环依赖,从大 Service 拆分子 Service 时先用工具类共享方法,再考虑 @Lazy self-invocation this.method() 不走代理,@Transactional/@Async/@Cacheable 全部失效;用 self-injection 或拆 Bean@Modifying JPA必须在 @Transactional 中调用,否则抛 Executing an update/delete query 分页参数 全栈统一 0-based,Controller defaultValue = "0",Service 直接 PageRequest.of(page, size)
详细规则、修复模板和检查清单见 references/spring.md
输入校验规范 规则 说明 ❌ 禁止 @RequestBody 不加 @Valid 所有请求体必须校验 ✅ DTO 字段加约束注解 @NotBlank、@Size、@Pattern 等✅ 数值字段加范围约束 @Min、@Max、@Positive 等✅ 分页参数加上限 size 必须 @Max(100) 防止大量查询✅ 枚举/状态字段白名单校验 自定义校验器或 @Pattern
字段类型 必须注解 说明 数量 quantity @NotNull @Min(1)防止 0 或负数(负数可导致反向操作) 金额 amount/price @NotNull @Positive或 @DecimalMin("0.01") 分页 size @Min(1) @Max(100)防止 size=999999 拖垮数据库 分页 page @Min(1)页码从 1 开始 百分比 rate @Min(0) @Max(100)视业务定义范围
@PostMapping("/ship")
public Result ship (@RequestBody ShippingRequest request) { ... }
@PostMapping("/ship")
public Result ship (@RequestBody @Valid ShippingRequest request) { ... }
public record ShippingRequest (
@NotNull Long orderId,
@NotBlank @Size(max = 500) String shippingInfo,
@Pattern(regexp = "pending|shipped|delivered") String giftStatus
) {}
public record CreateOrderRequest (
@NotNull Integer quantity // 可提交 0 或负数
) {}
public record CreateOrderRequest (
@NotNull @Min(1) Integer quantity
) {}
@GetMapping("/orders")
public Result list (@RequestParam int page, @RequestParam int size) { ... }
@GetMapping("/orders")
public Result list (@RequestParam @Min(1) int page,
@RequestParam @Min(1) @Max(100) int size) { ... }
性能优化 陷阱 解决方案 N+1 查询 见「N+1 查询防范」章节 循环拼接字符串 使用 StringBuilder 频繁装箱拆箱 使用原始类型流 未指定集合初始容量 new ArrayList<>(size)
第三方 API HTTP 客户端选型 规则 说明 ❌ 避免 RestTemplate 默认客户端调用国内平台 API 默认 HttpURLConnection 的 POST 请求与微信/支付宝等 CDN 存在兼容性问题(已知触发 412/403) ✅ 优先用 java.net.http.HttpClient(JDK 11+) 现代 HTTP 客户端,无 CDN 兼容性问题 ✅ 或配置 HttpComponentsClientHttpRequestFactory 让 RestTemplate 底层走 Apache HttpClient
诊断特征 :HTTP 错误 + body 为空 + response headers 极简(只有 Connection/Content-Length)= CDN 层拦截,不是 API 本身的响应。同一 API 的 GET 正常但 POST 异常时,优先怀疑 HTTP 客户端兼容性。
Native SQL 规范
别名避免 MySQL 保留字 @Query(nativeQuery = true) 中的列别名如果是 MySQL 保留字,会导致语法错误。
高频踩坑保留字 :year_month, order, status, key, value, name, type, date, time, rank, range, rows, column, user, role, group
规则 说明 ✅ 使用短别名或缩写 ym, ord_status, cnt✅ 或用反引号转义 `year_month`❌ 禁止直接用保留字做别名 as year_month、as order、as rank
@Query(value = """
SELECT DATE_FORMAT(o.order_date, '%Y-%m') as year_month,
COUNT(*) as cnt
FROM orders o
GROUP BY year_month
""", nativeQuery = true)
@Query(value = """
SELECT DATE_FORMAT(o.order_date, '%Y-%m') as ym,
COUNT(*) as cnt
FROM orders o
GROUP BY ym
""", nativeQuery = true)
日志规范
log.debug("Finding user by id: {}" , userId);
log.info("User {} logged in successfully" , username);
log.error("Failed to process order {}" , orderId, exception);
log.debug("Finding user by id: " + userId);
Spring Bean 命名冲突 场景 :Spring 启动时 BeanDefinitionStoreException,或 "no qualifying bean
of type X found" 看似找不到 Bean。
问题根因 Spring 默认按类名首字母小写生成 bean name。冲突有两类:
子场景 A:同名同类型 —— 两个 @Service/@Controller 类同名(不同包)
子场景 B:同名异类型 —— @Bean(name=X) 注册的对象与 @Component/@Service
类的默认 bean name 撞车 ⚠️ 更隐蔽
子场景 A:两个同名 Controller / Service
@RestController
public class MiniAppConfigController { }
@RestController
public class MiniAppConfigController { }
@RestController
public class MiniPublicConfigController { }
@RestController("miniPublicConfigController")
public class MiniAppConfigController { }
子场景 B:@Bean 与 @Service 异类型同名 ⚠️ 最坑的一种 —— 错误信息可能表现为"找不到 Bean",根因却是命名覆盖。
@Configuration
public class AsyncConfig {
@Bean(name = "priceBatchUpdateExecutor")
public Executor priceBatchUpdateExecutor () { ... }
}
@Service
public class PriceBatchUpdateExecutor { ... }
@RequiredArgsConstructor
public class PriceBatchUpdateService {
private final PriceBatchUpdateExecutor priceBatchUpdateExecutor;
}
状态 表现 误诊概率 未开 overriding BeanDefinitionStoreException,定位明确低 开了 overriding 线程池 Bean 覆盖 Service Bean → 注入 PriceBatchUpdateExecutor 时找到的是 Executor 类型 → 报 no qualifying bean of type,根因被掩盖 高,常排查几小时
# ❌ AI 反射式开全局开关,掩盖问题
spring.main.allow-bean-definition-overriding=true
详见 claude-code-defensive.md - 严格禁止 #8「用全局开关掩盖错误」
@Bean(name = "priceBatchUpdateThreadPool")
public Executor priceBatchUpdateThreadPool () { ... }
@Service
public class PriceBatchUpdateExecutor { ... }
@RequiredArgsConstructor
public class PriceBatchUpdateTaskScheduler {
@Qualifier("priceBatchUpdateThreadPool")
private final Executor threadPool;
private final PriceBatchUpdateExecutor executor;
}
Bean 命名约定(避免冲突) 类型 后缀 / 命名约定 业务服务 XxxService / XxxManager / XxxExecutor(如果"执行器"是业务概念)线程池 Bean XxxThreadPool / XxxTaskExecutor配置 Bean XxxConfig / XxxProperties数据源 Bean XxxDataSource / XxxJdbcTemplateHTTP 客户端 XxxRestTemplate / XxxHttpClientMQ Bean XxxListenerContainer / XxxTemplate
铁律 :@Bean(name=) 的命名不要和任何 @Component/@Service/@Controller/@Repository
类的默认 Bean name 撞车。
排查命令
grep -rn "@Bean(name = \"priceBatchUpdateExecutor\"" src/
grep -rn "class PriceBatchUpdateExecutor" src/
检查清单
详细参考 文件 内容 references/java-style.md命名约定、异常处理、Spring Boot、测试规范 references/spring.mdSpring Boot 完整规范:注入、Auth 降级、循环依赖、self-invocation 陷阱、分页参数 references/collections.md不可变集合(Guava)、字符串分割 references/concurrency.md线程池配置、CompletableFuture 超时 references/concurrency-db-patterns.mdGet-Or-Create 并发、N+1 防范、原子更新、Redis+DB 一致性 references/code-patterns.md卫语句、枚举优化、策略工厂模式 references/date-time.md日期加减、账期计算、禁止月末对齐 references/http-client.md第三方 API HTTP 客户端选型、CDN 兼容性问题
📋 本回复遵循:java-dev - [具体章节]