NipGeihou's blog NipGeihou's blog
  • Java

    • 开发规范
    • 进阶笔记
    • 微服务
    • 快速开始
    • 设计模式
  • 其他

    • Golang
    • Python
    • Drat
  • Redis
  • MongoDB
  • 数据结构与算法
  • 计算机网络
  • 应用

    • Grafana
    • Prometheus
  • 容器与编排

    • KubeSphere
    • Kubernetes
    • Docker Compose
    • Docker
  • 组网

    • TailScale
    • WireGuard
  • 密码生成器
  • 英文单词生成器
🍳烹饪
🧑‍💻关于
  • 分类
  • 标签
  • 归档

NipGeihou

我见青山多妩媚,料青山见我应如是
  • Java

    • 开发规范
    • 进阶笔记
    • 微服务
    • 快速开始
    • 设计模式
  • 其他

    • Golang
    • Python
    • Drat
  • Redis
  • MongoDB
  • 数据结构与算法
  • 计算机网络
  • 应用

    • Grafana
    • Prometheus
  • 容器与编排

    • KubeSphere
    • Kubernetes
    • Docker Compose
    • Docker
  • 组网

    • TailScale
    • WireGuard
  • 密码生成器
  • 英文单词生成器
🍳烹饪
🧑‍💻关于
  • 分类
  • 标签
  • 归档
  • 设计模式

  • 开发规范

    • 前言
    • Java规范
      • 环境规范
        • IDE
      • 目录结构
      • 编码规范
        • 通用
        • 依赖注入
        • 服务发现
        • Controller层(含HTTP API)
        • 职责
        • 类
        • HTTP接口
        • 方法
        • Service层
        • 类
        • 方法
        • Repository层
        • 类
        • 方法
        • Domain
        • 实体对象(Entity)
        • 数据传输对象(DTO)
        • 查询对象(Query)
        • 请求对象(Req)
        • 响应对象(Resp)
        • 命名
        • Util 工具类
    • Redis规范
    • MySQL规范
    • Git规范
    • MQ规范
  • 经验分享

  • 记录

  • 快速开始

  • 笔记

  • 面试题

  • 微服务

  • 踩过的坑

  • Java
  • 开发规范
NipGeihou
2022-01-05
目录

Java规范

# 环境规范

# IDE

image-20230308104445751

  • 使用 LF(\n) 换行符,而非 CRLF(\r\n)

说明:由于项目通常在 Linux 环境下编译,为了不必要的麻烦统一使用 LF(\n) 换行符,尤其是使用 windows 的同学,windows 默认为 CRLF(\r\n) ,而新版的 macOS 或 Linux 用户通常不需要修改,默认就是 LF(\n)

IDEA 设置默认: File | Settings | Editor | Code Style ,Line separator: Unix and macOS(\n)

  • 使用 4个空格 缩进,而非 Tab 字符

说明:在使用 IDEA 开发中,我们仍然使用 tab 按键进行缩进,但需要在 IDEA 右下方配置为 4 spaces ,通常默认就是

  • 使用 UTF-8 编码,而非 GBK

# 目录结构

cn.nipx. 业务线.[子业务线],最多 4 级

包名 说明
controller 控制器
service 业务服务
constant 常量
client 供其他服务调用的 feign 接口,此包应存在于供其他服务依赖的 api 模块中,后缀 Client
client 供其他服务调用的 feign 接口实现,实际为控制器,此包应存在于服务模块中,后缀 ClientImpl
listener 消息队列消费者

# 编码规范

以教务模块(Academic)下的学生管理(Student)为例

分层

# 通用

# 依赖注入

  • 使用构造器注入,而非 @Autowired

说明:Spring (4.x+) 推荐使用构造器注入

正例:


 

 


@RestController
@RequiredArgsConstructor
class StudentController{
    private final StudentService studentService;
}

反例:



 
 


@RestController
class StudentController{
    @Autowired
    private StudentService studentService;
}
  • 集合对象变量名应使用接口后缀,而非实现类后缀

正例:

List<Student> studentList = new ArrayList<>();
Set<String, Student> studentSet = new HashSet<>();
Map<String, Student> studentMap = new HashMap<>();

反例:

ArrayList<Student> studentArrayList = new ArrayList<>();
HashSet<String, Student> studentHashSet = new HashSet<>();
HashMap<String, Student> studentHashMap = new HashMap<>();
  • 对象列表变量名使用 List 后缀,而非复数

说明:List 后缀相较于复数有更好的阅读性

