Độ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

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.

Dennis Sosnoski, Nhà tư vấn, Sosnoski Software Solutions, Inc.

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.



04 12 2009

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()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à 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 ý 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.


Tải về

Mô tảTênKích thước
Mã ví dụj-dyn0203-source.zip318 KB

Tài nguyên

Bình luận

developerWorks: Đăng nhập

Các trường được đánh dấu hoa thị là bắt buộc (*).


Bạn cần một ID của IBM?
Bạn quên định danh?


Bạn quên mật khẩu?
Đổi mật khẩu

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Ở lần bạn đăng nhập đầu tiên vào trang developerWorks, một hồ sơ cá nhân của bạn được tạo ra. Thông tin trong bản hồ sơ này (tên bạn, nước/vùng lãnh thổ, và tên cơ quan) sẽ được trưng ra cho mọi người và sẽ đi cùng các nội dung mà bạn đăng, trừ khi bạn chọn việc ẩn tên cơ quan của bạn. Bạn có thể cập nhật tài khoản trên trang IBM bất cứ khi nào.

Thông tin gửi đi được đảm bảo an toàn.

Chọn tên hiển thị của bạn



Lần đầu tiên bạn đăng nhập vào trang developerWorks, một bản trích ngang được tạo ra cho bạn, bạn cần phải chọn một tên để hiển thị. Tên hiển thị của bạn sẽ đi kèm theo các nội dung mà bạn đăng tải trên developerWorks.

Tên hiển thị cần có từ 3 đến 30 ký tự. Tên xuất hiện của bạn phải là duy nhất trên trang Cộng đồng developerWorks và vì lí do an ninh nó không phải là địa chỉ email của bạn.

Các trường được đánh dấu hoa thị là bắt buộc (*).

(Tên hiển thị cần có từ 3 đến 30 ký tự)

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Thông tin gửi đi được đảm bảo an toàn.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=70
Zone=Công nghệ Java
ArticleID=452532
ArticleTitle=Độ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
publish-date=12042009