The dynamics of Java programming, part 6th: Using Javassist for aspect-oriented changes--reprint

Source: Internet
Author: User
Tags jboss jboss application server

Part 4 and 5th of this series discuss how to use Javassist to make local changes to a binary class. This time you will learn to use the framework in a more powerful way to take advantage of Javassist's support for finding all specific methods or fields in bytecode. For the Javassist feature, this feature is at least as important as its ability to specify bytecode in a similar way to source code. Support for the Select substitution operation also helps make javasssist a great tool for adding aspect-oriented programming functionality in standard Java code.

The 5th section describes how Javassist allows you to intercept the class loading process-even when the binary class indicates that it is being loaded. The system bytecode conversions discussed in this article can be used for static class file conversions or for runtime interception, but it is especially useful for runtime use.

Processing bytecode modifications

Javassist provides two different ways of handling system bytecode modifications. The first technique is javassist.CodeConverter to use classes, which are slightly simpler to use, but there are many limitations to the tasks that can be accomplished. The second technique uses javassist.ExprEditor the class's custom subclass, which is slightly more complex, but the added flexibility is enough to offset the effort. In this article I will analyze examples of these two methods.

Code Conversion

The first Javassist technique for system bytecode modification uses javassist.CodeConverter classes. To take advantage of this technique, you only need to create CodeConverter an instance of the class and configure it with one or more transformation operations. Each conversion is configured with a method that identifies the type of conversion. Conversion types can be divided into three categories: Method invocation transformations, field access transformations, and new object transformations.

Listing 1 shows an example of using a method invocation transformation. In this example, the conversion simply adds a notification that a method is being called. In the code, first get the instance that will be used javassist.ClassPool and configure it to work with a translator (as seen in part 5th above). Then, by ClassPool accessing the two method definitions. The first method defines a method for the type of "set" to be monitored (class and method names are from command-line arguments), the second method definition is for the reportSet() method ,它位于 TranslateConvert class, and a call to the first method is reported.

With the method information, you can CodeConverter insertBeforeMethod() Configure a transformation to add a call to the reporting method before each call to the set method. Then all you have to do is apply this converter to one or more classes. In the code in Listing 1, I do this by invoking the method of the class object in the method of the instrument() ConverterTranslator inner class onWrite() . This automatically ClassPool applies the transformation to each class that is loaded from the instance.

