JavaDoc 编程,书写自定义的 Doclet, 定制输出

前一篇介绍了 JavaDoc 编程,书写自定义的 Taglet 支持 @unmi 等, 那时提到了 Doclet,但是差点无视了 Doclet,现在才知道 Doclet 真是太强大了,有了它你会觉得 javadoc 已经不是原本的那个 javadoc 了,别再把 javadoc 看作只会生成一大堆 HTML 文件的工具了。尤其是搭配上自定义的 Taglet,那可是,自已体验吧。


Doclet 是 JavaDoc 的一个很隆重的扩展点,可以在执行 javadoc 时用 -doclet 来指定自己的 Doclet,那么 doclet 可以为我们做些什么呢?

可以为我们生成 HTML 的 JavaDoc API 文档,这就是默认的 com.sun.tools.doclets.standard.Standard 为我们做的事,还可以像以前那样从源文件中抽取信息生成各种 XML 文件,或是 PDF, Excel, UML 图等等任何可能的内容,或做任何有作为的事情。总之在 doclet 中可以感知道对任何包,类,方法,字段等的遍历。这里 Doclet.com 有大量的第三方的 doclet 供你选择,如:

AntDoclet, API Guide Doclet, EJBGen, Java2Rose Doclet, JDiff, JUnitDoclet, LaTeXtaglet, PDFDoclet, PublishedApiDoclet, ServletDoclet, Spell Check Doclet, UMLGraph, VelocityDoclet, XDoclet, xml-doclet 等数十种 Doclet, 还可以找到别的,所以你要是不想自定义 Doclet 的话,有第三方可用就直接用人家的就行。

总有特殊需求的时候,总有要自定义 Doclet 的时候。说那么多总要来看看 Doclet 可以用来做什么,见如下 DocumentDoclet:
 1package cc.unmi;
 2
 3import com.sun.javadoc.ClassDoc;
 4import com.sun.javadoc.DocErrorReporter;
 5import com.sun.javadoc.Doclet;
 6import com.sun.javadoc.MethodDoc;
 7import com.sun.javadoc.RootDoc;
 8import com.sun.javadoc.Tag;
 9import com.sun.tools.doclets.formats.html.HtmlDoclet;
10import com.sun.tools.doclets.standard.Standard;
11
12/**
13 * Customized Doclet DocumentDoclet
14 * @author Unmi
15 *
16 */
17public class DocumentDoclet extends Doclet{
18
19    public static boolean start(RootDoc root) {
20        doityouself(root.classes());
21        return true;
22        //return HtmlDoclet.start(root);
23    }
24
25    public static boolean validOptions(String[][] paramArrayOfString, DocErrorReporter paramDocErrorReporter) {
26        System.out.println("Print arguments:");
27        for (String[] strings : paramArrayOfString) {
28            System.out.println("  "+strings[0] + "->" + strings[1]);
29        }
30        System.out.println();
31        return Standard.validOptions(paramArrayOfString, paramDocErrorReporter);
32    }
33
34    private static void doityouself(ClassDoc[] classes) {
35        for (int i = 0; i < classes.length; i++) {
36            MethodDoc[] methods = classes[i].methods();
37            for (int j = 0; j < methods.length; j++) {
38                //add comments
39                methods[j].setRawCommentText("Add method comment text\n" +
40                        methods[j].getRawCommentText() + "\n@document new document annotation");
41               
42                Tag[] tags = methods[j].tags("myTag");
43                if (tags.length > 0) {
44                    System.out.println("\n" + classes[i].name() + ", Comment: " + classes[i].commentText());
45                    System.out.println("  " + methods[j].name());
46                    for (int k = 0; k < tags.length; k++) {
47                        System.out.println("     " + tags[k].name() + ": " + tags[k].text());
48                    }
49                }
50                
51                Tag[] documentTags = methods[j].tags("document");
52                if (tags.length > 0) {
53                    System.out.println("\n@document: " + documentTags[0].text());
54                }
55            }
56        }
57    }
58}

你可以让你的 Doclet 继承自 com.sun.javadoc.Doclet,也未强求,关键是要写上前面的 start(RootDoc) 方法,JavaDoc 会调用这个方法,此方法中可以获取到所有的与文档注释相关的信息。在 validOptions 方法中可对注释信息进行校验,其中可以得到传入的所有参数信息。

因为它用到了  tools.jar 中的类,所以编译的时候需要引入 JDK 目录中的 tools.jar 包。

