Động lực học lập trình Java - Phần 5: Việc chuyển đổi các lớp đang hoạt động

Tóm tắt: Sau thời gian gián đoạn ngắn, Dennis Sosnoski trở lại với phần 5 của

loạt bài Động lực học lập trình Javacủa mình. Bạn đã thấy cách viết một chương

trình chuyển đổi các tệp lớp Java để thay đổi hành vi mã. Trong bài báo này,

Dennis cho bạn thấy cách kết hợp chuyển đổi với việc nạp các lớp thực sự bằng

cách sử dụng khung công tác Javassist, để xử lý tính năng hướng khía cạnh "đúng

thời gian" linh hoạt. Cách tiếp cận này cho phép bạn quyết định những gì bạn

muốn thay đổi trong thời gian chạy và có khả năng thực hiện các thay đổi khác

nhau mỗi khi bạn chạy một chương trình. Theo cách này, bạn cũng sẽ xem xét sâu

hơn vào các vấn đề chung của việc nạp lớp (classloading) trong JVM.

pdf20 trang | Chuyên mục: Java | Chia sẻ: dkS00TYs | Lượt xem: 2199 | Lượt tải: 0download
Tóm tắt nội dung Động lực học lập trình Java - Phần 5: Việc chuyển đổi các lớp đang hoạt động, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
ữ liệu nhị phân. Trên thực tế Javassist bao 
gồm mã để thực hiện trực tiếp kiểu chặn này, vì vậy hơn là tiếp tục ví dụ này, 
chúng ta sẽ xem cách sử dụng việc thực hiện Javassist để thay thế. 
Việc chặn nạp lớp với Javassist xây dựng trên cùng lớp javassist.ClassPool mà 
chúng ta đã làm trong Phần 4. Trong bài viết này, chúng ta đã yêu cầu một lớp 
theo tên trực tiếp từ ClassPool, tìm lại việc biểu diễnJavassist của lớp đó dưới 
dạng một cá thể javassist.CtClass. Mặc dù, đây không phải là cách duy nhất để sử 
dụng một ClassPool -- Javassist cũng cung cấp một trình nạp lớp có sử dụng 
ClassPool như là nguồn dữ liệu lớp của nó, dưới dạng lớp javassist.Loader. 
Để cho phép bạn làm việc với các lớp khi chúng đang được nạp ClassPool sử dụng 
một mẫu Trình quan sát (Observer). Bạn có thể chuyển một cá thể của giao diện 
trình quan sát mong muốn, javassist.Translator, tới hàm tạo ClassPool. Mỗi khi 
một lớp mới được yêu cầu từ ClassPool nó gọi phương thức onWrite() của Trình 
quan sát để có thể thay đổi biểu diễn lớp trước khi nó được ClassPool phân phát. 
Lớp javassist.Loader này có phương thức run() thuận tiện để nạp một lớp đích và 
gọi phương thức main() của lớp đó với một mảng các đối số được cung cấp (như 
trong mã của Liệt kê 1). Liệt kê 4 chứng tỏ việc sử dụng các lớp Javassist và 
phương thức này để nạp và chạy một lớp ứng dụng đích. Việc thực hiện trình quan 
sát javassist.Translator đơn giản trong trường hợp này chỉ in ra một thông báo về 
lớp đang được yêu cầu. 
Liệt kê 4. Trình chạy ứng dụng Javassist 
public class JavassistRun 
{ 
 public static void main(String[] args) { 
 if (args.length >= 1) { 
 try { 
 // set up class loader with translator 
 Translator xlat = new VerboseTranslator(); 
 ClassPool pool = ClassPool.getDefault(xlat); 
 Loader loader = new Loader(pool); 
 // invoke "main" method of target class 
 String[] pargs = new String[args.length-1]; 
 System.arraycopy(args, 1, pargs, 0, pargs.length); 
 loader.run(args[0], pargs); 
 } catch ... 
 } 
 } else { 
 System.out.println 
 ("Usage: JavassistRun main-class args..."); 
 } 
 } 
 public static class VerboseTranslator implements Translator 
 { 
 public void start(ClassPool pool) {} 
 public void onWrite(ClassPool pool, String cname) { 
 System.out.println("onWrite called for " + cname); 
 } 
 } 
} 
Dưới đây là một ví dụ về dòng lệnh JavassistRun và kết quả, khi sử dụng nó để gọi 
ứng dụng Run từ Liệt kê 1: 
[dennis]$java -cp .:javassist.jar JavassistRun Run 
onWrite called for Run 
Usage: Run main-class args... 
Tính thời gian chạy 
Sự thay đổi tính thời gian của phương thức mà chúng ta đã xét trong Phần 4 có thể 
là một công cụ hữu ích để cô lập các vấn đề hiệu năng, nhưng nó thực sự cần một 
giao diện linh hoạt hơn. Trong bài viết đó, chúng ta đã chuyển đổi lớp và tên 
phương thức như là các tham số dòng lệnh tới chương trình của tôi, chương trình 
này đã nạp tệp lớp nhị phân, đã thêm vào mã tính thời gian, sau đó đã viết lại lớp 
đó sau. Đối với bài này, chúng ta sẽ chuyển đổi mã để sử dụng một cách tiếp cận 
thay đổi thời gian nạp và để hỗ trợ phối hợp mẫu cho việc xác định các lớp và các 
phương thức có tính thời gian. 
Việc thay đổi mã để xử lý các thay đổi như các lớp được nạp rất dễ dàng. Khi tách 
javassist.Translator khỏi Liệt kê 4, chúng ta chỉ có thể gọi phương thức bổ sung 
thêm các thông tin tính thời gian từ onWrite() khi tên lớp đang được viết khớp với 
tên lớp đích. Liệt kê 5 hiển thị điều này (không có tất cả các chi tiết về 
addTiming() -- xem Phần 4 của loạt bài này). 
Liệt kê 5. Thêm mã tính thời gian vào thời gian-nạp 
public class TranslateTiming 
{ 
 private static void addTiming(CtClass clas, String mname) 
 throws NotFoundException, CannotCompileException { 
 ... 
 } 
 public static void main(String[] args) { 
 if (args.length >= 3) { 
 try { 
 // set up class loader with translator 
 Translator xlat = 
 new SimpleTranslator(args[0], args[1]); 
 ClassPool pool = ClassPool.getDefault(xlat); 
 Loader loader = new Loader(pool); 
 // invoke "main" method of target class 
 String[] pargs = new String[args.length-3]; 
 System.arraycopy(args, 3, pargs, 0, pargs.length); 
 loader.run(args[2], pargs); 
 } catch (Throwable ex) { 
 ex.printStackTrace(); 
 } 
 } else { 
 System.out.println("Usage: TranslateTiming" + 
 " class-name method-mname main-class args..."); 
 } 
 } 
 public static class SimpleTranslator implements Translator 
 { 
 private String m_className; 
 private String m_methodName; 
 public SimpleTranslator(String cname, String mname) { 
 m_className = cname; 
 m_methodName = mname; 
 } 
 public void start(ClassPool pool) {} 
 public void onWrite(ClassPool pool, String cname) 
 throws NotFoundException, CannotCompileException { 
 if (cname.equals(m_className)) { 
 CtClass clas = pool.get(cname); 
 addTiming(clas, m_methodName); 
 } 
 } 
 } 
} 
Các phương thức mẫu 
Ngoài việc để cho mã tính thời gian của phương thức này làm việc trong thời gian-
nạp, như thể hiện trong Liệt kê 5, thật tốt để thêm tính linh hoạt trong việc xác 
định (các) phương thức được tính thời gian. Tôi bắt đầu triển khai thực hiện việc 
này bằng cách sử dụng biểu thức chính quy khớp với sự hỗ trợ trong gói 
java.util.regex của Java 1.4, sau đó nhận thấy nó đã không thực sự đem lại cho tôi 
phần nào sự linh hoạt mà tôi muốn. Vấn đề là một phần các mẫu có ý nghĩa với tôi 
để chọn các lớp và các phương thức đã thay đổi không hoàn toàn giống với mô 
hình biểu thức chính quy. 
Vì vậy một phần các mẫu có ý nghĩa cho việc lựa chọn các lớp và các phương thức 
là gì? Những gì mà tôi muốn là khả năng sử dụng bất kỳ trong số một vài đặc điểm 
của lớp và phương thức trong các mẫu này, bao gồm lớp thực tế và tên phương 
thức, kiểu trả về và (các) kiểu tham số gọi. Mặt khác, tôi đã không thực sự cần các 
phép so sánh linh hoạt trên các tên và các kiểu -- một phép so sánh bằng đơn giản 
đã giải quyết hầu hết các trường hợp tôi quan tâm đến và thêm các ký tự đại diện 
cơ bản cho các phép so sánh đã quan tâm còn lại. Cách tiếp cận dễ nhất để xử lý 
điều này là làm cho các mẫu trông giống như các khai báo phương thức Java 
chuẩn, với một vài phần mở rộng. 
Đối với một số ví dụ về cách tiếp cận này, đây là một vài ví dụ sẽ phù hợp với 
phương thức String buildString(int) của lớp test.StringBuilder: 
java.lang.String test.StringBuilder.buildString(int) 
test.StringBuilder.buildString(int) 
*buildString(int) 
*buildString 
Mẫu chung của các mẫu này trước tiên là một kiểu trả về tùy chọn (với văn bản 
chính xác), sau đó mẫu lớp và tên phương thức được kết hợp (bằng các ký tự "*" ) 
và cuối cùng là danh sách (các) kiểu tham số (với văn bản chính xác). Nếu kiểu trả 
về xuất hiện, nó phải được tách ra khỏi tên phương thức thích hợp bằng một 
khoảng trống, trong khi danh sách các tham số đi theo tên phương thức thích hợp. 
Để làm cho tham số phối hợp linh hoạt, tôi thiết lập nó để làm việc theo hai cách. 
Nếu các tham số đã cho là một danh sách có các dấu ngoặc đơn bao quanh, chúng 
phải giống hệt với các tham số phương thức. Thay vào đó nếu chúng được các dấu 
ngoặc vuông ("[]"), bao quanh, tất cả các kiểu đã liệt kê này phải xuất hiện như là 
các tham số của một phương thức phù hợp, nhưng phương thức này có thể sử dụng 
chúng theo thứ tự bất kỳ và cũng có thể sử dụng các tham số bổ sung thêm. Vì vậy 
*buildString(int, java.lang.String) giống với bất kỳ phương thức nào có một tên 
kết thúc bằng "buildString" và lấy chính xác hai tham số, một int và một String, 
theo đúng thứ tự. *buildString[int,java.lang.String] giống các phương thức có 
cùng các tên, nhưng dùng hai hoặc nhiều tham số hơn, một trong các tham số đó là 
int và tham số khác là java.lang.String. 
Liệt kê 6 đưa ra một phiên bản rút gọn của lớp con javassist.Translator mà tôi đã 
viết để xử lý các ví dụ này. Mã khớp thực sự thực ra không liên quan đến bài viết 
này, nhưng nó có trong tệp tải về (xem Tài nguyên) nếu bạn muốn xem nó kỹ hơn 
hoặc sử dụng nó cho mình. Lớp chương trình chính sử dụng TimingTranslator này 
là BatchTiming, cũng có trong tệp tải về này. 
Liệt kê 6. Trình dịch giống-mẫu 
public class TimingTranslator implements Translator 
{ 
 public TimingTranslator(String pattern) { 
 // build matching structures for supplied pattern 
 ... 
 } 
 private boolean matchType(CtMethod meth) { 
 ... 
 } 
 private boolean matchParameters(CtMethod meth) { 
 ... 
 } 
 private boolean matchName(CtMethod meth) { 
 ... 
 } 
 private void addTiming(CtMethod meth) { 
 ... 
 } 
 public void start(ClassPool pool) {} 
 public void onWrite(ClassPool pool, String cname) 
 throws NotFoundException, CannotCompileException { 
 // loop through all methods declared in class 
 CtClass clas = pool.get(cname); 
 CtMethod[] meths = clas.getDeclaredMethods(); 
 for (int i = 0; i < meths.length; i++) { 
 // check if method matches full pattern 
 CtMethod meth = meths[i]; 
 if (matchType(meth) && 
 matchParameters(meth) && matchName(meth)) { 
 // handle the actual timing modification 
 addTiming(meth); 
 } 
 } 
 } 
} 
Tiếp theo 
Trong hai bài viết mới đây, bây giờ bạn đã thấy cách sử dụng Javassist để xử lý 
các phép chuyển đổi cơ bản. Với bài viết tiếp theo, chúng ta sẽ xem xét các tính 
năng nâng cao của khung công tác này để cung cấp các kỹ thuật tìm kiếm-và-thay 
thế cho việc chỉnh sửa bytecode. Các tính năng này tạo ra các thay đổi có hệ thống 
để lập trình hành vi dễ dàng, bao gồm cả những thay đổi như là chặn tất cả các 
cuộc gọi đến một phương thức hay tất cả các truy cập của một trường. Chúng là 
chìa khóa để hiểu tại sao Javassist là một khung công tác quan trọng cho việc hỗ 
trợ hướng-khía cạnh trong các chương trình Java. Hãy quay lại vào tháng tới để 
xem cách bạn có thể sử dụng Javassist để mở khóa các khía cạnh trong các ứng 
dụng của bạn. 
Mục lục 
 Vùng nạp 
 Tính thời gian chạy 
 Tiếp theo 

File đính kèm:

  • pdfĐộng lực học lập trình Java, Phần 5 Việc chuyển đổi các lớp đang hoạt động.pdf
Tài liệu liên quan