Java 后端开发规范

前言

后端开发中,统一的编码规范能显著提升代码质量和团队协作效率。
本文整理一套简洁通用的 Java 后端开发规范,涵盖命名规范、代码风格、异常处理、项目结构与最佳实践。
适用于 Spring Boot + Java 17+ 技术栈,其他框架可按需调整。

命名规范

常见命名方式

开发中常见的命名风格主要有以下几种,了解它们有助于在不同场景下选择合适的写法。

命名方式 规则 示例 常见用途
PascalCase(大驼峰) 每个单词首字母大写 UserServiceOrderController 类名、接口名、枚举名
camelCase(小驼峰) 首单词小写,后续首字母大写 userNamegetOrderList 变量、方法名、参数
kebab-case(短横线) 全小写,单词间用 - 连接 user-serviceorder-detail 配置 key、URL 路径
snake_case(下划线) 全小写,单词间用 _ 连接 user_namecreate_time 数据库字段
UPPER_SNAKE_CASE(大写下划线) 全大写,单词间用 _ 连接 MAX_COUNTAPI_BASE_URL 常量

Java 后端项目中,最常用的是 PascalCasecamelCasesnake_case 三种。

类与接口命名

  • 类名使用 PascalCase,如 UserServiceOrderController
  • 接口名使用 PascalCase,如 UserRepositoryPayable
  • 抽象类以 Abstract 开头,如 AbstractBaseService
  • 实现类以 Impl 结尾,如 UserServiceImpl
1
2
3
4
5
6
7
8
9
// 推荐
public class UserServiceImpl implements UserService {
// ...
}

// 不推荐
public class userService {
// ...
}

方法命名

  • 方法名使用 camelCase,动词开头。
  • 查询方法以 getlist开头。
  • 新增方法以 add开头。
  • 修改方法以 update开头。
  • 删除方法以 del开头。
  • 判断方法以 ishascan开头。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 查询
public User getUserById(Long id) { ... }
public List<User> findUsersByStatus(Integer status) { ... }

// 新增
public void createUser(UserDTO dto) { ... }

// 修改
public void updateUser(UserDTO dto) { ... }

// 删除
public void deleteUser(Long id) { ... }

// 判断
public boolean isAdmin(Long userId) { ... }

变量与常量命名

  • 变量名使用 camelCase,如 userNamecreateTime
  • 常量使用 UPPER_SNAKE_CASE,如 MAX_RETRY_COUNTDEFAULT_PAGE_SIZE
  • 布尔变量以 ishascan 开头,如 isDeletedhasPermission
  • 集合变量使用复数形式,如 userListorderIds
1
2
3
4
5
6
7
8
9
10
// 推荐
private static final int MAX_RETRY_COUNT = 3;
private String userName;
private boolean isDeleted;
private List<Order> orderList;

// 不推荐
private static final int maxRetryCount = 3;
private String UserName;
private boolean deleted;

项目结构

推荐目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
src/main/java/com/example/project/
├── controller/ # 控制器层
├── service/ # 服务层
│ └── impl/ # 服务实现
├── mapper/ # 数据访问层
├── model/ # 实体类
│ ├── entity/ # 数据库实体
│ ├── dto/ # 数据传输对象
│ └── vo/ # 视图对象
├── config/ # 配置类
├── constant/ # 常量
├── enums/ # 枚举
├── exception/ # 自定义异常
├── util/ # 工具类
└── aspect/ # 切面

分层职责

  • Controller 层:接收请求、参数校验、调用 Service、返回响应。
  • Service 层:业务逻辑处理、事务管理。
  • Repository 层:数据访问、SQL 编写。
  • Model 层:数据模型定义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Controller 层
@RestController
@RequestMapping("/api/users")
public class UserController {

private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

@GetMapping("/{id}")
public Result<UserVO> getUser(@PathVariable Long id) {
UserVO user = userService.getUserById(id);
return Result.success(user);
}
}

// Service 层
public interface UserService {
UserVO getUserById(Long id);
}

