Động lực học lập trình Java, Phần 4: Chuyển đổi lớp bằng Javassist

Sử dụng Javassist để chuyển đổi các phương thức theo bytecode

Thật buồn tẻ với các lớp Java chỉ thực hiện theo cách mã nguồn đã được viết phải không? Sau đó, hãy vui vẻ lên, bởi vì bạn sắp sửa thấy việc kết hợp các lớp theo các hình dạng chưa bao giờ được trình biên dịch dự kiến! Trong bài viết này, nhà tư vấn Java Dennis Sosnoski đóng góp loại bài động lực học lập trình Java của mình vào việc tăng nhanh tốc độ xem xét Javassist, thư viện thao tác mã byte (bytecode), đây là cơ sở cho các tính năng lập trình hướng-khía cạnh được bổ sung cho máy chủ ứng dụng JBoss được sử dụng rộng rãi. Bạn sẽ tìm ra những điều cơ bản về việc chuyển đổi các lớp hiện có với Javassist và nhận thấy cả sức mạnh lẫn hạn chế của cách tiếp cận mã nguồn mở của khung công tác này với hoạt động lớp (classworking).

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

Sau khi trình bày những điều căn bản của việc định dạng lớp Java và truy cập trong lúc chạy qua phản chiếu, đây là lúc để di chuyển loạt bài này theo hướng tới nhiều chủ đề cao cấp hơn. Trong số tháng này tôi sẽ bắt đầu vào phần thứ hai của loạt bài, ở đây các thông tin về lớp Java chỉ trở thành một dạng cấu trúc dữ liệu khác được các ứng dụng xử lí. Tôi sẽ gọi toàn bộ lĩnh vực của chủ đề này là hoạt động lớp (classworking).

Tôi sẽ bắt đầu trình bày hoạt động lớp với thư viện thao tác bytecode Javassist. Javassist không phải là thư viện duy nhất để làm việc với bytecode, mà nó còn có một tính năng cụ thể làm cho nó trở thành một điểm khởi đầu quan trọng cho các thí nghiệm hoạt động lớp: bạn có thể sử dụng Javassist để làm thay đổi bytecode của một lớp Java mà trên thực tế không cần tìm hiểu bất cứ điều gì về kiến trúc bytecode hoặc kiến trúc máy ảo Java (JVM). Đây là một điều may mắn lẫn trong một số chi tiết cụ thể -- nói chung tôi không tán thành can thiệp vào công nghệ mà bạn không hiểu -- nhưng chắc chắn nó làm cho việc thao tác bytecode có khả năng truy cập nhiều hơn so với các khung công tác mà ở đó bạn làm việc ở mức các hướng dẫn riêng.

Những điều cơ bản về Javassist

Javassist cho phép bạn kiểm tra, chỉnh sửa và tạo các lớp Java nhị phân. Khía cạnh kiểm tra chủ yếu lặp lại chính xác những gì có sẵn trực tiếp trong Java thông qua Reflection API, nhưng việc có cách khác để truy cập thông tin này là rất có ích khi trên thực tế bạn đang sửa đổi các lớp thay vì chỉ cần thực hiện chúng. Điều này là do thiết kế JVM không cung cấp cho bạn bất kỳ quyền truy cập nào vào dữ liệu lớp thô sau khi nó được nạp vào JVM. Nếu bạn sắp làm việc với các lớp như là dữ liệu, bạn cần phải làm như thế bên ngoài JVM.

Đừ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 5, "Việc chuyển các lớp đang hoạt động" (02.2004)

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)

Javassist sử dụng lớp javassist.ClassPool để theo dõi và kiểm soát các lớp bạn đang thao tác. Lớp này làm việc rất giống như một trình nạp lớp (classloader) của JVM, nhưng có sự khác biệt quan trọng khác hơn việc kết nối các lớp đã nạp để thực hiện như một phần của ứng dụng của bạn, nhóm lớp giúp cho các lớp đã nạp có thể sử dụng như là dữ liệu thông qua Javassist API. Bạn có thể sử dụng một nhóm lớp mặc định để nạp từ đường dẫn tìm kiếm JVM hoặc xác định một đường dẫn để tìm kiếm danh sách các đường dẫn riêng của bạn. Bạn thậm chí có thể tải trực tiếp các lớp nhị phân từ các mảng hoặc luồng byte và tạo các lớp mới từ đầu.

