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

Sử dụng Javassist cho các phép chuyển đổi tìm kiếm-và-thay thế bytecode

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.

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

Phần 4Phần 5 củ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úc nà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.

Trong Phần 5 bạn đã thấy cách Javassist cho phép bạn chặn quá trình nạp lớp (classloading) -- và thậm chí thực hiện các thay đổi cho các biểu diễn lớp nhị phân khi chúng đang được nạp. Các phép biến đổi bytecode có hệ thống mà tôi đang trình bày trong bài viết này có thể được sử dụng hoặc cho các phép biến đổi tệp lớp tĩnh hoặc chặn trong thời gian chạy, nhưng chúng đặc biệt có ích khi được sử dụng trong thời gian chạy.

Xử lý các thay đổi bytecode

Javassist cung cấp hai cách riêng biệt để xử lý các thay đổi bytecode có hệ thống. Kỹ thuật đầu tiên, sử dụng lớp javassist.CodeConverter, là một cách sử dụng đơn giản hơn một chút nhưng có nhiều hạn chế về mặt bạn có thể thực hiện cái gì. Kỹ thuật thứ hai sử dụng các lớp con tùy chỉnh của lớp javassist.ExprEditor. Kỹ thuật này có nhiều việc hơn một chút, nhưng tính linh hoạt được bổ sung nhiều hơn bù đắp cho sự cố gắng đó. Tôi sẽ xem xét các ví dụ về cả hai cách tiếp cận trong bài viết này.


Chuyển đổi mã

Kỹ thuật Javassist đầu tiên để thay đổi bytecode có hệ thống sử dụng lớp javassist.CodeConverter. Để sử dụng tốt kỹ thuật này, bạn chỉ cần tạo một cá thể của lớp CodeConverter và cấu hình nó với một hoặc nhiều hoạt động chuyển đổi. Mỗi phép chuyển đổi được cấu hình bằng cách sử dụng một cuộc gọi phương thức nhận biết kiểu chuyển đổi. Các phép chuyển đổi này rơi vào ba loại: các phép chuyển đổi gọi phương thức, các phép chuyển đổi truy cập trường và một phép chuyển đổi đối tượng mới.

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

Liệt kê 1 cho một ví dụ về cách sử dụng một phép chuyển đổi phương thức gọi. Trong trường hợp này, phép chuyển đổi chỉ cần thêm một khai báo rằng phương thức này đang được gọi. Trong đoạn mã này, đầu tiên tôi nhận được cá thể javassist.ClassPool mà tôi sắp sử dụng trong suốt bài này, cấu hình nó để làm việc với một trình dịch (như đã có trước đây trong Phần 5). Sau đó, tôi truy cập một cặp các định nghĩa phương thức thông qua ClassPool. Định nghĩa phương thức đầu tiên dành cho phương thức kiểu "set" (thiết lập) để được theo dõi (với tên lớp và phương thức từ các đối số dòng lệnh), định nghĩa thứ hai cho phương thức reportSet() trong lớp TranslateConvert, lớp này sẽ thông báo một cuộc gọi cho phương thức đầu tiên.

Một khi đã có thông tin về phương thức, tôi có thể sử dụng CodeConverterinsertBeforeMethod() để cấu hình một phép chuyển đổi để thêm một cuộc gọi đến phương thức thông báo trước mỗi cuộc gọi đến phương thức set. Sau đó tất cả những thứ cần được thực hiện là áp dụng các bộ chuyển đổi (converter) này cho một hoặc nhiều lớp. Trong đoạn mã của Liệt kê 1, tôi thực hiện việc này trong phương thức onWrite() của lớp bên trong ConverterTranslator với cuộc gọi đến phương thức instrument() của đối tượng lớp. Việc này sẽ tự động áp dụng phép chuyển đổi này cho mọi lớp đã nạp từ cá thể ClassPool.

Liệt kê 1. Sử dụng CodeConverter
public class TranslateConvert
{
    public static void main(String[] args) {
        if (args.length >= 3) {
            try {
                
                // set up class loader with translator
                ConverterTranslator xlat =
                    new ConverterTranslator();
                ClassPool pool = ClassPool.getDefault(xlat);
                CodeConverter convert = new CodeConverter();
                CtMethod smeth = pool.get(args[0]).
                    getDeclaredMethod(args[1]);
                CtMethod pmeth = pool.get("TranslateConvert").
                    getDeclaredMethod("reportSet");
                convert.insertBeforeMethod(smeth, pmeth);
                xlat.setConverter(convert);
                Loader loader = new Loader(pool);
                
                // invoke "main" method of application class
                String[] pargs = new String[args.length-3];
                System.arraycopy(args, 3, pargs, 0, pargs.length);
                loader.run(args[2], pargs);
                
            } catch ...
            }
            
        } else {
            System.out.println("Usage: TranslateConvert " +
                "clas-name set-name main-class args...");
        }
    }
    
    public static void reportSet(Bean target, String value) {
        System.out.println("Call to set value " + value);
    }
    
    public static class ConverterTranslator implements Translator
    {
        private CodeConverter m_converter;
        
        private void setConverter(CodeConverter convert) {
            m_converter = convert;
        }
        
        public void start(ClassPool pool) {}
        
        public void onWrite(ClassPool pool, String cname)
            throws NotFoundException, CannotCompileException {
            CtClass clas = pool.get(cname);
            clas.instrument(m_converter);
        }
    }
}

