使用JDK自带接口解析Java源码文件的两种解决方案--简介

背景

Java开发过程中经常有大量重复代码的组件需要编写,Ctrl + c/v 做法有点坑,毕竟这种代码没有手写的必要,举个例子,在使用Spring Data Jpa 时,需要为每个实体@Entity编写对应的@Repository组件,而这@Repository组件基本就不会经常变动,如果去手写没什么意思,就是Ctrl +c/v。还有如果在前后端分离项目中,需要编写数据传输组件,DTO(Data Transfer Obejct),那么一个DTO对象的所有字段基本都来自于对应的@Entity。如果我们可以编写一个解析Java源码的工具,预先设置生成模板,根据解析得到的数据自动生成@Reository和DTO组件,那不爽了。

基础

经过一番搜索和尝试,现在三种解析Java源码的方案:
(1)使用JDK自带的Java语法树遍历接口解析源码
(2)使用JDK自带的Javadoc接口解析源码
(3)使用第三方语法解析工具,例如antlr4,但需要去找合适的语法定义文件
经过考虑,方案三直接被弃用,尽量不使用第三方依赖,JDK有对应接口,那就用它吧!

使用Java语法树遍历接口

一、首先,自定义Visitor,访问者模式是源码解析中常用模式,可以百度了解一下

package io.github.okay6.demo.javac;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreeScanner;

/**
 * @author Okay6
 * 继承com.sun.source.util.TreeScanner,构建遍历抽象语法树的Visitor
 * 重写部分方法 visitImport  visitClass  visitVariable 打印节点信息
 */
public class SourceVisitor extends TreeScanner<String, String> {
    @Override
    public String visitImport(ImportTree importTree, String s) {
        System.out.println("Find Import");
        System.out.println(importTree.getQualifiedIdentifier());
        return super.visitImport(importTree, s);
    }

    @Override
    public String visitClass(ClassTree classTree, String s) {
        System.out.println("Find Class Definition");
        System.out.println(classTree.getModifiers());
        System.out.println(classTree.getSimpleName());
        return super.visitClass(classTree, s);
    }

    @Override
    public String visitVariable(VariableTree variableTree, String s) {
        System.out.println("Find Variable Definition");
        System.out.println(variableTree.getType());
        System.out.println(variableTree.getName());
        return super.visitVariable(variableTree, s);
    }
}

二 、定义源码解析流程

package io.github.okay6.demo.javac;

import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.util.JavacTask;
import com.sun.tools.javac.api.JavacTool;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.util.Context;

import javax.tools.JavaFileObject;
import java.io.IOException;
import java.nio.charset.Charset;

/**
 * @author Okay6
 * 实现Java源码解析流程构建
 */
class ParserByJavac {

    private JavacFileManager fileManager;
    private JavacTool javacTool;

    ParserByJavac() {
        Context context = new Context();
        fileManager = new JavacFileManager(context, true, Charset.defaultCharset());
        javacTool = JavacTool.create();
    }
    @SuppressWarnings("all")
    void parseJava(String sourcePath) {
        // Java源码文件解析为JavaFileObject
        Iterable<? extends JavaFileObject> files = fileManager.getJavaFileObjects(sourcePath);
        // 创建JavacTask
        JavacTask javacTask = javacTool.getTask(null, fileManager, null, null, null, files);
        try {
            // 构建抽象语法树
            Iterable<? extends CompilationUnitTree> result = javacTask.parse();
            // 使用自定义Visitor遍历抽象语法树
            for (CompilationUnitTree tree : result) {
                tree.accept(new SourceVisitor(), null);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用Javadoc工具解析源码

package io.github.okay6.demo.javadoc;

import com.sun.javadoc.*;

/**
 * @author Okay6
 * 继承Docler,重写start languageVersion方法
 */
public class ParserByJavaDoc extends Doclet {
    private static RootDoc rootDoc;

    public static class Doclet {
        private Doclet() {
        }

        @SuppressWarnings("all")
        public static boolean start(RootDoc rootDoc) {
            ParserByJavaDoc.rootDoc = rootDoc;
            return true;
        }

        // 返回Java1.5 因为泛型是在Java1.5后加入的特性,获取变量类型时要获取泛型就需要重写此方法
        @SuppressWarnings("all")
        public static LanguageVersion languageVersion() {
            return LanguageVersion.JAVA_1_5;
        }
    }


    private static void parse() {
        ClassDoc[] classes = rootDoc.classes();
        ClassDoc classDoc = classes[0];
        System.out.println("Find Class Definition");
        System.out.println(classDoc.getRawCommentText());
        System.out.println(classDoc.qualifiedName());
        System.out.println("Find Imports");
        for (PackageDoc packageDoc : classDoc.importedPackages()) {
            System.out.println(packageDoc.name());
        }
        System.out.println("Find Field Definition");
        for (FieldDoc fieldDoc : classDoc.fields(false)) {
            System.out.println(fieldDoc.getRawCommentText());
            System.out.println(fieldDoc.type());
            System.out.println(fieldDoc.name());
        }
    }

    public void parseJava(String sourceFile) {
        String[] params = new String[]{"-doclet",
                Doclet.class.getName(),
                "-encoding", "utf-8", "-quiet", sourceFile};

        com.sun.tools.javadoc.Main.execute(params);

        parse();
    }
}

后记

在使用过程中可以发现,使用Javadoc解析工具可以获取比遍历语法树更加详细的源码信息,包括注释,这样我们甚至可以把所需要的注释,例如@author 字段说明等同时提取出来,并在新生成的组件中使用。如果文中有错误之处,欢迎指出。源码:JavaParserDemo

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值