javadoc导出成word文档

 更新说明:

    2021-10-14     增加文字说明,增加注意事项说明

     2022-10-16    增加详细原理说明和使用说明

 

 

刚刚上次弄完了一个坑爹的任务,这次我领导又给我一个让人脑瓜子疼的任务了。

 

基本上客户他在验收我们系统的时候,都会要求我们编写相关的文档,这次也不例外。

只是这次的客户要求我们给出接口文档。不仅是要整个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这个工具:

用法: javadoc [options] [packagenames] [sourcefiles] [@files]
  -overview <file>                 从 HTML 文件读取概览文档
  -public                          仅显示 public 类和成员
  -protected                       显示 protected/public 类和成员 (默认值)
  -package                         显示 package/protected/public 类和成员
  -private                         显示所有类和成员
  -help                            显示命令行选项并退出
  -doclet <class>                  通过替代 doclet 生成输出
  -docletpath <path>               指定查找 doclet 类文件的位置
  -sourcepath <pathlist>           指定查找源文件的位置
  -classpath <pathlist>            指定查找用户类文件的位置
  -cp <pathlist>                   指定查找用户类文件的位置
  -exclude <pkglist>               指定要排除的程序包列表
  -subpackages <subpkglist>        指定要递归加载的子程序包
  -breakiterator                   计算带有 BreakIterator 的第一个语句
  -bootclasspath <pathlist>        覆盖由引导类加载器所加载的
                                   类文件的位置
  -source <release>                提供与指定发行版的源兼容性
  -extdirs <dirlist>               覆盖所安装扩展的位置
  -verbose                         输出有关 Javadoc 正在执行的操作的信息
  -locale <name>                   要使用的区域设置, 例如 en_US 或 en_US_WIN
  -encoding <name>                 源文件编码名称
  -quiet                           不显示状态消息
  -J<flag>                         直接将 <flag> 传递到运行时系统
  -X                               输出非标准选项的提要

通过标准 doclet 提供:
  -d <directory>                   输出文件的目标目录
  -use                             创建类和程序包用法页面
  -version                         包含 @version 段
  -author                          包含 @author 段
  -docfilessubdirs                 递归复制文档文件子目录
  -splitindex                      将索引分为每个字母对应一个文件
  -windowtitle <text>              文档的浏览器窗口标题
  -doctitle <html-code>            包含概览页面的标题
  -header <html-code>              包含每个页面的页眉文本
  -footer <html-code>              包含每个页面的页脚文本
  -top    <html-code>              包含每个页面的顶部文本
  -bottom <html-code>              包含每个页面的底部文本
  -link <url>                      创建指向位于 <url> 的 javadoc 输出的链接
  -linkoffline <url> <url2>        利用位于 <url2> 的程序包列表链接至位于 <url>的文档
  -excludedocfilessubdir <name1>:.. 排除具有给定名称的所有文档文件子目录。
  -group <name> <p1>:<p2>..        在概览页面中, 将指定的程序包分组
  -nocomment                       不生成说明和标记, 只生成声明。
  -nodeprecated                    不包含 @deprecated 信息
  -noqualifier <name1>:<name2>:... 输出中不包括指定限定符的列表。
  -nosince                         不包含 @since 信息
  -notimestamp                     不包含隐藏时间戳
  -nodeprecatedlist                不生成已过时的列表
  -notree                          不生成类分层结构
  -noindex                         不生成索引
  -nohelp                          不生成帮助链接
  -nonavbar                        不生成导航栏
  -serialwarn                      生成有关 @serial 标记的警告
  -tag <name>:<locations>:<header> 指定单个参数定制标记
  -taglet                          要注册的 Taglet 的全限定名称
  -tagletpath                      Taglet 的路径
  -charset <charset>               用于跨平台查看生成的文档的字符集。
  -helpfile <file>                 包含帮助链接所链接到的文件
  -linksource                      以 HTML 格式生成源文件
  -sourcetab <tab length>          指定源中每个制表符占据的空格数
  -keywords                        使程序包, 类和成员信息附带 HTML 元标记
  -stylesheetfile <path>           用于更改生成文档的样式的文件
  -docencoding <name>              指定输出的字符编码

 

