我爱电脑技术论坛's Archiver

bbs23 发表于 2008-4-8 08:40

Java进阶:在SE6中调用编译器的两种方法

在很多Java应用中需要在程序中调用Java编译器来编译和运行。但在早期的版本中(Java SE5及以前版本)中只能通过tools.jar中的com.sun.tools.javac包来调用Java编译器,但由于tools.jar不是标准的Java库,在使用时必须要设置这个jar的路径。而在Java SE6中为我们提供了标准的包来操作Java编译器,这就是javax.tools包。使用这个包,我们可以不用将jar文件路径添加到classpath中了。
Uc0g8P2@{ rb'AcPU@.S A

3IIP2vo'gk2c$O~@a 一、使用JavaCompiler接口来编译Java源程序 k:Y,i[+V U!k
*t\zn1~

,y[$M3nz Vv S 使用Java API来编译Java源程序有很多方法,现在让我们来看一种最简单的方法,通过JavaCompiler进行编译。
w;G1e1]%G9gh m/U#Vs
iU4nJ |!J-N? dy7t)~fb3i*@
我们可以通过ToolProvider类的静态方法getSystemJavaCompiler来得到一个JavaCompiler接口的实例。 7}"d4E!o{ O7BI
0x#z _[/z6e
T)L9X?G UqM
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+H#mZ)g't6pMm
qbb#w6Q
f"n[p q6{ JavaCompiler中最核心的方法是run。通过这个方法可以编译java源程序。这个方法有3个固定参数和1个可变参数(可变参数是从Jave SE5开始提供的一个新的参数类型,用type… argu表示)。前3个参数分别用来为java编译器提供参数、得到Java编译器的输出信息以及接收编译器的错误信息,后面的可变参数可以传入一个或多个Java源程序文件。如果run编译成功,返回0。 1[b&[:kpl

