The dynamics of Java programming, part 5th: Dynamic Conversion Class--Reprint

Source: Internet
Author: User

In section 4, "Class conversion with Javassist", you learned how to use the Javassist framework to convert compiler-generated Java class files and write back the modified class file. This kind of file conversion procedure is ideal for making persistent changes, but this approach is not necessarily convenient if you want to make different changes each time the application is executed. For this temporary change, it is much better to take action when you actually start the application.

The JVM architecture provides us with a convenient way to do this-by using the ClassLoader implementation. By using the ClassLoader hooks, you can intercept the process of loading classes into the JVM and convert them before actually loading the classes. To illustrate how this process works, I will first demonstrate the direct interception of the class loading process, and then show how Javassist provides a convenient shortcut that can be used in your application. Throughout the process, I will take advantage of code snippets from previous articles in this series.

Load Zone

The usual way to run a Java application is to specify the main class as a parameter to the JVM. This is not a problem for standard operations, but it does not provide any way to intercept the class loading process in a timely manner, which is useful for most applications. As I discussed in part 1th, class and class loading, many classes have been loaded even before the main class has started to execute. To intercept the loading of these classes, you need to perform some level of redirection during the execution of the program.

Fortunately, simulating the JVM's work when running the main class of the application is fairly easy. All you need to do is use reflection (which is not covered in part 2nd) to first find the static method in the specified class main() , and then invoke it using the expected command-line arguments. Listing 1 provides sample code to complete this task (for simplicity, I omitted the import and exception handling statements):

Listing 1. Java Application Runner
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.ge                    Tclassloader ().                                    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 ..."); }    }}

To use this class to run a Java application, simply specify it as java the target class for the command, followed by the main class of the application and any other parameters that you want to pass to the application. In other words, if the command used to run the Java application is:

Java test. Test arg1 arg2 Arg3

You will use the following command to run the application as appropriate Run :

Java Run test. Test arg1 arg2 Arg3
Blocking class loading

For its part, the short class in Listing 1 Run  is not very useful. In order to achieve the target of the interception class loading process, we need to take further actions to define and use our own ClassLoader for the application class.

As we discussed in the 1th part, ClassLoader uses a tree-like hierarchy. Each classloader (except the root ClassLoader for the core Java Class) has a parent classloader. Classloader should examine their parent Classloader before loading classes on their own to prevent conflicts that might arise when multiple Classloader in a hierarchy load the same class. The process of first checking the parent ClassLoader is called delegating ――classloader delegating responsibility for loading the class to the ClassLoader closest to the root, which has access to the information to load the class.

When the program in Listing 1 Run starts executing, it has been loaded by the JVM's default System ClassLoader (the one specified by the classpath that you defined). To conform to the class-loaded delegate rules, we need to use exactly the same classpath information and delegates for the same parent ClassLoader, making our ClassLoader a real replacement for the System ClassLoader. Fortunately, the classes currently used by the JVM for System ClassLoader implementations java.net.URLClassLoader provide an easy way to retrieve classpath information, using getURLs()  methods. In order to write ClassLoader, we only need to java.net.URLClassLoader  derive the subclass and initialize the base class to use the same classpath and parent ClassLoader as the System classloader to load the main class. Listing 2 provides a concrete implementation of this approach:

Listing 2. A detailed ClassLoader
public class Verboseloader extends urlclassloader{protected verboseloader (url[] URLs, ClassLoader parent) {sup    ER (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 (nam        e);        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 is used for loading ClassLoader base = Classloader.getsystemclassl                Oader ();                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 Pat                HS: ");                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 ..."); }    }}

We have java.net.URLClassLoader derived our own VerboseLoader class, which lists all the classes that are being loaded, and also indicates which classes are loaded by this loader instance instead of the delegate parent ClassLoader. The import and exception handling statements are also omitted for brevity.