首先我们简单的找一个源文件的jar包,使用这个javadoc工具生成一个文档。

javadoc -encoding utf-8 -subpackages hongcheng.code_generator -classpath C:\Users\Administrator\Desktop\javadoc_generator-master\code_generator-sources.jar -d D:\dist
说明:
javadoc:jdk的bin目录中的javadoc.exe
-encoding urf-8:指定目标文件的编码
-subpackages hongcheng_code_generator:指定递归处理这个包里面所有.java文件
-classpath C:\xxxxxxxx\code_generator-sources.jar:要生成文档的目标文件,必须时源文件,里面都是.java文件,不能是时.class文件,因为class文件已经抹除了注释。
                              当然,有时候我们的jar文件依赖了很多其他的jar包,那么classpath就需要多个内容了,分号隔开就行,例如:-classpath 1.jar;2.jar
-d D:\dist:文档的目标输出目录

运行后你将会得到这么一堆东西:

 

 打开index.html,这就是一个标准的javadoc文档

 

 知道这个工具的使用后,我们就可以开始下面的学习了。

 

 我们先来认识一个java jdk中的jar包:tools.jar

这个依赖有很多功能,但是这次我们只用到javadoc这个包

 

 

 

 这个包给我们提供了一个通过代码来直接调用javadoc功能的接口,当然,就算没有,我们也可以通过java调用命令行的方式来运行javadoc.exe

 刚刚我们在cmd命令行中执行的那段命令,完全可以转到代码中来实现,效果是一模一样的。

    public static void main(String[] args) {
        com.sun.tools.javadoc.Main.execute(new String[]{"-encoding","utf-8"
                ,"-subpackages","hongcheng.code_generator"
                ,"-classpath","C:\\Users\\Administrator\\Desktop\\javadoc_generator-master\\1.jar"
                ,"-d","C:\\Users\\Administrator\\Desktop\\javadoc_generator-master\\1"});
    }

 

 但是现在最终生成了html文档,格式并不是我们想要的。

 前面我们看javadoc命令说明的时候,反复提到了doclet这个东西

  -doclet <class>                  通过替代 doclet 生成输出
  -docletpath <path>               指定查找 doclet 类文件的位置
通过标准 doclet 提供:
  -d <directory>                   输出文件的目标目录
  -use                             创建类和程序包用法页面
  -version                         包含 @version 段
  -author                          包含 @author 段
  -docfilessubdirs                 递归复制文档文件子目录
  -splitindex                      将索引分为每个字母对应一个文件
  -windowtitle <text>              文档的浏览器窗口标题
  -doctitle <html-code>            包含概览页面的标题
  -header <html-code>              包含每个页面的页眉文本
  -footer <html-code>              包含每个页面的页脚文本
  -top    <html-code>              包含每个页面的顶部文本
  -bottom <html-code>              包含每个页面的底部文本
  -link <url>                      创建指向位于 <url> 的 javadoc 输出的链接
  -linkoffline <url> <url2>        利用位于 <url2> 的程序包列表链接至位于 <url>的文档
  -excludedocfilessubdir <name1>:.. 排除具有给定名称的所有文档文件子目录。
  -group <name> <p1>:<p2>..        在概览页面中, 将指定的程序包分组
  -nocomment                       不生成说明和标记, 只生成声明。
  -nodeprecated                    不包含 @deprecated 信息
  -noqualifier <name1>:<name2>:... 输出中不包括指定限定符的列表。
  -nosince                         不包含 @since 信息
  -notimestamp                     不包含隐藏时间戳
  -nodeprecatedlist                不生成已过时的列表
  -notree                          不生成类分层结构
  -noindex                         不生成索引
  -nohelp                          不生成帮助链接
  -nonavbar                        不生成导航栏
  -serialwarn                      生成有关 @serial 标记的警告
  -tag <name>:<locations>:<header> 指定单个参数定制标记
  -taglet                          要注册的 Taglet 的全限定名称
  -tagletpath                      Taglet 的路径
  -charset <charset>               用于跨平台查看生成的文档的字符集。
  -helpfile <file>                 包含帮助链接所链接到的文件
  -linksource                      以 HTML 格式生成源文件
  -sourcetab <tab length>          指定源中每个制表符占据的空格数
  -keywords                        使程序包, 类和成员信息附带 HTML 元标记
  -stylesheetfile <path>           用于更改生成文档的样式的文件
  -docencoding <name>              指定输出的字符编码

 

 我们就可以猜测这doclet是不是用来控制html文档的。而且他也提供了参数来让我们自己选择一个doclet,那是不是意味着我们可以自己写一个新的doclet来控制生成的文档。

