Động lực học lập trình Java - Phần 6: Các thay đổi hướng-Khía cạnh với

Tóm tắt: Nhà tư vấn Java Dennis Sosnoski dành những điều tốt nhất cho phần

cuối trong ba phần trình bày của ông về khung công tác Javassist. Lúc này ông cho

thấy cách hỗ trợ tìm kiếm-và-thay thế Javassist khiến cho việc chỉnh sửa Java

bytecode trên thực tế dễ dàng như một lệnh Thay thế Tất cả (Replace All) của

trình soạn thảo văn bản. Bạn có muốn nộp báo cáo tất cả được viết vào một trường

cụ thể hoặc nối một sự thay đổi cho một tham số được chuyển vào một cuộc gọi

phương thức không? Javassist thực hiện nó rất dễ dàng và Dennis sẽ cho bạn cách

làm.

Phần 4và Phần 5của loạt bài này trình bày cách bạn có thể sử dụng Javassist để

đặt các thay đổi tới các lớp nhị phân. Lúcnày bạn sẽ tìm hiểu về một cách sử dụng

khung công tác còn mạnh hơn, tận dụng sự trợ giúp của Javassist để tìm tất cả các

lợi ích của một phương thức hoặc trường cụ thể trong mã byte (bytecode). Tính

năng này chí ít cũng quan trọng cho sức mạnh của Javassist như sự hỗ trợ của nó

cho một hướng đi giống như mã nguồn của bytecode cụ thể. Sự hỗ trợ cho việc

thay thế có chọn lọc các hoạt động cũng là tính năng làm cho Javassist trở thành

một công cụ xuất sắc để bổ sung thêm các tính năng lập trình hướng-khía cạnh cho

mã Java chuẩn.

pdf22 trang | Chuyên mục: Java | Chia sẻ: dkS00TYs | Lượt xem: 2170 | Lượt tải: 0download
Tóm tắt nội dung Động lực học lập trình Java - Phần 6: Các thay đổi hướng-Khía cạnh với, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
{ 
 String dir = arg.isReader() ? "read" : "write"; 
 System.out.println(" " + dir + " of " + arg.getClassName() + 
 "." + arg.getFieldName() + from(arg)); 
 } 
 public void edit(MethodCall arg) { 
 System.out.println(" call to " + arg.getClassName() + "." + 
 arg.getMethodName() + from(arg)); 
 } 
 public void edit(NewExpr arg) { 
 System.out.println(" new " + arg.getClassName() + from(arg)); 
 } 
 } 
} 
Liệt kê 4 hiển thị kết quả do việc chạy chương trình Phân tích (Dissect) của Liệt 
kê 4 trên chương trình BeanTest của Liệt kê 2 tạo ra. Kết quả này đưa ra thống kê 
chi tiết về những gì đang được thực hiện trong mỗi phương thức của mỗi lớp được 
nạp, liệt kê tất cả các cuộc gọi phương thức, các truy cập trường và các tạo phẩm 
đối tượng mới. 
Liệt kê 4. BeanTest được phân tích 
[dennis]$ java -cp .:javassist.jar Dissect BeanTest 
Dissecting class BeanTest 
 new Bean in BeanTest(BeanTest.java:7) 
 write of BeanTest.m_bean in BeanTest(BeanTest.java:7) 
 read of java.lang.System.out in print(BeanTest.java:11) 
 new java.lang.StringBuffer in print(BeanTest.java:11) 
 call to java.lang.StringBuffer.append in print(BeanTest.java:11) 
 read of BeanTest.m_bean in print(BeanTest.java:11) 
 call to Bean.getA in print(BeanTest.java:11) 
 call to java.lang.StringBuffer.append in print(BeanTest.java:11) 
 call to java.lang.StringBuffer.append in print(BeanTest.java:11) 
 read of BeanTest.m_bean in print(BeanTest.java:11) 
 call to Bean.getB in print(BeanTest.java:11) 
 call to java.lang.StringBuffer.append in print(BeanTest.java:11) 
 call to java.lang.StringBuffer.toString in print(BeanTest.java:11) 
 call to java.io.PrintStream.println in print(BeanTest.java:11) 
 read of BeanTest.m_bean in changeValues(BeanTest.java:16) 
 new java.lang.StringBuffer in changeValues(BeanTest.java:16) 
 call to java.lang.StringBuffer.append in changeValues(BeanTest.java:16) 
 call to java.lang.StringBuffer.append in changeValues(BeanTest.java:16) 
 call to java.lang.StringBuffer.toString in changeValues(BeanTest.java:16) 
 call to Bean.setA in changeValues(BeanTest.java:16) 
 read of BeanTest.m_bean in changeValues(BeanTest.java:17) 
 new java.lang.StringBuffer in changeValues(BeanTest.java:17) 
 call to java.lang.StringBuffer.append in changeValues(BeanTest.java:17) 
 call to java.lang.StringBuffer.append in changeValues(BeanTest.java:17) 
 call to java.lang.StringBuffer.toString in changeValues(BeanTest.java:17) 
 call to Bean.setB in changeValues(BeanTest.java:17) 
 new BeanTest in main(BeanTest.java:21) 
 call to BeanTest.print in main(BeanTest.java:22) 
 call to BeanTest.changeValues in main(BeanTest.java:23) 
 call to BeanTest.print in main(BeanTest.java:24) 
