一文帶你掌握J(rèn)ava如何自動(dòng)化生成目錄結(jié)構(gòu)文檔
前言
在開源世界的版圖上,目錄結(jié)構(gòu)就是項(xiàng)目的“城市沙盤”。第一次推開倉庫大門的開發(fā)者,往往先掃一眼根目錄下的文件夾與文件,再?zèng)Q定是留下來深耕,還是轉(zhuǎn)身離開??蛇@份“沙盤”卻常年處于失修狀態(tài):手寫 README 的樹狀圖隨著迭代迅速過時(shí),新加的模塊沒人補(bǔ)錄,刪掉的包路徑依舊躺在文檔里“詐尸”。于是,維護(hù)者陷入“改代碼五分鐘,改文檔半小時(shí)”的泥沼,貢獻(xiàn)者則在“代碼與描述對(duì)不上”的迷宮中兜圈。更嚴(yán)重的是,當(dāng)項(xiàng)目被 Maven Central、GitHub Package 收錄,或進(jìn)入企業(yè)內(nèi)網(wǎng)供上千人復(fù)用時(shí),一份過期目錄說明直接拉低了整個(gè)工程的可信度。開發(fā)者開始質(zhì)疑:如果連目錄都懶得同步,核心業(yè)務(wù)邏輯是否也藏著暗坑?這種“文檔債務(wù)”像復(fù)利一樣滾雪球,最終把技術(shù)品牌拖進(jìn)信任黑洞。