Các lớp được nạp trong một nhóm lớp được các cá thể javassist.CtClass đại diện. Như với lớp Java tiêu chuẩn java.lang.Class, CtClass cung cấp các phương thức để kiểm tra dữ liệu lớp như các trường và các phương thức. Đó chỉ là sự khởi đầu cho CtClass, tuy nhiên, cũng định nghĩa các phương thức để thêm vào các trường, các phương thức và các hàm tạo mới cho lớp đó và để thay đổi tên lớp, siêu lớp và các giao diện. Thật kỳ quặc, Javassist không cung cấp bất kỳ cách nào để xóa các trường, các phương thức hoặc các hàm tạo từ một lớp.

Các trường, các phương thức và các hàm tạo được các cá thể tương ứng javassist.CtField, javassist.CtMethodjavassist.CtConstructor biểu diễn. Các lớp này xác định các phương thức để sửa đổi tất cả các khía cạnh của mục được lớp đó đại diện, bao gồm cả phần bytecode thực sự của một phương thức hoặc hàm tạo.

Mã nguồn của tất cả bytecode

Javassist cho phép bạn thay thế hoàn toàn phần thân bytecode của một phương thức hoặc hàm tạo hoặc thêm vào khả năng chọn lọc bytecode ở đầu hoặc cuối của phần thân hiện có (cùng với một cặp các biến khác cho các hàm tạo). Dù bằng cách nào, các bytecode mới được chuyển qua như là một câu lệnh hay khối mã nguồn giống như Java trong một String. Các phương thức Javassist biên dịch có hiệu quả mã nguồn mà bạn cung cấp trong bytecode của Java, sau đó chúng chèn bytecode này vào trong phần thân của phương thức hoặc hàm tạo đích.

Mã nguồn được Javassist chấp nhận không khớp chính xác với ngôn ngữ Java, nhưng sự khác biệt chính là việc bổ sung một số trình nhận dạng đặc biệt dùng để mô tả các tham số của phương thức hoặc hàm tạo, giá trị trả về phương thức và các mục khác mà bạn có thể muốn sử dụng trong mã chèn vào của bạn. Tất cả các trình nhận dạng đặc biệt này bắt đầu bằng biểu tượng $, vì vậy chúng sẽ không can thiệp tới bất cứ điều gì mà bạn đã làm khác đi trong mã của bạn.

Cũng có một số hạn chế về những gì bạn có thể làm trong mã nguồn mà bạn chuyển tới Javassist. Hạn chế đầu tiên là định dạng thực tế, nó phải là một câu lệnh hay một khối. Đây là một hạn chế không tốt đối với hầu hết các mục đích, vì bạn có thể đặt bất cứ chuỗi các câu lệnh nào mà bạn muốn trong một khối. Dưới đây là một ví dụ khi sử dụng trình nhận dạng Javassist đặc biệt cho hai giá trị tham số phương thức đầu tiên để hiển thị cách điều này hoạt động:

{
  System.out.println("Argument 1: " + $1);
  System.out.println("Argument 2: " + $2);
}

Một hạn chế đáng kể hơn về mã nguồn là không có cách nào tham chiếu đến các biến cục bộ đã khai báo ngoài câu lệnh hoặc khối được thêm vào. Điều này có nghĩa rằng nếu bạn đang thêm mã ở cả hai phần bắt đầu và kết thúc của một phương thức, ví dụ, nói chung bạn sẽ không có khả năng chuyển các thông tin từ mã được thêm vào ở lúc bắt đầu đến mã được thêm vào ở lúc kết thúc. Có nhiều cách giải quyết xung quanh hạn chế này, nhưng cách giải quyết rất lộn xộn -- nói chung bạn cần phải tìm một cách để kết hợp mã riêng chèn vào trong một khối.


