doccker/cc-use-exp9 files

Java Dev

Java 开发规范,包含命名约定、异常处理、Spring Boot 最佳实践等

Specification
Skill ID
doccker/cc-use-exp/java-dev
Publisher
doccker
Repository
cc-use-exp
Installs
0
Files
9
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

doccker/cc-use-exp/java-devInstalls these files
  • SKILL.md
  • references/code-patterns.md
  • references/collections.md
  • references/concurrency-db-patterns.md
  • references/concurrency.md
  • references/date-time.md
  • references/http-client.md
  • references/java-style.md
  • references/spring.md

What this skill tells the agent

Java 开发规范

参考来源: Google Java Style Guide、阿里巴巴 Java 开发手册

工具链

# Maven
mvn clean compile                    # 编译
mvn test                             # 运行测试
mvn verify                           # 运行所有检查

# Gradle
./gradlew build                      # 构建
./gradlew test                       # 运行测试

命名约定

类型规则示例
包名全小写,域名反转com.example.project
类名大驼峰,名词/名词短语UserService, HttpClient
方法名小驼峰,动词开头findById, isValid
常量全大写下划线分隔MAX_RETRY_COUNT
布尔返回值is/has/can 前缀isActive(), hasPermission()

类成员顺序

public class Example {
    // 1. 静态常量
    public static final String CONSTANT = "value";

    // 2. 静态变量
    private static Logger logger = LoggerFactory.getLogger(Example.class);

    // 3. 实例变量
    private Long id;

    // 4. 构造函数
    public Example() { }

    // 5. 静态方法
    public static Example create() { return new Example(); }

    // 6. 实例方法(公共 → 私有)
    public void doSomething() { }
    private void helperMethod() { }

    // 7. getter/setter(或使用 Lombok)
}

DTO/VO 类规范

规则说明
❌ 禁止手写 getter/setterDTO、VO、Request、Response 类一律使用 Lombok
✅ 使用 @Data普通 DTO
✅ 使用 @Value不可变 DTO
✅ 使用 @Builder字段较多时配合使用
⚠️ Entity 类慎用 @DataJPA Entity 的 equals/hashCode 会影响 Hibernate 代理
// ❌ 手写 getter/setter
public class UserDTO {
    private Long id;
    private String name;
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    // ... 大量样板代码
}

// ✅ 使用 Lombok
@Data
public class UserDTO {
    private Long id;
    private String name;
}

批量查询规范

规则说明
❌ 禁止 IN 子句超过 500 个参数SQL 解析开销大,执行计划不稳定
✅ 超过时分批查询每批 500,合并结果
✅ 封装通用工具方法避免每处手写分批逻辑
// ❌ 1700 个 ID 一次查询
List<User> users = userRepository.findByIdIn(allIds); // IN 子句过长

// ✅ 分批查询工具方法
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/Mapperstream/forEach/for 内每次迭代触发一次查询
✅ 循环外批量查询,结果转 Map查询次数从 N 降为 1(或 distinct 数)
// ❌ N+1:循环内逐行查询 count
records.forEach(record -> {
    long count = deviceRepo.countByDeviceId(record.getDeviceId()); // 每条触发一次查询
    record.setDeviceCount(count);
});

// ✅ 循环外批量查询 + Map 查找
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)));

常见 N+1 场景及修复模式:

场景循环内(❌)循环外(✅)
countrepo.countByXxx(id)repo.countByXxxIn(ids) → Map<id, count>
findByIdrepo.findById(id)repo.findByIdIn(ids) → Map<id, entity>
existsrepo.existsByXxx(id)repo.findXxxIn(ids) → Set<id> + set.contains()

并发安全规范

规则说明
❌ 禁止 read-modify-write先读余额再写回,并发下丢失更新
❌ 禁止 check-then-act 无兜底先检查再操作,并发下条件失效
✅ 使用原子更新 SQLUPDATE SET balance = balance + :delta WHERE id = :id
✅ 或使用乐观锁@Version 字段 + 重试机制
✅ 唯一索引兜底防重复插入的最后防线
// ❌ read-modify-write 竞态条件
PointsAccount account = accountRepo.findById(id);
account.setBalance(account.getBalance() + points); // 并发时丢失更新
accountRepo.save(account);

// ✅ 方案一:原子更新 SQL
@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; // Entity 中添加版本字段
// ❌ check-then-act 无兜底(并发下可能重复结算)
if (!rewardRepo.existsByTenantIdAndPeriod(tenantId, period)) {
    rewardRepo.save(new RankingReward(...));
}

// ✅ 唯一索引兜底 + 异常捕获
// DDL: UNIQUE INDEX uk_tenant_period (tenant_id, ranking_type, period, rank_position)
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(); }

空值处理

// ✅ 使用 Optional
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
ExecutorService executor = Executors.newFixedThreadPool(10);
Future<Result> future = executor.submit(() -> doWork());

// ✅ 使用 CompletableFuture
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() {
        // given
        when(userRepository.findById(1L)).thenReturn(Optional.of(expected));

        // when
        Optional<User> result = userService.findById(1L);

        // then
        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-invocationthis.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

常见 DTO 字段校验速查:

字段类型必须注解说明
数量 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
) {}

// ❌ quantity 只有 @NotNull,负数会导致 Redis DECRBY 反向加库存
public record CreateOrderRequest(

Use this skill in a project

Open any RiverX project, search the Skills panel for doccker/cc-use-exp/java-dev, and install it in one click.

Use this skill