Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 20 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
20
Dung lượng
204,99 KB
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 Tìm hiểu cách thay đổi các lớp khi chúng đang được nạp bằng Javassist Dennis Sosnoski, Nhà tư vấn, Sosnoski Software Solutions, Inc. 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 Java củ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. Trong Phần 4, "Các phép biến đổi lớp bằng Javassist," bạn đã học được cách sử dụng khung công tác Javassist để chuyển đổi các tệp lớp Java do trình biên dịch tạo ra, viết lại các tệp lớp đã sửa đổi. Bước chuyển đổi tệp lớp này rất quan trọng để thực hiện các thay đổi liên tục, nhưng không nhất thiết phải tiện lợi khi bạn muốn thực hiện các thay đổi khác nhau mỗi khi bạn thực hiện ứng dụng của bạn. Đối với các thay đổi thoáng qua như vậy, một cách tiếp cận hoạt động khi bạn thực sự khởi động ứng dụng của bạn là tốt hơn. Kiến trúc JVM cho chúng ta làm điều này thuận tiện bằng cách làm việc với việc thực hiện trình nạp lớp (classloader). Khi sử dụng các dấu móc của trình nạp lớp, bạn có thể ngăn chặn quá trình nạp các lớp vào JVM và chuyển đổi các biểu diễn lớp trước khi chúng thực sự được nạp. Để minh họa cách làm việc này, đầu tiên tôi sẽ giải thích việc chặn nạp lớp trực tiếp, sau đó chỉ ra cách Javassist cung cấp một phím tắt thuận tiện để bạn có thể sử dụng trong các ứng dụng của bạn. Theo cách này, tôi sẽ sử dụng các đoạn mã từ các bài viết trước trong loạt bài này. Đừng bỏ lỡ phần còn lại của loạt bài này Phần 1, "Các lớp Java và nạp lớp" (04.2003) Phần 2, "Giới thiệu sự phản chiếu" (06.2003) Phần 3, "Ứng dụng sự phản chiếu" (07.2003) Phần 4, "Chuyển đổi lớp bằng Javassist" (09.2003) Phần 6, "Các thay đổi hướng-khía cạnh với Javassist" (03.2004) Phần 7, "Kỹ thuật bytecode với BCEL" (04.2004) Phần 8, "Thay thế sự phản chiếu bằng việc tạo mã" (06.2004) Vùng nạp Bình thường, bạn chạy một ứng dụng Java bằng cách xác định lớp chính như là một tham số cho JVM. Điều này làm việc tốt với các hoạt động tiêu chuẩn, nhưng không cung cấp cách nối bất kỳ đúng lúc vào quá trình nạp lớp có ích cho hầu hết các ứng dụng. Như tôi đã thảo luận trong Phần 1 "Các lớp và việc nạp lớp," nhiều lớp được nạp ngay trước khi lớp chính của bạn bắt đầu thực hiện. Việc ngăn chặn nạp các lớp này đòi hỏi một mức gián tiếp trong việc thực hiện chương trình. May mắn thay, rất dễ dàng để sao chép công việc JVM đã thực hiện trong khi chạy lớp chính của ứng dụng của bạn. Tất cả những thứ mà bạn cần làm là sử dụng sự phản chiếu (như đã trình bày trong Phần 2) để trước tiên tìm phương thức tĩnh main() trong lớp cụ thể, sau đó gọi nó bằng các đối số dòng lệnh mong muốn. Liệt kê 1 đưa ra mã ví dụ để thực hiện điều này (tôi đã để ngoài các phương thức nhập khẩu và các lỗi ngoại lệ để giữ cho đoạn mã này ngắn gọn): Liệt kê 1. Trình chạy (runner) ứng dụng Java public class Run { public static void main(String[] args) { if (args.length >= 1) { try { // load the target class to be run Class clas = Run.class.getClassLoader(). loadClass(args[0]); // invoke "main" method of target class Class[] ptypes = new Class[] { args.getClass() }; Method main = clas.getDeclaredMethod("main", ptypes); String[] pargs = new String[args.length-1]; System.arraycopy(args, 1, pargs, 0, pargs.length); main.invoke(null, new Object[] { pargs }); } catch } } else { System.out.println ("Usage: Run main-class args "); } } } Để chạy ứng dụng Java của bạn khi sử dụng lớp này, bạn chỉ cần đặt tên nó làm đích cho lệnh java, tiếp sau nó là lớp chính cho ứng dụng của bạn và bất kỳ đối số nào mà bạn muốn chuyển tới ứng dụng của bạn. Nói cách khác, nếu lệnh mà bạn sử dụng để khởi chạy ứng dụng Java của bạn thường là: java test.Test arg1 arg2 arg3 Thì thay vào đó bạn khởi chạy nó khi sử dụng lớp Run bằng lệnh: java Run test.Test arg1 arg2 arg3 Chặn nạp lớp Thật đúng với riêng nó, lớp Run nhỏ bé từ Liệt kê 1 rất không thực sự có ích. Để hoàn thành mục tiêu của tôi về việc chặn quá trình nạp lớp chúng ta cần phải tiến một bước xa hơn, bằng cách định nghĩa và sử dụng trình nạp lớp riêng của mình cho các lớp ứng dụng. Hỏi chuyên gia: Dennis Sosnoski về các vấn đề JVM và bytecode Đối với các ý kiến hay các câu hỏi về tài liệu được trình bày trong loạt bài này, cũng như bất cứ điều gì khác có liên quan đến Java bytecode, định dạng lớp nhị phân Java hoặc các vấn đề JVM chung, hãy truy cập vào diễn đàn thảo luận JVM và Bytecode, do Dennis Sosnoski kiểm soát. Như chúng ta đã thảo luận trong Phần 1, các trình nạp lớp sử dụng một hệ thống phân cấp có cấu trúc cây. Mỗi trình nạp lớp (trừ trình nạp lớp gốc được JVM sử dụng cho các lớp Java cốt lõi) có trình nạp lớp cha mẹ. Các trình nạp lớp có nhiệm vụ xác nhận lại trình nạp lớp cha mẹ của chúng trước khi nạp một lớp cho riêng mình, để ngăn ngừa các xung đột có thể nảy sinh khi cùng một lớp được nạp bởi nhiều hơn một trình nạp lớp trong một hệ thống phân cấp. Quá trình này xác nhận lại với trình nạp lớp cha mẹ đầu tiên được gọi là delegation (ủy quyền) các trình nạp lớp ủy quyền trách nhiệm để nạp một lớp cho trình nạp lớp gần với trình nạp lớp gốc nhất có quyền truy cập vào thông tin lớp đó. Khi chương trình Run từ Liệt kê 1 bắt đầu thực hiện, nó đã được trình nạp lớp Hệ thống (System) mặc định cho JVM (JVM loại bỏ đường dẫn lớp-classpath mà bạn xác định) nạp. Để tuân theo nguyên tắc ủy quyền nạp lớp này, chúng ta cần phải tạo cho trình nạp lớp của mình một sự thay thế thật sự cho trình nạp lớp System, khi sử dụng tất cả các thông tin đường dẫn lớp tương tự và ủy thác cho các trình nạp lớp cha mẹ giống nhau. May mắn thay, lớp java.net.URLClassLoader được các JVM hiện hành sử dụng để triển khai thực hiện trình nạp lớp System cung cấp một cách dễ dàng để lấy ra thông tin đường dẫn lớp, khi sử dụng phương thức getURLs() Để viết các trình nạp lớp của chúng ta, chúng ta có thể chỉ phân lớp java.net.URLClassLoadervà khởi tạo lớp cơ sở để sử dụng cùng một đường dẫn lớp và trình nạp lớp cha mẹ như là trình nạp lớp System để nạp lớp chính. Liệt kê 2 cho thấy việc thực hiện thực sự của cách tiếp cận này: Liệt kê 2. Một trình nạp lớp dài dòng public class VerboseLoader extends URLClassLoader { protected VerboseLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } public Class loadClass(String name) throws ClassNotFoundException { System.out.println("loadClass: " + name); return super.loadClass(name); } protected Class findClass(String name) throws ClassNotFoundException { Class clas = super.findClass(name); System.out.println("findclass: loaded " + name + " from this loader"); return clas; } public static void main(String[] args) { if (args.length >= 1) { try { // get paths to be used for loading ClassLoader base = ClassLoader.getSystemClassLoader(); URL[] urls; if (base instanceof URLClassLoader) { urls = ((URLClassLoader)base).getURLs(); } else { urls = new URL[] { new File(".").toURI().toURL() }; } // list the paths actually being used System.out.println("Loading from paths:"); for (int i = 0; i < urls.length; i++) { System.out.println(" " + urls[i]); } // load target class using custom class loader VerboseLoader loader = new VerboseLoader(urls, base.getParent()); Class clas = loader.loadClass(args[0]); // invoke "main" method of target class Class[] ptypes = new Class[] { args.getClass() }; Method main = clas.getDeclaredMethod("main", ptypes); String[] pargs = new String[args.length-1]; System.arraycopy(args, 1, pargs, 0, pargs.length); Thread.currentThread(). setContextClassLoader(loader); main.invoke(null, new Object[] { pargs }); } catch } } else { System.out.println ("Usage: VerboseLoader main-class args "); } } } Chúng ta đã phân lớp java.net.URLClassLoader bằng lớp riêng VerboseLoader của chúng ta để liệt kê ra tất cả các lớp đang được nạp, ghi nhận những lớp nào đã được nạp bởi cá thể trình nạp này (chứ không phải là một trình nạp lớp cha mẹ ủy quyền). Ở đây một lần nữa tôi đã bỏ qua các phương thức nhập khẩu và các lỗi ngoại lệ để giữ cho đoạn mã ngắn gọn. Hai phương thức đầu tiên trong lớp VerboseLoader, loadClass() và findClass() là quan trọng hơn các phương thức của trình nạp lớp tiêu chuẩn. Phương thức loadClass() được gọi cho mỗi lớp được yêu cầu từ trình nạp lớp. Trong trường hợp này, chúng ta dùng nó chỉ để in một thông báo ra bàn điều khiển và sau đó gọi phiên bản lớp cơ sở để xử lý thực sự. Phương thức lớp cơ sở triển khai thực hiện hành vi ủy quyền của trình nạp lớp tiêu chuẩn, đầu tiên kiểm tra xem trình nạp lớp cha mẹ có thể nạp lớp cần thiết không và chỉ cố gắng nạp lớp trực tiếp bằng cách sử dụng phương thức findClass() có bảo vệ nếu trình nạp lớp cha mẹ bị hỏng. Đối với việc thực hiện VerboseLoader của findClass(), trước tiên chúng ta gọi việc thực hiện lớp cơ sở quan trọng hơn, sau đó in ra một thông báo nếu cuộc gọi thành công (trả về mà không đưa ra một lỗi ngoại lệ). Phương thức main() của VerboseLoader hoặc nhận được danh sách các địa chỉ URL của đường dẫn lớp từ trình nạp được sử dụng cho lớp đang có hoặc, nếu được sử dụng với một trình nạp không có một cá thể URLClassLoader, thì chỉ cần sử dụng thư mục hiện tại làm lối vào đường dẫn lớp duy nhất. Dù bằng cách nào đi nữa, nó sẽ liệt kê ra các đường dẫn đang được sử dụng trên thực tế, sau đó tạo một cá thể của lớp VerboseLoader và sử dụng nó để nạp lớp đích có tên trên dòng lệnh. Phần còn lại của logic này, để tìm và gọi phương thức main() của lớp đích, giống như mã Run của Liệt kê 1. Liệt kê 3 cho thấy một ví dụ về dòng lệnh VerboseLoader và kết quả được sử dụng để gọi các ứng dụng Run từ Liệt kê 1: Liệt kê 3. Ví dụ kết quả từ chương trình của Liệt kê 2 [dennis]$ java VerboseLoader Run Loading from paths: file:/home/dennis/writing/articles/devworks/dynamic/code5/ loadClass: Run loadClass: java.lang.Object findclass: loaded Run from this loader loadClass: java.lang.Throwable loadClass: java.lang.reflect.InvocationTargetException loadClass: java.lang.IllegalAccessException loadClass: java.lang.IllegalArgumentException loadClass: java.lang.NoSuchMethodException loadClass: java.lang.ClassNotFoundException loadClass: java.lang.NoClassDefFoundError loadClass: java.lang.Class loadClass: java.lang.String loadClass: java.lang.System loadClass: java.io.PrintStream Usage: Run main-class args Trong trường hợp này, lớp duy nhất được VerboseLoader nạp trực tiếp là lớp Run. Tất cả các lớp khác được lớp Run sử dụng là các lớp Java lõi, các lớp lõi này được nạp bằng sự ủy quyền thông qua trình nạp lớp cha mẹ. Hầu hết nếu không phải tất cả các lớp Java lõi trên thực tế được nạp trong quá trình tự khởi động của ứng dụng VerboseLoader vì vậy trình nạp lớp cha mẹ sẽ chỉ trả về một tham chiếu đến cá thể java.lang.Class được tạo ra trước đó. Javassist chặn VerboseClassloader từ Liệt kê 2 cho thấy những điều căn bản về việc chặn nạp lớp. Để thay đổi các lớp khi chúng đang được nạp, chúng ta có thể lấy thêm việc này, thêm mã vào phương thức findClass() để truy cập tệp lớp nhị phân như một tài nguyên và sau đó làm việc với các dữ 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à [...]... 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... 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... 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... 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 đó... 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. .. 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... 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 giannạ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...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) {... 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 ... 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ự . Độ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 hiểu cách thay đổi các lớp khi chúng đang được nạp bằng Javassist Dennis. trong Phần 1, các trình nạp lớp sử dụng một hệ thống phân cấp có cấu trúc cây. Mỗi trình nạp lớp (trừ trình nạp lớp gốc được JVM sử dụng cho các lớp Java cốt lõi) có trình nạp lớp cha mẹ. Các trình. Trong Phần 4, " ;Các phép biến đổi lớp bằng Javassist," bạn đã học được cách sử dụng khung công tác Javassist để chuyển đổi các tệp lớp Java do trình biên dịch tạo ra, viết lại các tệp lớp