Hoạt động lớp với Javassist

Với một ví dụ về việc áp dụng Javassist, tôi sẽ sử dụng một nhiệm vụ mà tôi đã thường xuyên xử lý trực tiếp trong mã nguồn: đo thời gian đã mất để thực hiện một phương thức. Phép đo này là đủ dễ dàng để thực hiện trong mã nguồn; bạn chỉ cần ghi lại thời gian hiện tại ở đầu phương thức, sau đó kiểm tra lại thời gian hiện tại ở cuối phương thức và tìm thấy sự khác biệt giữa hai giá trị đó. Nếu bạn không có mã nguồn, sẽ khó khăn hơn nhiều để có được kiểu thông tin tính thời gian này. Đó là nơi mà hoạt động lớp có ích -- nó cho phép bạn thực hiện các thay đổi giống như điều này với phương thức bất kỳ, mà không cần mã nguồn.

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.

Liệt kê 1 hiển thị một phương thức ví dụ (xấu) mà tôi sẽ sử dụng như là một thí nghiệm tính toán thời gian của tôi: phương thức buildString của lớp StringBuilder. Phương thức này xây dựng một String với độ dài bất kì bằng cách thực hiện chính xác những gì mà bất kỳ chuyên gia hiệu năng Java nào sẽ nói bạn không phải làm -- nó nhiều lần gắn thêm chỉ một ký tự vào cuối chuỗi để tạo một chuỗi dài hơn. Do các chuỗi không thể thay đổi được, cách tiếp cận này có nghĩa là một chuỗi mới sẽ được xây dựng mỗi lần qua vòng lặp, với các dữ liệu được sao chép từ chuỗi cũ và chỉ một ký tự được thêm vào cuối. Tác động cuối cùng là phương thức này gặp phải chi phí hoạt động càng ngày càng nhiều khi nó được sử dụng để tạo các chuỗi dài hơn.

Liệt kê 1. Phương thức có tính giờ
public class StringBuilder
{
    private String buildString(int length) {
        String result = "";
        for (int i = 0; i < length; i++) {
            result += (char)(i%26 + 'a');
        }
        return result;
    }
    
    public static void main(String[] argv) {
        StringBuilder inst = new StringBuilder();
        for (int i = 0; i < argv.length; i++) {
            String result = inst.buildString(Integer.parseInt(argv[i]));
            System.out.println("Constructed string of length " +
                result.length());
        }
    }
}

Thêm tính thời gian phương thức

Vì tôi có sẵn mã nguồn cho phương thức này, tôi sẽ cho bạn thấy cách tôi sẽ trực tiếp thêm vào thông tin tính thời gian. Điều này cũng sẽ dùng làm mô hình cho những gì mà tôi muốn làm khi sử dụng Javassist. Liệt kê 2 cho thấy phương thức buildString() có bổ sung thêm tính thời gian. Điều này chẳng có giá trị thay đổi nào. Mã được thêm vào này lưu trữ thời gian bắt đầu cho một biến cục bộ, sau đó tính thời gian trôi qua ở cuối của phương thức và in nó ra bàn điều khiển.

Liệt kê 2. Phương thức có tính thời gian
    private String buildString(int length) {
        long start = System.currentTimeMillis();
        String result = "";
        for (int i = 0; i < length; i++) {
            result += (char)(i%26 + 'a');
        }
        System.out.println("Call to buildString took " +
            (System.currentTimeMillis()-start) + " ms.");
        return result;
    }

Thực hiện nó với Javassist

Khai thác cùng tác dụng như khi sử dụng Javassist để xử lí bytecode lớp có vẻ như nó sẽ dễ dàng. Javassist cung cấp nhiều cách để thêm mã vào ở đầu và cuối của các phương thức, sau tất cả, mã này phải chính xác với những gì mà tôi đã thực hiện trong mã nguồn để thêm thông tin tính thời gian cho phương thức này.

