欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
動(dòng)態(tài)的Java JavaCompilerAPI中文指南

動(dòng)態(tài)的Java - 無(wú)廢話(huà)JavaCompilerAPI中文指南

| Comments

JavaCompiler API

1.6之后JDK提供了一套compiler API,定義在JSR199中, 提供在運行期動(dòng)態(tài)編譯java代碼為字節碼的功能
簡(jiǎn)單說(shuō)來(lái),這一套API就好比是在java程序中模擬javac程序,將java源文件編譯為class文件;其提供的默認實(shí)現也正是在文件系統上進(jìn)行查找、編譯工作的,用起來(lái)感覺(jué)與javac基本一致;
不過(guò),我們可以通過(guò)一些關(guān)鍵類(lèi)的繼承、方法重寫(xiě)和擴展,來(lái)達到一些特殊的目的,常見(jiàn)的就是“與文件系統解耦”(就是在內存或別的地方完成源文件的查找、讀取和class編譯)
需要強調的是,compiler API的相關(guān)實(shí)現被放在tools.jar中,JDK默認會(huì )將tools.jar放入classpath而jre沒(méi)有,因此如果發(fā)現compiler API相關(guān)類(lèi)找不到,那么請檢查一下tools.jar是否已經(jīng)在classpath中;
當然我指的是jdk1.6以上的版本提供的tools.jar包

基本使用

一個(gè)基本的例子