正例: List<Student> studentList

反例: List<Student> students

特例: List<Long> userIds ,id 列表使用复数

# 服务发现

  • 在本机添加环境变量 SPRING_CLOUD_NACOS_DISCOVERY_WEIGHT 值为 0 ,避免前端调试调到本机。

说明:而不是使用添加 SPRING_CLOUD_NACOS_DISCOVERY_ENABLED 值为 false ,这会导致本机无法通过服务发现调用其他服务。

# Controller 层(含 HTTP API)

# 职责

  • 接收前端请求
  • 对前端请求参数做基本的校验(@Validated)
  • 调用 Service 层完成前端需求

# 类

  • 类名命名时应将模块名前缀去掉,在同一个模块下还怕分不清这个对象是哪个模块的?

正例:StudentController

反例:AcademicStudentController

# HTTP 接口

  • 控制器资源使用复数命名

正例: /students/...

反例: /student/...

  • 资源、动作由多个单词组成时,使用 - 分割

正例: /professional-emphasis/...

反例: /professionalEmphasis/...

  • 使用 /资源/actions/动作 扩展 RESTful 接口,而不是直接在资源后面添加动作

正例: PUT /students/{id}/actions/disable 、 GET /students/actions/code-list

反例: PUT /students/{id}/disable 、 GET /students/code-list

  • 幂等性接口使用 PathVariable 接收必填参数

正例: GET /students/clazz-id/{clazzId} :获取某个班级的学生列表

反例: GET /students?clazz-id={clazzId}

  • 请求类型遵循 RESTful 规范

GET :

  • 安全且幂等
  • 获取表示
  • 变更时获取表示(缓存)

POST :

  • 不安全且不幂等
  • 使用服务端管理的(自动产生)的实例号创建资源
  • 创建子资源
  • 部分更新资源
  • 如果没有被修改,则不过更新资源(乐观锁)

PUT

  • 不安全但幂等
  • 用客户端管理的实例号创建一个资源
  • 通过替换的方式更新资源
  • 如果未被修改,则更新资源(乐观锁)

DELETE :

  • 不安全但幂等
  • 删除资源

参考:RESTful 架构详解 | 菜鸟教程 (opens new window)

参考:

  • AIP-136: Custom methods (opens new window)
  • “一把梭:REST API 全用 POST” | 酷 壳 - CoolShell (opens new window)

# 方法

  • 方法命名应尽量表达业务含义而非数据变化

正例: detail 、 distable 、 enable

反例: getById 、 updateIsDisable

  • 前台接口必须使用 Req (DTO) 对象入参,而非 Entity 对象

说明:确保 Req 对象中的属性都是业务必须的,严禁图省事使用 Entity 对象,避免前端传递非法参数(如创建时间等敏感字段)。

正例: create(@RequestBody @Validated StudentReq req)

反例: create(@RequestBody @Validated Student student)

  • 前台结构必须校验入参

  • 不应在 Controller 层编写任何业务逻辑,应全权交由 Service 层处理

正例:

pubilc Result<Void> create(@RequestBody @Validated StudentReq req){
    studentService.create(req);
    return Result.ok();
}

反例:


 
 



pubilc Result<Void> create(@RequestBody @Validated StudentReq req){
    Student student = BeanUtil.copyProperties(req, Student.class);
    Assert.isTrue(studentService.create(student), "新增失败");
    return Result.ok();
}
  • Service 层无返回 (void) 时,返回成功

说明:约定 Service 层方法处理过程中出现异常,会抛出 RuntimeException ,再通过全局异常处理返回 500 给前端,而如能够执行到 return 说明执行成功。

正例:

pubilc Result<Void> create(@RequestBody @Validated StudentReq req){
    studentService.create(req);
    return Result.ok();
}
@RequestMapping("/students") // 资源使用复数
class StudentController{
    
    // 列表
    @GetMapping
    Result<List<StudentResp>> list(StudentQueryReq query);
    
    // 分页列表
    @GetMapping
    ResultPage<StudentResp> page(StudentQueryReq query);
    
    // 学生学号列表
    @GetMapping("/actions/code-info-list")
    Result<List<StudentResp>> listCodeInfo();
     
    // 详情
    @GetMapping("/{id}")
    Result<StudentResp> detail(@PathVariable("id") Long id);
    
    // 新增
    @PostMapping
    Result<Void> create(@RequestBody @Validated StudentReq req);
        
    // 修改
    @PutMapping
    Result<Void> update(@RequestBody @Validated StudentReq req);
    
