一文带你掌握Java如何自动化生成目录结构文档

 更新时间:2026年01月15日 09:54:56   作者:夜郎king  
这篇文章主要为大家详细介绍了Java如何自动化生成目录结构文档,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下

前言

在开源世界的版图上,目录结构就是项目的“城市沙盘”。第一次推开仓库大门的开发者,往往先扫一眼根目录下的文件夹与文件,再决定是留下来深耕,还是转身离开。可这份“沙盘”却常年处于失修状态:手写 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、开源项目介绍

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

# 项目名称
项目描述...
## 📁 项目结构
```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来预定义一些常用的配置。这里默认采用静态块的模式进行加载设置:

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

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

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

/**
* -解析命令行参数
*/
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、加载自定义配置

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

/**
 * *加载自定义配置文件
 */
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中的层次展示,这里需要使用递归的方式进行调用。入口函数如下:

 /**
  * -生成目录树
  */
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中可以直接使用以下代码进行生成,非常方便,当然这里也是考虑递归生成的,源代码如下:

/**
 *- 收集描述信息生成表格
 */
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中可以在命令中输入以下命令进行运行,参数添加方式如下:

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

--path=../blueengine --output=DIY_PROJECT_TREE_BLUEENGINE.md --depth=10

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

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

内容基本是符合我们的生成预期的。到此,我们的自定义目录生成并输出功能结束。

四、总结

本文主要对纯Java实现工程的目录自定义输出进行介绍,文章详细介绍了开源项目为什么需要进行目录输入,然后详细介绍了纯Java的原生实现的核心函数,最后介绍了两种不同的模式,通过给当前工程生成目录说明和生成其它工程目录。

以上就是一文带你掌握Java如何自动化生成目录结构文档的详细内容,更多关于Java生成目录结构的资料请关注脚本之家其它相关文章!

相关文章

  • Java NIO 文件通道 FileChannel 用法及原理

    Java NIO 文件通道 FileChannel 用法及原理

    这篇文章主要介绍了Java NIO 文件通道 FileChannel 用法和原理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01
  • java内存异常使用导致full gc频繁

    java内存异常使用导致full gc频繁

    Full GC是Java虚拟机中垃圾回收的一种方式,它会暂停应用程序所有的线程并清理整个堆内存。频繁的Full GC会导致应用程序的性能下降,甚至出现长时间的停顿。Java内存异常使用常常是Full GC频繁出现的原因之一,如使用大量的静态变量、内存泄漏等。
    2023-04-04
  • Java实现全排列的三种算法详解

    Java实现全排列的三种算法详解

    从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫全排列。本文总结了Java实现全排列的三种算法,需要的可以参考下
    2022-06-06
  • java简单实现多线程及线程池实例详解

    java简单实现多线程及线程池实例详解

    这篇文章主要为大家详细介绍了java简单实现多线程,及java爬虫使用线程池实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03
  • 整理总结Java多线程程序编写的要点

    整理总结Java多线程程序编写的要点

    这篇文章主要介绍了Java多线程程序编写的要点,包括线程的状态控制和优先级以及线程的通信问题等方面,非常之全面!需要的朋友可以参考下
    2016-01-01
  • 解析Spring中的静态代理和动态代理

    解析Spring中的静态代理和动态代理

    学习 Spring 的过程中,不可避免要掌握代理模式。这篇文章总结一下代理模式。顾名思义,代理,就是你委托别人帮你办事,所以代理模式也有人称作委托模式的。比如领导要做什么事,可以委托他的秘书去帮忙做,这时就可以把秘书看做领导的代理
    2021-06-06
  • Java基于Swing和netty实现仿QQ界面聊天小项目

    Java基于Swing和netty实现仿QQ界面聊天小项目

    这篇文章主要为大家详细介绍了Java如何利用Swing和netty实现仿QQ界面聊天小项目,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2022-09-09
  • Spring框架应用的权限控制系统详解

    Spring框架应用的权限控制系统详解

    在本篇文章里小编给大家整理的是关于基于Spring框架应用的权限控制系统的研究和实现,需要的朋友们可以学习下。
    2019-08-08
  • IDEA源码修改器JarEditor使用(反编译-打包一步到位)

    IDEA源码修改器JarEditor使用(反编译-打包一步到位)

    JarEditor是一个IDEA插件,用于修改jar包中的类文件,它允许用户在不解压jar包的情况下,直接在IDEA中编辑和修改类文件的源码,修改完成后,可以一键编译并生成新的jar包,替换原jar包
    2025-01-01
  • SpringBoot自定义start详细图文教程

    SpringBoot自定义start详细图文教程

    这篇文章主要给大家介绍了关于SpringBoot自定义start的相关资料,主要讲述如何自定义start,实现一些自定义类的自动装配,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-11-11

最新评论