Listing 1. Using Codeconverter
public class translateconvert{public static void Main (string[] args) {if (Args.length >= 3) {TR                    Y {//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" + V    Alue);                } 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); }    }}

Configuring a transform is a fairly complex operation, but when set up, you don't have to worry about it when it's working. Listing 2 shows a sample code that can be used as a test case. A Bean test object with a similar bean-like get and set method is provided, which is used by the BeanTest program to access the values.

Listing 2. A Bean test program
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;    The public void SetA (string string) {m_a = string;    The 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 is" + 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 (); }}

If you run the program in Listing 2 directly 中的 BeanTest , the output is as follows:

[dennis]$ JAVA-CP. Beantestbean values is Originala and Originalbbean values are Newa and NEWB

If you run it with the program in Listing 1 TranslateConvert and specify that you monitor one of the set methods, the output looks like this:

[dennis]$ java-cp.: Javassist.jar translateconvert Bean SetA beantestbean values is Originala and originalbcall to set V Alue Newabean values are Newa and NEWB

Each work is the same as before, but there is now a notification when the selected method is invoked when the program is executed.

In this example, the same effect can be easily achieved in other ways, for example by adding code to the actual set method body by using the techniques in section 4. The difference here is that adding code at the point of use gives me the flexibility. For example, you can easily modify the TranslateConvert.ConverterTranslator onWrite() method to check the class name being loaded, and only convert the classes listed in the manifest for the classes I want to monitor. Adding code directly in the body of the set method does not make this selective monitoring possible.

System bytecode conversion provides the flexibility to make it a powerful tool for implementing aspect-oriented extensions of standard Java code. You'll see more about this later in this article.

Conversion restrictions

CodeConverterconversion by processing is useful, but has limitations. For example, if you want to invoke a monitoring method before or after the target method is called, the monitoring method must be defined as static void a parameter of the class of the target method, followed by the same number and type of parameters as the target method requires.

This strict structure means that the monitoring method needs to match the target class and method exactly. For example, let's say I changed the definition of the method in Listing 1 reportSet() so that it accepts a generic java.lang.Object parameter and wants it to be used in different target classes:

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

The compilation is fine, but when I run it it will break:

[dennis]$ java-cp.: Javassist.jar translateconvert Bean SetA beantestbean values are A and Bjava.lang.NoSuchMethodError: Translateconvert.reportset (Lbean; ljava/lang/string;) V at        beantest.changevalues (beantest.java:17) at        beantest.main (beantest.java:23)        At ...

There are ways to circumvent this limitation. One solution is to actually generate a custom monitoring method that matches the target method at run time. But this is going to be a lot of work, and I'm not going to test this in this article. Fortunately, Javassist also provides another way to handle system bytecode conversions. This method is javassist.ExprEditor CodeConverter more flexible and more powerful than it is to use.

Back to top of page

Easy-to-sort anatomy

CodeConverterthe same principle used for byte-code conversion javassist.ExprEditor . However, the ExprEditor way may be more difficult to understand, so I first show the rationale, and then add the actual conversion.

Listing 3 shows a basic project on how to ExprEditor report the possible goals of a facet-oriented transformation. Here I derive subclasses in my own VerboseEditor ExprEditor , overriding three basic class methods--their names are all edit() but have different parameter types. As the code in Listing 1, I am actually DissectionTranslator using this subclass in the methods of the inner class to onWrite() ClassPool pass an instance to each class that is loaded from the instance in a call to the method of the class object instrument() .

Listing 3. A class Profiler
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 ("Diss            Ecting 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)); }    }}

Listing 4 shows the output from the  中的 BeanTest program in Listing 3 running in Listing 2 Dissect . It gives a detailed analysis of the work done in each method of each class loaded, listing all method invocations, field access, and new object creation.

Listing 4. Profiled Beantest
[dennis]$ java-cp.: Javassist.jar dissect beantestdissecting class beantest new Bean in Beantest (beantest.java:7) Write O F 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 on print (beantest.java:11) call to Java.lang.StringBu Ffer.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 P Rint (beantest.java:11) call-java.lang.StringBuffer.toString in print (beantest.java:11) 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) 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 : +) call to Beantest.print in main (beantest.java:22) call to Beantest.changevalues in main (beantest.java:23) call to Bean Test.print in Main (beantest.java:24) dissecting class beans write of Bean.m_a in Beans (bean.java:10) write of Bean.m_b in Bea N (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) beans values are Originala and OR Iginalbbean values are Newa and NEWB

By VerboseEditor implementing the appropriate method in, it is easy to increase support for reporting coercion type conversions, instanceof checks, and catch blocks. But it's a bit tedious to just list information about these component items, so let's actually modify the project.

To dissect

Listing 4 provides a breakdown of the classes with basic component operations. It is easy to see how useful it is to use these operations when implementing aspect-oriented functionality. For example, a logger (logger) that reports all write access to the selected field will work in many applications. Anyway, I have promised to show you how to do this kind of work.

Fortunately, for the topics discussed in this article, ExprEditor not only do I know what's going on in the code, it also allows me to modify the reported actions. The ExprEditor.edit() parameter types passed in different method invocations define a method, respectively replace() . If you pass an ordinary Javassist source-code statement to this method (described in part 4th), the statement is compiled into bytecode and is used to replace the original operation. This makes it easy to slice and dice the bytecode.

Listing 5 shows an application that replaces the code. Here I am not recording the operation, but choosing to actually modify the value stored in the selected field String . In FieldSetEditor , I implemented a method signature that matches the field access. In this method, I check only two things: whether the field name is what I am looking for, and whether the operation is a stored procedure. Once a match is found, the original storage is replaced with the result of the TranslateEditor method call in the actual application class reverse() . The reverse() method is to reverse the alphabetic order in the original string and output a message indicating that it has been used.

Listing 5. Invert string Set
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)) {CtCl                The 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 (). Equal                S (m_fieldname) && arg.iswriter ()) {StringBuffer code = new StringBuffer ();                Code.append ("$.");                Code.append (Arg.getfieldname ());                Code.append ("=translateeditor.reverse ($);");            Arg.replace (Code.tostring ()); }        }    }}

If you BeanTest run the program in Listing 5 against the program in Listing 2 TranslateEditor , the result is as follows:

[dennis]$ java-cp.: Javassist.jar translateeditor Bean m_a Beantesttranslateeditor.reverse returning AlanigiroBean Values are Alanigiro and Originalbtranslateeditor.reverse returning Awenbean values is Awen and newb

I succeeded in adding a call to the added code each time I saved to the Bean.m_a field (once in the constructor, once in the Set method). I can get the reverse effect by implementing a similar modification to the load from the field, but I personally think that upside down values are much more interesting than the values that I started using, so I chose to use them.

Back to top of page

Packaging Javassist

This article describes the system bytecode conversion that can be done easily with Javassist. Combining this article with the previous two articles, you should have a solid foundation for implementing your own aspect-oriented transformations in your Java application, either as a separate compilation step or at run time.

To better understand the power of this approach, you can also analyze the JBoss Aspect oriented Programming Project (JBOSSAOP) built with Javassis. JBOSSAOP uses an XML configuration file to define all the different operations that are done in the application class. This includes using Interceptors for field access or method calls, adding Mix-in interface implementations to existing classes, and so on. JBOSSAOP will be added to the version of the JBoss application server being developed, but it can also be used as a separate tool outside of JBoss for applications.

The next step in this series is to introduce the Byte Code Engineering Library (BCEL), which is part of the Jakarta project of the Apache software Foundation. BCEL is the most widely used framework in Java classworking. It uses a different approach to the Javassist approach that we saw in the last three articles, focusing on individual bytecode directives rather than on the source-level work that Javassist emphasizes. The full details of working at the bytecode assembler (assembler) level will be analyzed next month.

Original: http://www.ibm.com/developerworks/cn/java/j-dyn0302/index.html

The dynamics of Java programming, part 6th: Using Javassist for aspect-oriented changes--reprint

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.