用于测试的代码 TestJavaDoclet.java:
 1/**
 2 * Test class for DocumentDoclet
 3 * @author Unmi
 4 */
 5public class TestJavaDoclet {
 6
 7    /**
 8     * Main method of TestJavaDoclet
 9     * @myTag myTag value
10     */
11    public static void main(String[] args) {
12        // TODO Auto-generated method stub
13
14    }
15}

假设编译出的 DocumentDoclet 生成在 c:/classes/cc/unmi/DocumentDoclet.class,现在命令行进到 TestJavaDoclet.java 所在的目录执行:

javadoc -doclet cc.unmi.DocumentDoclet -docletpath c:/classes *.java

控制台会输出:

Print arguments:
  -doclet->cc.unmi.DocumentDoclet
  -docletpath->bin
  -sourcepath->srcLoading source file src/TestJavaDoclet.java...
Constructing Javadoc information...TestJavaDoclet, Comment: Test class for DocumentDoclet
  main
     @myTag: myTag value@document: new document annotation

从上面的输出我们可以看到自己的 DocumentDoclet 知道 javadoc 命令传入的参数,也可获知每一个包,类,方法,字段的注释信息,并且可以通过 setRawCommentText() 方法来动态的修改注释信息。同时你应该找不到  javadoc api 的 HTML 帮助文件了,对了,因为它们根本就没有生成,这似乎不像原来的 javadoc 了。

这就是 Doclet 的价值所在,不再纯粹的生成 javadoc api 文档,你可把它挪作他用。这个 Doclet 为什么没有生成那些 HTML 文件呢?因为其实生成 HTML 文件是由默认的 com.sun.tools.doclets.standard.Standard 干的,而今用了自己的 DocumentDoclet,故而不在生成那些 HTML 文件了。

谁曾说过 javadoc 命令就是用来生成 JavaDoc API 的 HTML 参考文档的呢?

如果仍想那些 HTML 文件照样的产生出来,可以在 DocumentDoclet.start(Rootdoc root) 方法中不直接 return true, 而是

return HtmlDoclet.start(root)

return Standard.start(root) 都可以。

经历过了上面的代码的理解,我们就可以在 start(RootDoc root) 中做更多的事情了,收集源文件中的注释信息生成任何你想要的文档格式文件都行。用了上面其中的一个 return 方法,再看下生成的 API 文档中的显示:


在 DocumentDoclet 中新加的注释信息也生成到了 API 文档中了。

接着再来看下自定义的 DocumentDoclet 与 Taglet 怎么结果使用,比如前面在 DocumentDoclet 中加上的 @document new document annotation 内容并未出现在 API 文档中,那我们试着用命令:

javadoc -doclet cc.unmi.DocumentDoclet -docletpath c:/classes -tag document:a:Document *.java

提示错误:  javadoc: error - invalid flag: -tag , 此路不通!

因为用 javadoc -help 命令看下就发现 -tag 参数是 Provided by Standard doclet: , 由 com.sun.tools.doclets.standard.Standard这个默认的 Doclet 提供了。

于是我们让前面的 DocumentDoclet 继承自 com.sun.tools.doclets.standard.Standard 后,再执行前面的命令,正常工作,并且新加的 @document 的内容反映在了 API 文档中了:


再进一步深入,如果我们使用前文 JavaDoc 编程,书写自定义的 Taglet 支持 @unmi 等 的 DocumentTaglet 的话,需要执行命令:

javadoc -doclet cc.unmi.DocumentDoclet -docletpath c:/classes -taglet cc.unmi.DocumentTaglet -tagletpath c:/classes *.java


见红了,也就说明了前面自定义的 DocumentTaglet 应用上了,注意的是我们的 DocumentDoclet 仍然需要继承自 Standard 类,否则还是会报 -taglet 参数无效。

再加几个说明:

1. DocumentDoclet 比 DocumentTaglet 要更早执行
2. DocumentDoclet 中可以获取到执行 javadoc 命令时所携带的参数,这好像 DocumentTaglet 做不到的
3. 虽然这里的 DocumentDoclet 和 DocumentTaglet 是由 javadoc 发起来同一个 JVM 中执行,但它们却由不同的类加载器加载的,这两类加载器有共同的父类。所以它们两的类实例是不同的,具体表现就是在 DocumentDoclet 中对某个类(如 Helper) 的静态属性所赋的值对于 DocumentTaglet 是不可见的。除非都把 Helper 类委托给他们的父类加载器加载,虽把 Helper 放在父类加载器的 classpath 下。

参考: 1. Code generation using Javadoc
           2. Doclet Overview
           3. Doclava: Custom Javadoc Doclet from Google
           4. Create a Taglet to document database access (Javadoc) 永久链接 https://yanbin.blog/javadoc-customize-doclet/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。