痛點(diǎn)催生需求,需求催生輪子。Java 生態(tài)歷來“萬物皆庫”,把文檔生成做成一個(gè)可嵌入的 JAR,比任何外部腳本都更輕、更快、更可移植。思路可以拆成三步:第一步,用 java.nio.file.Files遞歸掃描,把路徑、文件大小、最后修改時(shí)間一次性收進(jìn)內(nèi)存,形成一棵“物理樹”;第二步,借 JavaParser 掃描 src/main/java,把包名、類名映射成“語義樹”,再與物理樹按路徑合并,讓目錄節(jié)點(diǎn)瞬間擁有“做什么”的業(yè)務(wù)標(biāo)簽;第三步,把整棵樹渲染成 Markdown,直接寫回 docs/structure.md,Maven 只需在 compile 階段掛一條 exec:java 指令,就能在每次打包前完成自動(dòng)更新。
今天,我們?nèi)膰@一個(gè)真實(shí)的 Spring Boot 單體倉庫演示,每一步都是可拷貝的 .java 文件,不依賴任何外部 CLI。讀完這篇,你將把“目錄說明”從手工清單變成編譯產(chǎn)物,像 class 文件一樣,隨構(gòu)建永遠(yuǎn)保持最新。開源項(xiàng)目的門面,從此只靠 Java 代碼自己說話。
一、來看看優(yōu)秀的項(xiàng)目
本節(jié)我們來看看一些在Gitee和Github中的優(yōu)秀項(xiàng)目案例,來看看他們的項(xiàng)目目錄又是怎么規(guī)劃和展示的,拋磚引玉,通過本節(jié)的說明,讓大家了解在正規(guī)的項(xiàng)目中都是如何來規(guī)劃這些目錄的。讓人一看就知道他的規(guī)劃非常清晰。
1、開源項(xiàng)目介紹
首先來看看在社區(qū)中常見的對(duì)于開源項(xiàng)目的目錄介紹文本,其主要內(nèi)容如下所示:
# 項(xiàng)目名稱
項(xiàng)目描述...
## ?? 項(xiàng)目結(jié)構(gòu)
```text
.
├── src/
│ ├── main/
│ │ ├── java/com/example/
│ │ │ ├── controller/
│ │ │ ├── service/
│ │ │ ├── repository/
│ │ │ └── model/
│ │ └── resources/
│ │ ├── application.properties
│ │ └── static/
│ └── test/
│ └── java/com/example/
├── pom.xml
├── README.md
└── .gitignore
```
*注:此目錄樹自動(dòng)生成,更新于 $(date)*
2、開源項(xiàng)目目錄分析
可以看到這就是一個(gè)比較標(biāo)準(zhǔn)的SpringBoot項(xiàng)目。在項(xiàng)目中使用Maven進(jìn)行項(xiàng)目管理和構(gòu)建。版本控制使用的是Git這個(gè)軟件。在源碼方面,我們可以看到正式工程目錄main和測(cè)試工程目錄test。而在Main中,由同時(shí)包含控制層、業(yè)務(wù)層、模型層和響應(yīng)的資源目錄信息。應(yīng)該說這是一個(gè)比較完整的開源項(xiàng)目目錄說明文件了。但是美中不足的地方是,這些文本的描述缺乏對(duì)項(xiàng)目目錄的中文詳細(xì)說明,還有不同的目錄和文件,沒有進(jìn)行相應(yīng)的標(biāo)注。雖然中文的標(biāo)注可以修改,但是由標(biāo)注會(huì)讓工程看起來更直觀和清晰。
二、純Java原生實(shí)現(xiàn)
本節(jié)將重點(diǎn)詳細(xì)介紹如何使用Java原生來進(jìn)行實(shí)現(xiàn)。包括常用的配置說明,默認(rèn)的配置設(shè)置,如何去解析命令中的參數(shù)和如何加載自定義的配置。通過本節(jié)的描述,大家都能夠掌握如何使用Java進(jìn)行原生的目錄解釋實(shí)現(xiàn)。
1、Java常用配置說明
在Java中,尤其是后端的Web項(xiàng)目中,我們通常會(huì)包含以下的目錄,比如總體的工程目錄、src表示源碼目錄、java表示java源代碼目錄、resources表示資源目錄、controller表示控制層目錄、service表示業(yè)務(wù)層目錄、repository表示數(shù)據(jù)訪問層目錄等等。我們?cè)谶M(jìn)行工程模板的設(shè)置時(shí)往往可以自由進(jìn)行設(shè)置,這里我們使用Java來預(yù)定義一些常用的配置。這里默認(rèn)采用靜態(tài)塊的模式進(jìn)行加載設(shè)置:
static {
// 初始化常用目錄描述
DIRECTORY_DESCRIPTIONS.put("src", "源代碼目錄");
DIRECTORY_DESCRIPTIONS.put("src/main", "主代碼目錄");
DIRECTORY_DESCRIPTIONS.put("src/main/java", "Java源代碼");
DIRECTORY_DESCRIPTIONS.put("src/main/resources", "資源文件");
DIRECTORY_DESCRIPTIONS.put("src/test", "測(cè)試代碼目錄");
DIRECTORY_DESCRIPTIONS.put("src/test/java", "Java測(cè)試代碼");
DIRECTORY_DESCRIPTIONS.put("src/test/resources", "測(cè)試資源文件");
DIRECTORY_DESCRIPTIONS.put("com", "Java包目錄");
DIRECTORY_DESCRIPTIONS.put("controller", "控制器層");
DIRECTORY_DESCRIPTIONS.put("service", "服務(wù)層");
DIRECTORY_DESCRIPTIONS.put("service/impl", "服務(wù)實(shí)現(xiàn)層");
DIRECTORY_DESCRIPTIONS.put("repository", "數(shù)據(jù)訪問層");
DIRECTORY_DESCRIPTIONS.put("dao", "數(shù)據(jù)訪問對(duì)象層");
DIRECTORY_DESCRIPTIONS.put("entity", "實(shí)體類");
DIRECTORY_DESCRIPTIONS.put("model", "模型層");
DIRECTORY_DESCRIPTIONS.put("dto", "數(shù)據(jù)傳輸對(duì)象");
DIRECTORY_DESCRIPTIONS.put("vo", "視圖對(duì)象");
DIRECTORY_DESCRIPTIONS.put("config", "配置類");
DIRECTORY_DESCRIPTIONS.put("util", "工具類");
DIRECTORY_DESCRIPTIONS.put("utils", "工具類");
DIRECTORY_DESCRIPTIONS.put("constant", "常量類");
DIRECTORY_DESCRIPTIONS.put("exception", "異常處理");
DIRECTORY_DESCRIPTIONS.put("interceptor", "攔截器");
DIRECTORY_DESCRIPTIONS.put("filter", "過濾器");
DIRECTORY_DESCRIPTIONS.put("aop", "面向切面編程");
DIRECTORY_DESCRIPTIONS.put("aspect", "切面類");
DIRECTORY_DESCRIPTIONS.put("handler", "處理器");
DIRECTORY_DESCRIPTIONS.put("listener", "監(jiān)聽器");
DIRECTORY_DESCRIPTIONS.put("task", "定時(shí)任務(wù)");
DIRECTORY_DESCRIPTIONS.put("job", "定時(shí)任務(wù)");
// 初始化常用文件描述
FILE_DESCRIPTIONS.put("pom.xml", "Maven項(xiàng)目配置文件");
FILE_DESCRIPTIONS.put("build.gradle", "Gradle構(gòu)建文件");
FILE_DESCRIPTIONS.put("gradle.properties", "Gradle屬性文件");
FILE_DESCRIPTIONS.put("settings.gradle", "Gradle設(shè)置文件");
FILE_DESCRIPTIONS.put("package.json", "Node.js包配置文件");
FILE_DESCRIPTIONS.put("package-lock.json", "Node.js包鎖定文件");
FILE_DESCRIPTIONS.put("yarn.lock", "Yarn包鎖定文件");
FILE_DESCRIPTIONS.put("README.md", "項(xiàng)目說明文檔");
FILE_DESCRIPTIONS.put("README-CN.md", "中文項(xiàng)目說明文檔");
FILE_DESCRIPTIONS.put("README_EN.md", "英文項(xiàng)目說明文檔");
FILE_DESCRIPTIONS.put("CONTRIBUTING.md", "貢獻(xiàn)指南");
FILE_DESCRIPTIONS.put("CHANGELOG.md", "更新日志");
FILE_DESCRIPTIONS.put("LICENSE", "許可證文件");
FILE_DESCRIPTIONS.put(".gitignore", "Git忽略文件配置");
FILE_DESCRIPTIONS.put(".gitattributes", "Git屬性配置");
FILE_DESCRIPTIONS.put(".editorconfig", "編輯器配置");
FILE_DESCRIPTIONS.put("docker-compose.yml", "Docker Compose配置");
FILE_DESCRIPTIONS.put("Dockerfile", "Docker構(gòu)建文件");
FILE_DESCRIPTIONS.put("docker-compose.yaml", "Docker Compose配置");
FILE_DESCRIPTIONS.put("application.properties", "Spring Boot配置文件");
FILE_DESCRIPTIONS.put("application.yml", "Spring Boot配置文件(YAML)");
FILE_DESCRIPTIONS.put("application.yaml", "Spring Boot配置文件(YAML)");
FILE_DESCRIPTIONS.put("application-dev.properties", "Spring Boot開發(fā)環(huán)境配置");
FILE_DESCRIPTIONS.put("application-prod.properties", "Spring Boot生產(chǎn)環(huán)境配置");
FILE_DESCRIPTIONS.put("application-test.properties", "Spring Boot測(cè)試環(huán)境配置");
FILE_DESCRIPTIONS.put("bootstrap.properties", "Spring Cloud啟動(dòng)配置");
FILE_DESCRIPTIONS.put("bootstrap.yml", "Spring Cloud啟動(dòng)配置(YAML)");
FILE_DESCRIPTIONS.put("logback-spring.xml", "Logback日志配置");
FILE_DESCRIPTIONS.put("logback.xml", "Logback日志配置");
FILE_DESCRIPTIONS.put("log4j2.xml", "Log4j2日志配置");
FILE_DESCRIPTIONS.put("log4j.properties", "Log4j配置文件");
}在上面的常用配置中,大家可以根據(jù)自己項(xiàng)目的實(shí)際情況進(jìn)行設(shè)置,將一些目錄刪除或者替換掉即可。
2、默認(rèn)配置
講完了Java中的常用配置,下面我們?yōu)榱丝刂圃谏赡夸洉r(shí)能歐控制輸出的內(nèi)容和處理?xiàng)l件。在這里我們需要定義一些常用的參數(shù)。參數(shù)說明如下:
| 序號(hào) | 參數(shù)名 | 說明 |
| 1 | private static final Set<String> IGNORED_DIRS | 需要忽略的目錄 |
| 2 | private static final Set<String> IGNORED_FILES | 需要忽略的文件擴(kuò)展名 |
| 3 | private static final Map<String, String> DIRECTORY_DESCRIPTIONS | 目錄描述映射(可以擴(kuò)展這個(gè)映射來為特定目錄添加描述) |
| 4 | private static final Map<String, String> FILE_DESCRIPTIONS | 文件描述映射 |
| 5 | private static String configFile | 配置文件路徑 |
| 6 | private static int maxDepth | 最大層級(jí)限制 |
| 7 | private static boolean showFiles | 是否顯示文件 |
| 8 | private static boolean showDescriptions | 是否顯示描述 |
| 9 | private static boolean dirsOnly | 是否只顯示目錄 |
示例代碼如下:
// 需要忽略的目錄
private static final Set<String> IGNORED_DIRS = new HashSet<>(Arrays.asList(
".git", ".idea", "target", "build", "out", "node_modules",
".gradle", ".settings", "bin", "dist", "logs",
".vscode", ".history", "__pycache__", ".metadata"
));
// 需要忽略的文件擴(kuò)展名
private static final Set<String> IGNORED_FILES = new HashSet<>(Arrays.asList(
".class", ".jar", ".war", ".iml", ".project", ".classpath",
".log", ".tmp", ".cache", ".lock", ".swp", ".swo", ".pyc"
));其它更多的參數(shù)就在此不詳細(xì)列出,感興趣的朋友可以留言或者下載鏈接的內(nèi)容。
3、解析命令參數(shù)
這里我們使用命令行參數(shù)來進(jìn)行統(tǒng)一運(yùn)行,為了能在運(yùn)行命令時(shí)傳入相關(guān)參數(shù),因此需要對(duì)命令行參數(shù)進(jìn)行解析,解析的邏輯和過程我們不做過多的設(shè)計(jì)。僅涉及相關(guān)參數(shù)的讀取,解析方法如一下代碼:
/**
* -解析命令行參數(shù)
*/
private static Map<String, String> parseArgs(String[] args) {
Map<String, String> params = new HashMap<>();
for (int i = 0; i < args.length; i++) {
String arg = args[i];
if (arg.startsWith("--")) {
int eqIndex = arg.indexOf('=');
if (eqIndex > 0) {
String key = arg.substring(2, eqIndex);
String value = arg.substring(eqIndex + 1);
params.put(key, value);
} else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
String key = arg.substring(2);
params.put(key, args[++i]);
}
} else if (i == 0 && !arg.startsWith("--")) {
params.put("path", arg);
} else if (i == 1 && !arg.startsWith("--")) {
params.put("output", arg);
}
}
return params;
}可以看到,對(duì)命令行的參數(shù)進(jìn)行解析的方法也比較簡單。
4、加載自定義配置
為了方便用戶可以自定義的進(jìn)行配置的修改,我們不僅可以在程序中加載默認(rèn)的參數(shù),同時(shí)也可以加載外部的配置文件,當(dāng)我們?cè)谶\(yùn)行的時(shí)候,就可以動(dòng)態(tài)的修改參數(shù)也不需要修改代碼,這樣程序的擴(kuò)展性就更強(qiáng)了。
/**
* *加載自定義配置文件
*/
private static void loadCustomConfig() {
File config = new File(configFile);
if (config.exists()) {
try {
Properties props = new Properties();
props.load(Files.newInputStream(config.toPath()));
// 加載忽略目錄
String ignoredDirs = props.getProperty("ignored.dirs");
if (ignoredDirs != null) {
Collections.addAll(IGNORED_DIRS, ignoredDirs.split(","));
}
// 加載忽略文件
String ignoredFiles = props.getProperty("ignored.files");
if (ignoredFiles != null) {
Collections.addAll(IGNORED_FILES, ignoredFiles.split(","));
}
// 加載目錄描述
for (String key : props.stringPropertyNames()) {
if (key.startsWith("dir.")) {
DIRECTORY_DESCRIPTIONS.put(key.substring(4), props.getProperty(key));
} else if (key.startsWith("file.")) {
FILE_DESCRIPTIONS.put(key.substring(5), props.getProperty(key));
}
}
System.out.println("?? 已加載配置文件: " + configFile);
} catch (IOException e) {
System.err.println("?? 無法加載配置文件: " + configFile);
}
}
}5、生成目錄樹
在做了上述的功能之后,接下來我們就可以進(jìn)行目錄樹的生成,為了實(shí)現(xiàn)在Java中的層次展示,這里需要使用遞歸的方式進(jìn)行調(diào)用。入口函數(shù)如下:
/**
* -生成目錄樹
*/
private static void generateTree(File node, String prefix, boolean isLast, int depth, StringBuilder tree) {
// 檢查深限制
if (depth > maxDepth) {
return;
}
// 跳過忽略的文件和目錄
if (shouldIgnore(node, depth)) {
return;
}
String name = node.getName().isEmpty() ? "." : node.getName();
// 構(gòu)建節(jié)點(diǎn)行
StringBuilder line = new StringBuilder();
line.append(prefix).append(isLast ? "└── " : "├── ").append(name);
// 添加描述(如果啟用)
if (showDescriptions) {
String description = getDescription(node, depth);
if (!description.isEmpty()) {
line.append(" ").append(description);
}
}
tree.append(line).append("\n");
// 如果是文件且不需要遞歸,或者只顯示目錄且當(dāng)前是文件,則返回
if (!node.isDirectory() || (dirsOnly && !node.isDirectory())) {
return;
}
// 如果是目錄,遞歸處理子節(jié)點(diǎn)
File[] children = getSortedChildren(node);
for (int i = 0; i < children.length; i++) {
String newPrefix = prefix + (isLast ? " " : "│ ");
boolean childIsLast = (i == children.length - 1);
generateTree(children[i], newPrefix, childIsLast, depth + 1, tree);
}
}篇幅有限,還有很多代碼無法全部呈現(xiàn)。如果需要完整的代碼,可以從下載相應(yīng)的源碼(同時(shí)包含最簡單的配置文件示例)。
三、成果展示
本節(jié)將重點(diǎn)展示一下,如何對(duì)當(dāng)前項(xiàng)目工程和其他工程目錄進(jìn)行目錄的輸出。使用命令行的方式進(jìn)行程序輸出。讓大家可以快速的對(duì)目標(biāo)工程進(jìn)行目錄的輸出和介紹。
1、生成目錄樹實(shí)現(xiàn)
在工程中,不僅要將目錄樹生成出來,同時(shí)還要生成一個(gè)可以解釋說明的表格。在Markdown中可以直接使用以下代碼進(jìn)行生成,非常方便,當(dāng)然這里也是考慮遞歸生成的,源代碼如下:
/**
*- 收集描述信息生成表格
*/
private static void collectDescriptions(File node, String path, StringBuilder result) {
if (shouldIgnore(node, 0)) {
return;
}
String name = node.getName();
String fullPath = path.isEmpty() ? name : path + "/" + name;
if (node.isDirectory()) {
// 添加目錄描述到表格
String description = getDescription(node, 0);
if (!description.isEmpty()) {
result.append("| `").append(fullPath).append("/` | ").append(description).append(" |\n");
}
// 遞歸處理子目錄
File[] children = node.listFiles();
if (children != null) {
for (File child : children) {
if (child.isDirectory() && !shouldIgnore(child, 0)) {
collectDescriptions(child, fullPath, result);
}
}
}
}
}2、生成當(dāng)前工程目錄
默認(rèn)情況下生成的是當(dāng)前的工程目錄。因此我們可以直接在類中直接運(yùn)行Main方法,運(yùn)行后會(huì)直接在工程跟目錄下生成一個(gè)md文件,同時(shí)在控制臺(tái)中可以看到以下輸出:

打開文件夾,可以看到以下內(nèi)容:

3、生成其它工程目錄
如果想在一個(gè)工程中為其它的工程目錄生成目錄結(jié)構(gòu)說明,并且進(jìn)行相關(guān)目錄的設(shè)置說明,就可以直接調(diào)用命令行參數(shù)來進(jìn)行設(shè)置參數(shù)即可。在Eclipse中可以在命令中輸入以下命令進(jìn)行運(yùn)行,參數(shù)添加方式如下:

這里將命令行參數(shù)參數(shù)貼出來:
--path=../blueengine --output=DIY_PROJECT_TREE_BLUEENGINE.md --depth=10
這個(gè)命令的意思是給本工程同目錄下的blueengine項(xiàng)目生成指定文件名的文件,路徑深度為10層。程序運(yùn)行后可以看到以下內(nèi)容:

最后來看看生成的目錄格式如下:

內(nèi)容基本是符合我們的生成預(yù)期的。到此,我們的自定義目錄生成并輸出功能結(jié)束。
四、總結(jié)
本文主要對(duì)純Java實(shí)現(xiàn)工程的目錄自定義輸出進(jìn)行介紹,文章詳細(xì)介紹了開源項(xiàng)目為什么需要進(jìn)行目錄輸入,然后詳細(xì)介紹了純Java的原生實(shí)現(xiàn)的核心函數(shù),最后介紹了兩種不同的模式,通過給當(dāng)前工程生成目錄說明和生成其它工程目錄。
以上就是一文帶你掌握J(rèn)ava如何自動(dòng)化生成目錄結(jié)構(gòu)文檔的詳細(xì)內(nèi)容,更多關(guān)于Java生成目錄結(jié)構(gòu)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot3實(shí)現(xiàn)國際化的代碼步驟
國際化,簡稱 i18n,源自國際化英文單詞 internationalization 中首字母 i 與尾字母 n 之間有 18 個(gè)字母,本文給大家介紹了SpringBoot3實(shí)現(xiàn)國際化的操作步驟,并通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-12-12
Eclipse項(xiàng)目怎么導(dǎo)入IDEA并運(yùn)行(超詳細(xì))
這篇文章主要介紹了Eclipse項(xiàng)目怎么導(dǎo)入IDEA并運(yùn)行(超詳細(xì)),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10