    // 删除
    @DeleteMapping("/{id}")
    Result<Void> delete(@PathVariable("id") Long id);
    
    // 禁用
    @PutMapping("/{id}/acitons/disable")
    Result<Void> disable(@PathVariable("id") Long id);
   
}

列表(list)与分页列表(page)

这两个接口互斥,只可使用其中一个,如果前端即用到分页又用到全部,那么使用分页列表 (page),在需要返回全部时传递 pageSize=-1 ,如果前端只需要返回全部,则使用列表 (list)。

为什么使用delete而不是remove?

  • 在一些规范中约定 remove 是逻辑删除,delete 是物理删除;这里的命名跟这些规范没有任何关系,纯属约定。

  • 而且逻辑还是物理删除这不是 controller 层也不是 service 层考虑的事,这应由持久层决定。

  • 而在有回收站这一概念的业务场景中,应使用另外的字段 ( waitDelete ) 来区分, remove 为删除到回收站, delete 为彻底删除。

# Service 层

# 类

注意

本规范下的 Service 层结构有别于大部分的框架,不使用 接口-实现类 的结构,而是直接使用 实现类 ,在笔者有限的项目经验中未能找到任何必须使用 接口-实现类 结构的场景。

详细理由参考:Do I need an interface with Spring boot? | Dimitri's tutorials (opens new window)

  • 严禁实现 MyBatis-Plus 的 IService 接口及实现

说明:MyBatis-Plus (mp) 的 IService 接口及实现虽能节省编写通用 CURD 方法的时间,但同时会使得 mp 与 service 耦合,如之后重构需更改持久化框架,需要大量的修改,不能满足开闭原则。

正例: public class StudentService{}

反例: public class StudentService extends ServiceImpl<StudentMapper, Student> implements IService<Student>{}

# 方法

  • 命名应与 Controller 层保持一致,即应尽量表达业务含义而非数据变化

说明:在能用简短词汇描述时,优先使用词汇命名,即 detail 优先于 getById 。

  1. 获取单个对象的方法用 get 做前缀。 getCodeInfoById
  2. 获取多个对象的方法用 list 做前缀。如: list 、 listCodeInfo
  3. 获取统计值的方法用 count、sum、min、max 做前缀。
  4. 插入的方法用 create 做前缀。
  5. 删除的方法用 remove 做前缀。 remove
  6. 修改的方法用 update 做前缀。 update
  • 词汇 优于 For 优于 By

说明:在命名时应优先这个方法能不能用一个词汇概括(如: create 、 update ),再考虑能不能用用于 (For) 来概括(如:listForMe),最后再考虑通过 (By) 概括(如:listByStatus)。

对于调用者抽象,无需了解功能实现的细节,只需知道调用 update 方法就能完成更新操作,只需传递合法入参即可 (控制器 + service 校验),而不需要通过方法名中的 ById 告知是通过 ID 更新的。

正例: changePassword 优于 updatePasswordById

  • 获取当前 Service 对应实体时无需在方法名中强调

说明:StudentService 对应的实体类即是 Student,因此在获取 Student 对象的方法名省略 Student,而 StudentResp 不是对应实体,因此需要标明。

正例: getById(id) 、 getStudentResqById(id) 、 list(query) 、 listStudentResq(query)

反例: getStudentById(id) 、 listStudent(query)

  • 禁止在本层使用任何 MyBatis-Plus 包下的东西

说明:MyBatis-Plus 作为一个具体的持久化框架,不应该与 Service 有任何直接接触,而是面向持久层接口。

# Repository 层

StudentRepository --> StudentRepositoryImpl

在很多项目中,Service 层下面就到了 Mapper 层,但本规范在这两层之间增加了 Repository 层,原因如下:

  • MyBatis-Plus (mp) 与 Service 层耦合:mp 的条件构造器能够通过 Java 代码代替大部分 CURD SQL 编写,而这些代码在没有 Repository 层之前,需要编写在 Service 层,从而导致业务逻辑代码与数据查询逻辑代码耦合在一起,降低可读性
  • MyBatis 与 Service 层耦合:即使抛开 mp,直接依赖 Mapper 仍然会导致 Service 与 MyBatis 耦合,Mapper Interface 作为 MyBatis 特有的接口命名,在更换 ORM 框架时,仍然需要大量修改 Service 层的依赖。

因此 Service 层应依赖一个自定义的持久层接口,无论项目更改了什么 ORM 框架或是数据库都不会影响到 Service 层之上,只需将新的持久层实现类注册的 Spring 容器即可以完成切换。