@Service
public class UserServiceImpl implements UserService {

private final UserRepository userRepository;

public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}

@Override
public UserVO getUserById(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new BusinessException("用户不存在"));
return convertToVO(user);
}
}

文件命名

  • Controller 类以 Controller 结尾,如 UserController.java
  • Service 接口以 Service 结尾,实现类以 ServiceImpl 结尾。
  • Repository 接口以 RepositoryMapper 结尾。
  • DTO 类以 Dto 结尾,VO 类以 Vo 结尾。

代码逻辑

  • 不要出现单个逻辑让前端判断不同逻辑进行不同业务处理。

代码风格

格式规范

  • 使用 4 空格缩进,不使用 Tab。
  • 每行不超过 120 个字符,超长时换行对齐。
  • 左大括号 { 不换行,右大括号 } 独占一行。
  • 方法之间空一行,类内逻辑块之间空一行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 推荐
public class UserService {

private final UserRepository userRepository;

public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}

public User getUserById(Long id) {
if (id == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
return userRepository.findById(id)
.orElseThrow(() -> new BusinessException("用户不存在"));
}
}

注释规范

  • 类和接口必须添加 Javadoc 注释,说明用途。
  • 公共方法必须添加 Javadoc 注释,说明参数、返回值、异常。
  • 复杂逻辑必须添加行内注释,说明意图而非描述代码。
  • TODO 注释格式:// TODO(作者): 说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 用户服务类
* 处理用户相关的业务逻辑
*/
public class UserService {

/**
* 根据ID查询用户
*
* @param id 用户ID
* @return 用户信息
* @throws BusinessException 用户不存在时抛出
*/
public User getUserById(Long id) {
// 缓存命中则直接返回
User cachedUser = cacheManager.get(id);
if (cachedUser != null) {
return cachedUser;
}

// TODO(zhangsan): 添加分布式锁防止缓存击穿
return userRepository.findById(id)
.orElseThrow(() -> new BusinessException("用户不存在"));
}
}

导入规范

  • 使用显式导入,禁止使用通配符 import xxx.*
  • 导入顺序:java.*javax.* → 第三方库 → 项目内包。
  • 静态导入仅用于常量和工具方法,如 import static org.junit.Assert.*
1
2
3
4
5
6
7
8
9
10
11
12
// 推荐
import java.time.LocalDateTime;
import java.util.List;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

import com.example.project.model.User;
import com.example.project.repository.UserRepository;

// 不推荐
import java.util.*;
import com.example.project.model.*;

异常处理

异常定义

  • 业务异常继承 RuntimeException,如 BusinessException
  • 异常信息使用枚举或常量管理,避免硬编码。
  • 异常码使用数字或字符串,便于前端处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 业务异常
*/
public class BusinessException extends RuntimeException {

private final String code;

public BusinessException(String message) {
super(message);
this.code = "BUSINESS_ERROR";
}

public BusinessException(String code, String message) {
super(message);
this.code = code;
}

public String getCode() {
return code;
}
}

错误码

错误码是前后端协作的重要约定,规范的错误码设计能快速定位问题。

错误码格式

推荐使用 模块前缀 + 数字编号 的格式,便于分类和扩展。

1
2
格式:{模块}_{编号}
示例:USER_001、ORDER_002、SYSTEM_001

错误码分类

类型 前缀 范围 说明
成功 SUCCESS 0 请求成功
系统错误 SYSTEM 1xxx 服务器内部错误、第三方服务异常
参数错误 PARAM 2xxx 参数校验失败、格式错误
认证授权 AUTH 3xxx 登录过期、权限不足
业务错误 按模块定义 各模块自定义 具体业务逻辑异常

错误码枚举