5l;J(qx6H-e O!y P 0h-oSK\H,p1t4{+u
int run(InputStream in, OutputStream out, OutputStream err, String... arguments) .G5X*I4wl
7Kuk/e,U BrjT0H%y

w+s!uR-jvmf 如果前3个参数传入的是null,那么run方法将以标准的输入、输出代替,即System.in、System.out和System.err。如果我们要编译一个test.java文件,并将使用标准输入输出,run的使用方法如下:
'g~\5G3pB)D6^0? xHhtm-?udsS?^
W e:B)g ['F
int results = tool.run(null, null, null, "test.java");
7w6|,s H%N X*CNVb!MyR%t|j
bvXiXH
下面是使用JavaCompiler的完整代码: |PG!`V!Yy

'{,@ H Bk`!]/U I ] Wq \0k.M2M
import java.io.*;
Zb5`3W#L*\s!C+S @
7E.I}g H+z8Gm g import javax.tools.*; X\1[)R JK^

~8F bl;x"In
2?1o @W n.K&Nv;w public class test_compilerapi
8u^ji Z*k
,Y3?P7j.~I { yDx@ }+e tO;q
Es[H5jQ?.|2vA
 public static void main(String args[]) throws IOException
/g7e&L-m8I G7t4B/Ukl
 { 'y| U1]r;i
e}8h9~n ?9S,h*E v
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
2Ao}Zz9ta+zj1R
1Pxz]t e int results = compiler.run(null, null, null, "test.java");
3T-FL]J? x-?3@d1? \$K3Ip
System.out.println((results == 0)?"编译成功":"编译失败");
|5k`DNpJ
3Jrm~2y1@^ // 在程序中运行test
;m SU4{ap$W _$_7q'@:I2Q2Q2l w j
Runtime run = Runtime.getRuntime(); D6IGSw#dRv

4p*j%T"mekZ0Q Process p = run.exec("java test");
/LRWG(Mt
Nj i$i`$T#Z e} BufferedInputStream in = new BufferedInputStream(p.getInputStream());
UX-As3q.`m
d\+H(v)e%UETQu BufferedReader br = new BufferedReader(new InputStreamReader(in));
E8}l[ d(F8o+U@ $jR"f[c\1w~;D
String s; ]O*CR`I!V1@

.L-_8\9^#KV|1zTT] while ((s = br.readLine()) != null) h2v;V2[Z,G:v2]pi
\5p`gAq'@$kZ
 System.out.println(s);
.DI y1G.G Z$X-av:Nf v4N
 }
!H(cS4H*s&y s
]&S~f"D3c }
o2zr%G+mWc
/Kf mO-s B
wofuf P%f;yF public class test
r `t)UK3e8D)B @p,j%fu%F _Yq)N
{
2Yk7D3i3W%?(a
k:Pk/iN9r2[y-[(]l  public static void main(String[] args) throws Exception
L7hG`w_
ldPpxpl!IQ3C0L  {
Uz+iO {E0I_ z"bW1Rtn$mR
System.out.println("JavaCompiler测试成功!"); @F^0^DqeD i"E4I
(Q9i!jD+k8X3i
 } E[/{"V__+lFVL

1kDR_Dn[ o6f$zh }
1| fZ&vm1I U'h/^ },I\ r*W

3y2n"Xa A DS@B 编译成功的输出结果: fs_c$}}t,^x

&Y}*BkW%Ex{Y:d u/t2a"k P%g
编译成功 T U,v.y ls9u}
tS+^l;E6?&?
!WAje:a8{Q m
JavaCompiler测试成功
}1~R&?q9`N 5`2Ew6hi3MI N

%^ f5W+lC 编译失败的输出结果:
_W*d0ID-q %Qn(^tEWoO2e

fuf7Q%b'Qa0D test.java:9: 找不到符号 dY5p'QX%A9m
\SM5`n C
符号: 方法 printlnln(java.lang.String)
b l Y n_,\ c] ] C e/@
位置: 类 java.io.PrintStream M0Mr.`^8j@
qJ0m7l C3RY
System.out.printlnln("JavaCompiler测试成功!");
'z4IFL p/y7Fe&B0q(F L+Pw1@kU
^
9WhL Cw,G?
9I2e Y~?eV [2AF 1 错误
5z Lo1j9z4LX%F
4c;_$\ CnG 编译失败 /eq6x2K;^V

W ~%nPC+o'zn z
\3v d RhU NX 二、使用StandardJavaFileManager编译Java源程序
9oT?Zr3p
b0`^:dRC.Ly
u4Zo Ch E 在第一部分我们讨论调用java编译器的最容易的方法。这种方法可以很好地工作,但它确不能更有效地得到我们所需要的信息,如标准的输入、输出信息。而在Java SE6中最好的方法是使用StandardJavaFileManager类。这个类可以很好地控制输入、输出,并且可以通过DiagnosticListener得到诊断信息,而DiagnosticCollector类就是listener的实现。
~iZF9p2T]1bY qLQ!Ih!]^

%wp9Or@#?nbX[ 使用StandardJavaFileManager需要两步。首先建立一个DiagnosticCollector实例以及通过JavaCompiler的getStandardFileManager()方法得到一个StandardFileManager对象。最后通过CompilationTask中的call方法编译源程序。
7ZO_"IAQ,u;wi%CW %p;e"uW:_7E IQ7`_
在使用这种方法调用Java编译时最复杂的方法就是getTask,下面让我们讨论一下getTask方法。这个方法有如下所示的6个参数。
P P?[mV \ E%l0O-d8I
z{/?9aO\ r?.DsO
getTask(Writer out,JavaFileManager fileManager, 2`Fmx2{Ysg:i7o
4y0A$a5i)Rl2I
DiagnosticListener diagnosticListener,
IEK b;|'UJ-xZ H$S
)}~"L@*cLb6Q;w z[ Iterable options, *s5UE/}h.n

/fR~ OT { Iterable classes,
|Ar)z{_p9I 1}M})x2Z"O h&U
Iterable compilationUnits) ? _r0{*em/I

MO7\pH6G
NK s3fG L:j4McE 这些参数大多数都可为null。它们的含义所下。
x2L3R] Z7X9Hu+[+M +S&v U3VL|7t'_,t6R:J
9?7Y8q!LG7h-XU
·out::用于输出错误的流,默认是System.err。
*M-^`t6p1u
r&{ V2ey3Ci p7X HU P
-WY} fR/^ ·fileManager::标准的文件管理。 (aFL0F6X h`]0z

X9S2R]MBan[
0M$B1y;l"]R ·diagnosticListener: 编译器的默认行为。 ,o hp:z O*yB
4_,C+?5o1vP[)L [+A
3w q`;vM4l/^
·options: 编译器的选项
u4]Qd-O;[0O
0dhkm1Lg+N
.oS_&Mh]?c ~#nDKE ·classes:参与编译的class。 T5Q Uj#rd8KWM
/Jo"v(i m'e/n@

,WX#wx6ZV] 最后一个参数compilationUnits不能为null,因为这个对象保存了你想编译的Java文件。 @#D.ng)p l
sTy;{3jM

6V I:^gCk 在使用完getTask后,需要通过StandardJavaFileManager的getJavaFileObjectsFromFiles或getJavaFileObjectsFromStrings方法得到compilationUnits对象。调用这两个方法的方式如下:.
$s,d4w1_jDMNm 'O^#M TO L2v
)SZo5``&uP4MwN
Iterable getJavaFileObjectsFromFiles(
W*YE`-\}7i){(k] x"@1P Dq#P HV4l u
Iterable files) I4chMy^'e"~

-pkk;t)Q'U#^(E&k7A Iterable getJavaFileObjectsFromStrings(
'p y+f@cdV$`
x3JW`&X!^^"C Iterable names) 4r"VJX|
cHMBQy5UZ

3X6\0N;~ds1I+V&A"~z2M String[] filenames = …;
@&ig&q ?"j` X (N4GI&G?j
Iterable compilationUnits = y6AbuUe}0P:@

'tK'u4jbuP fileManager.getJavaFileObjectsFromFiles(Arrays.asList(filenames));
1?_b4] TnHbsY)F
(r PPw-\E?
GL3J]wf JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, w`DFE8~$n{lSwf
9{^q }4`
diagnostics, options, null, compilationUnits);
JI9V*S{Z!y9G j "`yU$Y"G3m:v+{

V;b[-qv#qK 最后需要关闭fileManager.close();
q;y%TOuI d(KC
6f*[3P2c3m f5TP+o '~9V0LmY8\ x^D y
下面是一个完整的演示程序。
)K:D/Vuz/O-@f Y}AnZHo|

_7s-r6lVNfEu{ import java.io.*; O x'{WT p
:]0nD!RT,G N5h
import java.util.*; 6|Y(r\3G1R
*S+Xl,])G0@.[,A.p O
import javax.tools.*; wd%x&B)Hs,WR

8Y A P:x;HkWJ!@Ff
L?c ?r0@/CD9D0FrV public class test_compilerapi mG2}$J O3_G\
g(b6G ];b |mj+q
{ l9L(FI:r+MT;R:ac

)dxS(Ad8];d3l  private static void compilejava() throws Exception
&Q:Yb6cSr0a"w0N
,I3Av2y6\+xH  { j4N DD2u|#P:Y0Ui
eL b.u*\1y r7i
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
;f(vFv X AF,[ D]%S6P9I&I2]R;OC
// 建立DiagnosticCollector对象 #]#Yf7]/jQ}V#E*S
K'{D'r#jalq
DiagnosticCollector diagnostics = new DiagnosticCollector(); 6h ?%ZioJ B.U

;VJ J\l StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
'N&l E3?dm/}{6{-tr ,PS#QY,D
// 建立用于保存被编译文件名的对象 G*{2`!Ug7|2p,b

Z zQ"esJO&e(z6NN // 每个文件被保存在一个从JavaFileObject继承的类中
^I&iW m!u T/}
L,tG O/K.F*P(? Iterable compilationUnits = fileManager
zD4`,i/i;Z9s&~ SFs F1Q8G k&W _L
.getJavaFileObjectsFromStrings(Arrays asList("test3.java")); Vt&B{h-\0Ea
*aA6r+VFF$D
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, +{]{H7ER6OZ

%Of3yT ]1T diagnostics, null, null, compilationUnits); 6]'{-eGU$YV9E

t0Q`3I'de"~5XtQe // 编译源程序 Y.h~juQ*G3}I4C fz
P6x5Y] A;Ln
boolean success = task.call(); Vt;W_9J |sz.S
$K!x/m ec/sRc
fileManager.close();
T'\#HT{ ;V ~bp~m
System.out.println((success)?"编译成功":"编译失败");
/^}bc z
]sS2gZ)R%@.S-B8f  }
f'M |)@2g O'J3{)L7u)O
%KT3bg1z*l9?x7z  public static void main(String args[]) throws Exception
.p4~U5i,lz5_9x zV(A ~X#`W
 { ~%g5l'\@#kYNY
T [$xo;\+n
compilejava();
:I~4ZdU &Vfo W)|"Q:`1So
 }
$s0g;v]Wc@b
,v \(fXx` }
(z'HVa&FHB
U'wh.Ve o6J
W[1dF G)X)z;^5A] 如果想得到具体的编译错误,可以对Diagnostics进行扫描,代码如下: w)z8g GgLw

;}!_}%N2U1P @ 7U@-H\q4gnxH
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) @*f"K3cL$M
;F+m;bTh(TB
System.out.printf(
]pr&er&Q j]'s.Af'JP
"Code: %s%n" + {4jQiA/ZM

]6N,p'N%jT"j$Sp)G4~ "Kind: %s%n" + v,} JN1`M:Y

;qS~s&V6P%Te "Position: %s%n" +
z:m4VAP)E #T6q`wX!W[A
"Start Position: %s%n" +
QpHIk:O p!h
WC'd8T&^ "End Position: %s%n" +
;bRP7P0Y;t O
+^$u k.nCc5O "Source: %s%n" +
3Q"G(m/L#]"},R1q ]3D&v /C1B)QABG
"Message: %s%n", { KxZS:W%B
4Tr3b;p W?M
diagnostic.getCode(), diagnostic.getKind(),
*mEq a\9}|4\ ~B +CT YF)|H
diagnostic.getPosition(), diagnostic.getStartPosition(),
iTMg-lN+oQF
F^oV|%f[e diagnostic.getEndPosition(), diagnostic.getSource(), 5I(X_0va|RK`

7riW1_ ` V diagnostic.getMessage(null)); ;M}8kMu']?

