| Comments
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包
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ò)程
一個(gè)廣義的、管理“文件”資源的接口,并不一定指“操作系統的磁盤(pán)文件系統”
其實(shí)JavaFileManager只是一個(gè)接口,只要行為正確,那么就無(wú)所謂“文件”到底以何種形式、實(shí)際被存放在哪里
這是基于磁盤(pán)文件的JavaFileManager實(shí)現, 所有的文件查找、新文件輸出位置都在磁盤(pán)上完成;也就是說(shuō),如果直接使用默認的StandardJavaFileManager來(lái)做動(dòng)態(tài)編譯,那么得到的效果就跟命令行中直接使用javac編譯差不多
如果想要將編譯好的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文件放在內存中”這一策略所影響,所以需要兼容
下面想介紹的其實(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í)現了
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í)現及其用途:
有了上面這個(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*************}visitClass的處理,對“定義嵌套類(lèi)”這種行為進(jìn)行了報錯visitAssert的處理,凡是遇到代碼中出現assert語(yǔ)句的,均給出錯誤信息visitBreak和visitContinue的處理禁止了goto語(yǔ)句(即帶label的break和continue語(yǔ)句)visitDoWhileLoop等循環(huán)語(yǔ)法結構的訪(fǎng)問(wèn),以及部分表達式結構的訪(fǎng)問(wèn)(如visitBinary、visitLiteral),禁止了如while(true)、for(;;)或while(1<2)等明顯死循環(huán)接下來(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)對應的行號、列號的方法,用于輸出錯誤信息
在processor的init方法中被置入的Trees工具實(shí)例,最大的用處就是獲取對應AST節點(diǎn)的行號、列號,具體代碼參見(jiàn)上述resolveRowAndCol方法
有必要討論下什么樣的控制,適合用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í)現的功能很酷
聯(lián)系客服