JDK tools.jar 中 javadoc 自定义 doclet 的妙用

相信大家都用过 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

时间: 2024-10-14 06:31:39

JDK tools.jar 中 javadoc 自定义 doclet 的妙用的相关文章

【maven】解决Missing artifact jdk.tools:jdk.tools:jar:1.6

解决在pom.xml文件中出现的Missing artifact jdk.tools:jdk.tools:jar:1.6问题, <dependency> <groupId>jdk.tools</groupId> <artifactId>jdk.tools</artifactId> <version>1.7</version> <scope>system</scope> <systemPath&

Missing artifact jdk.tools:jdk.tools:jar:1.6

在eclipse中搭建CarbonData源码工程有时候会遇到Missing artifact jdk.tools:jdk.tools:jar:1.6的问题. 网上搜索一大堆都是在pom.xml中添加jdk.tools的依赖,这些典型指标不治本的.这个问题别人机器上没有,公司机器没有,就家里机器有而且pom.xml配置都一样的.所以这样的改法肯定不合理.有个建议非常好解决了我的问题: 在eclipse.ini文件中配置 -vm的jdk路径,如果不配置mvn会找到eclipse默认的jvm,找不到

Maven报错Missing artifact jdk.tools:jdk.tools:jar:1.7--转

原文地址:http://blog.csdn.net/u013281331/article/details/40824707 在Eclipse中检出Maven工程,一直报这个错:“Missing artifact jdk.tools:jdk.tools:jar:1.7” 看整个pom.xml文件也不见其他异常. 而tools.jar包是JDK自带的,于是怀疑pom.xml中以来的包隐式依赖tools.jar包,而tools.jar并未在库中, 好比:当前工程依赖A包,而A包在开发打包过程依赖too

解决:eclipse或STS运行maven工程出现Missing artifact jdk.tools:jdk.tools:jar:1.7问题

eclipse或STS运行maven工程出现Missing artifact jdk.tools:jdk.tools:jar:1.7问题 最近项目中使用到大数据平台,代码中应用了hbase-client.0.98.6-hadoop2.jar包,该包中引用了jdk.tools.1.7,所以导致eclipse中pom.xml老是提示Missing artifact jdk.tools:jdk.tools:jar:1.7.从网上找了一下原因大多都说是工程maven找不到系统内的jdk1.7,根本原因是

解决首次在eclipse中使用maven构建hadoop等项目时报Missing artifact sun.jdk:tools:jar:1.5.0的问题

问题原因: eclipse中的maven插件默认没有引用环境变量,所以找不到jdk的路径,也就找不到tool.jar. 解决办法: 步骤如下: 1.关闭eclips 2.在eclipse的解压目录中与eclipse启动图标相同的文件夹下找到eclipse.ini文件,在该文件的-vmargs上面加上 -vmC:\Program Files\Java\jdk1.7.0_79\jre\bin\server\jvm.dll 注意:第二行的C:\Program Files\Java\jdk1.7.0_7

Maven引入Hadoop依赖报错:Missing artifact jdk.tools:jdk.tools:jar:1.6

原因是缺少tools.jar的依赖,tools.jar在jdk的安装目录中提供了,所以改成如下形式解决此问题:添加依赖 <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-mapreduce-client-core</artifactId> <version>2.4.0</version> <exclusions> <e

maven 项目 missing jdk.tools.jar

jdk.tools:jdk.tools是与JDK一起分发的一个JAR文件, 可以如下方式加入到Maven项目中: <dependency>     <groupId>jdk.tools</groupId>     <artifactId>jdk.tools</artifactId>     <version>1.7</version>     <scope>system</scope>     &l

Maven搭建hadoop环境报Missing artifact jdk.tools:jdk.tools:jar:1.7(5种办法,2种正解)

刚刚写的那一篇,是网上比较主流的解决办法. 鉴于实际情况,有伙伴的机器上没有遇到这个问题,我们再探究原因,最终还有4种情况需要说明. 先说,另外一种"正解". <dependency> <groupId>org.apache.hbase</groupId> <artifactId>hbase-client</artifactId> <version>1.0.1.1</version> <exclu

Maven报错Missing artifact jdk.tools:jdk.tools:jar:1.7

1.eclipse中Maven项目的pom文件报错: 2.解决方法: 直接在pom.xml中加上一个依赖项目: <dependency>      <groupId>jdk.tools</groupId>      <artifactId>jdk.tools</artifactId>     <version>1.7</version>     <scope>system</scope>