Java是一個開放的平臺,對於除釋出編譯器/直譯器/基礎類庫之外,該語言的負責機構更多的是制定一系列標準,任何符合標準的廠商產品均可用於市場投放。甚至包括其編譯器及直譯器。
(比如Hibernate提供了JPA實現;Tomcat實現了Java EE伺服器標準,其Servlet容器通過了Java認證;各資料庫或中介軟體廠商也根據JDBC介面開發驅動。說白了,Java基本就是都提供介面,然後讓廠商開發實現,因此有時候我會罵,邊罵邊編碼!)
GCC有java編譯器,可以看看。
我們主要主要介紹Eclipse自己開發和使用的針對Java的編譯器:(ecj) the Eclipse Compiler for Java。Eclipse沒有使用JDK自帶的編譯器,而是自己開發的,ecj也通過了java的驗證。
除了Eclipse之外,Tomcat也用到了Ecj,用於動態編譯jsp檔案。我們安裝Tomcat後可在lib資料夾下找到ecj:
現在問題來了:怎麼取得ecj原始碼呢?
別急,我們從tomcat原始碼中檢視一下:
雖然我不熟ant,但我也能知道,Tomcat6.0.37中使用的ecj下載路徑是:
http://archive.eclipse.org/eclipse/downloads/drops4/R-4.2.2-201302041200/ecj-4.2.2.jar
這個還是class檔案,剛開始我也沒轍,不過後來我靈機一動:在ecj後面加個src竟然成了!-_-
http://archive.eclipse.org/eclipse/downloads/drops4/R-4.2.2-201302041200/ecjsrc-4.2.2.jar
下面是我下載好後倒入專案檔案後截圖:
這個檔案報錯,不過可以把他刪除了看,我先沒有刪除,因為這個檔案是ecj與ant的橋樑。從原始碼可以看出這個JDTCompilerAdapter是繼承自ant的DefaultCompilerAdapter,用於ant的編譯器介面卡。個人感覺ecj從程式碼(技術)上並沒有耦合任何一個呼叫者,這裡的ant也只是一個介面卡,你刪除或者留著沒有任何影響。Tomcat裡也沒有使用ant。
我從這裡主要是想看看高層怎麼呼叫ecj來編譯程式碼,我們看看關鍵程式碼:
1 private static String compilerClass = "org.eclipse.jdt.internal.compiler.batch.Main"; //$NON-NLS-1$ 2 3 /** 4 * Performs a compile using the JDT batch compiler 5 * @throws BuildException if anything wrong happen during the compilation 6 * @return boolean true if the compilation is ok, false otherwise 7 */ 8 public boolean execute() throws BuildException { 9 this.attributes.log(AntAdapterMessages.getString("ant.jdtadapter.info.usingJDTCompiler"), Project.MSG_VERBOSE); //$NON-NLS-1$ 10 Commandline cmd = setupJavacCommand(); 11 12 try { 13 Class c = Class.forName(compilerClass); 14 Constructor batchCompilerConstructor = 15 c.getConstructor(new Class[] { 16 PrintWriter.class, 17 PrintWriter.class, 18 Boolean.TYPE, 19 Map.class}); 20 Object batchCompilerInstance = 21 batchCompilerConstructor.newInstance(new Object[] { 22 new PrintWriter(System.out), 23 new PrintWriter(System.err), 24 Boolean.TRUE, 25 this.customDefaultOptions}); 26 Method compile = 27 c.getMethod("compile", new Class[] {String[].class}); //$NON-NLS-1$ 28 Object result = 29 compile.invoke(batchCompilerInstance, new Object[] { 30 cmd.getArguments()}); 31 final boolean resultValue = ((Boolean) result).booleanValue(); 32 if (!resultValue && this.logFileName != null) { 33 this.attributes.log(AntAdapterMessages.getString("ant.jdtadapter.error.compilationFailed", this.logFileName)); //$NON-NLS-1$ 34 } 35 return resultValue; 36 } catch (ClassNotFoundException cnfe) { 37 throw new BuildException(AntAdapterMessages.getString("ant.jdtadapter.error.cannotFindJDTCompiler")); //$NON-NLS-1$ 38 } catch (Exception ex) { 39 throw new BuildException(ex); 40 } 41 }
我把程式碼換了下行,大家看13和26行,可以看出這裡使用了
org.eclipse.jdt.internal.compiler.batch.Main#compile(String[])方法來進行編譯,我們可以稍微看看:
從原始碼上來看1664是配置,1684可能是編譯,不過我們先不細看。
我們再看看Tomcat怎麼使用ecj的,我們檢視org.apache.jasper.compiler.JDTCompiler原始碼(我貼出了原始碼,不過有點長):
1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.jasper.compiler; 19 20 import java.io.BufferedOutputStream; 21 import java.io.BufferedReader; 22 import java.io.ByteArrayOutputStream; 23 import java.io.File; 24 import java.io.FileInputStream; 25 import java.io.FileNotFoundException; 26 import java.io.FileOutputStream; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.io.InputStreamReader; 30 import java.io.Reader; 31 import java.util.ArrayList; 32 import java.util.HashMap; 33 import java.util.Locale; 34 import java.util.Map; 35 import java.util.StringTokenizer; 36 37 import org.apache.jasper.JasperException; 38 import org.eclipse.jdt.core.compiler.IProblem; 39 import org.eclipse.jdt.internal.compiler.ClassFile; 40 import org.eclipse.jdt.internal.compiler.CompilationResult; 41 import org.eclipse.jdt.internal.compiler.Compiler; 42 import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies; 43 import org.eclipse.jdt.internal.compiler.ICompilerRequestor; 44 import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; 45 import org.eclipse.jdt.internal.compiler.IProblemFactory; 46 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; 47 import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; 48 import org.eclipse.jdt.internal.compiler.env.INameEnvironment; 49 import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; 50 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; 51 import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; 52 53 /** 54 * JDT class compiler. This compiler will load source dependencies from the 55 * context classloader, reducing dramatically disk access during 56 * the compilation process. 57 * 58 * @author Cocoon2 59 * @author Remy Maucherat 60 */ 61 public class JDTCompiler extends org.apache.jasper.compiler.Compiler { 62 63 64 /** 65 * Compile the servlet from .java file to .class file 66 */ 67 protected void generateClass(String[] smap) 68 throws FileNotFoundException, JasperException, Exception { 69 70 long t1 = 0; 71 if (log.isDebugEnabled()) { 72 t1 = System.currentTimeMillis(); 73 } 74 75 final String sourceFile = ctxt.getServletJavaFileName(); 76 final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath(); 77 String packageName = ctxt.getServletPackageName(); 78 final String targetClassName = 79 ((packageName.length() != 0) ? (packageName + ".") : "") 80 + ctxt.getServletClassName(); 81 final ClassLoader classLoader = ctxt.getJspLoader(); 82 String[] fileNames = new String[] {sourceFile}; 83 String[] classNames = new String[] {targetClassName}; 84 final ArrayList problemList = new ArrayList(); 85 86 class CompilationUnit implements ICompilationUnit { 87 88 String className; 89 String sourceFile; 90 91 CompilationUnit(String sourceFile, String className) { 92 this.className = className; 93 this.sourceFile = sourceFile; 94 } 95 96 public char[] getFileName() { 97 return sourceFile.toCharArray(); 98 } 99 100 public char[] getContents() { 101 char[] result = null; 102 FileInputStream is = null; 103 try { 104 is = new FileInputStream(sourceFile); 105 Reader reader = 106 new BufferedReader(new InputStreamReader(is, ctxt.getOptions().getJavaEncoding())); 107 if (reader != null) { 108 char[] chars = new char[8192]; 109 StringBuffer buf = new StringBuffer(); 110 int count; 111 while ((count = reader.read(chars, 0, 112 chars.length)) > 0) { 113 buf.append(chars, 0, count); 114 } 115 result = new char[buf.length()]; 116 buf.getChars(0, result.length, result, 0); 117 } 118 } catch (IOException e) { 119 log.error("Compilation error", e); 120 } finally { 121 if (is != null) { 122 try { 123 is.close(); 124 } catch (IOException exc) { 125 // Ignore 126 } 127 } 128 } 129 return result; 130 } 131 132 public char[] getMainTypeName() { 133 int dot = className.lastIndexOf('.'); 134 if (dot > 0) { 135 return className.substring(dot + 1).toCharArray(); 136 } 137 return className.toCharArray(); 138 } 139 140 public char[][] getPackageName() { 141 StringTokenizer izer = 142 new StringTokenizer(className, "."); 143 char[][] result = new char[izer.countTokens()-1][]; 144 for (int i = 0; i < result.length; i++) { 145 String tok = izer.nextToken(); 146 result[i] = tok.toCharArray(); 147 } 148 return result; 149 } 150 151 public boolean ignoreOptionalProblems() { 152 return false; 153 } 154 } 155 156 final INameEnvironment env = new INameEnvironment() { 157 158 public NameEnvironmentAnswer 159 findType(char[][] compoundTypeName) { 160 String result = ""; 161 String sep = ""; 162 for (int i = 0; i < compoundTypeName.length; i++) { 163 result += sep; 164 result += new String(compoundTypeName[i]); 165 sep = "."; 166 } 167 return findType(result); 168 } 169 170 public NameEnvironmentAnswer 171 findType(char[] typeName, 172 char[][] packageName) { 173 String result = ""; 174 String sep = ""; 175 for (int i = 0; i < packageName.length; i++) { 176 result += sep; 177 result += new String(packageName[i]); 178 sep = "."; 179 } 180 result += sep; 181 result += new String(typeName); 182 return findType(result); 183 } 184 185 private NameEnvironmentAnswer findType(String className) { 186 187 InputStream is = null; 188 try { 189 if (className.equals(targetClassName)) { 190 ICompilationUnit compilationUnit = 191 new CompilationUnit(sourceFile, className); 192 return 193 new NameEnvironmentAnswer(compilationUnit, null); 194 } 195 String resourceName = 196 className.replace('.', '/') + ".class"; 197 is = classLoader.getResourceAsStream(resourceName); 198 if (is != null) { 199 byte[] classBytes; 200 byte[] buf = new byte[8192]; 201 ByteArrayOutputStream baos = 202 new ByteArrayOutputStream(buf.length); 203 int count; 204 while ((count = is.read(buf, 0, buf.length)) > 0) { 205 baos.write(buf, 0, count); 206 } 207 baos.flush(); 208 classBytes = baos.toByteArray(); 209 char[] fileName = className.toCharArray(); 210 ClassFileReader classFileReader = 211 new ClassFileReader(classBytes, fileName, 212 true); 213 return 214 new NameEnvironmentAnswer(classFileReader, null); 215 } 216 } catch (IOException exc) { 217 log.error("Compilation error", exc); 218 } catch (org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException exc) { 219 log.error("Compilation error", exc); 220 } finally { 221 if (is != null) { 222 try { 223 is.close(); 224 } catch (IOException exc) { 225 // Ignore 226 } 227 } 228 } 229 return null; 230 } 231 232 private boolean isPackage(String result) { 233 if (result.equals(targetClassName)) { 234 return false; 235 } 236 String resourceName = result.replace('.', '/') + ".class"; 237 InputStream is = 238 classLoader.getResourceAsStream(resourceName); 239 return is == null; 240 } 241 242 public boolean isPackage(char[][] parentPackageName, 243 char[] packageName) { 244 String result = ""; 245 String sep = ""; 246 if (parentPackageName != null) { 247 for (int i = 0; i < parentPackageName.length; i++) { 248 result += sep; 249 String str = new String(parentPackageName[i]); 250 result += str; 251 sep = "."; 252 } 253 } 254 String str = new String(packageName); 255 if (Character.isUpperCase(str.charAt(0))) { 256 if (!isPackage(result)) { 257 return false; 258 } 259 } 260 result += sep; 261 result += str; 262 return isPackage(result); 263 } 264 265 public void cleanup() { 266 } 267 268 }; 269 270 final IErrorHandlingPolicy policy = 271 DefaultErrorHandlingPolicies.proceedWithAllProblems(); 272 273 final Map settings = new HashMap(); 274 settings.put(CompilerOptions.OPTION_LineNumberAttribute, 275 CompilerOptions.GENERATE); 276 settings.put(CompilerOptions.OPTION_SourceFileAttribute, 277 CompilerOptions.GENERATE); 278 settings.put(CompilerOptions.OPTION_ReportDeprecation, 279 CompilerOptions.IGNORE); 280 if (ctxt.getOptions().getJavaEncoding() != null) { 281 settings.put(CompilerOptions.OPTION_Encoding, 282 ctxt.getOptions().getJavaEncoding()); 283 } 284 if (ctxt.getOptions().getClassDebugInfo()) { 285 settings.put(CompilerOptions.OPTION_LocalVariableAttribute, 286 CompilerOptions.GENERATE); 287 } 288 289 // Source JVM 290 if(ctxt.getOptions().getCompilerSourceVM() != null) { 291 String opt = ctxt.getOptions().getCompilerSourceVM(); 292 if(opt.equals("1.1")) { 293 settings.put(CompilerOptions.OPTION_Source, 294 CompilerOptions.VERSION_1_1); 295 } else if(opt.equals("1.2")) { 296 settings.put(CompilerOptions.OPTION_Source, 297 CompilerOptions.VERSION_1_2); 298 } else if(opt.equals("1.3")) { 299 settings.put(CompilerOptions.OPTION_Source, 300 CompilerOptions.VERSION_1_3); 301 } else if(opt.equals("1.4")) { 302 settings.put(CompilerOptions.OPTION_Source, 303 CompilerOptions.VERSION_1_4); 304 } else if(opt.equals("1.5")) { 305 settings.put(CompilerOptions.OPTION_Source, 306 CompilerOptions.VERSION_1_5); 307 } else if(opt.equals("1.6")) { 308 settings.put(CompilerOptions.OPTION_Source, 309 CompilerOptions.VERSION_1_6); 310 } else if(opt.equals("1.7")) { 311 settings.put(CompilerOptions.OPTION_Source, 312 CompilerOptions.VERSION_1_7); 313 } else { 314 log.warn("Unknown source VM " + opt + " ignored."); 315 settings.put(CompilerOptions.OPTION_Source, 316 CompilerOptions.VERSION_1_5); 317 } 318 } else { 319 // Default to 1.5 320 settings.put(CompilerOptions.OPTION_Source, 321 CompilerOptions.VERSION_1_5); 322 } 323 324 // Target JVM 325 if(ctxt.getOptions().getCompilerTargetVM() != null) { 326 String opt = ctxt.getOptions().getCompilerTargetVM(); 327 if(opt.equals("1.1")) { 328 settings.put(CompilerOptions.OPTION_TargetPlatform, 329 CompilerOptions.VERSION_1_1); 330 } else if(opt.equals("1.2")) { 331 settings.put(CompilerOptions.OPTION_TargetPlatform, 332 CompilerOptions.VERSION_1_2); 333 } else if(opt.equals("1.3")) { 334 settings.put(CompilerOptions.OPTION_TargetPlatform, 335 CompilerOptions.VERSION_1_3); 336 } else if(opt.equals("1.4")) { 337 settings.put(CompilerOptions.OPTION_TargetPlatform, 338 CompilerOptions.VERSION_1_4); 339 } else if(opt.equals("1.5")) { 340 settings.put(CompilerOptions.OPTION_TargetPlatform, 341 CompilerOptions.VERSION_1_5); 342 settings.put(CompilerOptions.OPTION_Compliance, 343 CompilerOptions.VERSION_1_5); 344 } else if(opt.equals("1.6")) { 345 settings.put(CompilerOptions.OPTION_TargetPlatform, 346 CompilerOptions.VERSION_1_6); 347 settings.put(CompilerOptions.OPTION_Compliance, 348 CompilerOptions.VERSION_1_6); 349 } else if(opt.equals("1.7")) { 350 settings.put(CompilerOptions.OPTION_TargetPlatform, 351 CompilerOptions.VERSION_1_7); 352 settings.put(CompilerOptions.OPTION_Compliance, 353 CompilerOptions.VERSION_1_7); 354 } else { 355 log.warn("Unknown target VM " + opt + " ignored."); 356 settings.put(CompilerOptions.OPTION_TargetPlatform, 357 CompilerOptions.VERSION_1_5); 358 } 359 } else { 360 // Default to 1.5 361 settings.put(CompilerOptions.OPTION_TargetPlatform, 362 CompilerOptions.VERSION_1_5); 363 settings.put(CompilerOptions.OPTION_Compliance, 364 CompilerOptions.VERSION_1_5); 365 } 366 367 final IProblemFactory problemFactory = 368 new DefaultProblemFactory(Locale.getDefault()); 369 370 final ICompilerRequestor requestor = new ICompilerRequestor() { 371 public void acceptResult(CompilationResult result) { 372 try { 373 if (result.hasProblems()) { 374 IProblem[] problems = result.getProblems(); 375 for (int i = 0; i < problems.length; i++) { 376 IProblem problem = problems[i]; 377 if (problem.isError()) { 378 String name = 379 new String(problems[i].getOriginatingFileName()); 380 try { 381 problemList.add(ErrorDispatcher.createJavacError 382 (name, pageNodes, new StringBuffer(problem.getMessage()), 383 problem.getSourceLineNumber(), ctxt)); 384 } catch (JasperException e) { 385 log.error("Error visiting node", e); 386 } 387 } 388 } 389 } 390 if (problemList.isEmpty()) { 391 ClassFile[] classFiles = result.getClassFiles(); 392 for (int i = 0; i < classFiles.length; i++) { 393 ClassFile classFile = classFiles[i]; 394 char[][] compoundName = 395 classFile.getCompoundName(); 396 String className = ""; 397 String sep = ""; 398 for (int j = 0; 399 j < compoundName.length; j++) { 400 className += sep; 401 className += new String(compoundName[j]); 402 sep = "."; 403 } 404 byte[] bytes = classFile.getBytes(); 405 String outFile = outputDir + "/" + 406 className.replace('.', '/') + ".class"; 407 FileOutputStream fout = 408 new FileOutputStream(outFile); 409 BufferedOutputStream bos = 410 new BufferedOutputStream(fout); 411 bos.write(bytes); 412 bos.close(); 413 } 414 } 415 } catch (IOException exc) { 416 log.error("Compilation error", exc); 417 } 418 } 419 }; 420 421 ICompilationUnit[] compilationUnits = 422 new ICompilationUnit[classNames.length]; 423 for (int i = 0; i < compilationUnits.length; i++) { 424 String className = classNames[i]; 425 compilationUnits[i] = new CompilationUnit(fileNames[i], className); 426 } 427 Compiler compiler = new Compiler(env, 428 policy, 429 settings, 430 requestor, 431 problemFactory, 432 true); 433 compiler.compile(compilationUnits); 434 435 if (!ctxt.keepGenerated()) { 436 File javaFile = new File(ctxt.getServletJavaFileName()); 437 javaFile.delete(); 438 } 439 440 if (!problemList.isEmpty()) { 441 JavacErrorDetail[] jeds = 442 (JavacErrorDetail[]) problemList.toArray(new JavacErrorDetail[0]); 443 errDispatcher.javacError(jeds); 444 } 445 446 if( log.isDebugEnabled() ) { 447 long t2=System.currentTimeMillis(); 448 log.debug("Compiled " + ctxt.getServletJavaFileName() + " " 449 + (t2-t1) + "ms"); 450 } 451 452 if (ctxt.isPrototypeMode()) { 453 return; 454 } 455 456 // JSR45 Support 457 if (! options.isSmapSuppressed()) { 458 SmapUtil.installSmap(smap); 459 } 460 461 } 462 463 464 }
從427可以知道,Tomcat使用了org.eclipse.jdt.internal.compiler.Compiler#compile(ICompilationUnit[])
當然,在這之前使用了很多程式碼來進行配置。
第一篇文章就到這裡。
(最後編輯時間2014-01-18 13:59:01)