一文詳解Java如何自動生成簡單的Mermaid類圖
背景
雖說手動生成類圖的過程有利于加深自己的理解,但是查看各個(gè)類/接口的信息畢竟比較麻煩,如果可以把生成類圖的過程自動化,就可以大大提升畫類圖的效率了。
本文展示了我自己寫的可以生成簡單類圖的 java 代碼。文中展示了用它生成的以下類的類圖
ArrayListLinkedListjava.util.Arrays$ArrayList(Arrays.asList(...)方法返回的是它的實(shí)例)List/Set/DequeHashMap/LinkedHashMap/ConcurrentHashMapSet12/SetN(Set.of(...)方法返回的是它們的實(shí)例)java.util.JumboEnumSet和java.util.RegularEnumSet(EnumSet.of(...)方法返回的是它們的實(shí)例)java.util.concurrent.ThreadPoolExecutor
代碼
我寫了如下的 java 代碼,它可以自動生成簡單的 Mermaid 類圖。 請將以下代碼保存為 ClassDiagramGenerator.java
import java.lang.reflect.AccessFlag;
import java.util.*;
public class ClassDiagramGenerator {
private final Map<Class<?>, Class<?>[]> realizationRelations = new HashMap<>();
private final Map<Class<?>, Class<?>> 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).getSimpleName(), c.getSimpleName());
}
if (realizationRelations.containsKey(c)) {
String type = c.isInterface() ? "<|--" : "<|..";
Arrays.stream(realizationRelations.get(c)).forEach(item -> {
System.out.printf("%s %s %s%n", item.getSimpleName(), type, c.getSimpleName());
});
}
});
generateSpecialClassAnnotation();
}
/**
* This method generates 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, superclass);
}
private void analyzeInterfaces(Class<?> currClass) {
Class<?>[] interfaces = currClass.getInterfaces();
for (Class<?> item : interfaces) {
analyzeHierarchy(item);
}
realizationRelations.put(currClass, interfaces);
}
}
用以下命令可以編譯 ClassDiagramGenerator.java
javac ClassDiagramGenerator.java
注意事項(xiàng)
請注意,ClassDiagramGenerator 生成的類圖中,不包含任何泛型信息,而且也不展示任何字段/方法。
例子
下面舉一些例子,說明 ClassDiagramGenerator.java 的用法。
例1: 生成ArrayList的類圖
請運(yùn)行以下命令以生成對應(yīng)的內(nèi)容
java ClassDiagramGenerator 'java.util.ArrayList'
運(yùn)行結(jié)果是 Markdown 格式的
運(yùn)行結(jié)果

| 在上圖中的類名/接口名 | 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/ 來查看對應(yīng)的類圖,我在 mermaid.live/ 看到的效果如下 (需要自行將結(jié)果復(fù)制到那個(gè)網(wǎng)頁去)

例2: 生成LinkedList的類圖
請運(yùn)行以下命令以生成對應(yīng)的內(nèi)容
java ClassDiagramGenerator 'java.util.LinkedList'
運(yùn)行結(jié)果是 Markdown 格式的,展示如下
運(yùn)行結(jié)果

| 在上圖中的類名/接口名 | 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: 生成java.util.Arrays$ArrayList的類圖
調(diào)用 Arrays.asList(...) 方法,得到的是 java.util.Arrays$ArrayList 的實(shí)例。 請運(yùn)行以下命令以生成對應(yīng)的內(nèi)容
java ClassDiagramGenerator 'java.util.Arrays$ArrayList'
運(yùn)行結(jié)果是 Markdown 格式的,展示如下
運(yùn)行結(jié)果
請注意下圖中的 ArrayList 是 java.util.Arrays 中的一個(gè)嵌套類,而不是我們平時(shí)常用的 java.util.ArrayList

| 在上圖中的類名/接口名 | Fully Qualified Name |
|---|---|
| AbstractCollection | java.util.AbstractCollection |
| AbstractList | java.util.AbstractList |
| ArrayList | java.util.Arrays$ArrayList |
| Collection | java.util.Collection |
| Iterable | java.lang.Iterable |
| List | java.util.List |
| RandomAccess | java.util.RandomAccess |
| SequencedCollection | java.util.SequencedCollection |
| Serializable | java.io.Serializable |
例4: 生成List/Set/Deque的類圖
請運(yùn)行以下命令以生成對應(yīng)的內(nèi)容
java ClassDiagramGenerator 'java.util.List' 'java.util.Set' 'java.util.Deque'
運(yùn)行結(jié)果是 Markdown 格式的,展示如下
運(yùn)行結(jié)果

| 在上圖中的類名/接口名 | Fully Qualified Name |
|---|---|
| Collection | java.util.Collection |
| Deque | java.util.Deque |
| Iterable | java.lang.Iterable |
| List | java.util.List |
| Queue | java.util.Queue |
| SequencedCollection | java.util.SequencedCollection |
| Set | java.util.Set |
例5: 生成HashMap/LinkedHashMap/ConcurrentHashMap的類圖
請運(yùn)行以下命令以生成對應(yīng)的內(nèi)容
java ClassDiagramGenerator 'java.util.HashMap' 'java.util.LinkedHashMap' 'java.util.concurrent.ConcurrentHashMap'
運(yùn)行結(jié)果是 Markdown 格式的,展示如下
運(yùn)行結(jié)果

| 在上圖中的類名/接口名 | 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 |
例6: 生成java.util.ImmutableCollections$Set12和java.util.ImmutableCollections$SetN的類圖
當(dāng)我們調(diào)用 Set.of(...) 方法時(shí),會得到以下兩個(gè)類的實(shí)例
java.util.ImmutableCollections$Set12java.util.ImmutableCollections$SetN
請運(yùn)行以下命令以生成對應(yīng)的內(nèi)容
java ClassDiagramGenerator 'java.util.ImmutableCollections$Set12' 'java.util.ImmutableCollections$SetN'
運(yùn)行結(jié)果是 Markdown 格式的,展示如下
運(yùn)行結(jié)果

| 在上圖中的類名/接口名 | Fully Qualified Name |
|---|---|
| AbstractCollection | java.util.AbstractCollection |
| AbstractImmutableCollection | java.util.ImmutableCollections$AbstractImmutableCollection |
| AbstractImmutableSet | java.util.ImmutableCollections$AbstractImmutableSet |
| Collection | java.util.Collection |
| Iterable | java.lang.Iterable |
| Serializable | java.io.Serializable |
| Set | java.util.Set |
| Set12 | java.util.ImmutableCollections$Set12 |
| SetN | java.util.ImmutableCollections$SetN |
例7: 生成java.util.JumboEnumSet和java.util.RegularEnumSet的類圖
當(dāng)我們調(diào)用 EnumSet.of(...) 方法時(shí),會得到以下兩個(gè)類的實(shí)例
java.util.JumboEnumSetjava.util.RegularEnumSet
請運(yùn)行以下命令以生成對應(yīng)的內(nèi)容
java ClassDiagramGenerator 'java.util.JumboEnumSet' 'java.util.RegularEnumSet'
運(yùn)行結(jié)果是 Markdown 格式的,展示如下
運(yùn)行結(jié)果

| 在上圖中的類名/接口名 | 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 |
例8: 生成ThreadPoolExecutor的類圖
請運(yùn)行以下命令以生成對應(yīng)的內(nèi)容
java ClassDiagramGenerator 'java.util.concurrent.ThreadPoolExecutor'
運(yùn)行結(jié)果是 Markdown 格式的,展示如下
運(yùn)行結(jié)果

| 在上圖中的類名/接口名 | 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 |
到此這篇關(guān)于一文詳解Java如何自動生成簡單的Mermaid類圖的文章就介紹到這了,更多相關(guān)Java生成Mermaid類圖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java利用for循環(huán)輸出空心菱形的實(shí)例代碼
這篇文章主要介紹了Java利用for循環(huán)輸出空心菱形的實(shí)例代碼,需要的朋友可以參考下2014-02-02
SpringBoot項(xiàng)目部署到阿里云服務(wù)器的實(shí)現(xiàn)步驟
本文主要介紹了SpringBoot項(xiàng)目部署到阿里云服務(wù)器的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
利用Spring Data MongoDB持久化文檔數(shù)據(jù)的方法教程
這篇文章主要給大家介紹了關(guān)于利用Spring Data MongoDB持久化文檔數(shù)據(jù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考借鑒,下面來一起看看吧。2017-08-08
基于spring boot 2和shiro實(shí)現(xiàn)身份驗(yàn)證案例
這篇文章主要介紹了基于spring boot 2和shiro實(shí)現(xiàn)身份驗(yàn)證案例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
LeetCode?動態(tài)規(guī)劃之矩陣區(qū)域和詳情
這篇文章主要介紹了LeetCode?動態(tài)規(guī)劃之矩陣區(qū)域和詳情,文章基于Java的相關(guān)資料展開對LeetCode?動態(tài)規(guī)劃的詳細(xì)介紹,需要的小伙伴可以參考一下2022-04-04
Java實(shí)戰(zhàn)個(gè)人博客系統(tǒng)的實(shí)現(xiàn)流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+springboot+mybatis+redis+vue+elementui+Mysql實(shí)現(xiàn)一個(gè)個(gè)人博客系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2022-01-01
SpringBoot?+?proguard+maven多模塊實(shí)現(xiàn)代碼混淆的方法
這篇文章主要介紹了SpringBoot?+?proguard+maven多模塊實(shí)現(xiàn)代碼混淆的方法,多模塊跟單模塊一樣,在需要混淆模塊的pom文件中加入proguard依賴及配置,本文給大家講解的非常詳細(xì),感興趣的朋友一起看看吧2024-02-02

