一文掌握:Java项目目录结构文档自动化生成

目录

前言

一、来看看优秀的项目

1、开源项目介绍

2、开源项目目录分析

二、纯Java原生实现

1、Java常用配置说明

2、默认配置

3、解析命令参数

4、加载自定义配置

5、生成目录树

三、成果展示

1、生成目录树实现

2、生成当前工程目录

3、生成其它工程目录

四、总结


前言

在开源世界的版图上,目录结构就是项目的"城市沙盘"。第一次推开仓库大门的开发者,往往先扫一眼根目录下的文件夹与文件,再决定是留下来深耕,还是转身离开。可这份"沙盘"却常年处于失修状态:手写 README 的树状图随着迭代迅速过时,新加的模块没人补录,删掉的包路径依旧躺在文档里"诈尸"。于是,维护者陷入"改代码五分钟,改文档半小时"的泥沼,贡献者则在"代码与描述对不上"的迷宫中兜圈。更严重的是,当项目被 Maven Central、GitHub Package 收录,或进入企业内网供上千人复用时,一份过期目录说明直接拉低了整个工程的可信度。开发者开始质疑:如果连目录都懒得同步,核心业务逻辑是否也藏着暗坑?这种"文档债务"像复利一样滚雪球,最终把技术品牌拖进信任黑洞。

痛点催生需求,需求催生轮子。Java 生态历来"万物皆库",把文档生成做成一个可嵌入的 JAR,比任何外部脚本都更轻、更快、更可移植。思路可以拆成三步:第一步,用 java.nio.file.Files递归扫描,把路径、文件大小、最后修改时间一次性收进内存,形成一棵"物理树";第二步,借 JavaParser 扫描 src/main/java,把包名、类名映射成"语义树",再与物理树按路径合并,让目录节点瞬间拥有"做什么"的业务标签;第三步,把整棵树渲染成 Markdown,直接写回 docs/structure.md,Maven 只需在 compile 阶段挂一条 exec:java 指令,就能在每次打包前完成自动更新。

今天,我们全文围绕一个真实的 Spring Boot 单体仓库演示,每一步都是可拷贝的 .java 文件,不依赖任何外部 CLI。读完这篇,你将把"目录说明"从手工清单变成编译产物,像 class 文件一样,随构建永远保持最新。开源项目的门面,从此只靠 Java 代码自己说话。

一、来看看优秀的项目

本节我们来看看一些在Gitee和Github中的优秀项目案例,来看看他们的项目目录又是怎么规划和展示的,抛砖引玉,通过本节的说明,让大家了解在正规的项目中都是如何来规划这些目录的。让人一看就知道他的规划非常清晰。

1、开源项目介绍

首先来看看在社区中常见的对于开源项目的目录介绍文本,其主要内容如下所示:

bash 复制代码
# 项目名称
项目描述...
## 📁 项目结构
```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("src/main/java", "Java源代码");
    DIRECTORY_DESCRIPTIONS.put("src/main/resources", "资源文件");
    DIRECTORY_DESCRIPTIONS.put("src/test", "测试代码目录");
    DIRECTORY_DESCRIPTIONS.put("src/test/java", "Java测试代码");
    DIRECTORY_DESCRIPTIONS.put("src/test/resources", "测试资源文件");
    DIRECTORY_DESCRIPTIONS.put("com", "Java包目录");
    DIRECTORY_DESCRIPTIONS.put("controller", "控制器层");
    DIRECTORY_DESCRIPTIONS.put("service", "服务层");
    DIRECTORY_DESCRIPTIONS.put("service/impl", "服务实现层");
    DIRECTORY_DESCRIPTIONS.put("repository", "数据访问层");
    DIRECTORY_DESCRIPTIONS.put("dao", "数据访问对象层");
    DIRECTORY_DESCRIPTIONS.put("entity", "实体类");
    DIRECTORY_DESCRIPTIONS.put("model", "模型层");
    DIRECTORY_DESCRIPTIONS.put("dto", "数据传输对象");
    DIRECTORY_DESCRIPTIONS.put("vo", "视图对象");
    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", "监听器");
    DIRECTORY_DESCRIPTIONS.put("task", "定时任务");
    DIRECTORY_DESCRIPTIONS.put("job", "定时任务");
    // 初始化常用文件描述
    FILE_DESCRIPTIONS.put("pom.xml", "Maven项目配置文件");
    FILE_DESCRIPTIONS.put("build.gradle", "Gradle构建文件");
    FILE_DESCRIPTIONS.put("gradle.properties", "Gradle属性文件");
    FILE_DESCRIPTIONS.put("settings.gradle", "Gradle设置文件");
    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", "项目说明文档");
    FILE_DESCRIPTIONS.put("README-CN.md", "中文项目说明文档");
    FILE_DESCRIPTIONS.put("README_EN.md", "英文项目说明文档");
    FILE_DESCRIPTIONS.put("CONTRIBUTING.md", "贡献指南");
    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构建文件");
    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开发环境配置");
    FILE_DESCRIPTIONS.put("application-prod.properties", "Spring Boot生产环境配置");
    FILE_DESCRIPTIONS.put("application-test.properties", "Spring Boot测试环境配置");
    FILE_DESCRIPTIONS.put("bootstrap.properties", "Spring Cloud启动配置");
    FILE_DESCRIPTIONS.put("bootstrap.yml", "Spring Cloud启动配置(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配置文件");
}

在上面的常用配置中,大家可以根据自己项目的实际情况进行设置,将一些目录删除或者替换掉即可。

2、默认配置

讲完了Java中的常用配置,下面我们为了控制在生成目录时能欧控制输出的内容和处理条件。在这里我们需要定义一些常用的参数。参数说明如下:

|----|-------------------------------------------------------------------|----------------------------|
| 序号 | 参数名 | 说明 |
| 1 | private static final Set<String> IGNORED_DIRS | 需要忽略的目录 |
| 2 | private static final Set<String> IGNORED_FILES | 需要忽略的文件扩展名 |
| 3 | private static final Map<String, String> DIRECTORY_DESCRIPTIONS | 目录描述映射(可以扩展这个映射来为特定目录添加描述) |
| 4 | private static final Map<String, String> FILE_DESCRIPTIONS | 文件描述映射 |
| 5 | private static String configFile | 配置文件路径 |
| 6 | private static int maxDepth | 最大层级限制 |
| 7 | private static boolean showFiles | 是否显示文件 |
| 8 | private static boolean showDescriptions | 是否显示描述 |
| 9 | private static boolean dirsOnly | 是否只显示目录 |

示例代码如下:

java 复制代码
// 需要忽略的目录
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"
));
    
// 需要忽略的文件扩展名
private static final Set<String> IGNORED_FILES = new HashSet<>(Arrays.asList(
        ".class", ".jar", ".war", ".iml", ".project", ".classpath",
        ".log", ".tmp", ".cache", ".lock", ".swp", ".swo", ".pyc"
));

其它更多的参数就在此不详细列出,感兴趣的朋友可以留言或者下载链接的内容。

3、解析命令参数

这里我们使用命令行参数来进行统一运行,为了能在运行命令时传入相关参数,因此需要对命令行参数进行解析,解析的逻辑和过程我们不做过多的设计。仅涉及相关参数的读取,解析方法如一下代码:

java 复制代码
/**
* -解析命令行参数
*/
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;
}

可以看到,对命令行的参数进行解析的方法也比较简单。

4、加载自定义配置

为了方便用户可以自定义的进行配置的修改,我们不仅可以在程序中加载默认的参数,同时也可以加载外部的配置文件,当我们在运行的时候,就可以动态的修改参数也不需要修改代码,这样程序的扩展性就更强了。

java 复制代码
/**
 * *加载自定义配置文件
 */
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、生成目录树

在做了上述的功能之后,接下来我们就可以进行目录树的生成,为了实现在Java中的层次展示,这里需要使用递归的方式进行调用。入口函数如下:

java 复制代码
 /**
  * -生成目录树
  */
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();
        // 构建节点行
        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");
        // 如果是文件且不需要递归,或者只显示目录且当前是文件,则返回
        if (!node.isDirectory() || (dirsOnly && !node.isDirectory())) {
            return;
        }
        // 如果是目录,递归处理子节点
        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);
        }
}

篇幅有限,还有很多代码无法全部呈现。如果需要完整的代码,可以从下载相应的源码(同时包含最简单的配置文件示例)。

三、成果展示

本节将重点展示一下,如何对当前项目工程和其他工程目录进行目录的输出。使用命令行的方式进行程序输出。让大家可以快速的对目标工程进行目录的输出和介绍。

1、生成目录树实现

在工程中,不仅要将目录树生成出来,同时还要生成一个可以解释说明的表格。在Markdown中可以直接使用以下代码进行生成,非常方便,当然这里也是考虑递归生成的,源代码如下:

java 复制代码
/**
 *- 收集描述信息生成表格
 */
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、生成当前工程目录

默认情况下生成的是当前的工程目录。因此我们可以直接在类中直接运行Main方法,运行后会直接在工程跟目录下生成一个md文件,同时在控制台中可以看到以下输出:

打开文件夹,可以看到以下内容:

3、生成其它工程目录

如果想在一个工程中为其它的工程目录生成目录结构说明,并且进行相关目录的设置说明,就可以直接调用命令行参数来进行设置参数即可。在Eclipse中可以在命令中输入以下命令进行运行,参数添加方式如下:

这里将命令行参数参数贴出来:

bash 复制代码
--path=../blueengine --output=DIY_PROJECT_TREE_BLUEENGINE.md --depth=10

这个命令的意思是给本工程同目录下的blueengine项目生成指定文件名的文件,路径深度为10层。程序运行后可以看到以下内容:

最后来看看生成的目录格式如下:

内容基本是符合我们的生成预期的。到此,我们的自定义目录生成并输出功能结束。完整代码链接:纯Java生成Markdown格式的工程目录源码

四、总结

以上就是本文的主要内容,本文主要对纯Java实现工程的目录自定义输出进行介绍,文章详细介绍了开源项目为什么需要进行目录输入,然后详细介绍了纯Java的原生实现的核心函数,最后介绍了两种不同的模式,通过给当前工程生成目录说明和生成其它工程目录。大家拿到源码后,可以根据自己的需要进行目录输出,非常便捷。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。

相关推荐
颜淡慕潇2 小时前
深度解读 Spring Boot 3.5.9— 工程视角的稳健演进与价值释放
java·spring boot·后端·spring
appearappear2 小时前
IntelliJ IDEA 2025.3.1 中 Export → SQL Updates 不带 WHERE 的真实原因与解决方案(OpenAI 协助整理)
java·数据库
玄〤2 小时前
黑马点评中的分布式锁设计与实现(Redis + Redisson)
java·数据库·redis·笔记·分布式·后端
码界奇点2 小时前
基于SpringBoot与Shiro的细粒度动态权限管理系统设计与实现
java·spring boot·后端·spring·毕业设计·源代码管理
小毅&Nora2 小时前
【Java线程安全实战】⑬ volatile的奥秘:从“共享冰箱“到内存可见性的终极解析
java·多线程·volatile
亓才孓2 小时前
Java第三代时间API
java·开发语言
码农水水2 小时前
京东Java面试被问:Spring Boot嵌入式容器的启动和端口绑定原理
java·开发语言·人工智能·spring boot·面试·职场和发展·php
博图光电2 小时前
博图双目结构光相机——叉车自动化视觉定位解决方案
运维·数码相机·自动化
前端切图仔0012 小时前
Chrome 扩展程序上架指南
android·java·javascript·google