u~2sC6UZ,s4g #E Rft|k
被编译的test.java代码如下: *[z? ^7O mI

Tmw"L,m-| #@-a}:B4[ bVa.Hk
public class test CX0x(g pM'DV!S%v@v

5FY5x9tG {
NsW?1W3o0fn#p
J8S/V Kw1rK5hqF  public static void main(String[] args) throws Exception
&{X}/LQ
.w A+N+@-LG]5W  {
c*jC1uy}k8?'oL
]pq/FL-n c aa; //错误语句 2hy1OsQDL
aa4u{%\!q pc
System.out.println("JavaCompiler测试成功!");
}3^)lU#Xh W3Q
,iNW"V1X}BK  }
@h#P x'?!Ndd.A_
I,j4y%T'fds }
(Oi[R3Xl-m
n;z^-Ku;U NX;Q}
&o9n&gEM| 在这段代码中多写了个aa,得到的编译错误为: 3Pw:Ww"_!`*] m!t,i@

e#B.G!\5F6r;Sr
rvX(|;lp+k Code: compiler.err.not.stmt A6h Y"OH }Up)PR%?
O9ev IR%r-S"h{C t?
Kind: ERROR b!RFb;nRl

u$nj zi&v,C.i:I2L8Y Position: 89
2^O,f1v-aJ(?L/P6c
7]l0i3V.v Start Position: 89 .O&Y1VTr?j$U @
5xc-wqC"g ASQ9YA
End Position: 89
a'f(\IM&x4]%x!m
,X+wO2qy,Eq Source: test.java YwW'}e9z |X4n
E3ADv"Xo&R
Message: test.java:5: 不是语句 CX.w/B;yK*q

-O0P3x(c8C"X Success: false bz/EW(mO!u

'@9OyJrc r
~Y6a a W h*Ic&`t 通过JavaCompiler进行编译都是在当前目录下生成.class文件,而使用编译选项可以改变这个默认目录。编译选项是一个元素为String类型的Iterable集合。如我们可以使用如下代码在D盘根目录下生成.class文件。
_b@1fr `-`(VIw
+X-Y_1aCHrI p mZQj Yi kPI
Iterable options = Arrays.asList("-d", "d:\\");
%m_$r1g/O G|Q.Z]S:L8n^^E#C
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, H;g \7j&b)SU
J*MO5e/@8l/?tu
diagnostics, options, null, compilationUnits); TL#EQo%Tj
'Y.T7JB4f-H
:Q^ xsI;u;~
在上面的例子中options处的参数为null,而要传递编译器的参数,就需要将options传入。
'R4a [.s5ED #[[_ B?2c$fU S

q#l&aE,R 有时我们编译一个Java源程序文件,而这个源程序文件需要另几个Java文件,而这些Java文件又在另外一个目录,那么这就需要为编译器指定这些文件所在的目录。 o6M!t}(Wy
B:iXn8lnP
f1uH6f(?
Iterable options = Arrays.asList("-sourcepath", "d:\\src"); $wW&Zv!ZC

S G#hR;K0rX3~
a@bQ%x!f 上面的代码指定的被编译Java文件所依赖的源文件所在的目录。
8{3_V,X)g
:v4\g\'qQ%?,S 三、从内存中编译
#b;x^T:g!e7?M d6dc
u4y leXOJP
7B%i3U+L4iMyHQ^ JavaCompiler不仅可以编译硬盘上的Java文件,而且还可以编译内存中的Java代码,然后使用reflection来运行它们。我们可以编写一个JavaSourceFromString类,通过这个类可以输入Java源代码。一但建立这个对象,你可以向其中输入任意的Java代码,然后编译和运行,而且无需向硬盘上写.class文件。
M P"_PP4`)o
{.b%z9[&|c-RBkJ4C I5m{`$^y{/y:|
import java.lang.reflect.*; j5V0oZ:mV sPf
~N*]eX"{f^o x
import java.io.*;
(_x-}%Mhg6B :qf#R6Go0r
import javax.tools.*; $I1QL7Q0@F-W

/|@#@R@F9t import javax.tools.JavaCompiler.CompilationTask; y ?.BrS+?}SF&skO
#J9^lD|
import java.util.*;
P.Nt oT/PD :`1wDF7ln(~+U
import java.net.*;
!`dy}2B&W +q(]$_1B+J?*AQ
&n;H0et J
public class test_compilerapi
?x dC5U;K
7My'@Y,]-} D { #[)m'VNJ:l6TU7W

lT&t!Xq(iN  private static void compilerJava() throws Exception
n-d7j,dmrbS 9l/{uRzz`z8E
 {
Dnb3Sa6e7g/C 9oqA3m&X/D0z#M4b2S
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
0c#[8q9g|-l|Cfd )o_Fj*w)v(i(u
DiagnosticCollector diagnostics = new DiagnosticCollector(); 0|V.q4j6P u&m

a)Lt,Xq2U1k+fZ // 定义一个StringWriter类,用于写Java程序 ?*RX!o @@rI2tn

2O2HW1OM y w StringWriter writer = new StringWriter(); ye8j6j k2zx6`YC
L\7H K:~
PrintWriter out = new PrintWriter(writer); p"C[l7p(y&O8o,G._
A n(Q;@1q/R#xxR
// 开始写Java程序 b KI!IR

v"E1``8w D B C out.println("public class HelloWorld {");
,{h _+m.sX}!l e tLD|U HM
out.println(" public static void main(String args[]) {");
d~3X,am e(Z4J'V 6H5G3_:}6LU _T'o
out.println(" System.out.println(\"Hello, World\");");
&f T4S%`$t$C,|\
O@3s ?%O/U out.println(" }");
t-In&]jLZ FAX VY[(y0h
out.println("}");
W ^!ED8X
4s5gJ)i0I out.close();
N+q"p"|G%j$V a4H?*yOZk
//为这段代码取个名子:HelloWorld,以便以后使用reflection调用 7K-ii!ia(oNE
"bSt0p8\{AX V
JavaFileObject file = new JavaSourceFromString("HelloWorld", writer.toString());
u K9j K#?1KE6K 5U7T,e(k$z6`s
Iterable compilationUnits = Arrays.asList(file); $g$n%rC'n:sl1IB q

(PsK.Z2[!WO JavaCompiler.CompilationTask task = compiler.getTask(null, null, E"q9m2M#?

\s;c)]/q  diagnostics, null, null, compilationUnits);
.h^OJu5K+KM[n R N4y,|A
boolean success = task.call(); OAb1n_ gSP
r*w6A3rsx x
System.out.println("Success: " + success); 7aLUW X Y.n]i
aB,Ln ^ @Q
// 如果成功,通过reflection执行这段Java程序
D!x b?QC;o
^J+t1k%j if (success) Yl8vHu(`y

T~(T/`L!v {
"Z+b/eXcdc4{b
%D"l3DA }%Cq`&as  System.out.println("-----输出-----");
)L hB x(O3tu
SSsmh  Class.forName("HelloWorld").getDeclaredMethod("main", new Class[]
\y ]+lACVN
_O Sf/` { String[].class }).invoke(null, new Object[]
,uP0mT-C oY y
9`Tov{!~ { null }); ~(xj-o8SL0\
9J Li({`5U+K
 System.out.println("-----输出 -----");
L:v8qq3@([&bX 2p"[*]O;B [
} I,kA2sQ?(fS

HPL @4g;eK*y Tf/c9E  }
*aS&w:]t LM c!spi A!s q
 public static void main(String args[]) throws Exception 0g1w}0w | |\

#v'cCy0c Io Dzw"qG  { )a&`5Q eHma

f1tb:j;{ F4KO8T H compilerJava();
^w j:kC;t,J Q5k;rg6b1F#]3n(i
 } 8k(b VH$q;g a/I
W7h,A2^8S%]
} y*Z;GkM+iu^.Fk
2Q+u K V$I;bRo*{
// 用于传递源程序的JavaSourceFromString类 {1lU[ c5kK

%G,GX!Y!x"M f%h5Rb class JavaSourceFromString extends SimpleJavaFileObject &O(k4L*d"lG

waMNs YQ { 9o PMw$hp

/\XkA#Lx%k9utb  final String code; 0I#? `dti5v eB ]
C$e P-S,VeW O D
 JavaSourceFromString(String name, String code) MN/y o kSM0x

xcx/_4X\L  {
\GCI6y
F4d4~-QWz!G-wp super(URI.create("string:///" + name.replace('.', '/')+ Kind.SOURCE.extension), Kind.SOURCE);
9L8C.`+O1|wcE&~&Fd%S zAE+H;^_r{
this.code = code; ,b%Wu!jy3KtZ
1I ZI"@IP,{sC
 }
az+b @:vl %n3hJ+a;b _Q-r}
 @Override G"f.b7M4fO| n%N
'T)` Y/\t
 public CharSequence getCharContent(boolean ignoreEncodingErrors) F!hb*el%a,e)yR)X[

1UU0O8g,q5N&HK  {
?v-n+sC J`5wJ+M ;W&Y H7\3g2P Ymt
return code; jvt OO0BU4zt"g1D
p3k$o}kK[ B%u
 }
+Briq,I2F[gI}I ]p3n q-\+@(\j
}

页: [1]

Powered by Discuz! Archiver 6.1.0  © 2001-2007 Comsenz Inc.