Tuy nhiên, cũng có một khó khăn. Khi tôi đã mô tả cách Javassist để cho phép bạn thêm mã, tôi đã nói rằng mã thêm vào không thể tham chiếu đến các biến cục bộ đã xác định ở những nơi khác trong phương thức. Sự hạn chế này ngăn cản tôi triển khai thực hiện mã tính thời gian trong Javassist theo cùng cách mà tôi đã thực hiện trong mã nguồn; trong trường hợp đó, tôi đã xác định một biến cục bộ mới trong mã được thêm vào ở lúc bắt đầu và đã tham chiếu biến đó trong mã được thêm vào ở cuối.

Vậy tôi có thể sử dụng cách tiếp cận khác nào để nhận được tác dụng tương tự? Đúng, tôi có thể thêm một trường thành viên mới vào lớp đó và sử dụng nó thay cho biến cục bộ. Tuy nhiên đó là hơi hướng của giải pháp và phạm phải một số hạn chế về sử dụng chung. Ví dụ, hãy xem điều gì sẽ xảy ra với một phương thức đệ quy. Mỗi lần phương thức tự gọi chính nó, giá trị thời gian bắt đầu được lưu trữ từ lần gọi cuối cùng sẽ bị ghi đè và bị mất.

May mắn thay có một giải pháp tốt hơn. Tôi có thể giữ cho mã phương thức ban đầu không thay đổi và chỉ cần thay đổi tên phương thức, sau đó thêm một phương thức mới bằng cách sử dụng tên ban đầu. Phương thức interceptor (chặn) này có thể sử dụng cùng chữ ký như phương thức ban đầu, kể cả trả về giá trị như nhau. Liệt kê 3 cho thấy một phiên bản mã nguồn của phương thức này sẽ trông như sau:

Liệt kê 3. Thêm một phương thức chặn trong mã nguồn
    private String buildString$impl(int length) {
        String result = "";
        for (int i = 0; i < length; i++) {
            result += (char)(i%26 + 'a');
        }
        return result;
    }
    private String buildString(int length) {
        long start = System.currentTimeMillis();
        String result = buildString$impl(length);
        System.out.println("Call to buildString took " +
            (System.currentTimeMillis()-start) + " ms.");
        return result;
    }

Cách tiếp cận của việc sử dụng một phương thức chặn này hoạt động tốt với Javassist. Vì toàn bộ phần thân của phương thức này là một khối, tôi có thể xác định và sử dụng các biến cục bộ trong phần thân mà không có bất kỳ vấn đề nào. Tạo mã nguồn cho phương thức chặn này cũng dễ dàng; chỉ cần một vài thay đổi để làm việc với phương thức có thể bất kì.

Chạy phương thức chặn

Triển khai thực hiện các đoạn mã để thêm vào tính thời gian của phương thức sử dụng một số các Javassist API đã mô tả trong những điều cơ bản về Javassist. Liệt kê 4 cho thấy mã này, dưới dạng một ứng dụng lấy một cặp các đối số dòng lệnh cho tên lớp và tên phương thức được tính thời gian. Phần thân phương thức main() chỉ tìm thấy các thông tin lớp và sau đó chuyển nó tới phương thức addTiming() để xử lý các sửa đổi thực sự. Phương thức addTiming() đầu tiên đặt lại tên cho phương thức hiện tại bằng cách gắn thêm "$impl" vào cuối tên, sau đó tạo một bản sao của phương thức khi sử dụng tên ban đầu. Sau đó nó thay thế thân phương thức đã sao chép với mã có tính thời gian đang bao bọc một cuộc gọi đến phương thức ban đầu đã đổi tên.