Đó là một hoạt động khá phức tạp để cấu hình, nhưng sau khi nó được thiết lập nó sẽ hoạt động dễ dàng. Liệt kê 2 cho một ví dụ mã để sử dụng như là một trường hợp thử nghiệm. Ở đây Bean cung cấp một đối tượng thử nghiệm với các phương thức get và set giống như Bean, các phương thức này được chương trình BeanTest sử dụng để truy cập các giá trị.

Liệt kê 2. Một trình thử nghiệm bean
public class Bean
{
    private String m_a;
    private String m_b;
    
    public Bean() {}
    
    public Bean(String a, String b) {
        m_a = a;
        m_b = b;
    }
    
    public String getA() {
        return m_a;
    }

    public String getB() {
        return m_b;
    }

    public void setA(String string) {
        m_a = string;
    }

    public void setB(String string) {
        m_b = string;
    }
}

public class BeanTest
{
    private Bean m_bean;
    
    private BeanTest() {
        m_bean = new Bean("originalA", "originalB");
    }
    
    private void print() {
        System.out.println("Bean values are " +
            m_bean.getA() + " and " + m_bean.getB());
    }
    
    private void changeValues(String lead) {
        m_bean.setA(lead + "A");
        m_bean.setB(lead + "B");
    }
    
    public static void main(String[] args) {
        BeanTest inst = new BeanTest();
        inst.print();
        inst.changeValues("new");
        inst.print();
    }
}

Đây là kết quả khi tôi vừa chạy trực tiếp chương trình BeanTest BeanTest của Liệt kê 2:

[dennis]$ java -cp . BeanTest
Bean values are originalA and originalB
Bean values are newA and newB

Nếu tôi chạy nó bằng cách sử dụng chương trình TranslateConvert của Liệt kê 1 và chỉ định một trong các phương thức set để theo dõi, thì kết quả sẽ như sau:

[dennis]$ java -cp .:javassist.jar TranslateConvert Bean setA BeanTest
Bean values are originalA and originalB
Call to set value newA
Bean values are newA and newB

Tất cả mọi thứ hoạt động giống như trước, nhưng bây giờ có một khai báo rằng phương thức được chọn này đang được gọi trong lúc thực hiện chương trình.

Trong trường hợp này, tác dụng tương tự có thể dễ dàng đạt được theo các cách khác, ví dụ bằng cách thêm mã vào phần thân của phương thức set thực tế khi sử dụng các kỹ thuật từ Phần 4. Sự khác biệt ở đây là do thêm đoạn mã tại điểm sử dụng này, tôi đạt được tính linh hoạt. Ví dụ, tôi có thể dễ dàng thay đổi phương thức TranslateConvert.ConverterTranslatoronWrite() để kiểm tra tên lớp đang được nạp và chỉ biến đổi các lớp được tính đến trong một danh sách mà tôi quan tâm theo dõi. Việc thêm mã trực tiếp vào phần thân của phương thức set sẽ không cho phép theo dõi có chọn lọc như vậy.

Tính linh hoạt do các phép chuyển đổi bytecode có hệ thống cung cấp là điều làm cho chúng trở thành một công cụ mạnh trong việc triển khai thực hiện các mở rộng theo hướng-khía cạnh cho các mã Java tiêu chuẩn. Bạn sẽ thấy nhiều hơn về điều này trong phần còn lại của bài viết này.

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.

Các hạn chế chuyển đổi

Các phép chuyển đổi do CodeConverter thực hiện có lợi, nhưng bị hạn chế. Ví dụ, nếu bạn muốn gọi một phương thức theo dõi trước hoặc sau khi gọi một phương thức đích, thì phương thức theo dõi đó phải được định nghĩa như là static void (khoảng trống cố định) và phải chọn một tham số lớp của phương thức đích, tiếp theo là số và các kiểu tham số như phương thức đích. Khi phương thức theo dõi này được gọi, đối tượng đích thực sự chuyển qua như là đối số đầu tiên của nó, tiếp theo là tất cả các đối số cho phương thức đích.

Cấu trúc cứng nhắc này muốn nói rằng các phương thức theo dõi cần phải giống hệt với lớp và phương thức đích. Với ví dụ này, giả sử tôi thay đổi định nghĩa của phương thức reportSet() trong Liệt kê 1 để chọn một tham số java.lang.Object chung với hy vọng làm cho nó có thể sử dụng với các lớp đích khác nhau:

    public static void reportSet(Object target, String value) {
        System.out.println("Call to set value " + value);
    }