Dissecting class Bean 
 write of Bean.m_a in Bean(Bean.java:10) 
 write of Bean.m_b in Bean(Bean.java:11) 
 read of Bean.m_a in getA(Bean.java:15) 
 read of Bean.m_b in getB(Bean.java:19) 
 write of Bean.m_a in setA(Bean.java:23) 
 write of Bean.m_b in setB(Bean.java:27) 
Bean values are originalA and originalB 
Bean values are newA and newB 
Tôi có thể dễ dàng bổ sung thêm sự hỗ trợ cho các khuôn mẫu báo cáo, đưa ra 
(instanceof) các thử nghiệm và bắt giữ (catch) các khối bằng cách triển khai thực 
hiện các phương thức thích hợp trong VerboseEditor. Nhưng việc liệt kê không có 
các thông tin về các mục thành phần này trở nên tẻ nhạt, vì thế chúng ta hãy xem 
xét kỹ việc thay đổi các mục này trên thực tế. . 
Quá trình phân tích đang diễn ra 
Việc phân tích các lớp của Lịệt kê 4 liệt kê các hoạt động thành phần cơ bản. Thật 
dễ dàng nhận thấy để làm việc với các hoạt động này sẽ có ích như thế nào khi 
triển khai thực hiện các tính năng theo hướng-khía cạnh. Ví dụ bộ ghi nhật ký để 
thông báo tất cả các truy cập đã viết vào các trường đã chọn sẽ là một khía cạnh có 
ích để áp dụng trong nhiều ứng dụng. Đó là một phần công việc mà tôi đã hứa chỉ 
cho bạn cách làm, sau này. 
Thật may mắn cho chủ đề của bài viết này, ExprEditor không chỉ cho phép tôi biết 
các hoạt động nào đang diễn ra trong đoạn mã này, mà nó còn cho phép tôi thay 
đổi các hoạt động đang được thông báo. Các kiểu tham số được chuyển vào các 
cuộc gọi phương thức ExprEditor.edit() khác nhau mà mỗi cuộc gọi xác định một 
phương thức replace(). Nếu tôi chuyển qua phương thức này một câu lệnh dưới 
dạng mã nguồn Javassist thông thường (đã trình bày trong Phần 4), thì câu lệnh đó 
sẽ được biên dịch sang bytecode và được sử dụng để thay thế hoạt động ban đầu. 
Điều này làm cho việc phân chia và cắt nhỏ bytecode của bạn dễ dàng. 
Liệt kê 5 cho thấy một ứng dụng thay thế mã. Thay vì chỉ có các hoạt động ghi 
nhật ký, ở đây tôi đã chọn để thay đổi thực sự giá trị String đang được lưu trữ vào 
một trường đã chọn. Trong FieldSetEditor, tôi triển khai thực hiện chữ ký phương 
thức giống với các truy cập trường. Trong phương thức này, tôi chỉ kiểm tra hai 
điều: tên trường là một tên tôi đang tìm kiếm và hoạt động này là một hoạt động 
lưu trữ. Khi tôi tìm thấy một sự giống nhau, tôi thay thế hoạt động lưu trữ ban đầu 
bằng một hoạt động sử dụng kết quả của một cuộc gọi đến phương thức reverse() 
trong lớp ứng dụng TranslateEditor hiện tại. Phương thức reverse() chỉ cần đảo 
ngược thứ tự của các ký tự trong chuỗi ban đầu và in ra một thông báo để cho biết 
rằng nó đã được sử dụng. 
Liệt kê 5. Đảo ngược các tập String 
public class TranslateEditor 
{ 
 public static void main(String[] args) { 
 if (args.length >= 3) { 
 try { 
 // set up class loader with translator 
 EditorTranslator xlat = 
 new EditorTranslator(args[0], new FieldSetEditor(args[1])); 
 ClassPool pool = ClassPool.getDefault(xlat); 
 Loader loader = new Loader(pool); 
 // invoke the "main" method of the application 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: TranslateEditor clas-name " + 
 "field-name main-class args..."); 
 } 
 } 
 public static String reverse(String value) { 
 int length = value.length(); 
 StringBuffer buff = new StringBuffer(length); 
 for (int i = length-1; i >= 0; i--) { 
 buff.append(value.charAt(i)); 
 } 
 System.out.println("TranslateEditor.reverse returning " + buff); 
 return buff.toString(); 
 } 
 public static class EditorTranslator implements Translator 
 { 
 private String m_className; 
 private ExprEditor m_editor; 
 private EditorTranslator(String cname, ExprEditor editor) { 
 m_className = cname; 
 m_editor = editor; 
 } 
 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); 
 clas.instrument(m_editor); 
 } 
 } 
 } 
 public static class FieldSetEditor extends ExprEditor 
 { 
 private String m_fieldName; 
 private FieldSetEditor(String fname) { 
 m_fieldName = fname; 
 } 
 public void edit(FieldAccess arg) throws CannotCompileException { 
 if (arg.getFieldName().equals(m_fieldName) && arg.isWriter()) { 
 StringBuffer code = new StringBuffer(); 
 code.append("$0."); 
 code.append(arg.getFieldName()); 
 code.append("=TranslateEditor.reverse($1);"); 
 arg.replace(code.toString()); 
 } 
 } 
 } 
} 
Đây là những gì sẽ xảy ra nếu tôi chạy phần này trên chương trình BeanTest của 
Liệt kê 2 : 
[dennis]$ java -cp .:javassist.jar TranslateEditor Bean m_a BeanTest 
TranslateEditor.reverse returning Alanigiro 
Bean values are Alanigiro and originalB 
TranslateEditor.reverse returning Awen 
Bean values are Awen and newB 
Tôi đã ghép thành công một cuộc gọi đến mã bổ sung ở mỗi hoạt động lưu trữ 
trong trường Bean.m_a (một trong hàm tạo và một trong phương thức thiết lập). 
Tôi có thể chống lại tác động này bằng cách triển khai thay đổi tương tự như trong 
lúc nạp từ trường này, nhưng về phần mình tôi thấy các giá trị đảo ngược gây chú 
ý nhiều hơn những gì chúng ta đã bắt đầu, vì vậy tôi sẽ chọn dừng lại vấn đề này. 
Tóm tắt Javassist 
Trong bài viết này, bạn đã thấy cách chuyển đổi bytecode có hệ thống có thể dễ 
dàng thực hiện khi sử dụng Javassist. Kết hợp nó với hai bài viết cuối cùng, bạn 
cần phải có một cơ sở vững chắc để thực hiện các phép chuyển đổi của các ứng 
dụng Java theo hướng khía cạnh riêng của mình hoặc như một bước xây dựng 
riêng biệt hoặc trong lúc chạy. 
Để có một ý tưởng tốt hơn về sức mạnh của cách tiếp cận này, bạn có thể cũng 
muốn xem xét dự án JBoss Aspect Oriented Programming (JBossAOP-Lập trình 
hướng-khía cạnh JBoss), được xây dựng xung quanh Javassist. JBossAOP sử dụng 
một tệp cấu hình XML để xác định bất kỳ một trong nhiều hoạt động khác nhau 
được thực hiện cho lớp ứng dụng của bạn. Chúng bao gồm việc sử dụng các trình 
chặn (interceptor) trên các sự truy cập trường hoặc các cuộc gọi phương thức, 
thêm các việc thực hiện giao diện hỗn hợp vào các lớp hiện có và nhiều hơn nữa. 
JBossAOP được gắn vào trong phiên bản máy chủ ứng dụng JBoss hiện đang 
được phát triển, nhưng cũng có sẵn như là một công cụ độc lập để sử dụng với các 
ứng dụng của bạn ở ngoài của JBoss. 
Phần tiếp theo cho loạt bài này xem xét Byte Code Engineering Library (BCEL- 
Thư viện kỹ thuật mã byte), một phần của dự án Jakarta của quỹ Apache Software. 
BCEL là một trong những khung công tác được sử dụng rộng rãi nhất cho các hoạt 
động lớp Java. Nó đưa ra cách làm việc rất khác nhau với bytecode từ cách tiếp 
cận Javassist mà chúng ta đã thấy trong ba bài viết gần đây, tập trung vào các 
hướng dẫn bytecode riêng hơn là làm việc ở mức mã nguồn, là sức mạnh của 
Javassist. Hãy xem tiếp với các chi tiết đầy đủ khi làm việc ở mức chương trình 
dịch hợp ngữ (assembler) bytecode vào tháng tới. 
Mục lục 
 Xử lý các thay đổi bytecode 
 Chuyển đổi mã 
 Phân lớp được thực hiện dễ dàng 
 Tóm tắt Javassist 

File đính kèm:

  • pdfĐộng lực học lập trình Java, Phần 6 Các thay đổi hướng-khía cạnh với .pdf
Tài liệu liên quan