美文网首页
10、java编译原理

10、java编译原理

作者: 霸体 | 来源:发表于2020-08-21 15:48 被阅读0次

    javac编译java源代码主要分为4个过程:
    1、词法分析:通过词法分析器将java源文件处理为token流;
    2、语法分析:通过语法分析器,初步生成抽象语法树,抽象语法树由sun包里的一组内部类实现;
    3、语法优化:这个阶段包含复杂语法的优化(如foreach)、默认构造函数创建、源代码注解处理等等;
    4、字节码生成:通过sun内部类Gen遍历抽象语法树,生成jvm可识别的字节码文件;

    编译阶段包含上面的4个过程,我们可以干预第三个阶段,通过注解来干预源代码的生成;

    Android 很多框架都是 “编译时”框架,java服务器大部分都是 “运行时”框架, 因为客户端的性能要求更高往往要求提前编译好,而服务器一般使用 “反射”能力,做服务端的运行时动态处理;
    “编译时”框架,通常都是参考RSF269标准(从JDK1.6开始支持),服务器最常用的Lombok插件就是基于此开发的。
    通常情况下,编译时处理器都是做一些简单校验或者生成新的java源代码文件,这时候不需要使用JDK编译器的内部API。
    如果要想Lombok那样修改原始的java代码,需要熟悉Java编译器的内部API和AST(抽象语法树)。

    public class Log {
    public static void print(String msg){
    System.out.println(msg);
    }
    }
    //定义源代码处理器的注解
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Probe {
    }
    
    //demo版本 源代码处理器
    public class ProbeProcessor extends AbstractProcessor {
    /**
    * AST 抽象语法树
    */
    private Trees trees;
    /**
    * 语法树节点工具类
    */
    private TreeMaker make;
    /**
    * 标识符工具类
    */
    private Name.Table names;
    /**
    * java compiler 内部上下文
    */
    private Context context;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, ">>> probe processor 初始化了! ");
    trees = Trees.instance(processingEnv);
    context = ((JavacProcessingEnvironment)
    processingEnv).getContext();
    make = TreeMaker.instance(context);
    names = Names.instance(context).table;
    }
    /**
    * 针对特定的java类
    *
    * @return
    */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
    HashSet<String> supportTypes = new LinkedHashSet<>();
    supportTypes.add(Probe.class.getCanonicalName());
    return supportTypes;
    }
    @Override
    public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.RELEASE_8;
    }
    /**
    * 源代码修改入口
    * 注意此方法会被调用多次
    *
    * @param annotations
    * @param roundEnv
    * @return
    */
    @Override
    public boolean process(Set<? extends TypeElement> annotations,
    RoundEnvironment roundEnv) {
    processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, " >>> probe processor execute. ");
    if (!roundEnv.processingOver()) {
    Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Probe.class);
    processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, " >>> probe processor execute and size= " + elements.size());
    for (Element each : elements) {
    if (each.getKind() != ElementKind.CLASS) {
    continue;
    }
    processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
    " >>> handing probed class " + ((TypeElement) each).getQualifiedName().toString());
    //添加工具包import
    // addImportInfo(each);
    //修改java compiler的抽象语法树中的方法定义
    JCTree tree = (JCTree) trees.getTree(each);
    TreeTranslator visitor = new ProbeTranslator();
    tree.accept(visitor);
    }
    } else {
    //round结束后,还会调用所有的注解处理器一次
    processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, ">>> probe modify source code finished.");
    }
    //true 代表独占 false代表共享,会继续被其他注解处理器继续处理
    return true;
    }
    private void addImportInfo(Element element) {
    TreePath treePath = trees.getPath(element);
    Tree leaf = treePath.getLeaf();
    if (treePath.getCompilationUnit() instanceof JCTree.JCCompilationUnit && leaf instanceof JCTree) {
    processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, ">>> addImportInfo if success.");
    JCTree.JCCompilationUnit jccu = (JCTree.JCCompilationUnit) treePath.getCompilationUnit();
    for (JCTree jcTree : jccu.getImports()) {
    if (jcTree != null && jcTree instanceof JCTree.JCImport) {
    JCTree.JCImport jcImport = (JCTree.JCImport) jcTree;
    if (jcImport.qualid != null && jcImport.qualid instanceof JCTree.JCFieldAccess) {
    JCTree.JCFieldAccess jcFieldAccess = (JCTree.JCFieldAccess) jcImport.qualid;
    try {
    if ("zxl.org.processor".equals(jcFieldAccess.selected.toString()) && "Log".equals(jcFieldAccess.name.toString())) {
    processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, ">>> addImportInfo contains zxl.Log , so return");
    return;
    }
    } catch (NullPointerException e) {
    e.printStackTrace();
    }
    }
    }
    }
    java.util.List<JCTree> trees = new ArrayList<>();
    trees.addAll(jccu.defs);
    JCTree.JCIdent ident = make.Ident(names.fromString("zxl.org.processor"));
    JCTree.JCImport jcImport = make.Import(make.Select(ident, names.fromString("Log")), false);
    if (!trees.contains(jcImport)) {
    trees.add(0, jcImport);
    }
    processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, ">>> addImportInfo finished.");
    jccu.defs = List.from(trees);
    }
    }
    private class ProbeTranslator extends TreeTranslator {
    @Override
    public void visitMethodDef(JCTree.JCMethodDecl jcMethodDecl) {
    super.visitMethodDef(jcMethodDecl);
    if (jcMethodDecl.getName().toString().equalsIgnoreCase("<init>")) {
    return;
    }
    JCTree.JCBlock body = jcMethodDecl.getBody();
    JCTree.JCExpressionStatement printVar = make.Exec(make.Apply(
    List.of(memberAccess("java.lang.String")),//参数类型
    memberAccess("zxl.org.processor.Log.print"),
    List.of(make.Literal("Auto Generated."))
    ));
    List<JCTree.JCStatement> modifiedStatements = body.getStatements();
    modifiedStatements = modifiedStatements.prepend(printVar);
    modifiedStatements = modifiedStatements.append(printVar);
    body.stats = modifiedStatements;
    JCTree.JCMethodDecl methodDecl = make.MethodDef(jcMethodDecl.getModifiers(), jcMethodDecl.getName(),
    jcMethodDecl.restype, jcMethodDecl.getTypeParameters(), jcMethodDecl.getParameters(), jcMethodDecl.getThrows(),
    body, jcMethodDecl.defaultValue);
    processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, ">>> visitMethodDef finished. methodDecl=" + methodDecl +
    " stats=" + body.stats);
    result = methodDecl;
    }
    private JCTree.JCExpression memberAccess(String components) {
    String[] componentArray = components.split("\\.");
    JCTree.JCExpression expr = make.Ident(names.fromString(componentArray[0]));
    for (int i = 1; i < componentArray.length; i++) {
    expr = make.Select(expr, names.fromString(componentArray[i]));
    }
    return expr;
    }
    @Override
    public void visitImport(JCTree.JCImport jcImport) {
    super.visitImport(jcImport);
    }
    }
    }
    
    //稍微复杂一点的实现
    import com.google.common.collect.Lists;
    import com.sun.source.util.Trees;
    import com.sun.tools.javac.code.TypeTag;
    import com.sun.tools.javac.processing.JavacProcessingEnvironment;
    import com.sun.tools.javac.tree.JCTree;
    import com.sun.tools.javac.tree.TreeMaker;
    import com.sun.tools.javac.tree.TreeTranslator;
    import com.sun.tools.javac.util.Context;
    import com.sun.tools.javac.util.List;
    import com.sun.tools.javac.util.Name;
    import com.sun.tools.javac.util.Names;
    
    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.ProcessingEnvironment;
    import javax.annotation.processing.RoundEnvironment;
    import javax.lang.model.SourceVersion;
    import javax.lang.model.element.Element;
    import javax.lang.model.element.ElementKind;
    import javax.lang.model.element.TypeElement;
    import javax.tools.Diagnostic;
    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.LinkedHashSet;
    import java.util.Set;
    import java.util.TreeMap;
    
    /**
     * java编译器的注解处理器
     * 需要在mvn compiler插件中激活
     *
     * @author zhouxiliang
     * @date 2020/7/19 17:11
     */
    public class ProbeProcessor extends AbstractProcessor {
        /**
         * AST 抽象语法树
         */
        private Trees trees;
        /**
         * 语法树节点工具类
         */
        private TreeMaker make;
        /**
         * 标识符工具类
         */
        private Name.Table names;
        /**
         * java compiler 内部上下文
         */
        private Context context;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "probe processor initialized.");
            trees = Trees.instance(processingEnv);
            context = ((JavacProcessingEnvironment)
                    processingEnv).getContext();
            make = TreeMaker.instance(context);
            names = Names.instance(context).table;
        }
    
        /**
         * 针对特定的java类
         *
         * @return
         */
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            HashSet<String> supportTypes = new LinkedHashSet<>();
            supportTypes.add(EnableProbe.class.getCanonicalName());
            return supportTypes;
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.RELEASE_8;
        }
    
        /**
         * 源代码修改入口
         * 注意此方法会被调用多次
         *
         * @param annotations
         * @param roundEnv
         * @return
         */
        @Override
        public boolean process(Set<? extends TypeElement> annotations,
                               RoundEnvironment roundEnv) {
            if (!roundEnv.processingOver()) {
                Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(EnableProbe.class);
                for (Element each : elements) {
                    if (each.getKind() != ElementKind.CLASS) {
                        continue;
                    }
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
                            "handing probed class " + ((TypeElement) each).getQualifiedName().toString());
                    //修改java compiler的抽象语法树中的方法定义
                    JCTree tree = (JCTree) trees.getTree(each);
                    TreeTranslator visitor = new ProbeTranslator();
                    tree.accept(visitor);
                }
            } else {
                //round结束后,还会调用所有的注解处理器一次
                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, ">>> probe modify source code finished.");
            }
            //true 代表独占 false代表共享,会继续被其他注解处理器继续处理
            return true;
        }
    
    
        private class ProbeTranslator extends TreeTranslator {
            @Override
            public void visitMethodDef(JCTree.JCMethodDecl jcMethodDecl) {
                super.visitMethodDef(jcMethodDecl);
                String methodName = jcMethodDecl.getName().toString();
                if (methodName.equalsIgnoreCase("<init>")) {
                    //构造方法跳过
                    return;
                }
                JCTree.JCBlock body = jcMethodDecl.getBody();
                JCTree.JCExpressionStatement startProbe = make.Exec(make.Apply(
                        List.of(memberAccess("java.lang.String")),
                        memberAccess("com.common.monitor.MonitorUtils.probe"),
                        List.of(make.Literal("[" + methodName + "] start. (Auto Generated)"))
                ));
                List<JCTree.JCStatement> modifiedStatements = body.getStatements();
                modifiedStatements = modifiedStatements.prepend(startProbe);
                boolean hasReturn = modifiedStatements.stream().filter(x -> x instanceof JCTree.JCReturn).findAny().isPresent();
                if (!hasReturn) {
                    JCTree.JCExpressionStatement endProbe = make.Exec(make.Apply(
                            List.of(memberAccess("java.lang.String")),
                            memberAccess("com.common.monitor.MonitorUtils.probe"),
                            List.of(make.Literal("[" + methodName + "] finished.(Auto Generated)(no return)"))
                    ));
                    modifiedStatements = modifiedStatements.append(endProbe);
                } else {
                    //改写每个return
                    JCTree.JCExpression returnType = jcMethodDecl.restype;
                    modifiedStatements = modifyReturn(returnType, modifiedStatements, methodName);
                }
    
                body.stats = modifiedStatements;
                JCTree.JCMethodDecl methodDecl = make.MethodDef(jcMethodDecl.getModifiers(), jcMethodDecl.getName(),
                        jcMethodDecl.restype, jcMethodDecl.getTypeParameters(), jcMethodDecl.getParameters(), jcMethodDecl.getThrows(),
                        body, jcMethodDecl.defaultValue);
                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "add probe finished. method=" + jcMethodDecl.getName());
                result = methodDecl;
            }
    
            private boolean isVoid(JCTree.JCExpression jcExpression) {
                if (jcExpression == null) {
                    return true;
                }
                if (!(jcExpression instanceof JCTree.JCPrimitiveTypeTree)) {
                    return false;
                }
    
                JCTree.JCPrimitiveTypeTree jcPrimitiveTypeTree = (JCTree.JCPrimitiveTypeTree) jcExpression;
                return jcPrimitiveTypeTree.typetag == TypeTag.VOID;
            }
    
            /**
             * 这里只改写了第一层,如果要完整改写,需要递归整个JCTree,对每一种 JCStatement都要处理
             * 精力有限,并且一般第一层也够用了
             *
             * @param returnType
             * @param statements
             * @param methodName
             * @return
             */
            private List<JCTree.JCStatement> modifyReturn(JCTree.JCExpression returnType, List<JCTree.JCStatement> statements, String methodName) {
                TreeMap<Integer, java.util.List<JCTree.JCStatement>> treeMap = new TreeMap<>();
                for (int i = 0; i < statements.size(); ++i) {
                    if (statements.get(i) instanceof JCTree.JCReturn) {
                        treeMap.put(i, expandReturn(returnType, (JCTree.JCReturn) statements.get(i), methodName));
                    }
                    if (statements.get(i) instanceof JCTree.JCIf) {
    
                    }
                }
                if (treeMap.isEmpty()) {
                    return statements;
                }
                ArrayList<JCTree.JCStatement> result = new ArrayList<>();
                for (int i = 0; i < statements.size(); ++i) {
                    if (statements.get(i) instanceof JCTree.JCReturn) {
                        result.addAll(treeMap.get(i));
                    } else {
                        result.add(statements.get(i));
                    }
                }
                return List.from(result);
            }
    
            private ArrayList<JCTree.JCStatement> expandReturn(JCTree.JCExpression returnType, JCTree.JCReturn jcReturn, String methodName) {
                //拆成两个语句
                JCTree.JCExpression expression = jcReturn.getExpression();
                JCTree.JCVariableDecl resultDefinition = makeVarDef(make.Modifiers(0), "_probe_result", returnType, expression);
                JCTree.JCReturn returnProbeResult = make.Return(make.Ident(names.fromString("_probe_result")));
                JCTree.JCExpressionStatement endProbe = make.Exec(make.Apply(
                        List.of(memberAccess("java.lang.String")),
                        memberAccess("com.common.monitor.MonitorUtils.probe"),
                        List.of(make.Literal("[" + methodName + "] finished.(Auto Generated)(modify return)"))
                ));
                if (isVoid(returnType)) {
                    return Lists.newArrayList(endProbe, jcReturn);
                }
                return Lists.newArrayList(resultDefinition, endProbe, returnProbeResult);
            }
    
    
            private JCTree.JCVariableDecl makeVarDef(JCTree.JCModifiers modifiers, String name, JCTree.JCExpression vartype, JCTree.JCExpression init) {
                return make.VarDef(
                        modifiers,
                        names.fromString(name),
                        vartype,
                        init
                );
            }
    
    
            private JCTree.JCExpression memberAccess(String components) {
                String[] componentArray = components.split("\\.");
                JCTree.JCExpression expr = make.Ident(names.fromString(componentArray[0]));
                for (int i = 1; i < componentArray.length; i++) {
                    expr = make.Select(expr, names.fromString(componentArray[i]));
                }
                return expr;
            }
    
            @Override
            public void visitImport(JCTree.JCImport jcImport) {
                super.visitImport(jcImport);
            }
        }
    }
    

    java编译命令

    javac -d /Users/zxl \
     -classpath  /Users/zhouxiliang/.m2/repository/zxl/org/zxl-demo/1.0-SNAPSHOT/zxl-demo-1.0-SNAPSHOT.jar \
     -processor zxl.org.processor.ProbeProcessor \
    -target 1.8 -source 1.8 -encoding UTF-8 -g -verbose Test.java
    
    

    Demo版本的执行效果:

    image.png image.png

    Maven插件的激活方式

       <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-compiler-plugin</artifactId>
                  <version>3.1</version>
                  <configuration>
                      <source>1.8</source>
                      <target>1.8</target>
                      <fork>true</fork>
                      <verbose>true</verbose>
                      <encoding>UTF-8</encoding>
                      <compilerArguments>
                          war
                          <sourcepath>
                              ${project.basedir}/src/main/java
                          </sourcepath>
                      </compilerArguments>
                      <annotationProcessors>
                          <processor>
                              com.xxx.ProbeProcessor
                          </processor>
                      </annotationProcessors>
                  </configuration>
              </plugin>
    

    总结:基于注解的源代码修改方式是可行的,但是也有许多缺点,比如晦涩难懂,修改源代码后远程debug也会出现问题;知道原理即可,实际项目开发用到的还是比较少的;

    相关文章

      网友评论

          本文标题:10、java编译原理

          本文链接:https://www.haomeiwen.com/subject/oywujktx.html