[Java] 如何自动生成简单的 Mermaid 类图

如何自动生成简单的 Mermaid 类图

背景

我在 [Java] 一些类图 这篇文章里画了一些类图,但是那些类图是我手动生成的,虽说画图的过程有利于加深自己的理解,但是查看各个类/接口的信息毕竟比较麻烦,如果可以把生成类图的过程自动化,那么就可以大大提升画类图的效率了。

本文展示了我自己写的可以生成简单类图的 java 代码。文中展示了用它生成的以下类的类图

  1. ArrayList
  2. LinkedList
  3. HashMap/LinkedHashMap/ConcurrentHashMap
  4. java.util.JumboEnumSetjava.util.RegularEnumSet (它们是 EnumSet 仅有的子类)
  5. java.util.concurrent.ThreadPoolExecutor

代码

我写了些代码,可以自动生成简单的 mermaid 类图。 请将以下代码保存为 ClassDiagramGenerator.java ⬇️

java 复制代码
import java.lang.reflect.AccessFlag;
import java.util.*;

public class ClassDiagramGenerator {
    private final Map<Class<?>, RealizationRelation> realizationRelations = new HashMap<>();
    private final Map<Class<?>, InheritanceRelation> inheritanceRelations = new HashMap<>();

    private final Set<Class<?>> analyzedClasses = new HashSet<>();

    public static void main(String[] args) {
        ClassDiagramGenerator generator = new ClassDiagramGenerator();
        generator.convert(args).forEach(generator::analyzeHierarchy);

        generator.generateClassDiagram();
        generator.generateNameMappingTable();
    }

    /**
     * Convert class name to the corresponding class object
     *
     * @param classNames give class names
     * @return a list that contains corresponding class objects
     */
    private List<Class<?>> convert(String[] classNames) {
        if (classNames.length == 0) {
            String hint = "Please refer to below usage and specify at least ONE class name!";
            String usage = "Usage: java ClassDiagramGenerator 'java.util.ArrayList' 'java.util.LinkedList'";
            throw new IllegalArgumentException(String.join(System.lineSeparator(), hint, usage));
        }

        List<Class<?>> classList = new ArrayList<>(classNames.length);
        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);
                classList.add(clazz);
            } catch (ClassNotFoundException e) {
                String message = String.format("Class with name=%s can't be found, please check!", className);
                throw new RuntimeException(message);
            }
        }
        return classList;
    }

    /**
     * Generate header for mermaid class diagram
     */
    private void generateHeader() {
        System.out.println("```mermaid");
        System.out.println("classDiagram");
        System.out.println();
    }

    /**
     * Generate footer for mermaid class diagram
     */
    private void generateFooter() {
        System.out.println("```");
    }

    /**
     * Generate main content in mermaid class diagram
     */
    private void generateClassDiagram() {
        generateHeader();
        doGenerateClassDiagram();
        generateFooter();
    }

    /**
     * Generate a Markdown table that contains name mapping
     */
    private void generateNameMappingTable() {
        System.out.println();
        System.out.println("| 在上图中的类名/接口名 | `Fully Qualified Name` |");
        System.out.println("| --- | --- |");
        Map<String, String> classNames = new TreeMap<>();
        analyzedClasses.forEach(c -> {
            String simpleName = c.getSimpleName();
            if (classNames.containsKey(simpleName)) {
                String prevName = classNames.get(simpleName);
                String currName = c.getName();
                String message = String.format("Duplicated simple class name detected! (%s and %s have the same simple name)", prevName, currName);
                throw new IllegalArgumentException(message);
            }
            classNames.put(simpleName, c.getName());
        });

        classNames.forEach((simpleName, name) -> {
            String row = String.format("| `%s` | `%s` |", simpleName, name);
            System.out.println(row);
        });
    }

    private void doGenerateClassDiagram() {
        analyzedClasses.forEach(c -> {
            if (inheritanceRelations.containsKey(c)) {
                System.out.printf("%s <|-- %s%n", inheritanceRelations.get(c).superNode().getSimpleName(), c.getSimpleName());
            }
            if (realizationRelations.containsKey(c)) {
                String type = c.isInterface() ? "<|--" : "<|..";
                realizationRelations.get(c).interfaceList().forEach(item -> {
                    System.out.printf("%s %s %s%n", item.getSimpleName(), type, c.getSimpleName());
                });
            }
        });

        generateSpecialClassAnnotation();
    }

    /**
     * This method generated annotation for
     * 1. Abstract classes
     * 2. Interfaces
     */
    private void generateSpecialClassAnnotation() {
        Set<Class<?>> abstractClasses = new LinkedHashSet<>();
        Set<Class<?>> interfaces = new LinkedHashSet<>();

        analyzedClasses.forEach(c -> {
            if (c.isInterface()) {
                interfaces.add(c);
            } else if (c.accessFlags().contains(AccessFlag.ABSTRACT)) {
                abstractClasses.add(c);
            }
        });

        if (!abstractClasses.isEmpty() || !interfaces.isEmpty()) {
            System.out.println();
            abstractClasses.forEach(c -> System.out.println("<<Abstract>> " + c.getSimpleName()));
            interfaces.forEach(c -> System.out.println("<<interface>> " + c.getSimpleName()));
        }
    }

    private void analyzeHierarchy(Class<?> currClass) {
        if (!analyzedClasses.contains(currClass)) {
            analyzeSuperClass(currClass);
            analyzeInterfaces(currClass);

            analyzedClasses.add(currClass);
        }
    }

    private void analyzeSuperClass(Class<?> currClass) {
        Class<?> superclass = currClass.getSuperclass();
        if (superclass == null || superclass == Object.class) {
            return;
        }
        analyzeHierarchy(superclass);
        inheritanceRelations.put(currClass, new InheritanceRelation(currClass, superclass));
    }

    private void analyzeInterfaces(Class<?> currClass) {
        Class<?>[] interfaces = currClass.getInterfaces();
        for (Class<?> item : interfaces) {
            analyzeHierarchy(item);
        }

        var interfaceList = Arrays.stream(interfaces).toList();
        realizationRelations.put(currClass, new RealizationRelation(currClass, interfaceList));
    }
}