Liệt kê 4. Thêm phương thức chặn với Javassist
public class JassistTiming 
{
    public static void main(String[] argv) {
        if (argv.length == 2) {
            try {
                
                // start by getting the class file and method
                CtClass clas = ClassPool.getDefault().get(argv[0]);
                if (clas == null) {
                    System.err.println("Class " + argv[0] + " not found");
                } else {
                    
                    // add timing interceptor to the class
                    addTiming(clas, argv[1]);
                    clas.writeFile();
                    System.out.println("Added timing to method " +
                        argv[0] + "." + argv[1]);
                    
                }
                
            } catch (CannotCompileException ex) {
                ex.printStackTrace();
            } catch (NotFoundException ex) {
                ex.printStackTrace();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            
        } else {
            System.out.println("Usage: JassistTiming class method-name");
        }
    }
    
    private static void addTiming(CtClass clas, String mname)
        throws NotFoundException, CannotCompileException {
        
        //  get the method information (throws exception if method with
        //  given name is not declared directly by this class, returns
        //  arbitrary choice if more than one with the given name)
        CtMethod mold = clas.getDeclaredMethod(mname);
        
        //  rename old method to synthetic name, then duplicate the
        //  method with original name for use as interceptor
        String nname = mname+"$impl";
        mold.setName(nname);
        CtMethod mnew = CtNewMethod.copy(mold, mname, clas, null);
        
        //  start the body text generation by saving the start time
        //  to a local variable, then call the timed method; the
        //  actual code generated needs to depend on whether the
        //  timed method returns a value
        String type = mold.getReturnType().getName();
        StringBuffer body = new StringBuffer();
        body.append("{\nlong start = System.currentTimeMillis();\n");
        if (!"void".equals(type)) {
            body.append(type + " result = ");
        }
        body.append(nname + "($$);\n");
        
        //  finish body text generation with call to print the timing
        //  information, and return saved value (if not void)
        body.append("System.out.println(\"Call to method " + mname +
            " took \" +\n (System.currentTimeMillis()-start) + " +
            "\" ms.\");\n");
        if (!"void".equals(type)) {
            body.append("return result;\n");
        }
        body.append("}");
        
        //  replace the body of the interceptor method with generated
        //  code block and add it to class
        mnew.setBody(body.toString());
        clas.addMethod(mnew);
        
        //  print the generated code block just to show what was done
        System.out.println("Interceptor method body:");
        System.out.println(body.toString());
    }
}

Xây dựng phần thân phương thức chặn sử dụng một java.lang.StringBuffer để tích lũy phần văn bản thân (chỉ ra một cách thích hợp để xử lý xây dựng String , trái với cách tiếp cận đã dùng trong StringBuilder). Phần thân văn bản thay đổi dựa vào phương thức ban đầu có trả về một giá trị hay không. Nếu nó thực sự trả về một giá trị, mã đã xây dựng lưu trữ giá trị đó trong một biến cục bộ sao cho nó có thể được trả về ở cuối của phương thức chặn. Nếu phương thức ban đầu là kiểu khoảng trống, thì chẳng có gì để lưu trữ cả và chẳng có gì để trả về từ phương thức chặn.

Phần văn bản thân trên thực tế trông giống như mã Java tiêu chuẩn trừ các cuộc gọi đến (được đổi tên) phương thức ban đầu. Đây là một dòng body.append(nname + "($$);\n"); trong đoạn mã đó, ở đây nname là tên đã sửa đổi cho phương thức ban đầu. Trình nhận dạng $$ được sử dụng trong cuộc gọi là cách mà Javassist trình bày danh sách các tham số cho phương thức trong xây dựng. Bằng cách sử dụng trình nhận dạng này trong cuộc gọi đến phương thức ban đầu, tất cả các đối số được cung cấp trong cuộc gọi này đến phương thức chặn được chuyển qua tới phương thức ban đầu.

Liệt kê 5 cho thấy các kết quả của lần đầu tiên chạy chương trình StringBuilder ở dạng chưa sửa đổi, sau đó chạy chương trình JassistTiming để thêm thông tin tính thời gian và cuối cùng là chạy chương trình StringBuilder sau khi nó được sửa đổi. Bạn có thể thấy cách StringBuilder chạy sau thay đổi về các thời gian thực hiện và bao nhiêu lần tăng nhanh hơn so với chiều dài của chuỗi được xây dựng, do đoạn mã xây dựng chuỗi không hiệu quả.

Liệt kê 5. Chạy các chương trình
[dennis]$ java StringBuilder 1000 2000 4000 8000 16000
Constructed string of length 1000
Constructed string of length 2000
Constructed string of length 4000
Constructed string of length 8000
Constructed string of length 16000

[dennis]$ java -cp javassist.jar:. JassistTiming StringBuilder buildString
Interceptor method body:
{
long start = System.currentTimeMillis();
java.lang.String result = buildString$impl($$);
System.out.println("Call to method buildString took " +
 (System.currentTimeMillis()-start) + " ms.");
return result;
}
Added timing to method StringBuilder.buildString

[dennis]$ java StringBuilder 1000 2000 4000 8000 16000
Call to method buildString took 37 ms.
Constructed string of length 1000
Call to method buildString took 59 ms.
Constructed string of length 2000
Call to method buildString took 181 ms.
Constructed string of length 4000
Call to method buildString took 863 ms.
Constructed string of length 8000
Call to method buildString took 4154 ms.
Constructed string of length 16000

Tin tưởng vào mã nguồn, Luke?

Javassist thực hiện một công việc rất lớn làm cho hoạt động lớp dễ dàng bằng cách cho phép bạn làm việc với mã nguồn chứ không phải là các danh sách hướng dẫn bytecode thực sự. Nhưng điều dễ sử dụng này đi kèm với một số hạn chế. Như tôi đã đề cập lại trong mã nguồn của tất cả các bytecode, mã nguồn được Javassist sử dụng không chính xác là ngôn ngữ Java. Bên cạnh việc thừa nhận các trình nhận dạng đặc biệt trong mã này, Javassist triển khai thực hiện kiểm tra thời gian biên dịch lỏng lẻo trên mã hơn được đặc tả ngôn ngữ Java yêu cầu. Vì điều này, nó sẽ tạo bytecode từ mã nguồn theo những cách có thể có các kết quả gây ngạc nhiên nếu bạn không cẩn thận.

Ví dụ, Liệt kê 6 cho thấy những gì xảy ra khi tôi thay đổi kiểu của biến cục bộ thường sử dụng cho thời gian bắt đầu phương thức trong mã chặn từ biến long sang int. Javassist chấp nhận mã nguồn và biến đổi nó thành bytecode hợp lệ, nhưng các thời gian kết quả là sai. Nếu bạn đã thử biên dịch trực tiếp nhiệm vụ này vào trong một chương trình Java, bạn sẽ nhận được một lỗi biên dịch vì nó vi phạm một trong các quy tắc của ngôn ngữ Java: một nhiệm vụ hẹp đòi hỏi một khuôn mẫu.

Liệt kê 6. . Lưu trữ một biến long trong một biến int
[dennis]$ java -cp javassist.jar:. JassistTiming StringBuilder buildString
Interceptor method body:
{
int start = System.currentTimeMillis();
java.lang.String result = buildString$impl($$);
System.out.println("Call to method buildString took " +
 (System.currentTimeMillis()-start) + " ms.");
return result;
}
Added timing to method StringBuilder.buildString
[dennis]$ java StringBuilder 1000 2000 4000 8000 16000
Call to method buildString took 1060856922184 ms.
Constructed string of length 1000
Call to method buildString took 1060856922172 ms.
Constructed string of length 2000
Call to method buildString took 1060856922382 ms.
Constructed string of length 4000
Call to method buildString took 1060856922809 ms.
Constructed string of length 8000
Call to method buildString took 1060856926253 ms.
Constructed string of length 16000

Tùy thuộc vào những gì bạn làm trong mã nguồn, thậm chí bạn có thể nhận được Javassist để tạo bytecode không hợp lệ. Liệt kê 7 cho thấy một ví dụ về điều này, ở đây tôi đã vá lỗi đoạn mã JassistTiming để luôn luôn xử lý phương thức có tính thời gian như sự trả về một giá trị int. Javassist một lần nữa chấp nhận mã nguồn mà không đưa ra lỗi, nhưng bytecode không thực hiện xác nhận tính hợp lệ khi tôi cố gắng thực hiện nó.

Liệt kê 7. Lưu trữ một biến String vào một biến int
[dennis]$ java -cp javassist.jar:. JassistTiming StringBuilder buildString
Interceptor method body:
{
long start = System.currentTimeMillis();
int result = buildString$impl($$);
System.out.println("Call to method buildString took " +
 (System.currentTimeMillis()-start) + " ms.");
return result;
}
Added timing to method StringBuilder.buildString
[dennis]$ java StringBuilder 1000 2000 4000 8000 16000
Exception in thread "main" java.lang.VerifyError:
 (class: StringBuilder, method: buildString signature:
 (I)Ljava/lang/String;) Expecting to find integer on stack

Kiểu kết quả này không phải là một vấn đề miễn là bạn cẩn thận với mã nguồn mà bạn cung cấp cho Javassist. Tuy nhiên thật quan trọng để nhận ra rằng Javassist sẽ không nhất thiết phải bắt giữ bất kỳ lỗi nào trong mã này và rằng các kết quả của một lỗi có thể khó dự đoán.


Nhìn về phía trước

Có rất nhiều thứ với Javassist hơn những gì chúng tôi đã trình bày trong bài viết này. Tháng tới, chúng ta sẽ nghiên cứu sâu hơn một chút bằng việc xem xét một số các kết nối đặc biệt mà Javassist cung cấp cho việc sửa đổi số lượng lớn các lớp và cho việc sửa đổi đang thực hiện như là các lớp được nạp trong thời gian chạy. Đây là những tính năng cung cấp cho Javassist một công cụ quan trọng để triển khai thực hiện các khía cạnh trong các ứng dụng của bạn, vì vậy hãy chắc chắn rằng bạn nắm bắt được các phần tiếp theo với câu chuyện đầy đủ về công cụ mạnh mẽ này.

Tài nguyên

