更新說明:
2021-10-14 增加文字說明,增加注意事項說明
剛剛上次弄完了一個坑爹的任務,這次我領導又給我一個讓人腦瓜子疼的任務了。
基本上客戶他在驗收我們系統的時候,都會要求我們編寫相關的文檔,這次也不例外。
只是這次的客戶要求我們給出接口文檔。不僅是要整個controller的rest接口文檔,還要給出service層的接口,連工具類都要有。這不是在為難我胖虎么。
想我辛辛苦苦給全部controller方法都加上了swagger注解,想我辛辛苦苦把整個項目全部方法都加上了完美的javadoc注釋,想我關鍵代碼都留了邏輯注釋,
為什么還要我給你弄個word文檔。
抱怨歸抱怨,該干的活還是要干的,誰讓這該死的錢真香。
但是要怎么弄呢?這是個很嚴重的問題,畢竟我的項目方法沒一千都有一百,總不能一個個給他Ctrl C + Ctrl V吧,那我的弄到何年何月。
swagger的我可以找工具導出成word文檔,可是service層的呢?我的工具類呢?
有沒有現成的工具呢?問下度娘,~~~~~~~~~成功摸魚半天,毫無收獲
那能不能像上次那樣用正則自己搞呢?試一哈 ~~~~~~~~~~~~~ 成功摸魚半天,失敗了,有些代碼的文檔注釋太坑爹了,方法內用文檔注釋,搞事情吧,怎么還有部分前面的*號都沒了。
idea不是可以導出javadoc文檔么?能不能導出word呢?試一哈 ~~~~~~~~~~~~成功摸魚半天,毫無辦法,不過我找到了一個神奇的方法 com.sun.tools.javadoc.Main.execute(initAgrs); 有沒有覺得很眼熟,沒錯,這就是jdk自己的javadoc工具。
先讓我們來膜拜下前輩的文章:
https://blog.csdn.net/10km/article/details/78252586
原理其實很簡單,javadoc 這個jdk的 tools.jar 中的包的類已經幫我們解析好了java文件,我們需要做的僅僅是重寫一個Doclet,然后在它里面處理下javadoc給我弄好的RootDoc就行,剩下就只剩操作RootDoc它了。
先看下javadoc這個包先

其中我們將直接用到com.sun.tools.javadoc.Main這個類

那我們需要做啥呢?最簡單的就3步。
1、創建一個新項目。
2、寫一個自己的Doclet類。

3、寫個main方法,里面調用com.sun.tools.javadoc.Main.execute()這個方法,傳入參數

4、拿到數據后(也就是RootDoc對象),自己解析class類型、方法名、類名、方法注釋、類注釋、字段注釋、參數注釋............然后愛生成HTML、愛生成Word、愛生成Excel,隨你們喜歡(可以在MyDoclet.start()方法里面編寫導出功能)。
大概怎么用說完了,展示下我的生成后的效果吧,我沒調樣式,畢竟用poi操作word真的是難受

上代碼:
項目結構:(一定要有tools.jar,他在你們jdk的lib目錄里,另外POI操作word的相關包要導齊全,不然各種坑)

