一文詳解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)文章
SpringSecurity集成第三方登錄過程詳解(最新推薦)
在ThirdAuthenticationFilter 類的attemptAuthentication()方法中,我們通過authType類型,然后創(chuàng)建對應(yīng)的Authentication實(shí)現(xiàn)來實(shí)現(xiàn)不同方式的登錄,下面給大家分享SpringSecurity集成第三方登錄過程,感興趣的朋友一起看看吧2024-05-05
Java 確保某個(gè)Bean類被最后執(zhí)行的幾種實(shí)現(xiàn)方式
這篇文章主要介紹了Java 確保某個(gè)BeanDefinitionRegistryPostProcessor Bean被最后執(zhí)行的幾種實(shí)現(xiàn)方式,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下2021-03-03
SpringBoot中基于JWT的單token授權(quán)和續(xù)期方案步驟詳解
在前后端分離架構(gòu)中,用戶登錄后,后端生成JWT?Token返給前端存于LocalStorage,每次請求攜帶Token驗(yàn)證身份,后端校驗(yàn)其有效性,本文給大家介紹SpringBoot中基于JWT的單token授權(quán)和續(xù)期方案步驟詳解,感興趣的朋友一起看看吧2024-09-09
SpringBoot根據(jù)目錄結(jié)構(gòu)自動生成路由前綴的實(shí)現(xiàn)代碼
本文介紹如何根據(jù)目錄結(jié)構(gòu)給RequestMapping添加路由前綴,具體實(shí)現(xiàn)方法,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-08-08
MyBatis分頁插件PageHelper深度解析與實(shí)踐指南
在數(shù)據(jù)庫操作中,分頁查詢是最常見的需求之一,傳統(tǒng)的分頁方式通常有兩種內(nèi)存分頁和SQL分頁,MyBatis作為優(yōu)秀的ORM框架,本身并未提供統(tǒng)一的分頁解決方案,這正是PageHelper誕生的背景,下面小編給大家詳細(xì)說說MyBatis分頁插件PageHelper,需要的朋友可以參考下2025-05-05
Mybatis返回?cái)?shù)組的兩種實(shí)現(xiàn)方式
這篇文章主要介紹了Mybatis返回?cái)?shù)組的兩種實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03
Java數(shù)據(jù)庫連接池之proxool_動力節(jié)點(diǎn)Java學(xué)院整理
Proxool是一種Java數(shù)據(jù)庫連接池技術(shù)。方便易用,便于發(fā)現(xiàn)連接泄漏的情況2017-08-08