使用枚举统一管理错误码,避免硬编码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public enum ErrorCode {

// 成功
SUCCESS("0", "成功"),

// 系统错误 1xxx
SYSTEM_ERROR("SYSTEM_1001", "系统繁忙,请稍后重试"),
SYSTEM_TIMEOUT("SYSTEM_1002", "请求超时"),
SYSTEM_BUSY("SYSTEM_1003", "系统繁忙"),

// 参数错误 2xxx
PARAM_MISSING("PARAM_2001", "参数缺失"),
PARAM_INVALID("PARAM_2002", "参数无效"),
PARAM_TYPE_ERROR("PARAM_2003", "参数类型错误"),

// 认证授权 3xxx
AUTH_TOKEN_EXPIRED("AUTH_3001", "登录已过期,请重新登录"),
AUTH_TOKEN_INVALID("AUTH_3002", "无效的Token"),
AUTH_NO_PERMISSION("AUTH_3003", "没有操作权限"),

// 用户模块 Uxxx
USER_NOT_FOUND("USER_001", "用户不存在"),
USER_NAME_DUPLICATE("USER_002", "用户名已存在"),
USER_PASSWORD_ERROR("USER_003", "密码错误"),

// 订单模块 Oxxx
ORDER_NOT_FOUND("ORDER_001", "订单不存在"),
ORDER_STATUS_ERROR("ORDER_002", "订单状态异常"),
ORDER_AMOUNT_ERROR("ORDER_003", "订单金额异常");

private final String code;
private final String message;

ErrorCode(String code, String message) {
this.code = code;
this.message = message;
}

public String getCode() {
return code;
}

public String getMessage() {
return message;
}
}

统一响应体

定义统一的响应格式,前端可据此判断请求结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Data
public class Result<T> {

private String code;
private String message;
private T data;

public static <T> Result<T> success() {
return success(null);
}

public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(ErrorCode.SUCCESS.getCode());
result.setMessage(ErrorCode.SUCCESS.getMessage());
result.setData(data);
return result;
}

public static <T> Result<T> fail(ErrorCode errorCode) {
Result<T> result = new Result<>();
result.setCode(errorCode.getCode());
result.setMessage(errorCode.getMessage());
return result;
}

public static <T> Result<T> fail(ErrorCode errorCode, String message) {
Result<T> result = new Result<>();
result.setCode(errorCode.getCode());
result.setMessage(message);
return result;
}
}

使用示例

在业务代码中使用错误码枚举抛出异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 业务异常类
public class BusinessException extends RuntimeException {

private final String code;

public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}

public BusinessException(ErrorCode errorCode, String message) {
super(message);
this.code = errorCode.getCode();
}

public String getCode() {
return code;
}
}

// 业务代码使用
public UserVO getUserById(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
return convertToVO(user);
}

// 全局异常处理返回
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
log.warn("业务异常: code={}, message={}", e.getCode(), e.getMessage());
Result<Void> result = new Result<>();
result.setCode(e.getCode());
result.setMessage(e.getMessage());
return result;
}

规范要求

  • 错误码必须使用枚举或常量类统一管理,禁止在代码中硬编码。
  • 每个模块的错误码前缀不同,便于快速定位问题模块。
  • 错误信息要对用户友好,避免暴露技术细节。
  • 新增错误码时同步更新枚举类,保持错误码列表完整。

异常抛出

  • 业务逻辑异常使用 throw new BusinessException(...)
  • 参数校验异常使用 throw new IllegalArgumentException(...)
  • 不要捕获异常后不处理,至少打印日志。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 推荐
public void createUser(UserDTO dto) {
if (dto == null) {
throw new IllegalArgumentException("用户信息不能为空");
}
if (StringUtils.isBlank(dto.getUserName())) {
throw new BusinessException("USER_NAME_EMPTY", "用户名不能为空");
}
// ...
}

// 不推荐
public void createUser(UserDTO dto) {
try {
// 业务逻辑
} catch (Exception e) {
// 吞掉异常
}
}

全局异常处理

使用 @RestControllerAdvice 统一处理异常,返回标准响应格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestControllerAdvice
public class GlobalExceptionHandler {

private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
log.warn("业务异常: {}", e.getMessage());
return Result.fail(e.getCode(), e.getMessage());
}