pom.xml主要就是為了引入POI的包
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.hongcheng</groupId> <artifactId>javadoc_generator</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>javadoc_generator</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.0.0</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-scratchpad</artifactId> <version>4.0.0</version> </dependency> </dependencies> </project>
ClassComment.java用來保存類數據的實體
package com.hongcheng.javadoc_generator.entity; import java.util.List; /** * java類的相關信息 */ public class ClassComment { /** 類的全類名 */ private String className; /** 類的簡單類名 */ private String simpleClassName; /** 類注釋 */ private String classComment; /** 字段相關信息 */ private List<FieldComment> fields; /** 方法相關信息 */ private List<MethodComment> methods; public String getClassName() { return className; } public String getSimpleClassName() { return simpleClassName; } public String getClassComment() { return classComment; } public List<FieldComment> getFields() { return fields; } public List<MethodComment> getMethods() { return methods; } public void setClassName(String className) { this.className = className; } public void setSimpleClassName(String simpleClassName) { this.simpleClassName = simpleClassName; } public void setClassComment(String classComment) { this.classComment = classComment; } public void setFields(List<FieldComment> fields) { this.fields = fields; } public void setMethods(List<MethodComment> methods) { this.methods = methods; } @Override public String toString() { return "{className: " + className + ", simpleClassName: " + simpleClassName + ", classComment: " + classComment + ", fields: " + fields + ", methods: " + methods + "}"; } }
FieldComment.java用來保存字段和參數的實體
package com.hongcheng.javadoc_generator.entity; /** * java類中字段的相關信息 */ public class FieldComment { /** 字段類型 */ private String clasz; /** 類的簡單類名 */ private String simpleClassName; /** 字段注釋 */ private String fieldComment; /** 字段名 */ private String fieldName; /** 默認值,必須是final修飾的基本數據類型及其包裝類 */ private Object defaultValue; public String getSimpleClassName() { return simpleClassName; } public void setSimpleClassName(String simpleClassName) { this.simpleClassName = simpleClassName; } public String getClasz() { return clasz; } public String getFieldComment() { return fieldComment; } public String getFieldName() { return fieldName; } public Object getDefaultValue() { return defaultValue; } public void setClasz(String clasz) { this.clasz = clasz; } public void setFieldComment(String fieldComment) { this.fieldComment = fieldComment; } public void setFieldName(String fieldName) { this.fieldName = fieldName; } public void setDefaultValue(Object defaultValue) { this.defaultValue = defaultValue; } @Override public String toString() { return "{clasz: " + clasz + ", simpleClassName: " + simpleClassName + ", fieldComment: " + fieldComment + ", fieldName: " + fieldName + ", defaultValue: " + defaultValue + "}"; } }
MethodComment.java用來保存方法數據的實體
package com.hongcheng.javadoc_generator.entity; import java.util.List; /** * java類中方法的相關信息 */ public class MethodComment { /** 方法注釋 */ private String methodComment; /** 方法名 */ private String methodName; /** 參數 */ private List<FieldComment> params; /** 返回值 */ private FieldComment returnEntity; public String getMethodComment() { return methodComment; } public String getMethodName() { return methodName; } public List<FieldComment> getParams() { return params; } public FieldComment getReturnEntity() { return returnEntity; } public void setMethodComment(String methodComment) { this.methodComment = methodComment; } public void setMethodName(String methodName) { this.methodName = methodName; } public void setParams(List<FieldComment> params) { this.params = params; } public void setReturnEntity(FieldComment returnEntity) { this.returnEntity = returnEntity; } @Override public String toString() { return "{methodComment: " + methodComment + ", methodName: " + methodName + ", params: " + params + ", returnEntity: " + returnEntity + "}"; } }
MyDoclet.java很關鍵的類
package com.hongcheng.javadoc_generator; import com.sun.javadoc.Doclet; import com.sun.javadoc.RootDoc; /** * 用來獲取javadoc解析完成后生成的語法樹根節點 * */ public class MyDoclet extends Doclet { /** * 靜態對象,用於接收javadoc解析完成后生成的語法樹根節點<br> * 在后面我們會用他來獲取我們需要的數據 * */ private static RootDoc root; /** * 在javadoc解析完java文件后,生成語法樹,然后就會調用這個方法去讓Doclet生成doc文檔 * */ public static boolean start(RootDoc rootDoc) { MyDoclet.root = rootDoc; return true; } /** * 獲取語法樹的根節點 * */ public static RootDoc getRoot() { return root; } }
Modifier.java用來處理下哪些方法和字段才是我們需要的
package com.hongcheng.javadoc_generator; /** * 可見性修飾符 * */ public enum Modifier{ PUBLIC,PROTECTED,PRIVATE; }
RootClassParser.java用來解析RootDoc,轉成我們自己的那三個實體類
package com.hongcheng.javadoc_generator; import java.util.LinkedList; import java.util.List; import com.hongcheng.javadoc_generator.entity.ClassComment; import com.hongcheng.javadoc_generator.entity.FieldComment; import com.hongcheng.javadoc_generator.entity.MethodComment; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.FieldDoc; import com.sun.javadoc.MethodDoc; import com.sun.javadoc.ParamTag; import com.sun.javadoc.Parameter; import com.sun.javadoc.RootDoc; import com.sun.javadoc.Tag; /** * RootClass對象的解析器,用於根據RootClass構建我們自己的ClassComment * */ public class RootClassParser { /** 需要處理的類字段可見性,默認公有 */ private Modifier fieldModifier = Modifier.PUBLIC; /** 需要處理的類方法可見性,默認公有 */ private Modifier methodModifier = Modifier.PUBLIC; public RootClassParser(Modifier fieldModifier,Modifier methodModifier) { this.fieldModifier = fieldModifier; this.methodModifier = methodModifier; } /** * 解析 * */ public List<ClassComment> parse(RootDoc root) { if(root == null) { return new LinkedList<ClassComment>(); } List<ClassComment> classComments = new LinkedList<ClassComment>(); ClassDoc[] classes = root.classes(); for (ClassDoc clasz:classes) { ClassComment classComment = new ClassComment(); classComment.setClassName(clasz.qualifiedTypeName()); classComment.setSimpleClassName(clasz.simpleTypeName()); classComment.setClassComment(clasz.commentText()); classComment.setFields( this.parseFields(clasz.fields())); classComment.setMethods( this.parseMethods(clasz.methods())); classComments.add(classComment); } return classComments; } /** * 解析字段 * */ private List<FieldComment> parseFields(FieldDoc[] fields){ if(fields == null || fields.length <= 0) { return new LinkedList<FieldComment>(); } List<FieldComment> fieldList = new LinkedList<FieldComment>(); for (FieldDoc field : fields) { if(!this.checkModifier(field)) { continue; } FieldComment fieldComment = new FieldComment(); fieldList.add(fieldComment); fieldComment.setClasz(field.type().qualifiedTypeName()); fieldComment.setSimpleClassName(field.type().simpleTypeName()); fieldComment.setFieldComment(field.commentText()); fieldComment.setFieldName(field.name()); fieldComment.setDefaultValue(field.constantValue()); } return fieldList; } /** * 檢查字段修飾語,也就是public、protected、private * @return 如果該字段的訪問權限修飾語滿足我們需要的級別,那就返回true * */ private boolean checkModifier(FieldDoc field) { if(this.getFieldModifier().toString().equalsIgnoreCase(field.modifiers())) { return true; } return false; } /** * 檢查方法修飾語,也就是public、protected、private * @return 如果該方法的訪問權限修飾語滿足我們需要的級別,那就返回true * */ private boolean checkModifier(MethodDoc method) { if(this.getMethodModifier().toString().equalsIgnoreCase(method.modifiers())) { return true; } return false; } /** * 解析方法 * */ private List<MethodComment> parseMethods(MethodDoc[] methods){ if(methods == null || methods.length <= 0) { return new LinkedList<MethodComment>(); } List<MethodComment> methodsList = new LinkedList<MethodComment>(); for (MethodDoc method : methods) { if(!this.checkModifier(method)) { continue; } MethodComment methodComment = new MethodComment(); methodsList.add(methodComment); methodComment.setMethodComment(method.commentText()); methodComment.setMethodName(method.name()); methodComment.setReturnEntity(this.parseMethodReturn(method)); methodComment.setParams(this.parseMethodParam(method)); } return methodsList; } /*** * 解析方法的返回值 * */ private FieldComment parseMethodReturn(MethodDoc method){ // 返回值 FieldComment returnEntity = new FieldComment(); returnEntity.setClasz(method.returnType().qualifiedTypeName()); returnEntity.setSimpleClassName(method.returnType().simpleTypeName()); for(Tag tag:method.tags()) { if(tag.name().equals("@return")) { returnEntity.setFieldComment(tag.text()); break; } } return returnEntity; } /*** * 解析方法的參數 * */ private List<FieldComment> parseMethodParam(MethodDoc method){ // 參數 List<FieldComment> params = new LinkedList<FieldComment>(); for(Parameter parameter:method.parameters()) { FieldComment param = new FieldComment(); param.setClasz(parameter.type().qualifiedTypeName()); param.setSimpleClassName(parameter.type().simpleTypeName()); param.setFieldName(parameter.name()); for(ParamTag paramTag :method.paramTags()) { if(paramTag.parameterName().equals(param.getFieldName())) { param.setFieldComment(paramTag.parameterComment());; break; } } params.add(param); } return params; } public Modifier getFieldModifier() { return fieldModifier; } public Modifier getMethodModifier() { return methodModifier; } public void setFieldModifier(Modifier fieldModifier) { this.fieldModifier = fieldModifier; } public void setMethodModifier(Modifier methodModifier) { this.methodModifier = methodModifier; } }
WordExport.java用來生成word文檔的
package com.hongcheng.javadoc_generator; import java.io.FileOutputStream; import java.util.List; import org.apache.poi.xwpf.usermodel.TableWidthType; import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.poi.xwpf.usermodel.XWPFParagraph; import org.apache.poi.xwpf.usermodel.XWPFTable; import org.apache.poi.xwpf.usermodel.XWPFTableCell; import org.apache.poi.xwpf.usermodel.XWPFTableRow; import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge; import com.hongcheng.javadoc_generator.entity.ClassComment; import com.hongcheng.javadoc_generator.entity.FieldComment; import com.hongcheng.javadoc_generator.entity.MethodComment; public class WordExport { public void export(List<ClassComment> result,String path) throws Exception { XWPFDocument xwpfDocument = this.newWord(); for(ClassComment classComment: result) { this.newParagraph(xwpfDocument, "類名:" + classComment.getClassName()); this.newParagraph(xwpfDocument, "說明:" + classComment.getClassComment()); // 字段 if(classComment.getFields() != null && !classComment.getFields().isEmpty()) { XWPFTable table = this.newTable(xwpfDocument, classComment.getFields().size() + 3, 5); this.mergeCell(table.getRow(0), 0, 4, "類名:" + classComment.getClassName()); this.mergeCell(table.getRow(1), 0, 4, "屬性:"); this.setTableRowText(table.getRow(2), 0, 4, "序號","參數類型","參數名","常量值","說明"); this.setCellWidth(table.getRow(2), 0, 4, "10%","22.5%","22.5%","22.5%","22.5%"); for(int i = 0,j = 3;i < classComment.getFields().size();i++,j++ ) { FieldComment field = classComment.getFields().get(i); this.setTableRowText(table.getRow(j), 0, 4, String.valueOf(i + 1) ,field.getSimpleClassName() ,field.getFieldName() ,field.getDefaultValue() == null?"":field.getDefaultValue().toString() ,field.getFieldComment()); } this.newBlankLine(xwpfDocument); this.newBlankLine(xwpfDocument); } // 方法 if(classComment.getMethods() != null && !classComment.getMethods().isEmpty()) { for(MethodComment method: classComment.getMethods()) { XWPFTable table = this.newTable(xwpfDocument, 3, 4); this.mergeCell(table.getRow(0), 0, 3, "類名:" + classComment.getClassName()); this.mergeCell(table.getRow(1), 0, 3, "方法名:" + method.getMethodName()); this.mergeCell(table.getRow(2), 0, 3, "方法說明:" + method.getMethodComment()); // 參數 if(method.getParams() == null || method.getParams().isEmpty()) { this.mergeCell(table.createRow(), 0, 3, "參數:無" ); }else { this.mergeCell(table.createRow(), 0, 3, "參數:" ); this.setTableRowText(table.createRow(), 0, 3, "序號","參數類型","參數名","說明"); this.setCellWidth(table.getRow(table.getRows().size()-1), 0, 3, "10%","25%","25%","40%"); for(int i = 0;i < method.getParams().size(); i++) { FieldComment field = method.getParams().get(i); this.setTableRowText(table.createRow(), 0, 3, String.valueOf(i + 1) ,field.getSimpleClassName() ,field.getFieldName() ,field.getFieldComment()); } } // 返回值 this.mergeCell(table.createRow(), 0, 3, "返回值:" + method.getReturnEntity().getSimpleClassName() + " " + method.getMethodComment()); this.newBlankLine(xwpfDocument); this.newBlankLine(xwpfDocument); } } this.newBlankLine(xwpfDocument); this.newBlankLine(xwpfDocument); this.newBlankLine(xwpfDocument); this.newBlankLine(xwpfDocument); } this.writeFile(xwpfDocument, path); } /** * 設置單元格寬度 * @param row * @param startCell 起始單元格下標,row的單元格下標從0開始 * @param endCell 結束單元格下標 * @param percentages 各個單元格的百分百大小,例如"25.5%" * */ private void setCellWidth(XWPFTableRow row,int startCell,int endCell,String ...percentages) { if(percentages == null || percentages.length <= 0) { throw new IllegalArgumentException("percentages不能為空"); } if((endCell - startCell + 1) > percentages.length) { throw new IllegalArgumentException("percentages的元素不夠"); } int i = 0; for(XWPFTableCell cell: row.getTableCells()) { cell.setWidth(String.valueOf(percentages[i++])); cell.setWidthType(TableWidthType.PCT); } } /** * 設置單元格寬度 * @param row * @param startCell 起始單元格下標,row的單元格下標從0開始 * @param endCell 結束單元格下標 * @param sizes 各個單元格的寬度大小 * */ @SuppressWarnings("unused") private void setCellWidth(XWPFTableRow row,int startCell,int endCell,int ...sizes) { if(sizes == null || sizes.length <= 0) { throw new IllegalArgumentException("sizes不能為空"); } if((endCell - startCell + 1) > sizes.length) { throw new IllegalArgumentException("sizes的元素不夠"); } int i = 0; for(XWPFTableCell cell: row.getTableCells()) { cell.setWidth(String.valueOf(sizes[i++])); cell.setWidthType(TableWidthType.DXA); } } /** * 跨行合並單元格 * @param table * @param startRow 起始行下標,table的行下標從0開始 * @param endRow 結束行下標 * @param startCell 行內起始單元格下標,row的單元格下標從0開始 * @param endCell 行內結束單元格下標 * @param text 合並后的單元格文本 * */ @SuppressWarnings("unused") private void mergeRow(XWPFTable table,int startRow,int endRow,int startCell,int endCell,String text) { List<XWPFTableRow> rows = table.getRows(); for (int j = startRow; j <= endRow; j++) { List<XWPFTableCell> tableCells = rows.get(j).getTableCells(); // 對每個單元格進行操作 for (int i = startCell; i <= endCell; i++) { //對單元格進行合並的時候,要標志單元格是否為起點,或者是否為繼續合並 if (i == startCell ) tableCells.get(i).getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.RESTART); else tableCells.get(i).getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE);//繼續合並 } } for (int j = startRow; j <= endRow; j++) { List<XWPFTableCell> tableCells = rows.get(j).getTableCells(); // 對每個單元格進行操作 //對單元格進行合並的時候,要標志單元格是否為起點,或者是否為繼續合並 if (j == startRow ) tableCells.get(startCell).getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.RESTART); else tableCells.get(startCell).getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.CONTINUE);//繼續合並 } rows.get(startRow).getCell(startCell).setText(text);//為第1行1到4合並之后的單元格設置內容 } /** * 合並表格單元格,針對行內的單元格進行合並 * @param row * @param startCell 起始單元格下標,row的單元格下標從0開始 * @param endCell 結束單元格下標 * @param text 合並后的單元格文本 * */ private void mergeCell(XWPFTableRow row,int startCell,int endCell,String text) { List<XWPFTableCell> tableCells = row.getTableCells(); // 對每個單元格進行操作 for (int i = startCell; i <= endCell; i++) { //對單元格進行合並的時候,要標志單元格是否為起點,或者是否為繼續合並 if (i == startCell) tableCells.get(i).getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.RESTART); else tableCells.get(i).getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE);//繼續合並 } tableCells.get(startCell).setText(text);//為第1行1到4合並之后的單元格設置內容 } /** * 給表格一行賦值,實際設置值是包括首尾單元格的,例如startCell=0,endCell=2,實際會設置0、1、2這三個單元格 * @param row * @param startCell 起始單元格下標,row的單元格下標從0開始 * @param endCell 結束單元格下標 * @param texts 單元格的內容,依次賦值 * */ private void setTableRowText(XWPFTableRow row,int startCell,int endCell,String ...texts) { if(texts == null || texts.length <= 0) { throw new IllegalArgumentException("texts不能為空"); } if((endCell - startCell + 1) > texts.length) { throw new IllegalArgumentException("texts的元素不夠"); } List<XWPFTableCell> tableCells = row.getTableCells(); // 對每個單元格進行操作 for (int i = startCell,j = 0; i <= endCell; i++,j++) { tableCells.get(i).setText(texts[j]); } } /** * 創建一個table * @param xwpfDocument * @param rowNum 行數 * @param colNum 列數 * */ private XWPFTable newTable(XWPFDocument xwpfDocument,int rowNum,int colNum) { XWPFTable createTable = xwpfDocument.createTable(rowNum, colNum); createTable.setWidth("100%"); createTable.setWidthType(TableWidthType.PCT); return createTable; } /** * 創建一個文本行 * */ private XWPFParagraph newParagraph(XWPFDocument xwpfDocument,String text) { XWPFParagraph createParagraph = xwpfDocument.createParagraph(); createParagraph.createRun().setText(text); return createParagraph; } /** * 創建一個空行 * */ private XWPFParagraph newBlankLine(XWPFDocument xwpfDocument) { return this.newParagraph(xwpfDocument, ""); } /** * 創建一個word文檔 * */ private XWPFDocument newWord() { XWPFDocument xwpfDocument = new XWPFDocument(); return xwpfDocument; } /** * 寫文件 * */ private void writeFile(XWPFDocument xwpfDocument,String path) throws Exception { xwpfDocument.write(new FileOutputStream("C:\\Users\\HongCheng\\Desktop\\1.docx")); xwpfDocument.close(); } }
JavaDocReader.java用來驗證參數以及調用javadoc.Main類的
package com.hongcheng.javadoc_generator; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.hongcheng.javadoc_generator.entity.ClassComment; import com.sun.javadoc.RootDoc; /** * java文件doc讀取器 * */ public class JavaDocReader { /** 用於接收javadoc解析完成后生成的語法樹根節點 */ private MyDoclet doclet = new MyDoclet(); /** 類路徑,系統會在類路徑下去找一些相關的類文件 */ private List<String> classpath ; /** java源文件地址。可以是java源文件的絕對地址,也可以是包名。只能填寫一個, */ private String sourcepath; /** 是否遞歸處理子包,只有在sourcepath為包名時才起作用,subpackages不為空是才是true。默認false */ private boolean isSubpackages = false; /** 要遞歸的子包 */ private String subpackages ; /** 需要處理的類字段可見性,默認公有 */ private Modifier fieldModifier = Modifier.PUBLIC; /** 需要處理的類方法可見性,默認公有 */ private Modifier methodModifier = Modifier.PUBLIC; /** jdk的tools.jar的地址 */ private String jdkToolsJarPath = JavaDocReader.class.getResource("/").getPath() + "/tools.jar"; /** * 構造函數 * @param javaPackage 目標jar包,非空必填 * @param subpackages 需要遞歸處理的子包,可以為空 * @param classPath 相關的jar包的地址,絕對地址,javaPackage必須要能在這些路徑中找到,非空必填 * */ public JavaDocReader(String javaPackage,String subpackages,List<String> classPath) { this.init(javaPackage, subpackages, classPath, Modifier.PUBLIC, Modifier.PUBLIC); } /** * 構造函數 * @param javaPackage 目標jar包,非空必填 * @param subpackages 需要遞歸處理的子包,可以為空 * @param classPath 相關的jar包的地址,絕對地址,javaPackage必須要能在這些路徑中找到,非空必填 * @param fieldModifier 需要處理的類字段可見性,非空 * @param methodModifier 需要處理的類方法可見性,非空 * */ public JavaDocReader(String javaPackage,String subpackages,List<String> classPath ,Modifier fieldModifier,Modifier methodModifier) { this.init(javaPackage, subpackages, classPath, fieldModifier, methodModifier); } /** * 構造函數 * @param javaFilePath java文件地址,非空必填,絕對路徑 * @param classpath 源文件中引用的相關類的jar包地址,可選 * */ public JavaDocReader(String javaFilePath,List<String> classpath ) { this.init(javaFilePath, null, classpath, Modifier.PUBLIC, Modifier.PUBLIC); } /** * 構造函數 * @param javaFilePath java文件地址,非空必填,絕對路徑 * @param classpath 源文件中引用的相關類的jar包地址,可選 * @param fieldModifier 需要處理的類字段可見性,非空 * @param methodModifier 需要處理的類方法可見性,非空 * */ public JavaDocReader(String javaFilePath,List<String> classpath ,Modifier fieldModifier,Modifier methodModifier) { this.init(javaFilePath, null, classpath, fieldModifier, methodModifier); } /** * 構造函數 * @param sourcepath .java源文件地址,非空。可以是java源文件的絕對地址,也可以是包名。<br> * 如果是java源文件的絕對地址,只能填寫一個地址,不能填寫多個地址。<br> * 如果是包名,該包必須能在classpath下找到,只能填寫一個包名<br><br> * @param classpath 類路徑,系統會在類路徑下去找一些相關的類文件,可以為空 * @param subpackages 是否遞歸處理子包,只有在sourcepath為包名時才起作用,可以為空 * @param fieldModifier 需要處理的類字段可見性,非空 * @param methodModifier 需要處理的類方法可見性,非空 * */ private void init(String sourcepath,String subpackages,List<String> classpath ,Modifier fieldModifier,Modifier methodModifier) { this.checkNotEmpty(sourcepath, "目標java文件不能為空"); classpath = this.checkNotEmpty(classpath)?new LinkedList<String>(classpath):new LinkedList<String>(); classpath.add(this.getJdkToolsJarPath()); this.classpath = classpath; this.sourcepath = sourcepath; this.subpackages = subpackages; this.fieldModifier = fieldModifier == null?this.fieldModifier:fieldModifier; this.methodModifier = methodModifier == null?this.methodModifier:methodModifier; this.isSubpackages = this.checkNotEmpty(subpackages); } /** * 初始化參數 * @return String [] javadoc需要的參數數組 * */ private String [] initAgrs() { List<String> args = new LinkedList<String>(); args.add("-encoding"); args.add("utf-8"); args.add("-doclet"); args.add(MyDoclet.class.getName()); args.add("-docletpath"); args.add(MyDoclet.class.getResource("/").getPath()); if(this.isSubpackages()) { args.add("-subpackages"); args.add(this.getSubpackages()); } StringBuilder sb = new StringBuilder(); for(String classpath: this.getClasspath()) { sb.append(classpath).append(";"); } if(sb.length() > 0) { sb.deleteCharAt(sb.length() - 1); } args.add("-classpath"); args.add(sb.toString()); if(this.fieldModifier == Modifier.PRIVATE || this.methodModifier == Modifier.PRIVATE) { args.add("-private"); }else if(this.fieldModifier == Modifier.PROTECTED || this.methodModifier == Modifier.PROTECTED) { args.add("-protected"); }else { args.add("-public"); } args.add(this.sourcepath); return args.toArray(new String[args.size()]); } /** * 執行javadoc,解析源文件 * */ private void executeJavadoc() { String[] initAgrs = this.initAgrs(); com.sun.tools.javadoc.Main.execute(initAgrs); } /** * 獲取類注釋信息 * @return List<ClassComment> * */ public List<ClassComment> execute(){ this.executeJavadoc(); RootDoc root = MyDoclet.getRoot(); if(root == null) { return new LinkedList<ClassComment>(); } RootClassParser parser = new RootClassParser(this.getFieldModifier(),this.getMethodModifier()); List<ClassComment> parseResult = parser.parse(root); return parseResult; } public String getJdkToolsJarPath() { return jdkToolsJarPath; } public void setJdkToolsJarPath(String jdkToolsJarPath) { this.jdkToolsJarPath = jdkToolsJarPath; } public String getSubpackages() { return subpackages; } public void setSubpackages(String subpackages) { this.subpackages = subpackages; } public MyDoclet getDoclet() { return doclet; } public List<String> getClasspath() { return classpath; } public String getSourcepath() { return sourcepath; } public boolean isSubpackages() { return isSubpackages; } public Modifier getFieldModifier() { return fieldModifier; } public Modifier getMethodModifier() { return methodModifier; } public void setDoclet(MyDoclet doclet) { this.doclet = doclet; } public void setClasspath(List<String> classpath) { this.classpath = classpath; } public void setSourcepath(String sourcepath) { this.sourcepath = sourcepath; } public void setSubpackages(boolean isSubpackages) { this.isSubpackages = isSubpackages; } public void setFieldModifier(Modifier fieldModifier) { this.fieldModifier = fieldModifier; } public void setMethodModifier(Modifier methodModifier) { this.methodModifier = methodModifier; } @SuppressWarnings("rawtypes") private void checkNotEmpty(Object arg,String exceptionMsg) { if(exceptionMsg == null) { exceptionMsg = "參數不能為空。"; } if(arg == null) { throw new NullPointerException(exceptionMsg); } if(arg instanceof String) { String argStr = (String)arg; if(argStr.isEmpty()) { throw new IllegalArgumentException(exceptionMsg); } }else if(arg instanceof Collection) { Collection collection = (Collection)arg; if(collection.isEmpty()) { throw new IllegalArgumentException(exceptionMsg); } }else if(arg instanceof Map) { Map map = (Map)arg; if(map.isEmpty()) { throw new IllegalArgumentException(exceptionMsg); } } } @SuppressWarnings("rawtypes") private boolean checkNotEmpty(Object arg) { if(arg == null) { return false; } if(arg instanceof String) { String argStr = (String)arg; if(argStr.isEmpty()) { return false; } }else if(arg instanceof Collection) { Collection collection = (Collection)arg; if(collection.isEmpty()) { return false; } }else if(arg instanceof Map) { Map map = (Map)arg; if(map.isEmpty()) { return false; } } return true; } }
Test.java沒啥屁用的測試類,就是來看效果的
package com.hongcheng.javadoc_generator; import java.util.Collections; import java.util.List; import com.hongcheng.javadoc_generator.entity.ClassComment; public class Test { public static void main(String[] args) throws Exception { String jarPath = "C:\\Users\\HongCheng\\Desktop\\11.jar"; JavaDocReader javaDocReader = new JavaDocReader( "hongcheng.code_generator" ,"hongcheng.code_generator" ,Collections.singletonList(jarPath),Modifier.PRIVATE,Modifier.PUBLIC ); List<ClassComment> execute = javaDocReader.execute(); WordExport wordExport = new WordExport(); wordExport.export(execute,"C:\\Users\\HongCheng\\Desktop\\1.docx"); } }
這是javadoc的參數,你也可以自己設置

其實這個主要就是在代碼中使用javadoc這個工具,原理就是通過javadoc工具去解析目標類/jar,獲取到類信息和注釋信息后,在按照自己的格式去輸出
注意:
1、這個一般是你把你自己要導出文檔的項目打成jar包,再用這個工具進行處理,我沒試過把這個工具嵌入到當前項目中。
2、當然也可以指定一個java文件,但是它所依賴的一些jar你要加到classpath中。
3、打的jar包必須帶有java源碼,你可以打成源碼jar包。編譯后的jar是沒有注釋的。
4、這個的核心就是借助javadoc去解析java文件
代碼上傳碼雲了,相關jar包也在上面
https://gitee.com/1281003978/javadoc_generator
