相信大家都用过 javadoc 命令或者 IDE 封装命令生成 java api doc 文档吧,但是你有没有反思过 javadoc 命令是怎么解析文件生成的呢?其实 javadoc 在 jdk 目录下只是一个可执行程序,但是这个可执行程序是基于 jdk 的 tools.jar 的一个封装,也就是说 javadoc 实现在 tools.jar 中。
很多时候我们可能会有一些奇葩的需求,譬如获取 java 文档注释进行搞事情处理,我们该怎样解析 java 文件去获取这些注释信息呢?你可能一开始想过使用正则匹配,但是这个方案其实是有兼容性问题的。或者说,你考虑过使用一些第三方库来解析 java 源码文件,但是这些库很多都是针对 java 源码的,而非源码中的注释。所以有一个超级棒的方案就是自定义 doclet,采用 javadoc 操作。
方案验证
既然说到这个方案依赖 javadoc 和 doclet,那就先去看看这方面的文档进行一下技术评估,具体参见 oracle 官方文档:
通过文档我们可以发现,其实我们只用自定义一个 Doclet 类就行了,至于怎么定义其实文档中已经写的很详细了,还给出了具体代码片段,我们可以直接搬过来进行验证即可,代码如下:
123456789101112131415 |
public class extends Doclet { public static boolean start(RootDoc root) { ClassDoc[] classes = root.classes(); return true; }} public static void main(String[] args) { String[] docArgs = new String[] { "-doclet", CustomerDoclet.class.getName(), "/home/yan/test/cn/test/JavaSource.java" }; com.sun.tools.javadoc.Main.execute(docArgs); } |
简单吧,运行上面代码段就能自定义 javadoc 输出解析了。跑了下发现没问题,那就开始搞事情吧。
实现一个 gradle 插件进行 javadoc 自定义操作
这里我们为了简单和直接说明核心,所以打算实现一个检查 android、androidLibrary、java、javaLibrary 代码源文件中是否包含 javadoc @author 的插件,插件名称 gradle-javadoc-checker,具体完整插件源码可以访问 https://github.com/yanbober/gradle-javadoc-checker 获取。
注意:这部分内容需要你先对 gradle 插件开发比较熟悉才能看懂,所以建议先掌握所说的知识后进行研读。
添加依赖
123456 |
dependencies { compile gradleApi() compile 'com.android.tools.build:gradle:3.1.0' //tools.jar 的依赖 compile files(org.gradle.internal.jvm.Jvm.current().toolsJar)} |
编写自定义 javadoc 判断 @author 工具
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192 |
public class JavaDocReader { private static RootDoc root; //自定义 doclet public static class { public static boolean start(RootDoc root) { JavaDocReader.root = root; return true; } } //tools.jar 中 javadoc 的封装 public static RootDoc process(String[] extraArges) { List<String> argsOrderList = new ArrayList<>(); argsOrderList.add("-doclet"); argsOrderList.add(CustomerDoclet.class.getName()); argsOrderList.addAll(Arrays.asList(extraArges)); String[] args = argsOrderList.toArray(new String[argsOrderList.size()]); System.out.println(args); Main.execute(args); return root; } //tools.jar 中 javadoc 的封装 public static void process(List<String> sourcePaths, List<String> javapackages, List<String> excludePackages, String outputDir) throws Exception { String paths = list2formatString(sourcePaths, ";"); String includes = list2formatString(javapackages, ":"); String excludes = list2formatString(excludePackages, ":"); List<String> argsOrderList = new ArrayList<>(); argsOrderList.add("-doclet"); argsOrderList.add(CustomerDoclet.class.getName()); if (paths != null && paths.length() > 0) { argsOrderList.add("-sourcepath"); argsOrderList.add(paths); } argsOrderList.add("-encoding"); argsOrderList.add("utf-8"); argsOrderList.add("-verbose"); if (includes != null && includes.length() > 0) { argsOrderList.add("-subpackages"); argsOrderList.add(includes); } if (excludes != null && excludes.length() > 0) { argsOrderList.add("-exclude"); argsOrderList.add(excludes); } String[] args = argsOrderList.toArray(new String[argsOrderList.size()]); System.out.println(Arrays.toString(args)); //执行 tools.jar 中的 javadoc 命令 Main.execute(args); File file = new File(outputDir); if (!file.exists()) { file.mkdirs(); } file = new File(file, new Date().toString() + ".txt"); FileOutputStream outputStream = new FileOutputStream(file); //判断每个顶级 java class 是否有编写 @author 人,没有就筛出来写入一个文件记录 ClassDoc[] classes = root.classes(); if (classes != null) { for (int i = 0; i < classes.length; ++i) { if (classes[i].containingClass() == null && classes[i].isPublic()) { Tag[] authorTags = classes[i].tags("author"); if (authorTags == null || authorTags.length == 0) { String filename = classes[i].position().file().getAbsolutePath(); outputStream.write((filename+"rn").getBytes()); } } } } root = null; outputStream.flush(); outputStream.close(); } 大专栏 JDK tools.jar 中 javadoc 自定义 doclet 的妙用 private static String list2formatString(List<String> srcs, String div) { StringBuilder stringBuilder = new StringBuilder(); for (int index=0; index<srcs.size(); index++) { if (index > 0) { stringBuilder.append(div); } stringBuilder.append(srcs.get(index)); } return stringBuilder.toString(); }} |
有了 javadoc 自定义工具类,接下来编写 gradle 自定义 task 即可。
编写自定义 gradle task 进行检查
123456789101112131415161718192021222324252627282930 |
//groovy 编写class JavaDocCheckerTask extends DefaultTask { //自定义 task 的输入 List<String> includePackages List<String> excludePackages List<String> sourcePaths //自定义 task 的输出 @OutputDirectory String outputDir //自定义 task 的执行逻辑 @TaskAction void checker() { if (sourcePaths == null || sourcePaths.size() == 0) { throw new GradleScriptException("JavaDocCheckerTask sourcePaths params can't be null or empty!") } if (outputDir == null || outputDir.length() == 0) { throw new GradleScriptException("JavaDocCheckerTask outputDir params can't be null or empty!") } //task 依据输出输出参数进行 javadoc 命令操作 JavaDocReader.process(sourcePaths, includePackages, excludePackages, outputDir) }} |
有了自定义 gradle task 进行 javadoc 操作,接下来就该接入插件了。
将自定义 task 加入构建 project
先定义插件的 extension 拓展参数:
1234567891011 |
class CheckerExtension { public static final String NAME = "javadocChecker" List<String> includePackages List<String> excludePackages List<String> sourcePaths String outputDirectory} |
将拓展参数与 task 结合:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677 |
class JavaDocCheckerPlugin implements Plugin<Project> { @Override void apply(Project project) { //插件添加自定义 extension project.extensions.create(CheckerExtension.NAME, CheckerExtension) //将自定义任务加入 project project.tasks.create("javaDocChecker", JavaDocCheckerTask) //依据 apply 的是 java、androidlibrary、androidapplication 分别获取对应的拓展参数 JavaPluginConvention java = null BaseExtension android = null if (project.plugins.hasPlugin(AppPlugin)) { android = project.extensions.getByType(AppExtension) } else if(project.plugins.hasPlugin(LibraryPlugin)) { android = project.extensions.getByType(LibraryExtension) } else if (project.plugins.hasPlugin(JavaPlugin)) { java = project.convention.getPlugin(JavaPluginConvention) } if (java == null && android == null) { throw new GradleException("it's a not support plugin type!") } project.afterEvaluate { afterEvaluateInner(project, java, android) } } private void afterEvaluateInner(Project project, JavaPluginConvention java, BaseExtension android) { if (java != null) { //java 插件就进行 java 的 sourceSets 处理 processJava(project, java) } else if (android != null) { //Android 插件就进行 android 的 sourceSets 处理 processAndroid(project, android) } } private void processJava(Project project, JavaPluginConvention java) { List<String> sources = new ArrayList<>() //拿到 java sourceSets main 的 src 进行检查处理 SourceSet mainSourceSet = java.sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME) mainSourceSet.allJava.srcDirs.each { sources.add(it.absolutePath) } assignedTask(project, sources) } private void processAndroid(Project project, BaseExtension android) { List<String> sources = new ArrayList<>() //拿到 android sourceSets main 的 src 进行检查处理 AndroidSourceSet mainSourceSet = android.sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME) mainSourceSet.java.srcDirs.each { sources.add(it.absolutePath) } assignedTask(project, sources) } //把插件 extension 的自定义属性赋值给自定义 task 的 input 和 output private void assignedTask(Project project, List<String> sources) { def checker = project[CheckerExtension.NAME] if (checker == null) { return } project.getTasksByName("javaDocChecker", false).each { it.configure { includePackages = checker.includePackages == null ? [] : checker.includePackages excludePackages = checker.excludePackages == null ? [] : checker.excludePackages sourcePaths = sources outputDir = checker.outputDirectory } } }} |
到此插件核心主体就开发完了,然后就可以使用了,这就是一个完整的通过自定义 javadoc 输出来解决实际问题的小项目,感兴趣可以访问项目源码进行研究,也可以自定义自己的操作。具体完整插件源码可以访问 https://github.com/yanbober/gradle-javadoc-checker 获取。
总结
本文给出了一个实现思路,你可以发现,doclet 简直就是一个巨无霸,对于 java doc 文档操作只有你想不到的,没有他做不到的。希望对你有所启发。
原文地址:https://www.cnblogs.com/liuzhongrong/p/11874896.html