/**
 * A record class to hold inheritance relation
 */
record InheritanceRelation(Class<?> currNode, Class<?> superNode) {

}

/**
 * A record class to hold realization relation
 */
record RealizationRelation(Class<?> currNode, List<Class<?>> interfaceList) {

}

用以下命令可以编译 ClassDiagramGenerator.java

bash 复制代码
javac ClassDiagramGenerator.java

注意事项

请注意,以上代码生成的类图中,不包含任何泛型信息,而且自动生成的类图中也不展示任何字段/方法。

例子

下面举一些例子,说明 ClassDiagramGenerator.java 的用法。

1: 生成 ArrayList 的类图

请运行以下命令以生成对应的内容 ⬇️

bash 复制代码
java ClassDiagramGenerator 'java.util.ArrayList'

运行结果是 Markdown 格式的,掘金文档的编辑区是支持相关格式的,具体的结果如下

运行结果
classDiagram AbstractCollection <|-- AbstractList List <|.. AbstractList AbstractList <|-- ArrayList List <|.. ArrayList RandomAccess <|.. ArrayList Cloneable <|.. ArrayList Serializable <|.. ArrayList Iterable <|-- Collection Collection <|-- SequencedCollection Collection <|.. AbstractCollection SequencedCollection <|-- List <> AbstractList <> AbstractCollection <> RandomAccess <> Iterable <> Collection <> SequencedCollection <> List <> Cloneable <> Serializable
在上图中的类名/接口名 Fully Qualified Name
AbstractCollection java.util.AbstractCollection
AbstractList java.util.AbstractList
ArrayList java.util.ArrayList
Cloneable java.lang.Cloneable
Collection java.util.Collection
Iterable java.lang.Iterable
List java.util.List
RandomAccess java.util.RandomAccess
SequencedCollection java.util.SequencedCollection
Serializable java.io.Serializable

如果您无法在掘金的文档中使用 mermaid,那么也可以前往 mermaid.live/ 来查看对应的类图,我在 [mermaid.live/] 看到的效果如下 ⬇️ (需要自行将对应的结果复制过去)

2: 生成 LinkedList 的类图

bash 复制代码
java ClassDiagramGenerator 'java.util.LinkedList'

运行结果是 Markdown 格式的,展示如下

运行结果
classDiagram Queue <|-- Deque SequencedCollection <|-- Deque AbstractCollection <|-- AbstractList List <|.. AbstractList Iterable <|-- Collection Collection <|-- SequencedCollection AbstractList <|-- AbstractSequentialList Collection <|-- Queue AbstractSequentialList <|-- LinkedList List <|.. LinkedList Deque <|.. LinkedList Cloneable <|.. LinkedList Serializable <|.. LinkedList Collection <|.. AbstractCollection SequencedCollection <|-- List <> AbstractList <> AbstractSequentialList <> AbstractCollection <> Deque <> Iterable <> Collection <> SequencedCollection <> Queue <> List <> Cloneable <> Serializable
在上图中的类名/接口名 Fully Qualified Name
AbstractCollection java.util.AbstractCollection
AbstractList java.util.AbstractList
AbstractSequentialList java.util.AbstractSequentialList
Cloneable java.lang.Cloneable
Collection java.util.Collection
Deque java.util.Deque
Iterable java.lang.Iterable
LinkedList java.util.LinkedList
List java.util.List
Queue java.util.Queue
SequencedCollection java.util.SequencedCollection
Serializable java.io.Serializable