VerboseLoaderThe first two methods in a class loadClass() and the findClass() standard ClassLoader method are overloaded. loadClass()methods are called for each class of ClassLoader requests. In this example, we only let it print a message to the console and then call its base class version to perform the actual processing. The base class method implements the standard ClassLoader delegate behavior, which is to first check that the parent ClassLoader is able to load the requested class, and only try to load the class directly using a protected method only if the parent ClassLoader cannot load the class findClass() . For findClass() the VerboseLoader implementation, we first call the overloaded base class implementation and then print a message when the call succeeds (returns without throwing an exception).

VerboseLoadermain()method or get a list of classpath URLs from the loader used to contain the class, or use URLClassLoader the current directory as a unique Classpath entry in case of loader with an instance that does not belong. Either way, it lists the path that is actually in use, then creates VerboseLoader an instance of the class and uses that instance to load the target class specified on the command line. The remainder of the logic (that is, the method to find and invoke the target class) is the main() same as the code in Listing 1 Run .

Listing 3 shows VerboseLoader An example of the command line and output, which is used to invoke the application in Listing 1 Run :

Listing 3. Example output from the program in Listing 2
[dennis]$ Java verboseloader runloading from paths:file:/home/dennis/writing/articles/devworks/dynamic/code5/ LoadClass:RunloadClass:java.lang.Objectfindclass:loaded Run from this LoaderloadClass:java.lang.ThrowableloadClass : Java.lang.reflect.InvocationTargetExceptionloadClass:java.lang.IllegalAccessExceptionloadClass: Java.lang.IllegalArgumentExceptionloadClass:java.lang.NoSuchMethodExceptionloadClass: Java.lang.ClassNotFoundExceptionloadClass:java.lang.NoClassDefFoundErrorloadClass:java.lang.ClassloadClass: Java.lang.StringloadClass:java.lang.SystemloadClass:java.io.PrintStreamUsage:Run Main-class args ...

In this example, the only class that is loaded directly by is the class VerboseLoader Run  . RunAll other classes used are core Java classes that are loaded by using a delegate with the parent ClassLoader. Most, if not all, of these core classes are actually VerboseLoader loaded during the start of the application itself, so the parent ClassLoader will return only a reference to the previously created java.lang.Class instance.

Javassist Intercept

The VerboseClassloader basic process of intercepting class loading is shown in Listing 2. To modify a class at load time, we can go a step further by findClass() adding code to the method, accessing the binary class file as a resource, and then using the binary data. Javassist actually includes code to complete such interception directly, so instead of extending this example, let's look at how to implement it using Javassist.

The process of using Javassist to intercept class loading relies on the same class we used in part 4th javassist.ClassPool . In this article, we ClassPool javassist.CtClass retrieve the Javassist representation of the class by name directly from the request class, in the form of an instance. However, that is not ClassPool the only way to use ――javassist also javassist.Loader provides a classloader that is used ClassPool as its class data source in the form of a class.

To allow you to manipulate the classes as they are loaded, ClassPool a Observer mode is used. You can ClassPool pass an instance of the expected observer interface (Observer interface) to the constructor javassist.Translator . Whenever ClassPool a new class is requested from, it invokes the Observer's onWrite() method, which can ClassPool modify the class's representation before the class is delivered.

javassist.LoaderThe class includes a convenient run() method that loads the target class and invokes the method of the class using the supplied parameter array main() (as in Listing 1). Listing 4 shows how to use the Javassist class and this method to load and run the target application class. In this example, the simple javassist.Translator observer implementation simply prints a message about the class being requested.

Listing 4. Javassist Application Runner
public class javassistrun{public static void Main (string[] args) {if (args.length >= 1) {try { Set up class loader with translator translator Xlat = new Verbosetranslat                or ();                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); }    }}

The following is JavassistRun an example of command line and output, which is used to invoke the application in Listing 1 Run .

[Dennis] $java-CP.: Javassist.jar javassistrun runonwrite called for Runusage:run main-class args ...

Back to top of page

Run-time timing

