几年前写过一博客:《java:通过javadoc API读取java源码中的注释信息(comment)》,简单介绍了通过javadoc API读取源码注释的流程。
那时还是用JDK 1.8。但是在JDK9环境下JDK 1.8的那一套API就不能用了。JDK 9提供了一套新的javadoc API实现注释代码的读取,即jdk.javadoc
模块。
本文说明如何基于jdk.javadoc
来读取源码的注释。
module-info.java
需要在你的项目module-info.java中如下添加jdk.javadoc
引用。
module you_module_name{
exports you.package.name;
requires transitive jdk.javadoc;
}
类型迁移
从JDK 1.8迁移到JDK 9是个挺麻烦的事儿,
下面列表出了JDK 1.8下javadoc API与JDK 9提供的类型对应表。
此表只能做为基本的参照,其实许多类型在没有完全等价的类型。
Doclet
JDK 1.8中Doclet的定义很随意,只要有一个start静态方法就可以了。如下:
public static class Doclet {
public Doclet() {
}
public static boolean start(RootDoc root) {
// 获取 javadoc根数据对象,从该根对象可以提取所有其他程序结构信息
JavaDocReader.root = root;
return true;
}
}
但JDK9中对doclet类型做接口化约束,要求doclet必须实现jdk.javadoc.doclet.Doclet
接口,示例如下:
public static class DocletExample implements jdk.javadoc.doclet.Doclet {
private DocletEnvironment docEnv;
@Override
public void init(Locale locale, Reporter reporter) {
}
@Override
public boolean run(DocletEnvironment docEnv) {
// 表示单次调用doclet的操作环境。此对象可用于访问命令行上的程序结构、各种实用程序和用户指定的元素。
// 对应与JDK 1.8下的RootDoc
this.docEnv = docEnv;
return true;
}
@Override
public String getName() {
return "DocletExample";
}
@Override
public Set<? extends Option> getSupportedOptions() {
return Set.of();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
}
JavadocTool
JDK 1.8下程序执行javadoc直接调用com.sun.tools.javadoc.Main
中的main
或execute
静态方法。
在JDK 9下com.sun.tools.javadoc.Main
已经不存在了。JDK 9对JDK 命令行工具进行了统一封装为ToolProvider
接口,需要使用ToolProvider机制获取JavadocToolProvider
执行javadoc.
/** 获取 javadoc tool */
ToolProvider javadocTool = ToolProvider.findFirst("javadoc").orElseThrow();
/** 执行javadoc */
javadocTool.run(System.out,System.err,new String[] {....});
实现代码
以下是基于jdk.javadoc
实现的读取源码注释的示例代码,实现读取指定的包下的所有源码注释,并输出每个类及类成员的注解。
import org.junit.Test;
import static org.junit.Assert.*;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.spi.ToolProvider;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic.Kind;
import com.sun.source.doctree.BlockTagTree;
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.util.DocTrees;
import jdk.javadoc.doclet.Doclet;
import jdk.javadoc.doclet.DocletEnvironment;
import jdk.javadoc.doclet.Reporter;
/**
* JDK 9的javadoc tool 调用测试
* @author guyadong
*
*/
public class JavadocToolTest {
@Test
public void testJavadocTool() {
try {
/** 获取 javadoc tool */
ToolProvider javadocTool = ToolProvider.findFirst("javadoc").orElseThrow();
int returnCode = javadocTool.run(System.out,System.err,new String[] {
/** 指定自定义的 Doclet 接口实现类(全名) */
"-doclet", DocletExample.class.getName(),
/** 指定-doclet选项定义类名的所在的类搜索路径 */
"-docletpath", DocletExample.class.getProtectionDomain().getCodeSource().getLocation().getPath(),
/** --subpackages 要获取注释的包名 */
"-subpackages", "net.gdface.utils",
/** --sourcepath 要源码路径 */
"-sourcepath","D:/j/common-java/common-base/src/main/java",
/** --classpath 指定javadoc执行时搜索引用类的路径 */
"-classpath","D:/j/common-java/common-base/target/classes",
"-encoding","utf-8"});
if(0 != returnCode){
System.out.printf("javadoc ERROR CODE = %d\n", returnCode);
throw new IllegalStateException();
}
} catch (Throwable e) {
e.printStackTrace();
fail();
}
}
public static class DocletExample implements Doclet {
private Reporter reporter;
private Elements elementUtils;
@Override
public void init(Locale locale, Reporter reporter) {
reporter.print(Kind.NOTE, "Doclet using locale: " + locale);
this.reporter = reporter;
}
/** 输出类成员的注释 */
public void printElement(DocTrees trees, Element e) {
DocCommentTree docCommentTree = trees.getDocCommentTree(e);
if (docCommentTree != null) {
reporter.print(Kind.NOTE, "Element " + e.getKind() + ": "
+ e);
// 输出对应的注解
reporter.print(Kind.NOTE,elementUtils.getDocComment(e));
}
}
@Override
public boolean run(DocletEnvironment docEnv) {
// get the DocTrees utility class to access document comments
DocTrees docTrees = docEnv.getDocTrees();
elementUtils = docEnv.getElementUtils();
// 循环调用printElement输出每个类成员的注释
for (TypeElement t : ElementFilter.typesIn(docEnv.getIncludedElements())) {
reporter.print(Kind.NOTE, t.getKind() + ":" + t.getQualifiedName());
reporter.print(Kind.NOTE, "getDocComment:"+elementUtils.getDocComment(t));
for (Element e : t.getEnclosedElements()) {
printElement(docTrees, e);
}
}
return true;
}
@Override
public String getName() {
return "DocletExample";
}
@Override
public Set<? extends Option> getSupportedOptions() {
Option[] options = {};
return Set.of(options);
}
@Override
public SourceVersion getSupportedSourceVersion() {
// support the latest release
return SourceVersion.latest();
}
}
}
完整代码
完整代码参见码云仓库:https://gitee.com/l0km/javadocreader9