Java 项目目录结构文档自动化生成方案
前言
在开源世界的版图上,目录结构就是项目的'城市沙盘'。第一次推开仓库大门的开发者,往往先扫一眼根目录下的文件夹与文件,再决定是留下来深耕,还是转身离开。可这份'沙盘'却常年处于失修状态:手写 README 的树状图随着迭代迅速过时,新加的模块没人补录,删掉的包路径依旧躺在文档里'诈尸'。于是,维护者陷入'改代码五分钟,改文档半小时'的泥沼,贡献者则在'代码与描述对不上'的迷宫中兜圈。
痛点催生需求,需求催生轮子。Java 生态历来'万物皆库',把文档生成做成一个可嵌入的 JAR,比任何外部脚本都更轻、更快、更可移植。思路可以拆成三步:第一步,用 java.nio.file.Files 递归扫描,把路径、文件大小、最后修改时间一次性收进内存,形成一棵'物理树';第二步,借 JavaParser 扫描 src/main/java,把包名、类名映射成'语义树',再与物理树按路径合并,让目录节点瞬间拥有'做什么'的业务标签;第三步,把整棵树渲染成 Markdown,直接写回 docs/structure.md,Maven 只需在 compile 阶段挂一条 exec:java 指令,就能在每次打包前完成自动更新。
今天,我们全文围绕一个真实的 Spring Boot 单体仓库演示,每一步都是可拷贝的 .java 文件,不依赖任何外部 CLI。读完这篇,你将把'目录说明'从手工清单变成编译产物,像 class 文件一样,随构建永远保持最新。
一、来看看优秀的项目
本节我们来看看一些在 Gitee 和 Github 中的优秀项目案例,来看看他们的项目目录又是怎么规划和展示的,抛砖引玉,通过本节的说明,让大家了解在正规的项目中都是如何来规划这些目录的。
1、开源项目介绍
首先来看看在社区中常见的对于开源项目的目录介绍文本,其主要内容如下所示:
# 项目名称 项目描述...
## 📁 项目结构
```text
.
├── src/
│ ├── main/
│ │ ├── java/com/example/
│ │ │ ├── controller/
│ │ │ ├── service/
│ │ │ ├── repository/
│ │ │ └── model/
│ │ └── resources/
│ │ ├── application.properties
│ │ └── static/
│ └── test/
│ └── java/com/example/
├── pom.xml
├── README.md
└── .gitignore
注:此目录树自动生成,更新于 $(date)
### 2、开源项目目录分析
可以看到这就是一个比较标准的 SpringBoot 项目。在项目中使用 Maven 进行项目管理和构建。版本控制使用的是 Git 这个软件。在源码方面,我们可以看到正式工程目录 main 和测试工程目录 test。而在 Main 中,由同时包含控制层、业务层、模型层和响应的资源目录信息。应该说这是一个比较完整的开源项目目录说明文件了。但是美中不足的地方是,这些文本的描述缺乏对项目目录的中文详细说明,还有不同的目录和文件,没有进行相应的标注。虽然中文的标注可以修改,但是由标注会让工程看起来更直观和清晰。
## 二、纯 Java 原生实现
本节将重点详细介绍如何使用 Java 原生来进行实现。包括常用的配置说明,默认的配置设置,如何去解析命令中的参数和如何加载自定义的配置。通过本节的描述,大家都能够掌握如何使用 Java 进行原生的目录解释实现。
### 1、Java 常用配置说明
在 Java 中,尤其是后端的 Web 项目中,我们通常会包含以下的目录,比如总体的工程目录、src 表示源码目录、java 表示 java 源代码目录、resources 表示资源目录、controller 表示控制层目录、service 表示业务层目录、repository 表示数据访问层目录等等。我们在进行工程模板的设置时往往可以自由进行设置,这里我们使用 Java 来预定义一些常用的配置。这里默认采用静态块的模式进行加载设置:
```java
static {
// 初始化常用目录描述
DIRECTORY_DESCRIPTIONS.put("src", "源代码目录");
DIRECTORY_DESCRIPTIONS.put("src/main", );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
DIRECTORY_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
FILE_DESCRIPTIONS.put(, );
}