The method that we analyzed in part 4th is a useful tool for isolating performance problems, but it does require a more flexible interface. In this article, we simply pass the class and method names as parameters to the program, the program loads the binary class file, adds the timing code, and then writes back to the class. For this article, we will convert the code to use the load-time modification method and convert it to support pattern matching to specify the classes and methods to be timed.

It is easy to change the code to handle this modification when the class is loaded. Based on the code in Listing 4 javassist.Translator , when the class name being written out matches the name of the target class, we can only invoke the method that was onWrite() used to add the timing information. Listing 5 shows this ( addTiming() all the details are not included – see section 4 for these details).

Listing 5. Adding timing code at load time
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)) {CtCl                The Clas = Pool.get (CNAME);            Addtiming (Clas, m_methodname); }        }    }}
Mode method

As shown in Listing 5, in addition to making the method timing code work at load time, it is also desirable to add flexibility when specifying the method to be timed. I initially java.util.regex implemented this by using regular expression matching support in the Java 1.4 package, and realized that it didn't really bring the kind of flexibility I wanted. The problem is that the meaningful pattern types used to select the classes and methods to be modified do not fit well with the regular expression model.

So what kind of patterns make sense for choosing classes and methods? What I want is the ability to use any number of characteristics of classes and methods in a pattern, including the actual class and method names, return types, and invocation parameter types. On the other hand, I don't need a really flexible comparison of names and types-simple equality comparisons can handle most of the things I'm interested in, and adding a basic wildcard to that comparison can handle all the rest of the situation. The easiest way to deal with this situation is to make the pattern look like a standard Java method declaration, and then some extensions.

For an example of this approach, here are a few test.StringBuilder String buildString(int) patterns that match the methods of the class:

Java.lang.String test. stringbuilder.buildstring (int) test. stringbuilder.buildstring (int) *buildstring (int) *buildstring

The common pattern of these patterns is first an optional return type (with exact text), followed by a combination of class and method name patterns (with "*" wildcard characters), and finally a list of parameter types (with exact text). If you provide a return type, you must use a space to isolate it from the method name match, and the parameter list follows the method name match. To make the parameter matching more flexible, I set it in two ways. If the given argument is a list enclosed in parentheses, they must exactly match the method parameter. If they are using square brackets (&ldquo; []&rdquo;), the listed types must all be supplied as parameters of the matching method, but the method can be used in any order, and additional parameters can be used. Therefore *buildString(int, java.lang.String) , any method whose name ends with "buildstring" is matched, and these methods accurately accept a int type and a String type parameter in order. *buildString[int,java.lang.String]methods with the same name will be matched, but these methods accept two or more parameters, one of which is the int type and the other is the java.lang.String type.

Listing 6 shows a shorthand version of the subclass I wrote to handle these patterns javassist.Translator . The actual match code is not really relevant to this article, but if you want to view it or use it yourself, I've included it in the download file (see Resources). The TimingTranslator main program class that uses this is BatchTiming , it is also included in the download file.

Listing 6. Pattern matching Translator
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 (Cnam        e);        ctmethod[] meths = Clas.getdeclaredmethods (); for (int i = 0; i < meths.length; i++) {//Check if method matches full pattern Ctme            Thod meth = meths[i]; if (MatchType (meth) && matchparameters (Meth) && MatchName (meth)) {//Handle the actual timing modification addtiming (meth); }        }    }}

Back to top of page

Subsequent content

In the last two articles, you've seen how to use Javassist to handle basic transformations. For the next article, we'll explore the advanced features of this framework, which provide search and replace techniques for editing byte codes. These features make it easy to make systematic changes to the behavior of the program, including changes such as intercepting all method calls or all field accesses. They are the key to understanding why Javassist is an excellent framework for providing aspect-oriented support in Java programs. Please come back next month and see how you can use Javassist to uncover aspects of your application (aspect)

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

The dynamics of Java programming, part 5th: Dynamic Conversion Class--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.