外观
阿里巴巴 Java 开发手册
2026-04-01
本文基于《阿里巴巴 Java 开发手册》(嵩山版)核心内容整理,结合 Nebula 架构的实际开发方式进行裁剪与补充,聚焦最高频、最容易踩坑的规范,帮助团队快速对齐编码标准。
一、命名规范
通用原则
- 所有命名必须见名知意,禁止无意义缩写(如
a、tmp、data) - 类名:
UpperCamelCase(大驼峰) - 方法名、变量名:
lowerCamelCase(小驼峰) - 常量:
UPPER_SNAKE_CASE(全大写下划线) - 包名:全小写,点分隔,
com.huida.nebula.{域}.{模块}
Nebula 分层命名速查
| 层次 | 规则 | 示例 |
|---|---|---|
| 数据库表 | t_{业务蛇形} | t_demo、t_order_detail |
| 实体类 | {业务名}DO | DemoDO、SysUserDO |
| Mapper | {业务名}Mapper | DemoMapper |
| 动态查询 | {业务名}MapperExt | DemoMapperExt |
| Service 接口 | I{业务名}Service | IDemoService |
| Service 实现 | {业务名}ServiceImpl | DemoServiceImpl |
| Controller | {业务名}Controller | DemoController |
| 分页入参 VO | {业务名}PageParamVO | DemoPageParamVO |
| 新增/修改入参 | {业务名}SaveParamVO | DemoSaveParamVO |
| 列表响应 VO | {业务名}RespVO | DemoRespVO |
| 详情响应 VO | {业务名}DetailVO | DemoDetailVO |
| 对象转换 | {业务名}Convert | DemoConvert |
方法命名约定
| 操作 | Service 方法名 | Controller 方法名 |
|---|---|---|
| 分页查询 | pageDemo | pageDemo |
| 详情查询 | getDemoById | getDemoById |
| 新增 | createDemo | createDemo |
| 修改 | updateDemo | updateDemo |
| 逻辑删除 | deleteDemo | deleteDemo |
| 启用 | enableDemo | enableDemo |
| 禁用 | disableDemo | disableDemo |
语义化方法名
状态变更方法用 enableXxx / disableXxx,而非 updateStatus,更语义清晰。
常量命名
// ✅ 正确:公共常量集中到常量类,全大写下划线
public final class DemoConstants {
private DemoConstants() {}
public static final String STATUS_NORMAL = "0";
public static final String STATUS_DISABLE = "1";
public static final String TYPE_A = "A";
public static final String TYPE_B = "B";
}
// ❌ 错误:在 Service/Controller 中硬编码魔法字符串
if (status.equals("0")) { ... } // 禁止二、代码分层规范
Nebula 严格遵守四层职责隔离原则:
Controller ← 只做参数接收与 R<T> 响应包装,不写 if 业务判断
│
Service ← 业务逻辑、校验、事务边界;不写 LambdaQueryWrapper
│
MapperExt ← 动态 SQL(LambdaQueryWrapper)、分页、唯一性检查
│
Mapper ← 固定 SQL 基础 CRUD(继承 BaseMapper)
│
MySQL(Flyway 版本化管理)禁止事项速查
| 禁止 | 原因 |
|---|---|
Controller 中写 if 业务判断 | 违反分层,业务逻辑下沉 Service |
Controller 中直接操作 InputStream / OutputStream | 必须下沉到 Service |
Service 中注入 HttpServletRequest | 违反分层原则 |
Service 中写 LambdaQueryWrapper | 应放在 MapperExt |
| Controller 直接返回 DO | 泄露数据库字段结构 |
| 直接注入其他业务域的 Mapper | 跨模块必须通过对方 Service 接口 |
VO / RPC DTO 含存储实现字段(storagePath、bucketName) | 对外接口不暴露存储内部细节 |
三、OOP 与集合规范
空值处理
// ✅ String 判空:同时排除 null、""、纯空格
if (!StringUtils.hasText(demoCode)) { return; }
// ✅ 集合判空
if (ids == null || ids.isEmpty()) { return; }
// ✅ 框架内优先用 getByIdOrThrow(不存在自动抛 DATA_NOT_FOUND)
DemoDO demoDO = getByIdOrThrow(id);
// ❌ 不要手动 if null then throw
DemoDO demoDO = getById(id);
if (demoDO == null) { throw new RuntimeException("不存在"); } // 禁止返回值规范
// ✅ 集合类型永远不返回 null,返回空集合
public List<DemoRespVO> listXxx() {
return Collections.emptyList(); // 或 List.of()
}
// ✅ 分页接口永远返回 PageResult,不返回 null
if (count == 0) {
return PageResult.empty(query);
}慎用工具类的对象拷贝
// ❌ 不要在正式业务代码中用 BeanUtil.copyProperties(类型不匹配静默跳过)
TargetVO vo = BeanUtil.copyProperties(sourceDO, TargetVO.class);
// ✅ 使用 MapStruct(编译期生成,零反射,类型不匹配编译报错)
DemoRespVO vo = DemoConvert.INSTANCE.doToRespVO(demoDO);集合遍历删除
// ❌ 遍历 List 时直接 remove,ConcurrentModificationException
for (DemoDO item : list) {
if (item.getStatus() == 1) { list.remove(item); }
}
// ✅ 使用 Iterator 或 Stream filter
list.removeIf(item -> item.getStatus() == 1);
// 或
List<DemoDO> filtered = list.stream()
.filter(item -> item.getStatus() == 0)
.collect(Collectors.toList());四、异常处理规范
异常分类
| 场景 | 正确用法 |
|---|---|
| 业务校验失败(参数非法、数据不存在、状态不符) | BusinessException(GlobalErrorCode.PARAM_INVALID, "说明") |
| 系统级失败(IO 异常、外部调用失败、序列化失败) | SystemException(GlobalErrorCode.INTERNAL_SERVER_ERROR, "说明", e) |
| 查询不存在 | getByIdOrThrow(id),框架自动抛 DATA_NOT_FOUND |
// ✅ 业务异常
if (demoMapperExt.existsByCode(code, null)) {
throw new BusinessException(GlobalErrorCode.PARAM_INVALID.getCode(),
"编码 [" + code + "] 已存在");
}
// ✅ 系统异常(IO / 外部调用)
try {
StreamUtils.copy(in, response.getOutputStream());
} catch (IOException e) {
throw new SystemException(GlobalErrorCode.INTERNAL_SERVER_ERROR, "文件下载失败", e);
}
// ❌ 禁止:吞掉异常
catch (Exception e) { /* 什么都不做 */ }
// ❌ 禁止:直接抛 RuntimeException
throw new RuntimeException("文件读取失败", e);事务回滚
// ✅ 必须指定 rollbackFor,默认只回滚 RuntimeException
@Transactional(rollbackFor = Exception.class)
public Long createDemo(DemoSaveParamVO saveParam) { ... }
// ❌ 缺少 rollbackFor,IOException 不会回滚
@Transactional
public void doSomething() { ... }五、日志规范
基本用法
@Slf4j // Lombok 注解,自动注入 log 字段,禁止 System.out.println
@Service
public class DemoServiceImpl {
public Long createDemo(DemoSaveParamVO saveParam) {
// ...
log.info("[示例创建] 成功,id={},code={}", demoDO.getId(), demoDO.getDemoCode());
return demoDO.getId();
}
public void deleteDemo(List<Long> ids) {
// ...
log.info("[示例删除] 批量逻辑删除成功,ids={}", ids);
}
}日志级别选择
| 级别 | 场景 |
|---|---|
log.info | 关键业务操作(写操作成功、核心流程节点) |
log.warn | 非预期但可继续运行(降级、重试、数据异常) |
log.error | 需要关注的异常(必须带 exception 参数输出堆栈) |
log.debug | 调试信息(生产环境不开启) |
// ✅ error 级别必须传入异常对象(输出完整堆栈)
log.error("[文件下载] 失败,id={}", id, e);
// ❌ 只打消息,堆栈丢失
log.error("下载失败:" + e.getMessage());禁止打印敏感信息
// ❌ 禁止在日志中输出密码、Token、身份证号等
log.info("用户登录,password={}", password); // 严格禁止
log.info("Token={}", accessToken); // 严格禁止六、MySQL 与 ORM 规范
表设计规范
-- ✅ 每张业务表必须包含 BaseEntity 的 11 个公共字段,置于业务字段之前
-- 高频遗漏:remark 和 custom_fields
CREATE TABLE `t_demo` (
`id` BIGINT NOT NULL COMMENT '主键(雪花算法)',
`tenant_id` BIGINT DEFAULT NULL COMMENT '租户ID',
`create_user_id` BIGINT DEFAULT NULL COMMENT '创建人ID',
`creator` VARCHAR(128) DEFAULT NULL COMMENT '创建人用户名',
`create_time` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
`modify_user_id` BIGINT DEFAULT NULL COMMENT '最后修改人ID',
`updater` VARCHAR(128) DEFAULT NULL COMMENT '最后修改人用户名',
`modify_time` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
`delete_flag` INT NOT NULL DEFAULT 0 COMMENT '逻辑删除(0:未删除 1:已删除)',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注', -- ⚠️ 不可省略
`custom_fields` TEXT DEFAULT NULL COMMENT '扩展字段(JSON)', -- ⚠️ 不可省略
-- 业务字段放在公共字段之后
`demo_name` VARCHAR(100) NOT NULL COMMENT '示例名称',
`status` INT NOT NULL DEFAULT 0 COMMENT '状态(0:正常 1:禁用)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_demo_code` (`tenant_id`, `demo_code`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '示例表';字段类型规范
| 数据库类型 | Java 类型 | 注意 |
|---|---|---|
VARCHAR(N) | String | — |
INT | Integer | 不用 int 基本类型(MyBatis-Plus 默认值问题) |
BIGINT | Long | 主键、ID |
DATETIME(6) | LocalDateTime | 精确到微秒 |
DECIMAL(M,D) | BigDecimal | 金额必须用 BigDecimal,禁止用 Double |
多租户安全:@Select 注解的风险
// ❌ 危险:@Select 绕过 MyBatis-Plus 拦截器,不自动追加 tenant_id 和 delete_flag 过滤
@Select("SELECT * FROM t_demo WHERE demo_code = #{code}")
DemoDO selectByCode(@Param("code") String code);
// ✅ 正确:在 MapperExt 中使用 LambdaQueryWrapper,MP 自动追加租户和逻辑删除过滤
public DemoDO findByCode(String code) {
return demoMapper.selectOne(
new LambdaQueryWrapper<DemoDO>()
.eq(DemoDO::getDemoCode, code)
.last("LIMIT 1")
);
}| SQL 来源 | 自动过滤 delete_flag | 自动追加 tenant_id |
|---|---|---|
| MP BaseMapper / LambdaQueryWrapper | ✅ 自动 | ✅ 自动 |
@Select 注解 / XML Mapper | ❌ 需手动加 | ❌ 需手动加 |
分页两段式查询
// ✅ 必须先 count 后 fetch,count=0 时短路避免无效查询
public PageResult<DemoDO> pageQuery(DemoPageParamVO query) {
LambdaQueryWrapper<DemoDO> wrapper = buildWrapper(query);
Long count = demoMapper.selectCount(wrapper);
if (count == null || count == 0) {
return PageResult.empty(query);
}
Page<DemoDO> mpPage = new Page<>(query.getPageNo(), query.getPageSize(), false);
Page<DemoDO> result = demoMapper.selectPage(mpPage, wrapper);
return PageResult.of(result.getRecords(), count, query);
}禁用 Double 存储金额
// ❌ 禁止:Double 精度丢失
private Double price; // 1.1 + 2.2 ≠ 3.3
// ✅ 必须使用 BigDecimal
private BigDecimal price;
BigDecimal tax = price.multiply(new BigDecimal("0.13")); // 精确运算七、工程结构规范
包结构约定
com.huida.nebula.{业务域}.{模块}/
├── controller/
│ └── DemoController.java
├── service/
│ ├── IDemoService.java
│ └── impl/ ← 实现类必须在 impl/ 子目录
│ └── DemoServiceImpl.java
├── mapper/
│ ├── DemoMapper.java ← 固定 SQL 基础 CRUD
│ └── DemoMapperExt.java ← 动态查询(LambdaQueryWrapper)
├── entity/
│ └── DemoDO.java
├── vo/
│ ├── param/
│ │ ├── DemoPageParamVO.java
│ │ └── DemoSaveParamVO.java
│ └── resp/
│ ├── DemoRespVO.java
│ └── DemoDetailVO.java
└── convert/
└── DemoConvert.javaService 实现类位置
DemoServiceImpl.java 必须放在 service/impl/ 目录,不能直接放在 service/ 下,这是 Nebula 的包结构强约定。
依赖版本管理
// ❌ 禁止:在 build.gradle 中硬编码第三方库版本号
implementation 'cn.hutool:hutool-core:5.8.34'
// ✅ 版本由 nebula-support-dependencies BOM 统一管理,不写版本号
implementation 'cn.hutool:hutool-core'八、并发与线程安全
禁止用 StringRedisTemplate 实现分布式锁
// ❌ 禁止:非原子操作,高并发下锁可能提前释放
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent("lock:demo", "1", 30, TimeUnit.SECONDS);
// ✅ 使用框架封装的 NebulaLockHelper(基于 Redisson,原子操作)
NebulaLockHelper.lock("lock:demo:create", 30, TimeUnit.SECONDS, () -> {
// 临界区代码
});禁止直接使用 StringRedisTemplate 读写 Redis
// ❌ 禁止:Key 无统一前缀,JSON 序列化方式不统一
redisTemplate.opsForValue().set("user:123", objectMapper.writeValueAsString(user));
// ✅ 使用 NebulaCacheHelper(自动追加 Key 前缀、统一 JSON 序列化)
nebulaCacheHelper.set("user:123", user, 30, TimeUnit.MINUTES);共享可变状态
// ❌ 禁止在 Service Bean(单例)中声明可变实例变量
@Service
public class DemoService {
private List<String> cache = new ArrayList<>(); // 线程不安全!
}
// ✅ 用局部变量,或用线程安全容器,或用 Redis
// 或将状态交给框架(如 ThreadLocal / TenantContextHolder)九、注释规范
注释原则
- 注释写为什么,不写做了什么(代码本身已说明做什么)
- 禁止注释掉大段代码提交(删掉或用 Git 管理历史)
- 所有注释使用中文,文件编码 UTF-8
// ✅ 好注释:解释意图和约束
// 修改时排除自身 id(新增时 excludeId 为 null,修改时传入当前记录 id)
public boolean existsByCode(String code, Long excludeId) { ... }
// 两段式分页:先 count 短路,避免 count=0 时还执行 LIMIT 查询浪费数据库资源
Long count = demoMapper.selectCount(wrapper);
if (count == null || count == 0) {
return PageResult.empty(query);
}
// ❌ 坏注释:只是重复代码已经说的内容
// 查询 Demo 列表
List<DemoDO> list = demoMapper.selectList(wrapper);Swagger 接口注释
@Tag(name = "示例管理", description = "t_demo 增删改查接口")
@RestController
public class DemoController {
@Operation(summary = "分页查询示例列表")
@PostMapping("/page")
public R<PageResult<DemoRespVO>> pageDemo(@RequestBody DemoPageParamVO query) {
return R.ok(demoService.pageDemo(query));
}
}// VO 字段注释(用 @Schema 代替 Javadoc,Swagger 可直接展示)
@Schema(description = "示例名称", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "示例名称不能为空")
private String demoName;
@Schema(description = "状态(0:正常 1:禁用)")
private Integer status;十、安全规范
SQL 注入防范
// ✅ MyBatis-Plus 的 LambdaQueryWrapper 使用参数绑定,天然防注入
.like(DemoDO::getDemoName, query.getDemoName())
// ❌ 字符串拼接 SQL(严格禁止)
String sql = "SELECT * FROM t_demo WHERE demo_name = '" + name + "'";敏感信息处理
// ✅ 响应 VO 中不返回密码字段
@Schema(description = "密码(查询时不返回)")
@JsonIgnore // 序列化时忽略
private String password;
// ✅ 日志中不打印敏感字段
@ToString.Exclude
private String password;输入校验
// ✅ Controller 层开启校验(@Validated + Bean Validation 注解)
@Validated
@RestController
public class DemoController {
@PostMapping
public R<Long> createDemo(@Valid @RequestBody DemoSaveParamVO saveParam) { ... }
@PutMapping
public R<Void> updateDemo(
@Validated(DemoSaveParamVO.UpdateGroup.class) @RequestBody DemoSaveParamVO saveParam) { ... }
}
// ✅ SaveParamVO 上的校验注解
@NotBlank(message = "名称不能为空")
@Size(max = 100, message = "名称不能超过 100 个字符")
private String demoName;
@NotNull(groups = UpdateGroup.class, message = "修改时 id 不能为空")
private Long id;十一、新模块开发检查清单
每次开发新业务模块前,对照此清单逐项确认:
