javadoc:jdk 9通过javadoc API读取java源码中的注释信息(comment)

几年前写过一博客:《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)
AnnotatedTypejavax.lang.model.type.TypeMirror
AnnotationDescjavax.lang.model.element.AnnotationMirror
AnnotationDesc.ElementValuePairjavax.lang.model.element.AnnotationValue
AnnotationTypeDocjavax.lang.model.element.TypeElement
AnnotationTypeElementDocjavax.lang.model.element.ExecutableElement
AnnotationValuejavax.lang.model.element.AnnotationValue
ClassDocjavax.lang.model.element.TypeElement
ConstructorDocjavax.lang.model.element.ExecutableElement
Docjavax.lang.model.element.Element
DocErrorReporterjdk.javadoc.doclet.Reporter
Docletjdk.javadoc.doclet.Doclet
ExecutableMemberDocjavax.lang.model.element.ExecutableElement
FieldDocjavax.lang.model.element.VariableElement
LanguageVersionjavax.lang.model.SourceVersion
MemberDocjavax.lang.model.element.Element
MethodDocjavax.lang.model.element.ExecutableElement
PackageDocjavax.lang.model.element.PackageElement
Parameterjavax.lang.model.element.VariableElement
ParameterizedTypejavax.lang.model.type.DeclaredType
ParamTagcom.sun.source.doctree.ParamTree
ProgramElementDocjavax.lang.model.element.Element
RootDocjdk.javadoc.doclet.DocletEnvironment
SeeTagcom.sun.source.doctree.LinkTree
com.sun.source.doctree.SeeTree
SerialFieldTagcom.sun.source.doctree.SerialFieldTree
SourcePositioncom.sun.source.util.SourcePositions
Tagcom.sun.source.doctree.DocTree
ThrowsTagcom.sun.source.doctree.ThrowsTree
Typejavax.lang.model.type.TypeMirror
TypeVariablejavax.lang.model.type.TypeVariable
WildcardTypejavax.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中的mainexecute静态方法。
在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》

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值