Điều này biên dịch tốt, nhưng khi tôi tiến hành thử nó thì nó không hoạt động:

[dennis]$ java -cp .:javassist.jar TranslateConvert Bean setA BeanTest
Bean values are A and B
java.lang.NoSuchMethodError: TranslateConvert.reportSet(LBean;Ljava/lang/String;)V
        at BeanTest.changeValues(BeanTest.java:17)
        at BeanTest.main(BeanTest.java:23)
        at ...

Có nhiều cách để khắc phục được hạn chế này. Một giải pháp là thực sự tạo một phương thức theo dõi tùy chỉnh trong thời gian chạy giống với phương thức đích. Tuy nhiên có rất nhiều nỗ lực để vượt qua và tôi thậm chí sẽ không thử nó cho bài viết này. May mắn thay, Javassist cũng cung cấp một cách xử lý các phép biến đổi bytecode có hệ thống khác. Cách khác này, sử dụng javassist.ExprEditor, vừa linh hoạt hơn và vừa mạnh mẽ hơn CodeConverter.


Phân lớp được thực hiện dễ dàng

Các phép chuyển đổi Bytecode với javassist.ExprEditor xây dựng trên các nguyên tắc giống như các phép chuyển đổi đã làm khi sử dụng CodeConverter. Cách tiếp cận ExprEditor có lẽ hơi khó hiểu, nên tôi sẽ bắt đầu bằng cách giải thích các nguyên tắc cơ bản, rồi bổ sung các phép chuyển đổi thực sự.

Liệt kê 3 chỉ ra cách bạn có thể sử dụng ExprEditor để thông báo các mục cơ bản là các đích tiềm năng cho các phép chuyển đổi hướng-khía cạnh. Ở đây tôi phân lớp ExprEditor trong VerboseEditor riêng của mình, ghi đè ba trong số các phương thức lớp cơ sở -- tất cả có tên edit(), nhưng với các kiểu tham số khác nhau. Như trong đoạn mã của Liệt kê 1, trên thực tế tôi sử dụng lớp con này từ trong phương thức onWrite() của lớp bên trong DissectionTranslator, chuyển một cá thể trong cuộc gọi đến phương thức instrument() của đối tượng lớp cho mỗi lớp được nạp từ cá thể ClassPool của chúng ta.

Liệt kê 3. Một trình phân tích lớp (dissector)
public class Dissect
{
    public static void main(String[] args) {
        if (args.length >= 1) {
            try {
                
                // set up class loader with translator
                Translator xlat = new DissectionTranslator();
                ClassPool pool = ClassPool.getDefault(xlat);
                Loader loader = new Loader(pool);
                    
                // invoke the "main" method of the application class
                String[] pargs = new String[args.length-1];
                System.arraycopy(args, 1, pargs, 0, pargs.length);
                loader.run(args[0], pargs);
                
            } catch (Throwable ex) {
                ex.printStackTrace();
            }
            
        } else {
            System.out.println
                ("Usage: Dissect main-class args...");
        }
    }
    
    public static class DissectionTranslator implements Translator
    {
        public void start(ClassPool pool) {}
        
        public void onWrite(ClassPool pool, String cname)
            throws NotFoundException, CannotCompileException {
            System.out.println("Dissecting class " + cname);
            CtClass clas = pool.get(cname);
            clas.instrument(new VerboseEditor());
        }
    }
    
    public static class VerboseEditor extends ExprEditor
    {
        private String from(Expr expr) {
            CtBehavior source = expr.where();
            return " in " + source.getName() + "(" + expr.getFileName() + ":" +
                expr.getLineNumber() + ")";
        }

        public void edit(FieldAccess arg) {
            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. Đó 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.


Tải về

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

Tài nguyên

  • Xem phần còn lại của loạt bài Động lực học lập trình Java của Dennis Sosnoski.
  • 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 đó. Hãy tải về bản phát hành hiện tại của Javassist từ Trang các tệp dự án JBoss trên Sourceforge.
  • Tìm 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) của Peter Haggar.
  • 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ử "Cải thiện mô đun với việc lập trình theo hướng-khía cạnh" (developerWorks. 01. 2002) của Nicholas Lesiecki với một tổng quan về làm việc với ngôn ngữ AspectJ. Một bài báo gần đây, "AOP banishes the tight-coupling blues" (developerWorks, 02.2004) của Andrew Glover chỉ ra cách một trong những khái niệm thiết kế chức năng của AOP, sự chuyển động nhanh tĩnh, có thể biến những gì có lẽ là một đống mã ghép chặt rối loạn thành một ứng dụng doanh nghiệp mở rộng, mạnh mẽ.
  • 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.
  • Duyệt các sách về các chủ đề kỹ thuật này và khác.
  • 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=452533
ArticleTitle=Độ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 Javassist
publish-date=12042009