public static CompilationResult compile(String qualifiedName, String sourceCode,                                        Iterable<? extends Processor> processors) {    JavaStringSource source = new JavaStringSource(qualifiedName, sourceCode);    List<JavaStringSource> ss = Arrays.asList(source);    List<String> options = Arrays.asList("-classpath", HotCompileConstants.CLASSPATH);    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();    MemClsFileManager fileManager = null;    Map<String, JavaMemCls> clses = new HashMap<String, JavaMemCls>();    Map<String, JavaStringSource> srcs = new HashMap<String, JavaStringSource>();    srcs.put(source.getClsName(), source);    try {        fileManager = new MemClsFileManager(compiler.getStandardFileManager(null, null, null), clses, srcs);        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();        StringWriter out = new StringWriter();        CompilationTask task = compiler.getTask(out, fileManager, diagnostics, options, null, ss);        if (processors != null) task.setProcessors(processors);        boolean sucess = task.call();        if (!sucess) {            for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {                out.append("Error on line " + diagnostic.getLineNumber() + " in " + diagnostic).append('\n');            }            return new CompilationResult(out.toString());        }    } finally {        try {            fileManager.close();        } catch (Exception e) {            throw new RuntimeException(e);        }    }    // every parser class should be loaded by a new specific class loader    HotCompileClassLoader loader = new HotCompileClassLoader(Util.getParentClsLoader(), clses);    Class<?> cls = null;    try {        cls = loader.loadClass(qualifiedName);    } catch (ClassNotFoundException e) {        throw new RuntimeException(e);    }    return new CompilationResult(cls, loader);}

解釋一下這段程序:
這個(gè)static方法提供這樣一種功能:輸入希望的類(lèi)名和String形式的java代碼內容,動(dòng)態(tài)編譯并返回編譯好的class對象; 其中CompilationResult只是一個(gè)簡(jiǎn)單的pojo封裝,用于包裝返回結果和可能的錯誤信息
類(lèi)名和源碼首先被包裝成一個(gè)JavaStringSource對象, 該對象繼承自javax.tools.SimpleJavaFileObject類(lèi),是compiler API對一個(gè)“Java文件”(即源文件或class文件)的抽象;將源文件包裝成這個(gè)類(lèi)也就實(shí)現了“將java源文件放在內存中”的想法
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();這是取得一個(gè)默認的JavaCompiler工具的實(shí)例
由于我打算將編譯好的class文件直接存放在內存中,因此我自定義了一個(gè)MemClsFileManager:

public class MemClsFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {    private Map<String, JavaMemCls>       destFiles;    private Map<String, JavaStringSource> srcFiles;    protected MemClsFileManager(StandardJavaFileManager fileManager, Map<String, JavaMemCls> destFiles,                                Map<String, JavaStringSource> srcFiles){        super(fileManager);        this.destFiles = destFiles;        this.srcFiles = srcFiles;    }    public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling)                                                                                                                  throws IOException {        if (!(Kind.CLASS.equals(kind) && StandardLocation.CLASS_OUTPUT.equals(location))) return super.getJavaFileForOutput(location,                                                                                                                            className,                                                                                                                            kind,                                                                                                                            sibling);        if (destFiles.containsKey(className)) {            return destFiles.get(className);        } else {            JavaMemCls file = new JavaMemCls(className);            this.destFiles.put(className, file);            return file;        }    }    public void close() throws IOException {        super.close();        this.destFiles = null;    }    public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse)                                                                                                                 throws IOException {        List<JavaFileObject> ret = new ArrayList<JavaFileObject>();        if ((StandardLocation.CLASS_OUTPUT.equals(location) || StandardLocation.CLASS_PATH.equals(location))            && kinds.contains(Kind.CLASS)) {            for (Map.Entry<String, JavaMemCls> e : destFiles.entrySet()) {                String pkgName = resolvePkgName(e.getKey());                if (recurse) {                    if (pkgName.contains(packageName)) ret.add(e.getValue());                } else {                    if (pkgName.equals(packageName)) ret.add(e.getValue());                }            }        } else if (StandardLocation.SOURCE_PATH.equals(location) && kinds.contains(Kind.SOURCE)) {            for (Map.Entry<String, JavaStringSource> e : srcFiles.entrySet()) {                String pkgName = resolvePkgName(e.getKey());                if (recurse) {                    if (pkgName.contains(packageName)) ret.add(e.getValue());                } else {                    if (pkgName.equals(packageName)) ret.add(e.getValue());                }            }        }        // 也包含super.list        Iterable<JavaFileObject> superList = super.list(location, packageName, kinds, recurse);        if (superList != null) for (JavaFileObject f : superList)            ret.add(f);        return ret;    }    private String resolvePkgName(String fullQualifiedClsName) {        return fullQualifiedClsName.substring(0, fullQualifiedClsName.lastIndexOf('.'));    }    public String inferBinaryName(Location location, JavaFileObject file) {        if (file instanceof JavaMemCls) {            return ((JavaMemCls) file).getClsName();        } else if (file instanceof JavaStringSource) {            return ((JavaStringSource) file).getClsName();        } else {            return super.inferBinaryName(location, file);        }    }}

其中最主要的步驟就是重寫(xiě)了getJavaFileForOutput方法,使其使用內存中的map來(lái)作為生成文件(class文件)的輸出位置

CompilationTask task = compiler.getTask(out, fileManager, diagnostics, options, null, ss);boolean sucess = task.call();

上面這兩行是創(chuàng )建了一個(gè)編譯task,并調用
最后使用自定義ClassLoader來(lái)加載編譯好的類(lèi)并返回:

HotCompileClassLoader loader = new HotCompileClassLoader(Util.getParentClsLoader(), clses);Class<?> cls = null;try {    cls = loader.loadClass(qualifiedName);} catch (ClassNotFoundException e) {    throw new RuntimeException(e);}return new CompilationResult(cls, loader);

而該ClassLoader的實(shí)現關(guān)鍵在于“到內存中(即之前存放編譯好的class的map中)加載字節碼”:

public class HotCompileClassLoader extends ClassLoader {    private Map<String, JavaMemCls> inMemCls;    public HotCompileClassLoader(ClassLoader parent, Map<String, JavaMemCls> clses){        super(parent);        this.inMemCls = clses;    }    protected Class<?> findClass(String name) throws ClassNotFoundException {        byte[] b = this.inMemCls.get(name).getClsBytes();        return defineClass(name, b, 0, b.length);    }}

之后只要調用方法compile(className, source, null)這樣就算完成了一個(gè)基本的、不依賴(lài)實(shí)際的文件系統的動(dòng)態(tài)編譯過(guò)程

JavaFileManager的意義

一個(gè)廣義的、管理“文件”資源的接口,并不一定指“操作系統的磁盤(pán)文件系統”
其實(shí)JavaFileManager只是一個(gè)接口,只要行為正確,那么就無(wú)所謂“文件”到底以何種形式、實(shí)際被存放在哪里

StandardJavaFileManager的默認行為

這是基于磁盤(pán)文件的JavaFileManager實(shí)現, 所有的文件查找、新文件輸出位置都在磁盤(pán)上完成;也就是說(shuō),如果直接使用默認的StandardJavaFileManager來(lái)做動(dòng)態(tài)編譯,那么得到的效果就跟命令行中直接使用javac編譯差不多

繼承ForwardingJavaFileManager類(lèi),讓compiler API脫離對文件系統的依賴(lài)

如果想要將編譯好的class文件放在內存中而不是磁盤(pán)上,那么需要使用一個(gè)ForwardingJavaFileManager來(lái)包裝默認的StandardJavaFileManager并重寫(xiě)getJavaFileForOutput方法,將其實(shí)現改為內存操作;這個(gè)實(shí)現可參考上面的MemClsFileManager類(lèi)
不過(guò)ForwardingJavaFileManager還有許多別的方法,沒(méi)有文檔說(shuō)明動(dòng)態(tài)編譯過(guò)程中到底那些方法會(huì )被調用,原則上講,所有方法都有可能被調用
但具體哪些方法被調用了可以被實(shí)測出來(lái),這樣可以有選擇性地重寫(xiě)其中一些方法
比如MemClsFileManager中,重寫(xiě)了inferBinaryName, list, close, getJavaFileForOutput方法,因為這些方法都會(huì )被“class文件放在內存中”這一策略所影響,所以需要兼容

JavaCompiler Tree API

下面想介紹的其實(shí)是另一個(gè)更進(jìn)一步的話(huà)題:如何在動(dòng)態(tài)編譯的過(guò)程中分析被編譯的源代碼
因為幾乎所有打算用到j(luò )ava動(dòng)態(tài)編譯的應用場(chǎng)景,都會(huì )想要對被編譯的代碼做一些檢查、review,以防編譯運行了讓人出乎意料的代碼從而對系統造成破壞
那么這個(gè)事情,除了人工review之外,一些簡(jiǎn)單的驗證,完全可以在編譯期自動(dòng)地做到;而JavaCompiler Tree API就是用來(lái)做這個(gè)靜態(tài)編譯期檢查的
它的基本思路很簡(jiǎn)單,就是hook進(jìn)編譯的過(guò)程中,在java源碼被parse成AST的時(shí)候,使用visitor模式對該AST做遍歷分析,以找出你需要定位的語(yǔ)法結構,這樣來(lái)達到驗證目的; 比如”如果發(fā)現assert語(yǔ)句就報錯”或“不允許定義嵌套類(lèi)”這樣的檢查都很容易做
這之中的關(guān)鍵,當然是java的AST和其對應的visitor的實(shí)現了

Java的AST:

http://docs.oracle.com/javase/7/docs/jdk/api/javac/tree/com/sun/source/tree/package-summary.html
上述鏈接給出了java的AST類(lèi)結構,所有語(yǔ)法元素的節點(diǎn)都有
而對應的visitor接口TreeVisitor<R,P>與AST形成了標準的visitor模式
TreeVisitor<R,P>的默認實(shí)現及其用途:

  1. SimpleTreeVisitor: 簡(jiǎn)單的、“消極”的visitor實(shí)現,當訪(fǎng)問(wèn)一個(gè)分支節點(diǎn)時(shí)不會(huì )默認“往下”繼續遍歷它的所有子節點(diǎn), 若想要遍歷所有子節點(diǎn)需要繼承、編碼實(shí)現
  2. TreeScanner: 默認會(huì )遍歷所有子節點(diǎn)的“積極”的visitor實(shí)現,還能讓當前節點(diǎn)的遍歷邏輯取到上一個(gè)被訪(fǎng)問(wèn)的鄰接兄弟節點(diǎn)被訪(fǎng)問(wèn)的返回結果(換句話(huà)說(shuō)能夠拿到最近的、剛才被訪(fǎng)問(wèn)過(guò)的兄弟節點(diǎn)對象,或其它等效的自定義返回值)
  3. TreePathScanner: 跟TreeScanner一樣“積極”,且除了能拿到前一個(gè)鄰接兄弟節點(diǎn)的訪(fǎng)問(wèn)返回結果外,還能拿到父親節點(diǎn)(這樣來(lái)追蹤該節點(diǎn)的路徑)

有了上面這個(gè)visitor模式的腳手架,我們就能通過(guò)實(shí)現一個(gè)visitor來(lái)達到對java源碼的分析了
比如下面這個(gè)visitor, 它繼承自TreePathScanner(ExprCodeChecker是我自定義的一個(gè)TreePathScanner的子類(lèi)):

public class ForbiddenStructuresChecker extends ExprCodeChecker<Void, FbdStructContext> {    private StringBuilder errMsg = new StringBuilder();    public String getErrorMsg() {        return this.errMsg.toString();    }    public FbdStructContext getInitParam() {        return new FbdStructContext();    }    /**     * 禁止定義內部類(lèi)     */    public Void visitClass(ClassTree node, FbdStructContext p) {        if (p.isInClass) {            // 已經(jīng)位于另一個(gè)外層class定義中了,直接報錯返回,不再繼續遍歷子節點(diǎn)            this.errMsg.append("Nested class is not allowed in api expressions. Position: " + resolveRowAndCol(node)).append('\n');            return null;        } else {            boolean oldInClass = p.isInClass;            p.isInClass = true;            // 繼續遍歷子節點(diǎn)            super.visitClass(node, p);            p.isInClass = oldInClass;            return null;        }    }    /**     * 禁止定義'assert'語(yǔ)句     */    public Void visitAssert(AssertTree node, FbdStructContext p) {        this.errMsg.append("Assertions are not allowed in api expressions. Position: " + this.resolveRowAndCol(node)).append('\n');        return null;    }    /**     * 禁止定義goto(break or continue followed by a label)語(yǔ)句     */    public Void visitBreak(BreakTree node, FbdStructContext p) {        if (node.getLabel() != null) {            this.errMsg.append("'break' followed by a label is not allowed in api expressions. Position: "                                       + this.resolveRowAndCol(node)).append('\n');            return null;        } else {            return super.visitBreak(node, p);        }    }    public Void visitContinue(ContinueTree node, FbdStructContext p) {        if (node.getLabel() != null) {            this.errMsg.append("'continue' followed by a label is not allowed in api expressions. Position: "                                       + this.resolveRowAndCol(node)).append('\n');            return null;        } else {            return super.visitContinue(node, p);        }    }    // *************禁止定義goto end*************    /**     * 禁止定義死循環(huán),for/while/do-while loop, 只限制常量類(lèi)型的循環(huán)條件造成的明顯死循環(huán); 這種靜態(tài)校驗是不完善的,要做完善很復雜,沒(méi)必要加大投入;若要做到更精確的控制應從動(dòng)態(tài)期方案的方向考慮     */    public Void visitDoWhileLoop(DoWhileLoopTree node, FbdStructContext p) {        boolean condTemp = p.isConstantTrueCondition;        boolean isLoopExpTemp = p.isLoopConditionExpr;        p.isLoopConditionExpr = true;        node.getCondition().accept(this, p);        if (p.isConstantTrueCondition) {            // 死循環(huán)            this.errMsg.append("Dead loop is not allowed in api expressions. Position: " + this.resolveRowAndCol(node)).append('\n');        }        p.isConstantTrueCondition = condTemp;        p.isLoopConditionExpr = isLoopExpTemp;        return super.visitDoWhileLoop(node, p);    }    public Void visitForLoop(ForLoopTree node, FbdStructContext p) {        if (node.getCondition() == null) {            // 無(wú)條件,相當于'true'            this.errMsg.append("Dead loop is not allowed in api expressions. Position: " + this.resolveRowAndCol(node)).append('\n');        } else {            boolean condTemp = p.isConstantTrueCondition;            boolean isLoopExpTemp = p.isLoopConditionExpr;            p.isLoopConditionExpr = true;            node.getCondition().accept(this, p);            if (p.isConstantTrueCondition) {                // 死循環(huán)                this.errMsg.append("Dead loop is not allowed in api expressions. Position: "                                           + this.resolveRowAndCol(node)).append('\n');            }            p.isConstantTrueCondition = condTemp;            p.isLoopConditionExpr = isLoopExpTemp;        }        return super.visitForLoop(node, p);    }    public Void visitWhileLoop(WhileLoopTree node, FbdStructContext p) {        boolean condTemp = p.isConstantTrueCondition;        boolean isLoopExpTemp = p.isLoopConditionExpr;        p.isLoopConditionExpr = true;        node.getCondition().accept(this, p);        if (p.isConstantTrueCondition) {            // 死循環(huán)            this.errMsg.append("Dead loop is not allowed in api expressions. Position: " + this.resolveRowAndCol(node)).append('\n');        }        p.isConstantTrueCondition = condTemp;        p.isLoopConditionExpr = isLoopExpTemp;        return super.visitWhileLoop(node, p);    }    // 處理循環(huán)條件, 需要關(guān)心結果為boolean值的表達式    // 二元表達式    public Void visitBinary(BinaryTree node, FbdStructContext p) {        boolean isLoopCondTemp = p.isLoopConditionExpr;        // 求左值        p.isLoopConditionExpr = false;        node.getLeftOperand().accept(this, p);        p.isLoopConditionExpr = isLoopCondTemp;        Object leftVal = p.expValue;        // 求右值        p.isLoopConditionExpr = false;        node.getRightOperand().accept(this, p);        p.isLoopConditionExpr = isLoopCondTemp;        Object rightVal = p.expValue;        // 求整體值        Object val = null;        if (leftVal != null && rightVal != null) switch (node.getKind()) {            case MULTIPLY:                val = ((Number) leftVal).doubleValue() * ((Number) rightVal).doubleValue();                break;            case DIVIDE:                val = ((Number) leftVal).doubleValue() / ((Number) rightVal).doubleValue();                break;            case REMAINDER:                val = ((Number) leftVal).intValue() % ((Number) rightVal).intValue();                break;            case PLUS:                if (leftVal instanceof Number && rightVal instanceof Number) {                    val = ((Number) leftVal).doubleValue() + ((Number) rightVal).doubleValue();                } else {                    val = String.valueOf(leftVal) + String.valueOf(rightVal);                }                break;            case MINUS:                val = ((Number) leftVal).doubleValue() - ((Number) rightVal).doubleValue();                break;            case LEFT_SHIFT:                val = ((Number) leftVal).longValue() << ((Number) rightVal).intValue();                break;            case RIGHT_SHIFT:                val = ((Number) leftVal).longValue() >> ((Number) rightVal).intValue();                break;            case UNSIGNED_RIGHT_SHIFT:                val = ((Number) leftVal).longValue() >>> ((Number) rightVal).intValue();                break;            case LESS_THAN:                val = ((Number) leftVal).doubleValue() < ((Number) rightVal).doubleValue();                break;            case GREATER_THAN:                val = ((Number) leftVal).doubleValue() > ((Number) rightVal).doubleValue();                break;            case LESS_THAN_EQUAL:                val = ((Number) leftVal).doubleValue() <= ((Number) rightVal).doubleValue();                break;            case GREATER_THAN_EQUAL:                val = ((Number) leftVal).doubleValue() >= ((Number) rightVal).doubleValue();                break;            case EQUAL_TO:                val = leftVal == rightVal;                break;            case NOT_EQUAL_TO:                val = leftVal != rightVal;                break;            case AND:                if (leftVal instanceof Number) {                    val = ((Number) leftVal).longValue() & ((Number) rightVal).longValue();                } else {                    val = ((Boolean) leftVal) & ((Boolean) rightVal);                }                break;            case XOR:                if (leftVal instanceof Number) {                    val = ((Number) leftVal).longValue() ^ ((Number) rightVal).longValue();                } else {                    val = ((Boolean) leftVal) ^ ((Boolean) rightVal);                }                break;            case OR:                if (leftVal instanceof Number) {                    val = ((Number) leftVal).longValue() | ((Number) rightVal).longValue();                } else {                    val = ((Boolean) leftVal) | ((Boolean) rightVal);                }                break;            case CONDITIONAL_AND:                val = ((Boolean) leftVal) && ((Boolean) rightVal);                break;            case CONDITIONAL_OR:                val = ((Boolean) leftVal) || ((Boolean) rightVal);                break;            default:                val = null;        }        if (p.isLoopConditionExpr) {            if (val != null && val instanceof Boolean && (Boolean) val) p.isConstantTrueCondition = true;        } else {            p.expValue = val;        }        return null;    }    // 3元條件表達式    public Void visitConditionalExpression(ConditionalExpressionTree node, FbdStructContext p) {        boolean isLoopCondTemp = p.isLoopConditionExpr;        p.isLoopConditionExpr = false;        node.getCondition().accept(this, p);        p.isLoopConditionExpr = isLoopCondTemp;        Object val = null;        if (p.expValue != null) {            if ((Boolean) p.expValue) {                // 取true expr值                p.isLoopConditionExpr = false;                node.getTrueExpression().accept(this, p);                p.isLoopConditionExpr = isLoopCondTemp;                val = p.expValue;            } else {                // 取false expr值                p.isLoopConditionExpr = false;                node.getFalseExpression().accept(this, p);                p.isLoopConditionExpr = isLoopCondTemp;                val = p.expValue;            }        }        if (p.isLoopConditionExpr) {            p.isConstantTrueCondition = val != null && val instanceof Boolean && (Boolean) val;        } else {            p.expValue = val;        }        return null;    }    // 常量    public Void visitLiteral(LiteralTree node, FbdStructContext p) {        if (p.isLoopConditionExpr) {            p.isConstantTrueCondition = Boolean.TRUE.equals(node.getValue());        } else {            p.expValue = node.getValue();        }        return null;    }    // 括起來(lái)的表達式    public Void visitParenthesized(ParenthesizedTree node, FbdStructContext p) {        boolean isLoopCondTemp = p.isLoopConditionExpr;        if (p.isLoopConditionExpr) {            // 求值子表達式            p.isLoopConditionExpr = false;            node.getExpression().accept(this, p);            p.isLoopConditionExpr = isLoopCondTemp;            if (p.expValue != null && Boolean.TRUE.equals(p.expValue)) p.isConstantTrueCondition = true;        } else {            // 直接以子表達式的結果作為括號表達式的結果            p.isLoopConditionExpr = false;            node.getExpression().accept(this, p);            p.isLoopConditionExpr = isLoopCondTemp;        }        return null;    }    // 類(lèi)型轉換表達式    public Void visitTypeCast(TypeCastTree node, FbdStructContext p) {        boolean isLoopCondTemp = p.isLoopConditionExpr;        if (p.isLoopConditionExpr) {            p.isLoopConditionExpr = false;            node.getExpression().accept(this, p);            p.isLoopConditionExpr = isLoopCondTemp;            if (p.expValue != null                && ("Boolean".equals(node.getType().toString()) || "boolean".equals(node.getType().toString()))) p.isConstantTrueCondition = true;        } else {            p.isLoopConditionExpr = false;            node.getExpression().accept(this, p);            p.isLoopConditionExpr = isLoopCondTemp;        }        return null;    }    // 一元表達式    public Void visitUnary(UnaryTree node, FbdStructContext p) {        boolean isLoopCondTemp = p.isLoopConditionExpr;        // 求子表達式值        p.isLoopConditionExpr = false;        node.getExpression().accept(this, p);        p.isLoopConditionExpr = isLoopCondTemp;        Object val = null;        if (p.expValue != null) {            switch (node.getKind()) {                case POSTFIX_INCREMENT:                case POSTFIX_DECREMENT:                case PREFIX_INCREMENT:                case PREFIX_DECREMENT:                    val = null;                    break;                case UNARY_PLUS:                    val = p.expValue;                    break;                case UNARY_MINUS:                    val = -((Number) p.expValue).doubleValue();                    break;                case BITWISE_COMPLEMENT:                    val = ~((Number) p.expValue).longValue();                    break;                case LOGICAL_COMPLEMENT:                    val = !((Boolean) p.expValue);                    break;                default:                    val = null;            }        }        if (p.isLoopConditionExpr) {            if (val != null && val instanceof Boolean && (Boolean) val) p.isConstantTrueCondition = true;        } else {            p.expValue = val;        }        return null;    }    // *************禁止定義死循環(huán)end*************}
  • 上述visitor通過(guò)對visitClass的處理,對“定義嵌套類(lèi)”這種行為進(jìn)行了報錯
  • 通過(guò)對visitAssert的處理,凡是遇到代碼中出現assert語(yǔ)句的,均給出錯誤信息
  • 通過(guò)對visitBreakvisitContinue的處理禁止了goto語(yǔ)句(即帶label的break和continue語(yǔ)句)
  • 通過(guò)對visitDoWhileLoop等循環(huán)語(yǔ)法結構的訪(fǎng)問(wèn),以及部分表達式結構的訪(fǎng)問(wèn)(如visitBinary、visitLiteral),禁止了如while(true)、for(;;)while(1<2)等明顯死循環(huán)

在什么階段能將Java代碼轉化為AST從而被TreeVisitor分析?

接下來(lái)的問(wèn)題就是:我們有了處理AST的visitor,那么到底要在什么時(shí)候運行它呢?
答案就是使用jdk1.6的PluggableAnnotationProcessor機制, 在創(chuàng )建compilerTask時(shí)設置對應的Processor, 然后在該Processor中調用我們的visitor
下面是我們的processor實(shí)現:

@SupportedSourceVersion(SourceVersion.RELEASE_6)@SupportedAnnotationTypes("*")public class ExprCodeCheckProcessor extends AbstractProcessor {    // 工具實(shí)例類(lèi),用于將CompilerAPI, CompilerTreeAPI和AnnotationProcessing框架粘合起來(lái)    private Trees                       trees;    // 分析過(guò)程中可用的日志、信息打印工具    private Messager                    messager;    // 所有的CodeChecker    private List<ExprCodeChecker<?, ?>> codeCheckers = new ArrayList<ExprCodeChecker<?, ?>>();    // 搜集錯誤信息    private StringBuilder               errMsg       = new StringBuilder();    // 代碼檢查是否成功, 若false, 則'errMsg'里應該有具體錯誤信息    private boolean                     success      = true;    /**     * ==============在這里列出所有的checker實(shí)例==============     */    public ExprCodeCheckProcessor(){        // 檢查 —— 禁止定義一些不必要的結構,如內部類(lèi)        this.codeCheckers.add(new ForbiddenStructuresChecker());    }    public synchronized void init(ProcessingEnvironment processingEnv) {        super.init(processingEnv);        this.trees = Trees.instance(processingEnv);        this.messager = processingEnv.getMessager();        // 為所有checker置入工具實(shí)例        for (ExprCodeChecker<?, ?> c : this.codeCheckers) {            c.setTrees(trees);            c.setMessager(messager);        }    }    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {        if (!env.processingOver()) for (Element e : env.getRootElements()) {            for (ExprCodeChecker<?, ?> c : this.codeCheckers) {                c.check(this.trees.getPath(e));                if (!c.isSuccess()) {                    this.success = false;                    this.errMsg.append(c.getErrorMsg()).append('\n');                }            }        }        /*         * 這里若return true將阻止任何后續可能存在的Processor的運行,因此這里可以固定返回false         */        return false;    }    /**     * 獲取代碼檢查的錯誤信息     *      * @return     */    public String getErrMsg() {        return errMsg.toString();    }    /**     * 指示代碼檢查過(guò)程是否成功,若為false,則可調用getErrMsg取得具體錯誤信息     *      * @return     */    public boolean isSuccess() {        return success;    }}

這里面關(guān)鍵的就是實(shí)現init方法和process方法
init方法初始化了Trees工具并將其設置到了所有的ExprCodeChecker(其實(shí)就是visitor)中; Trees是一個(gè)很重要的工具類(lèi)實(shí)例,它能幫助我們獲取AST結構對應的行號、列號等重要信息
process方法則是真正對源碼進(jìn)行處理,在這里,真正調用了所有的ExprCodeChecker(也就是visitor)
然后在使用Compiler API的時(shí)候,將processor設置到CompilationTask中即可:

CompilationTask task = compiler.getTask(out, fileManager, diagnostics, options, null, ss);if (processors != null) task.setProcessors(processors);

順便貼下ExprCodeChecker的代碼如下,它就是一個(gè)對TreePathScanner的簡(jiǎn)單繼承,封裝了一些代碼分析過(guò)程中的基本屬性和常用方法, 所有的visitor只要繼承自它就可以了:

public abstract class ExprCodeChecker<R, P> extends TreePathScanner<R, P> {    // 當前被掃描代碼對應的節點(diǎn)轉換工具類(lèi), 運行時(shí)由Processor負責置入    protected Trees    trees;    // 錯誤信息打印、處理流程控制工具, 運行時(shí)由Processor負責置入    protected Messager messager;    /**     * 取得代碼檢查的錯誤信息, 返回結果為null或空字符串串則表示無(wú)錯誤, 否則認為有錯誤發(fā)生     *      * @return     */    public abstract String getErrorMsg();    /**     * 取得初始參數     *      * @return 用于遍歷代碼樹(shù)的初始參數     */    protected abstract P getInitParam();    /**     * 代碼檢查是否成功     *      * @return true - 成功,無(wú)問(wèn)題; false - 失敗,調用getErrorMsg可獲取錯誤信息     */    final boolean isSuccess() {        String err = this.getErrorMsg();        return err == null || err.length() == 0;    }    /**     * package訪(fǎng)問(wèn)權限,專(zhuān)門(mén)用于由Processor置入Trees工具實(shí)例     *      * @param trees     */    final void setTrees(Trees trees) {        this.trees = trees;    }    /**     * package訪(fǎng)問(wèn)權限,專(zhuān)門(mén)用于由Processor置入Messager工具實(shí)例     *      * @param messager     */    final void setMessager(Messager messager) {        this.messager = messager;    }    /**     * 開(kāi)始遍歷處理傳入的代碼樹(shù)節點(diǎn)     *      * @param path     */    final void check(TreePath path) {        this.scan(path, getInitParam());    }    /**     * 獲取指定語(yǔ)法節點(diǎn)縮在源文件中的行號和列號信息, 用于錯誤信息輸出     *      * @param node     * @return     */    protected final String resolveRowAndCol(Tree node) {        CompilationUnitTree unit = this.getCurrentPath().getCompilationUnit();        long pos = this.trees.getSourcePositions().getStartPosition(unit, node);        LineMap m = unit.getLineMap();        return "row: " + m.getLineNumber(pos) + ", col: " + m.getColumnNumber(pos);    }}

其中check方法是遍歷分析的起點(diǎn),由processor調用
resolveRowAndCol則是獲取AST節點(diǎn)對應的行號、列號的方法,用于輸出錯誤信息

使用trees.getSourcePositions()獲取節點(diǎn)的源碼位置,以及LineMap獲得指定位置的行號、列號

在processor的init方法中被置入的Trees工具實(shí)例,最大的用處就是獲取對應AST節點(diǎn)的行號、列號,具體代碼參見(jiàn)上述resolveRowAndCol方法

JavaCompiler Tree API的限制

有必要討論下什么樣的控制,適合用Tree API來(lái)做?
總結起來(lái)應該是:靜態(tài)的、簡(jiǎn)單的
比如“不能定義內部類(lèi)”、“不能寫(xiě)annotation”、“不能寫(xiě)assert語(yǔ)句”等
隨著(zhù)需求的復雜度增高,使用Tree API的編碼成本也會(huì )增高,畢竟使用visitor來(lái)分析復雜的AST模式并非十分容易的事情
比如上面的例子,“限制死循環(huán)”這種需求;如果說(shuō)非常簡(jiǎn)單的死循環(huán),比如while(true),這種是非常好做的
但如果稍微復雜一點(diǎn), 比如while(1<2),那么這里勢必會(huì )牽涉到一個(gè)”計算”過(guò)程,我們需要在分析過(guò)程中對1<2這個(gè)表達式做出計算,從而知曉該循環(huán)語(yǔ)句是否死循環(huán);雖然人眼對1<2的結果一目了然,但這里靠程序來(lái)做的話(huà),增加的復雜度還是相當可觀(guān)的
如果在繼續復雜下去,可以想象,其開(kāi)發(fā)成本會(huì )越來(lái)越高,且這個(gè)分析過(guò)程本身的“運行”成本也會(huì )越來(lái)越接近真正運行這段被分析的代碼的成本,這個(gè)時(shí)候使用Tree API來(lái)做分析就不劃算了
所以說(shuō),考慮到成本因素,Tree API并不適合做太復雜的分析
其次就是”靜態(tài)的”代碼,才能在編譯期做分析,如果是這樣的代碼:while(x<1),而x又是從方法參數中傳入,那么x的值就完全在運行期才能確定,那么Tree API就無(wú)法判斷該循環(huán)是否是死循環(huán)

還有就是Tree API很容易讓人聯(lián)想到一個(gè)問(wèn)題:可否在遍歷AST的過(guò)程中改變AST的結構?
這是個(gè)激動(dòng)人心的話(huà)題,運行期改變源碼的AST是一個(gè)想象空間很大的想法,就像在groovy和ruby中能辦到的那樣,這能成為一種強大的元編程機制
不過(guò),從java的Tree API規范上講,是不能在遍歷AST過(guò)程中修改AST的結構的,但目前有一個(gè)bug可以做到:Erni08b.pdf
并且目前的Project Lombok就是基于此bug實(shí)現; 就未來(lái)的版本發(fā)展來(lái)講,利用此bug來(lái)實(shí)現功能是不可靠的, Project Lombok的開(kāi)發(fā)者對此也表示擔心
不過(guò)這個(gè)bug也不失為一種必要時(shí)的選擇,畢竟通過(guò)它能實(shí)現的功能很酷

Posted by pf_miles java

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
JDK6的新特性之四:使用Compiler API
Java SE 6 新特性: 編譯器 API
InfoQ: Java深度歷險(一)——Java字節代碼的操縱
玩轉動(dòng)態(tài)編譯:一、初識
java動(dòng)態(tài)編譯執行
Java SE6調用Java編譯器的兩種新方法
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久