Bây giờ bạn đã thấy cách sử dụng các khung công tác Javassist và BCEL cho hoạt động lớp (xem liệt kê các bài viết trước trong loạt bài này), tôi sẽ cho bạn thấy một ứng dụng hoạt động lớp thực tế. Ứng dụng này đang thay thế việc sử dụng sự phản chiếu bằng các lớp được tạo trong thời gian chạy và được nạp trực tiếp vào JVM. Trong quá trình ráp nó lại với nhau, tôi sắp quay lại hai bài báo đầu tiên của loạt bài này cũng như trình bày Javassist và BCEL, vì thế nó tạo ra một sự kết thúc tốt đẹp cho những gì tạo thành một loạt các bài viết dài.
Các mã phản chiếu theo hiệu năng
Quay lại Phần 2, tôi đã cho thấy cách mã phản chiếu chậm hơn nhiều lần so với mã trực tiếp cho cả truy cập trường và cả các cuộc gọi phương thức. Sự chậm chạp này không phải là một vấn đề cho nhiều ứng dụng, nhưng luôn có các trường hợp ở đó hiệu năng rất quan trọng. Trong những trường hợp này, mã phản chiếu có thể biểu diễn một nút cổ chai thực. Mặc dù việc thay thế mã phản chiếu bằng mã được biên dịch tĩnh có thể rất lộn xộn và trong một số trường hợp (như trong các khung công tác ở đó các lớp hoặc các mục được mã phản chiếu truy cập được cung cấp trong thời gian chạy, chứ không phải là một phần của cùng một quá trình xây dựng) thậm chí không thể thực hiện được nếu không cấu trúc lại toàn bộ ứng dụng.
Hoạt động lớp cung cấp cho bạn một sự thay thế kết hợp hiệu năng của mã được biên dịch tĩnh với tính linh hoạt của mã phản chiếu. Cách tiếp cận cơ bản ở đây là để xây dựng một lớp tùy chỉnh trong thời gian chạy để bao bọc việc truy cập tới các lớp đích (trước đó đã đạt được bằng mã phản chiếu ) theo một cách mà mã mục đích chung của bạn có thể sử dụng. Sau khi nạp các lớp tuỳ chỉnh vào JVM, rồi bạn thiết lập để chạy hết tốc độ.
Liệt kê 1 đưa ra một
điểm khởi đầu cho ứng dụng. Ở đây tôi đã xác định một lớp bean đơn giản,
HolderBean và một lớp truy cập, ReflectAccess. Lớp truy cập có một đối số dòng
lệnh. Đối số này phải có tên của một trong các thuộc tính của lớp bean có
giá trị int (value1
hoặc value2). Nó tăng giá trị của thuộc tính có
tên, rồi in ra cả hai giá trị thuộc tính trước khi thoát
ra.
Liệt kê 1.Phản chiếu một bean
public class HolderBean
{
private int m_value1;
private int m_value2;
public int getValue1() {
return m_value1;
}
public void setValue1(int value) {
m_value1 = value;
}
public int getValue2() {
return m_value2;
}
public void setValue2(int value) {
m_value2 = value;
}
}
public class ReflectAccess
{
public void run(String[] args) throws Exception {
if (args.length == 1 && args[0].length() > 0) {
// create property name
char lead = args[0].charAt(0);
String pname = Character.toUpperCase(lead) +
args[0].substring(1);
// look up the get and set methods
Method gmeth = HolderBean.class.getDeclaredMethod
("get" + pname, new Class[0]);
Method smeth = HolderBean.class.getDeclaredMethod
("set" + pname, new Class[] { int.class });
// increment value using reflection
HolderBean bean = new HolderBean();
Object start = gmeth.invoke(bean, null);
int incr = ((Integer)start).intValue() + 1;
smeth.invoke(bean, new Object[] {new Integer(incr)});
// print the ending values
System.out.println("Result values " +
bean.getValue1() + ", " + bean.getValue2());
} else {
System.out.println("Usage: ReflectAccess value1|value2");
}
}
}
|
Dưới
đây là một cặp ví dụ chạy ReflectAccess để minh
họa cho các kết quả:
[dennis]$ java -cp . ReflectAccess value1 Result values 1, 0 [dennis]$ java -cp . ReflectAccess value2 Result values 0, 1 |
Bây giờ tôi đã giải thích phiên bản phản chiếu của mã này, tôi sẽ cho bạn thấy cách để thay thế một lớp được tạo để sử dụng sự phản chiếu. Có một vấn đề tế nhị liên quan đến việc làm cho sự thay thế này hoạt động đúng là quay lại chủ đề về hoạt động lớp trong Phần 1 của loạt bài này. Vấn đề là tôi sẽ tạo một lớp trong thời gian chạy mà tôi muốn truy cập từ mã được biên dịch tĩnh của lớp truy cập, nhưng do lớp được tạo không tồn tại với trình biên dịch, nên không có cách nào để tham chiếu nó trực tiếp.
Vì vậy tôi có thể liên kết mã được biên dịch tĩnh với lớp đã tạo ra như thế nào? Giải pháp cơ bản là xác định một lớp hoặc giao diện cơ sở có thể được truy cập bằng mã được biên dịch tĩnh, sau đó mở rộng cho lớp cơ sở hoặc triển khai thực hiện giao diện đó trong lớp đã tạo ra. Mã được biên dịch tĩnh sau đó có thể thực hiện cuộc gọi trực tiếp tới các phương thức, dù các phương thức này trên thực tế không được triển khai thực hiện trong thời gian chạy.
Trong Liệt kê
2, tôi đã định nghĩa một giao diện, IAccess,
nhằm cung cấp liên kết này cho mã đã tạo ra. Giao diện này bao gồm ba
phương thức. Phương thức đầu tiên chỉ thiết lập một đối tượng đích được
truy cập. Hai phương thức khác là các ủy quyền cho các phương thức get
(nhận) và set (thiết lập) dùng để truy cập giá trị thuộc tính int.
Liệt kê 2. Giao diện với lớp keo dán
public interface IAccess
{
public void setTarget(Object target);
public int getValue();
public void setValue(int value);
}
|
Ở
đây mục đích là thực hiện giao diện IAccess
được tạo ra sẽ cung cấp mã để gọi các phương thức get và set thích hợp của
một lớp đích. Liệt kê 3 cho thấy một ví dụ về giao diện này có thể được
triển khai thực hiện như thế nào, giả định rằng tôi muốn truy cập vào
thuộc tính value1 của lớp
HolderBean trong Liệt kê
1:
Liệt kê 3.Thực hiện ví dụ lớp keo dán
public class AccessValue1 implements IAccess
{
private HolderBean m_target;
public void setTarget(Object target) {
m_target = (HolderBean)target;
}
public int getValue() {
return m_target.getValue1();
}
public void setValue(int value) {
m_target.setValue1(value);
}
}
|
Giao diện trong Liệt kê 2 được thiết kế để được sử dụng với một thuộc tính cụ thể của một kiểu đối tượng cụ thể. Giao diện này giữ cho mã thực hiện đơn giản -- luôn luôn là một lợi thế khi làm việc với bytecode -- nhưng có nghĩa là lớp thực hiện rất cụ thể. Ở đó sẽ cần phải có một lớp thực hiện riêng biệt cho từng kiểu đối tượng và thuộc tính mà tôi muốn truy cập thông qua giao diện này, nó hạn chế việc sử dụng cách tiếp cận này như là một sự thay thế chung cho sự phản chiếu. Hạn chế này không phải là một vấn đề miễn là bạn chỉ áp dụng kỹ thuật có chọn lọc trong các trường hợp ở đó hiệu năng phản chiếu thực sự là một nút cổ chai.
Tạo
lớp thực hiện cho giao diện IAccess của Liệt kê 2 với Javassist thật dễ dàng -- Tôi chỉ cần
tạo một lớp mới để triển khai thực hiện giao diện đó, thêm vào một biến
thành viên cho tài liệu tham khảo đối tượng đích và kết thúc bằng cách
thêm một hàm tạo không có đối số và các phương thức thực hiện đơn giản.
Liệt kê 4 sẽ cho thấy đoạn mã Javassist để hoàn thành các bước này, được
cấu trúc như là một cuộc gọi phương thức để lấy thông tin lớp đích và
thông tin phương thức nhận/thiết lập và trả về sự biểu diễn nhị phân của
lớp được xây dựng:
Liệt kê 4. Xây dựng lớp keo dán Javassist
/** Parameter types for call with no parameters. */
private static final CtClass[] NO_ARGS = {};
/** Parameter types for call with single int value. */
private static final CtClass[] INT_ARGS = { CtClass.intType };
protected byte[] createAccess(Class tclas, Method gmeth,
Method smeth, String cname) throws Exception {
// build generator for the new class
String tname = tclas.getName();
ClassPool pool = ClassPool.getDefault();
CtClass clas = pool.makeClass(cname);
clas.addInterface(pool.get("IAccess"));
CtClass target = pool.get(tname);
// add target object field to class
CtField field = new CtField(target, "m_target", clas);
clas.addField(field);
// add public default constructor method to class
CtConstructor cons = new CtConstructor(NO_ARGS, clas);
cons.setBody(";");
clas.addConstructor(cons);
// add public setTarget method
CtMethod meth = new CtMethod(CtClass.voidType, "setTarget",
new CtClass[] { pool.get("java.lang.Object") }, clas);
meth.setBody("m_target = (" + tclas.getName() + ")$1;");
clas.addMethod(meth);
// add public getValue method
meth = new CtMethod(CtClass.intType, "getValue", NO_ARGS, clas);
meth.setBody("return m_target." + gmeth.getName() + "();");
clas.addMethod(meth);
// add public setValue method
meth = new CtMethod(CtClass.voidType, "setValue", INT_ARGS, clas);
meth.setBody("m_target." + smeth.getName() + "($1);");
clas.addMethod(meth);
// return binary representation of completed class
return clas.toBytecode();
}
|
Tôi sẽ không duyệt qua đoạn mã này một cách chi tiết bởi vì, nếu bạn đang theo dõi loạt bài này, thì hầu hết các hoạt động sẽ trông rất quen thuộc (và nếu bạn vẫn chưa theo kịp loạt bài này, ngay bây giờ hãy xem Phần 5 về một tổng quan làm việc với Javassist).
Tạo lớp
thực hiện cho giao diện IAccess của Liệt kê 2 bằng BCEL không phải khá dễ dàng như với
Javassist, nhưng nó vẫn không quá phức tạp. Liệt kê 5 đưa ra đoạn mã cho
mục đích này. Mã này sử dụng cùng một chuỗi các hoạt động như mã Javassist
của Liệt kê 4, nhưng chạy hơi lâu hơn một chút vì cần phải giải thích rõ
ràng mỗi lệnh bytecode cho BCEL. Như với phiên bản Javassist, tôi sẽ bỏ
qua các chi tiết thực hiện (quay lại Phần
7 để có một tổng quan về BCEL nếu có bất cứ điều gì chưa rõ).
Liệt kê 5. Xây dựng lớp keo dán BCEL
/** Parameter types for call with single int value. */
private static final Type[] INT_ARGS = { Type.INT };
/** Utility method for adding constructed method to class. */
private static void addMethod(MethodGen mgen, ClassGen cgen) {
mgen.setMaxStack();
mgen.setMaxLocals();
InstructionList ilist = mgen.getInstructionList();
Method method = mgen.getMethod();
ilist.dispose();
cgen.addMethod(method);
}
protected byte[] createAccess(Class tclas,
java.lang.reflect.Method gmeth, java.lang.reflect.Method smeth,
String cname) {
// build generators for the new class
String tname = tclas.getName();
ClassGen cgen = new ClassGen(cname, "java.lang.Object",
cname + ".java", Constants.ACC_PUBLIC,
new String[] { "IAccess" });
InstructionFactory ifact = new InstructionFactory(cgen);
ConstantPoolGen pgen = cgen.getConstantPool();
//. add target object field to class
FieldGen fgen = new FieldGen(Constants.ACC_PRIVATE,
new ObjectType(tname), "m_target", pgen);
cgen.addField(fgen.getField());
int findex = pgen.addFieldref(cname, "m_target",
Utility.getSignature(tname));
// create instruction list for default constructor
InstructionList ilist = new InstructionList();
ilist.append(InstructionConstants.ALOAD_0);
ilist.append(ifact.createInvoke("java.lang.Object", "<init>",
Type.VOID, Type.NO_ARGS, Constants.INVOKESPECIAL));
ilist.append(InstructionFactory.createReturn(Type.VOID));
// add public default constructor method to class
MethodGen mgen = new MethodGen(Constants.ACC_PUBLIC, Type.VOID,
Type.NO_ARGS, null, "<init>", cname, ilist, pgen);
addMethod(mgen, cgen);
// create instruction list for setTarget method
ilist = new InstructionList();
ilist.append(InstructionConstants.ALOAD_0);
ilist.append(InstructionConstants.ALOAD_1);
ilist.append(new CHECKCAST(pgen.addClass(tname)));
ilist.append(new PUTFIELD(findex));
ilist.append(InstructionConstants.RETURN);
// add public setTarget method
mgen = new MethodGen(Constants.ACC_PUBLIC, Type.VOID,
new Type[] { Type.OBJECT }, null, "setTarget", cname,
ilist, pgen);
addMethod(mgen, cgen);
// create instruction list for getValue method
ilist = new InstructionList();
ilist.append(InstructionConstants.ALOAD_0);
ilist.append(new GETFIELD(findex));
ilist.append(ifact.createInvoke(tname, gmeth.getName(),
Type.INT, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
ilist.append(InstructionConstants.IRETURN);
// add public getValue method
mgen = new MethodGen(Constants.ACC_PUBLIC, Type.INT,
Type.NO_ARGS, null, "getValue", cname, ilist, pgen);
addMethod(mgen, cgen);
// create instruction list for setValue method
ilist = new InstructionList();
ilist.append(InstructionConstants.ALOAD_0);
ilist.append(new GETFIELD(findex));
ilist.append(InstructionConstants.ILOAD_1);
ilist.append(ifact.createInvoke(tname, smeth.getName(),
Type.VOID, INT_ARGS, Constants.INVOKEVIRTUAL));
ilist.append(InstructionConstants.RETURN);
// add public setValue method
mgen = new MethodGen(Constants.ACC_PUBLIC, Type.VOID,
INT_ARGS, null, "setValue", cname, ilist, pgen);
addMethod(mgen, cgen);
// return bytecode of completed class
return cgen.getJavaClass().getBytes();
}
|
Bây giờ tôi đã có mã cho cả hai phiên bản Javassist và BCEL về xây dựng phương thức, tôi có thể tiến hành thử nghiệm chúng để xem chúng sẽ làm việc tốt như thế nào. Vì lý do ban đầu của tôi về việc tạo mã trong thời gian chạy là để thay thế sự phản chiếu bằng một cái gì đó nhanh hơn, vì vậy sẽ thật tốt để có một sự so sánh hiệu năng để xem tôi đã thành công tốt như thế nào. Chỉ cần chú ý đến nó, tôi cũng sẽ xem xét thời gian cần thiết để xây dựng lớp keo dán với mỗi một trong các khung công tác.
Liệt kê 6 thể
hiện các phần chính của mã kiểm tra mà tôi sẽ sử dụng để kiểm tra hiệu
năng. Phương thức runReflection() chạy phần
phản chiếu của thử nghiệm, runAccess() chạy
phần truy cập trực tiếp và run() kiểm soát toàn
bộ quá trình (bao gồm cả in ra các kết quả tính thời gian). Cả hai runReflection() và runAccess() lấy số các vòng lặp được thực hiện làm một tham
số, tham số này được chuyển lần lượt vào từ dòng lệnh (sử dụng mã không
được hiển thị trong Liệt kê này, nhưng có chứa trong phần tải về). Lớp
DirectLoader (ở cuối Liệt kê 6) chỉ cung
cấp một cách dễ dàng để nạp các lớp được tạo
ra.
Liệt kê 6. Mã thử nghiệm hiệu năng
/** Run timed loop using reflection for access to value. */
private int runReflection(int num, Method gmeth, Method smeth,
Object obj) {
int value = 0;
try {
Object[] gargs = new Object[0];
Object[] sargs = new Object[1];
for (int i = 0; i < num; i++) {
// messy usage of Integer values required in loop
Object result = gmeth.invoke(obj, gargs);
value = ((Integer)result).intValue() + 1;
sargs[0] = new Integer(value);
smeth.invoke(obj, sargs);
}
} catch (Exception ex) {
ex.printStackTrace(System.err);
System.exit(1);
}
return value;
}
/** Run timed loop using generated class for access to value. */
private int runAccess(int num, IAccess access, Object obj) {
access.setTarget(obj);
int value = 0;
for (int i = 0; i < num; i++) {
value = access.getValue() + 1;
access.setValue(value);
}
return value;
}
public void run(String name, int count) throws Exception {
// get instance and access methods
HolderBean bean = new HolderBean();
String pname = name;
char lead = pname.charAt(0);
pname = Character.toUpperCase(lead) + pname.substring(1);
Method gmeth = null;
Method smeth = null;
try {
gmeth = HolderBean.class.getDeclaredMethod("get" + pname,
new Class[0]);
smeth = HolderBean.class.getDeclaredMethod("set" + pname,
new Class[] { int.class });
} catch (Exception ex) {
System.err.println("No methods found for property " + pname);
ex.printStackTrace(System.err);
return;
}
// create the access class as a byte array
long base = System.currentTimeMillis();
String cname = "IAccess$impl_HolderBean_" + gmeth.getName() +
"_" + smeth.getName();
byte[] bytes = createAccess(HolderBean.class, gmeth, smeth, cname);
// load and construct an instance of the class
Class clas = s_classLoader.load(cname, bytes);
IAccess access = null;
try {
access = (IAccess)clas.newInstance();
} catch (IllegalAccessException ex) {
ex.printStackTrace(System.err);
System.exit(1);
} catch (InstantiationException ex) {
ex.printStackTrace(System.err);
System.exit(1);
}
System.out.println("Generate and load time of " +
(System.currentTimeMillis()-base) + " ms.");
// run the timing comparison
long start = System.currentTimeMillis();
int result = runReflection(count, gmeth, smeth, bean);
long time = System.currentTimeMillis() - start;
System.out.println("Reflection took " + time +
" ms. with result " + result + " (" + bean.getValue1() +
", " + bean.getValue2() + ")");
bean.setValue1(0);
bean.setValue2(0);
start = System.currentTimeMillis();
result = runAccess(count, access, bean);
time = System.currentTimeMillis() - start;
System.out.println("Generated took " + time +
" ms. with result " + result + " (" + bean.getValue1() +
", " + bean.getValue2() + ")");
}
/** Simple-minded loader for constructed classes. */
protected static class DirectLoader extends SecureClassLoader
{
protected DirectLoader() {
super(TimeCalls.class.getClassLoader());
}
protected Class load(String name, byte[] data) {
return super.defineClass(name, data, 0, data.length);
}
}
|
Với
một thử nghiệm tính thời gian đơn giản, tôi gọi phương thức run() hai lần, một lần cho một trong các thuộc
tính trong lớp HolderBean của Liệt kê 1. Chạy qua hai lần thử nghiệm là quan trọng cho một thử
nghiệm công bằng hợp lý -- lần vượt qua đầu tiên mã này sẽ nạp tất cả các
lớp cần thiết, các lớp này thêm vào nhiều chi phí hoạt động cho cả hai quá
trình tạo lớp Javassist và BCEL. Tuy vậy, chi phí hoạt động này không cần
thiết trong lần vượt qua thứ hai, cung cấp cho bạn một sự đánh giá tốt hơn
về việc tạo lớp sẽ yêu cầu kéo dài bao lâu khi được dùng trong một hệ
thống thực. Đây là một ví dụ về kết quả được tạo ra khi thực hiện cuộc thử
nghiệm
này:
[dennis]$$ java -cp .:bcel.jar BCELCalls 2000 Generate and load time of 409 ms. Reflection took 61 ms. with result 2000 (2000, 0) Generated took 2 ms. with result 2000 (2000, 0) Generate and load time of 1 ms. Reflection took 13 ms. with result 2000 (0, 2000) Generated took 2 ms. with result 2000 (0, 2000) |
Hình 1 cho thấy kết quả thử nghiệm tính thời gian này khi được gọi với số đếm vòng lặp nằm trong phạm vi từ 2K đến 512K (các thử nghiệm chạy trên một hệ thống Athlon 2200 XP + đang chạy Mandrake Linux 9.1, sử dụng JVM 1.4.2 của Sun). Ở đây tôi đã tính đến cả thời gian mã phản chiếu và thời gian mã được tạo cho thuộc tính thứ hai trong mỗi lần chạy thử nghiệm (vì thế cặp thời gian khi sử dụng sự tạo mã Javassist là lần đầu tiên, theo sau là cùng cặp thời gian khi sử dụng sự tạo mã BCEL). Các thời gian thực hiện gần như nhau bất kể Javassist hay BCEL được sử dụng để tạo các lớp keo dán, các lớp này là những gì tôi mong muốn nhìn thấy -- nhưng luôn luôn thích hợp để xác nhận!
Hình 1. Tốc độ mã phản chiếu so với tốc độ mã được tạo (thời gian tính bằng mili giây)
Như bạn thấy từ Hình 1, mã được tạo ra thực hiện nhanh hơn nhiều so với mã phản chiếu trong mọi trường hợp. Lợi thế tốc độ với mã được tạo ra tăng lên khi số vòng lặp lớn lên, bắt đầu ở khoảng 5:1 với các vòng lặp 2K và tăng lên đến khoảng 24:1 với các vòng lặp 512K. Việc xây dựng và nạp lớp keo dán đầu tiên cũng mất khoảng 320 mili giây (ms) cho Javassist và 370 ms cho BCEL, trong khi xây dựng với lớp keo dán thứ hai chỉ mất khoảng 4 ms cho Javassist và 2 ms cho BCEL (do phân giải đồng hồ chỉ là 1 ms, nên các thời gian này rất thô). Nếu bạn kết hợp các thời gian này, bạn sẽ thấy rằng ngay cả đối với vòng lặp 2K việc tạo một lớp sẽ cho tổng hiệu năng tốt hơn khi sử dụng mã phản chiếu (với tổng thời gian thực hiện khoảng 4 đến 6 ms ms, so với khoảng 14 ms với mã phản chiếu).
Trong thực tế, tình hình nghiêng nhiều hơn về phía ủng hộ mã được tạo ra so với chỉ thị trên biểu đồ này. Khi tôi đã cố gắng giảm nhỏ bằng 25 vòng lặp, các mã phản chiếu vẫn còn mất đến 6 ms đến 7 ms để thực hiện, trong khi mã được tạo ra quá nhanh để ghi nhận. Thời gian được thực hiện bởi mã phản chiếu với tổng số đếm vòng lặp tương đối nhỏ xuất hiện để phản chiếu một số sự tối ưu hóa đang xảy ra bên trong JVM khi đạt đến một ngưỡng; nếu tôi hạ thấp số đếm vòng lặp dưới khoảng 20, mã phản chiếu cũng đã trở nên quá nhanh để ghi nhận.
Tiến nhanh hơn trên con đường của bạn
Bây giờ bạn đã nhìn thấy các loại hiệu năng mà hoạt động lớp trong thời gian chạy có thể cung cấp cho các ứng dụng của bạn. Hãy chú ý thời gian tới bạn đang phải đối mặt với một vấn đề tối ưu hóa hiệu năng khó điều chỉnh -- đó có thể chỉ là một cách giải quyết nhanh chóng để có thể tránh cho bạn khỏi việc thiết kế lại phần lớn. Tuy vậy, hoạt động lớp tốt cho nhiều thứ hơn chỉ là hiệu năng. Nó cũng là cách tiếp cận linh hoạt duy nhất để sửa đổi ứng dụng của bạn theo các yêu cầu trong thời gian chạy. Ngay cả khi bạn không bao giờ có lý do để sử dụng nó trong mã của bạn, tôi nghĩ rằng đó là một trong những tính năng của Java để duy trì niềm vui và sự thú vị trong việc lập trình.
Sự mạo hiểm này trong một ứng dụng thế giới thực của hoạt động lớp kết thúc loạt bài về động lực học lập trình Java. Nhưng đừng tuyệt vọng -- bạn sẽ sớm có được cơ hội để thử một số ứng dụng hoạt động lớp tại buổi tiệc đứng của developerWorks khi tôi trình bày một số trong những công cụ đã được xây dựng xung quanh thao tác Java bytecode. Thử nghiệm đầu tiên sẽ là một bài viết về một cặp công cụ thử nghiệm ngoài Mother Goose.
| Tên | Kích thước | Phương thức tải |
|---|---|---|
| j-dyn0610.zip | 764KB | HTTP |
- Tìm thêm về việc thiết kế Java bytecode trong "Java bytecode: Hiểu bytecode giúp bạn trở thành một lập trình
viên tốt hơn" (developerWorks, 07.2001) của Peter
Haggar.
- Đọc "Cải thiện tính mô đun với việc lập trình hướng-khía cạnh"
(developerWorks, 01. 2002) của Nicholas Lesiecki để tìm
hiểu thêm về lập trình hướng-khía cạnh.
- Để tham chiếu xuất sắc đến tập lệnh và kiến trúc JVM, hãy xem Bên
trong máy ảo Java, của Bill Venners (Artima Software, Inc.,
2004). Bạn có thể xem
một vài chương ví dụ trực tuyến để lướt qua nó trước khi đặt
mua.
- Bạn có thể mua hoặc xem Đặc tả máy ảo Java chính thức trực tuyến với
nhận xét đáng tin cậy về tất cả các khía cạnh hoạt động JVM.
- Dự án Jikes mã nguồn mở
cung cấp trình biên dịch phục tùng cao và rất nhanh cho ngôn ngữ lập
trình Java. Hãy sử dụng nó để tạo bytecode của bạn theo kiểu cũ --từ
mã nguồn Java.
- Duyệt các sách về các chủ đề kỹ thuật này và khác.
- Tìm thêm hàng trăm tài nguyên công nghệ Java trên vùng công nghệ Java của developerWorks.
Dennis Sosnoski là một nhà tư vấn và nhà trợ giúp đào tạo chuyên về các dịch vụ Web và SOA dựa trên-Java. Kinh nghiệm phát triển phần mềm chuyên nghiệp của ông trải suốt hơn 30 năm qua, với một thập kỉ cuối tập trung vào các công nghệ XML và Java phía máy chủ. Dennis là nhà phát triển hàng đầu về dụng cụ liên kết dữ liệu XML JiBX mã nguồn mở, cũng là một người có duyên nợ với khung công tác của các dịch vụ Web Apache Axis2. Ông cũng là một trong những thành viên của nhóm chuyên gia đặc tả kỹ thuật của Jax-WS 2.0 và JAXB 2.0. Xem trang web của ông để có thông tin về các dịch vụ đào tạo và tư vấn của ông.