首先我们来看一看Doclet,这是个抽象类,不能直接用的,那他必定是有实现类的。

 

 

然而你会发现,压根就没有实现类。

不过你要是认真的找一下tools.jar,你会发现他其实就有一个doclet的包,并且有一个formats.html包,默认生成的文档又是html,你说这巧不巧。

 

 我们还找到一个Standard类,和doclet对比一下,就会发现一毛一样

 

 再认真看代码,他就是用了HtmlDoclet,如果还不信,你可以全部都打上断点,他百分百进来断点。

 

 每个方法我们都加一个断点,其实就只有start方法的参数还有点意义

 

认真看看RootDoc,就发现它里面有些不错的东西

 再继续深入下去,你就会发现,这玩意和我们常见的Class是差不多的,那套东西和反射的相差无几。

 

 重点是,他还有注释,那只要我们获取到了这个RootDoc,那剩下的内容不就可以随便我们自己搞了么!

 

 

 

 首先我们要自己建一个Doclet,因为start方法是javadoc的内部类去调用的,我们没法控制传参和获取返回值,所以必须找个地方存放我们需要的RootDoc。这里我直接用个静态对象来保存。

 

 

这个时候我们的测试代码就得做点修改了,加上我们自定义的Doclet

public static void main(String[] args) {
        com.sun.tools.javadoc.Main.execute(new String[]{"-encoding","utf-8"
                ,"-subpackages","hongcheng.code_generator"
                ,"-doclet", "com.hongcheng.javadoc_generator.MyDoclet"  // 这里加上我们的自定义Doclet
                ,"-classpath","C:\\Users\\Administrator\\Desktop\\javadoc_generator-master\\1.jar"});
        RootDoc root = MyDoclet.getRoot();
        System.out.println(root);
    }

 

我们已经获取到了想要的东西

 

 

 

 前面我们已经获取到了java文件解析结果类,剩下的其实就只剩怎么解析RootDoc,并且将其渲染成我们想要的展示方式而已。

 

 

再次总结,总共3步。

1、创建一个新项目。

2、写一个自己的Doclet类。

 

3、写个main方法,里面调用com.sun.tools.javadoc.Main.execute()这个方法,传入参数

 4、拿到数据后(也就是RootDoc对象),自己解析class类型、方法名、类名、方法注释、类注释、字段注释、参数注释............然后爱生成HTML、爱生成Word、爱生成Excel,随你们喜欢(可以在MyDoclet.start()方法里面编写导出功能)。

 

 

大概怎么用说完了,展示下我的生成后的效果吧,我没调样式,毕竟用poi操作word真的是难受

 

 

 下面的代码是我自己做了些封装的,你也可以按照你自己的想法来做,主要的原理还是通过自定义doclet来获取RootDoc,最后自己处理RootDoc。

主要是封装了三大部分:

1:封装了新的参数

2:将RootDoc解析成3个类,ClassComment -- 类注释,FieldComment -- 字段注释,MethodComment -- 方法注释

3:封装了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

 

posted @ 2020-05-26 22:59  _ME  阅读(3220)  评论(0)    收藏  举报