@ExceptionHandler(IllegalArgumentException.class)
public Result<Void> handleIllegalArgumentException(IllegalArgumentException e) {
log.warn("参数异常: {}", e.getMessage());
return Result.fail("PARAM_ERROR", e.getMessage());
}

@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常", e);
return Result.fail("SYSTEM_ERROR", "系统繁忙,请稍后重试");
}
}

代码习惯

跨域

接口中不要写跨域逻辑,跨域在Nginx中处理。

参数校验

使用 @Valid@Validated 注解进行参数校验,配合 JSR 303 注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// DTO 定义
public class UserDTO {

@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度2-20个字符")
private String userName;

@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;

@NotNull(message = "年龄不能为空")
@Min(value = 0, message = "年龄不能小于0")
@Max(value = 150, message = "年龄不能大于150")
private Integer age;
}

// Controller 使用
@PostMapping
public Result<Void> createUser(@Valid @RequestBody UserDTO dto) {
userService.createUser(dto);
return Result.success();
}

日志规范

  • 使用 SLF4J + Logback 日志框架。
  • 日志级别:ERROR > WARN > INFO > DEBUG
  • 生产环境默认 INFO 级别,开发环境可调至 DEBUG
  • 日志格式包含时间、级别、类名、方法名、行号。
  • 设置日志路径 logs文件夹下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static final Logger log = LoggerFactory.getLogger(UserService.class);

public void createUser(UserDTO dto) {
log.info("创建用户开始, userName={}", dto.getUserName());

try {
// 业务逻辑
User user = userRepository.save(convertToEntity(dto));
log.info("创建用户成功, userId={}", user.getId());
} catch (Exception e) {
log.error("创建用户失败, userName={}", dto.getUserName(), e);
throw e;
}
}

工具类设计

  • 工具类使用 final 修饰,防止被继承。
  • 构造函数私有化,防止实例化。
  • 方法使用 static 修饰,直接通过类名调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public final class StringUtils {

private StringUtils() {
throw new UnsupportedOperationException("工具类不允许实例化");
}

public static boolean isBlank(String str) {
return str == null || str.trim().isEmpty();
}

public static boolean isNotBlank(String str) {
return !isBlank(str);
}
}

避免的写法

  • 禁止使用 魔法值,统一使用常量定义。
  • 禁止在循环中进行数据库查询,应批量查询。
  • 禁止在 Service 层直接操作 HttpServletRequest
  • 禁止使用 System.out.println() 输出日志。
  • 多表关联
  • 避免过深的嵌套,超过 3 层时考虑拆分方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 推荐
private static final int MAX_PAGE_SIZE = 100;
private static final String DEFAULT_ORDER_BY = "create_time";

// 不推荐
public void queryUsers(int pageSize) {
if (pageSize > 100) { // 魔法值
throw new IllegalArgumentException("页码不能超过100");
}
}

// 推荐:批量查询
List<Long> userIds = orders.stream()
.map(Order::getUserId)
.collect(Collectors.toList());
Map<Long, User> userMap = userRepository.findByIds(userIds);

// 不推荐:循环查询
for (Order order : orders) {
User user = userRepository.findById(order.getUserId()); // N+1 问题
}

接口文档

  • 所有对外接口必须生成文档,不允许存在未文档化的接口。
  • 所有接口要有意义,不要有机械式无意义的增删改查。
  • 接口按照应用模块而不是表模块进行归类。
  • 多端如果有明显差异接口要分开。
  • 接口描述必须清晰,说明业务场景和使用注意事项。
  • 请求参数必须标注是否必填与类型,提供示例值。
  • 响应字段必须说明含义及类型,枚举值需列出可选项。
  • 接口变更时同步更新文档,保持文档与代码一致。
  • 文档返回值类型和必填要符合实际情况。
  • 字段不能缺失,也不能冗。

对其他端要求

原型/设计

  • 原型和设计保持一致

  • 需求变更原型和设计要同步变更