几年前写过一博客:《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提供的类型对应表。
此表只能做为基本的参照,其实许多类型在没有完全等价的类型。
| Old Type(JDK8) | New Type(JDK9) | 
|---|---|
| AnnotatedType | javax.lang.model.type.TypeMirror | 
| AnnotationDesc | javax.lang.model.element.AnnotationMirror | 
| AnnotationDesc.ElementValuePair | javax.lang.model.element.AnnotationValue | 
| AnnotationTypeDoc | javax.lang.model.element.TypeElement | 
| AnnotationTypeElementDoc | javax.lang.model.element.ExecutableElement | 
| AnnotationValue | javax.lang.model.element.AnnotationValue | 
| ClassDoc | javax.lang.model.element.TypeElement | 
| ConstructorDoc | javax.lang.model.element.ExecutableElement | 
| Doc | javax.lang.model.element.Element | 
| DocErrorReporter | jdk.javadoc.doclet.Reporter | 
| Doclet | jdk.javadoc.doclet.Doclet | 
| ExecutableMemberDoc | javax.lang.model.element.ExecutableElement | 
| FieldDoc | javax.lang.model.element.VariableElement | 
| LanguageVersion | javax.lang.model.SourceVersion | 
| MemberDoc | javax.lang.model.element.Element | 
| MethodDoc | javax.lang.model.element.ExecutableElement | 
| PackageDoc | javax.lang.model.element.PackageElement | 
| Parameter | javax.lang.model.element.VariableElement | 
| ParameterizedType | javax.lang.model.type.DeclaredType | 
| ParamTag | com.sun.source.doctree.ParamTree | 
| ProgramElementDoc | javax.lang.model.element.Element | 
| RootDoc | jdk.javadoc.doclet.DocletEnvironment | 
| SeeTag | com.sun.source.doctree.LinkTreecom.sun.source.doctree.SeeTree | 
| SerialFieldTag | com.sun.source.doctree.SerialFieldTree | 
| SourcePosition | com.sun.source.util.SourcePositions | 
| Tag | com.sun.source.doctree.DocTree | 
| ThrowsTag | com.sun.source.doctree.ThrowsTree | 
| Type | javax.lang.model.type.TypeMirror | 
| TypeVariable | javax.lang.model.type.TypeVariable | 
| WildcardType | javax.lang.model.type.WildcardType | 
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
参考资料
《javadoc》
 《Package jdk.javadoc.doclet》