# 类

  • Repository 接口禁止继承 MyBatis-Plus 的 IService

说明:在接口继承 IService 跟在 Service 继承没什么区别,但允许在 Repository 的实现类中实现及继承 ServiceImpl,由于 Service 是依赖 Repository 接口,实现类继承任何东西都不会使 Repository 接口暴露无关的方法,而在实现类继承 ServiceImpl 则能基于其内置方法实习大部分的接口方法,免于编写 SQL。

正例: public class StudentRepositoryImpl extends ServiceImpl<StudentMapper, Student> implements IService<Student>{}

反例: public interface StudentRepository extends IService<Student>{}

# 方法

  • 命名规范
  1. 获取单个对象的方法用 find 做前缀。如: findById
  2. 获取多个对象的方法用 select 做前缀。如: selectAll 、 selectByQuery 、 selectCodeInfo
  3. 获取分页对象的方法用 page 做前缀。如: page(query)
  4. 获取统计值的方法用 count、sum、min、max 做前缀。 countByStatus
  5. 插入的方法用 insert 做前缀。 如: insert
  6. 删除的方法用 delete 做前缀。 如: deleteById
  7. 修改的方法用 update 做前缀。如: updateById

参考 JPA 命名规则 (opens new window)

  • 分页查询方法命名使用 page(query) ,列表查询方法使用 slecteByQuery(query)

说明:约定认为分页默认携带查询参数,因此无需强调 byQuery

正例: ResultPage<Student> page(StudentQueryReq query)

反例: ResultPage<Student> pageByQuery(StudentQueryReq query)

# Domain

  • 在 Java 的世界中,万事万物皆对象,因此在开发中,不可避免会创建很多领域模型,在本规范中浅将其分为实体对象、数据传输对象。
  • 约定下面的所有对象存储到 domain 包下,如 /doamin/entity/Student 、 /damain/dto/req/StudentReq

为什么是domain包下,而不是model、pojo?

无论是 domain、model、pojo 要表达的意思都是相似的,包下的类都是一些只有 private 、 get 、 set 的简单对象,之所以使用 domain 纯属约定。

# 实体对象 (Entity)

  • 包名: /entiy

  • 后缀: 无

正例: Student

反例: StudentEntity

  • 属性:属性与数据库字段完全一致,不应包括其他属性

反例:数据库中只有班级 id ( clazz_id ) 字段,但在 Entity 对象中另外添加了班级名称 ( clazz_name ) 字段。如因 API 返回需要,应在 Resp 对象中添加。

# 数据传输对象 (DTO)

  • 包名: /dto

说明:在本规范中并没有以 DTO 为后缀的对象,因此 dto 包下并没有类文件,只有子包(如下)。

# 查询对象 (Query)
  • 包名: /query
  • 后缀: Query

正例: StudentQuery

说明:多用于接收列表查询条件对象

# 请求对象 (Req)
  • 包名: /req
  • 后缀: Req

正例: StudentAddReq

说明:用于 POST 操作的请求体接收,如创建对象、修改对象。

# 响应对象 (Resp)
  • 包名: /resp
  • 后缀: Resp

正例: StudentDetailResp

说明:用于方法返回对象,如对象列表、对象详情。

# 命名

  • 命名规范: [<服务端名>]<实体名称>[<动作>]<DTO类型>

正例: StudentAddReq 、 StudentEditReq 、 AppStudentAddReq 、 MpStudentResp

  • 约定后台服务 DTO 省略 Admin 服务端名前缀

正例: StudentReq

反例: AdminStudentReq

  • 约定新增和编辑请求 Req 属性除 id 以为一致时,共用 StudentReq ,反之分别使用 StudentAddReq 、 StudentEditReq

  • 通用的 CURD 请求对象均不需要添加动作名,此外的与 Controller 的 URL 中的 acitons/<动作> 一致

正例: HTTP /students/{id}/acitons/audit => StudentAuditReq

# Util 工具类

  • 使用 lombok 的 @NoArgsConstructor(access = AccessLevel.PRIVATE) 注解禁止实例化。
#Java#开发规范
上次更新: 2024/03/26, 12:18:59
前言
Redis规范

← 前言 Redis规范→

最近更新
01
Docker Swarm
04-18
02
安全隧道 - gost
04-17
03
Solana最佳实践
04-16
更多文章>
Theme by Vdoing | Copyright © 2018-2025 NipGeihou | 友情链接
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式