3: 生成 HashMap/LinkedHashMap/ConcurrentHashMap 的类图

这个例子举得不太好,因为 HashMapLinkedHashMapsuper class,所以 LinkedHashMap 的类图中一定会展示 HashMap

bash 复制代码
java ClassDiagramGenerator 'java.util.HashMap' 'java.util.LinkedHashMap' 'java.util.concurrent.ConcurrentHashMap'

运行结果是 Markdown 格式的,展示如下

运行结果
classDiagram AbstractMap <|-- HashMap Map <|.. HashMap Cloneable <|.. HashMap Serializable <|.. HashMap HashMap <|-- LinkedHashMap SequencedMap <|.. LinkedHashMap Map <|.. AbstractMap AbstractMap <|-- ConcurrentHashMap ConcurrentMap <|.. ConcurrentHashMap Serializable <|.. ConcurrentHashMap Map <|-- SequencedMap Map <|-- ConcurrentMap <> AbstractMap <> Map <> SequencedMap <> ConcurrentMap <> Cloneable <> Serializable
在上图中的类名/接口名 Fully Qualified Name
AbstractMap java.util.AbstractMap
Cloneable java.lang.Cloneable
ConcurrentHashMap java.util.concurrent.ConcurrentHashMap
ConcurrentMap java.util.concurrent.ConcurrentMap
HashMap java.util.HashMap
LinkedHashMap java.util.LinkedHashMap
Map java.util.Map
SequencedMap java.util.SequencedMap
Serializable java.io.Serializable

4: 生成 java.util.JumboEnumSetjava.util.RegularEnumSet 的类图

bash 复制代码
java ClassDiagramGenerator 'java.util.JumboEnumSet' 'java.util.RegularEnumSet'

运行结果是 Markdown 格式的,展示如下

运行结果
classDiagram Collection <|-- Set AbstractSet <|-- EnumSet Cloneable <|.. EnumSet Serializable <|.. EnumSet Iterable <|-- Collection EnumSet <|-- JumboEnumSet AbstractCollection <|-- AbstractSet Set <|.. AbstractSet Collection <|.. AbstractCollection EnumSet <|-- RegularEnumSet <> EnumSet <> AbstractSet <> AbstractCollection <> Set <> Iterable <> Collection <> Cloneable <> Serializable
在上图中的类名/接口名 Fully Qualified Name
AbstractCollection java.util.AbstractCollection
AbstractSet java.util.AbstractSet
Cloneable java.lang.Cloneable
Collection java.util.Collection
EnumSet java.util.EnumSet
Iterable java.lang.Iterable
JumboEnumSet java.util.JumboEnumSet
RegularEnumSet java.util.RegularEnumSet
Serializable java.io.Serializable
Set java.util.Set

5: 生成 ThreadPoolExecutor 的类图

bash 复制代码
java ClassDiagramGenerator 'java.util.concurrent.ThreadPoolExecutor'

运行结果是 Markdown 格式的,展示如下

运行结果
classDiagram Executor <|-- ExecutorService AutoCloseable <|-- ExecutorService AbstractExecutorService <|-- ThreadPoolExecutor ExecutorService <|.. AbstractExecutorService <> AbstractExecutorService <> AutoCloseable <> ExecutorService <> Executor
在上图中的类名/接口名 Fully Qualified Name
AbstractExecutorService java.util.concurrent.AbstractExecutorService
AutoCloseable java.lang.AutoCloseable
Executor java.util.concurrent.Executor
ExecutorService java.util.concurrent.ExecutorService
ThreadPoolExecutor java.util.concurrent.ThreadPoolExecutor
相关推荐
Hard but lovely3 小时前
C++---》stl : pair 从使用到模拟实现
c++·后端
纵横八荒3 小时前
Java基础加强13-集合框架、Stream流
java·开发语言
稚辉君.MCA_P8_Java3 小时前
kafka解决了什么问题?mmap 和sendfile
java·spring boot·分布式·kafka·kubernetes
乄bluefox3 小时前
保姆级docker部署nacos集群
java·docker·容器
欣然~3 小时前
百度地图收藏地址提取与格式转换工具 说明文档
java·开发语言·dubbo
app出海创收老李3 小时前
海外独立创收日记(5)-上个月收入回顾与本月计划
前端·后端·程序员
每天进步一点_JL3 小时前
Docker 是什么?
后端·docker·容器
玩毛线的包子3 小时前
Android Gradle学习(十三)- 配置读取和文件写入
java
app出海创收老李3 小时前
海外独立创收日记(4)-第一笔汇款
前端·后端·程序员