  • Javassist được bắt nguồn từ Shigeru Chiba ở Bộ môn Toán và Khoa học máy tính, trường đại học công nghệ Tokyo. Javassist gần đây đã gia nhập dự án máy chủ ứng dụng JBoss mã nguồn mở, nó là cơ sở cho việc bổ sung thêm các tính năng lập trình theo hướng-khía cạnh mới trong dự án đó. Javassist được phân phối theo Mozilla Public License (MPL- Giấy phép công cộng Mozilla) và các giấy phép mã nguồn mở Lesser GNU General Public License (LGPL-Giấy phép công công chung của GNU nhỏ hơn).
  • Tìm hiểu thêm về việc thiết kế Java bytecode trong "Java bytecode: Hiểu bytecode giúp bạn trở thành một lập trình viên tốt hơn" (developerWorks, 07.2001).
  • Bạn có muốn tìm hiểu thêm về lập trình hướng-khía cạnh không? Hãy thử nghiệm "Cải thiện mô đun với việc lập trình theo hướng-khía cạnh" (developerWorks, 01. 2002) với một tổng quan về làm việc với ngôn ngữ AspectJ.
  • Dự án Jikes mã nguồn mở cung cấp trình biên dịch phục tùng cao và rất nhanh cho ngôn ngữ lập trình Java. Hãy sử dụng nó để tạo bytecode của bạn theo kiểu cũ --từ mã nguồn Java.
  • Tìm thêm hàng trăm tài nguyên công nghệ Java trên vùng công nghệ Java của developerWorks.

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=452545
ArticleTitle=Động lực học lập trình Java, Phần 4: Chuyển đổi lớp bằng Javassist
publish-date=12042009