Độ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.
ữ 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:
- Độ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