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 }
TomcatAdapter
从427可以知道,Tomcat使用了org.eclipse.jdt.internal.compiler.Compiler#compile(ICompilationUnit[])
当然,在这之前使用了很多代码来进行配置。
第一篇文章就到这里。