Dynamics of Java programming, Part 5: dynamic conversion classes — reprint

In part 4, "", you learned how to use the javassist framework to convert the Java class file generated by the compiler and write back the modified class file. This kind of file conversion step is ideal for making persistent changes, but it is not necessarily convenient if you want to make different changes each time the application is executed. For this temporary change, it's much better to take the approach that works when you actually start the application.

The JVM architecture provides us with a convenient way to do this -- through the use of classloaders. By using the classloader hook, you can intercept the process of loading classes into the JVM and convert them before actually loading them. To illustrate how this process works, I'll first show 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'll use code snippets from previous articles in this series.

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 operation, but it does not provide any way to intercept the class loading process in time, which is very useful for most programs. As I discussed in part 1, "", many classes are loaded even before the main class starts executing. To intercept the loading of these classes, you need some redirection during the execution of the program. Fortunately, it is fairly easy to simulate what the JVM does when running the main class of the application. All you need to do is use reflection, which is described in, to first find the static main () method in the specified class, and then call it with the expected command-line arguments. Listing 1 provides sample code to accomplish this task (I omitted the import and exception handling statements for simplicity):

= 1) { try { // load the target class to be run

On its own, the short run class in Listing 1 is not very useful. In order to achieve the goal of intercepting the class loading process, we need to take further action to define and use our own classloader for the application class. As we discussed in part 1, the classloader uses a tree hierarchy. Each classloader (except the root classloader used by the JVM for core Java classes) has a parent classloader. Classloaders should check their parent classloaders before loading classes alone to prevent possible conflicts when multiple classloaders in a hierarchy load the same class. First, the process of checking the parent classloader is called delegation - the classloader delegates the responsibility of loading the class to the classloader closest to the root, which can access the information of the class to be loaded. When the run program starts executing, it has been loaded by the JVM default system classloader (the one specified by the classpath you defined). In order to comply with the delegation rules of class loading, we need to use exactly the same classpath information and delegation for the same parent classloader, so as to make our classloader a real substitute for system classloader. Fortunately, the JVM is currently used for the Java. Java implementation of the system classloader net. The urlclassloader class provides an easy way to retrieve classpath information using the geturls () method. In order to write the classloader, we just need to start from Java net. Urlclassloader subclasses and initializes the base class to use the same classpath and parent classloader as the system classloader that loads the main class. Listing 2 provides a concrete implementation of this approach:

public Class loadClass(String name) throws ClassNotFoundException { System.out.println("loadClass: " + name); return super.loadClass(name); } protected Class findClass(String name) throws ClassNotFoundException { Class clas = super.findClass(name); System.out.println("findclass: loaded " + name + " from this loader"); return clas; } public static void main(String[] args) { if (args.length >= 1) { try { // get paths to be used for loading ClassLoader base = ClassLoader.getSystemClassLoader(); URL[] urls; if (base instanceof urlclassloader) { urls = ((urlclassloader)base).getURLs(); } else { urls = new URL[] { new File(".").toURI().toURL() }; } // list the paths actually being used System.out.println("Loading from paths:"); for (int i = 0; i < urls.length; i++) { System.out.println(" " + urls[i]); } // load target class using custom class loader VerboseLoader loader = new VerboseLoader(urls,base.getParent()); Class clas = loader.loadClass(args[0]); // invoke "main" method of target class Class[] ptypes = new Class[] { args.getClass() }; Method main = clas.getDeclaredMethod("main ",pargs.length); Thread.currentThread(). setContextClassLoader(loader); main.invoke(null,new Object[] { pargs }); } catch ... } } else { Sy stem < / a >. Out. Println (" usage: verboseloader main class args... ");}}}} we have derived our own verboseloader class from java.net.urlclassloader, which lists all classes being loaded and indicates which classes are generated by this loader instance (instead of delegating the parent classloader). The import and exception handling statements are also omitted here for brevity. The first two methods loadclass() and findclass() in the verboseloader class overload the standard classloader methods. The loadclass () method is called for each class requested by the classloader. In this case, we only let it print a message to the console, then invoke its base class version to perform the actual processing. The base class method implements the standard classloader delegate behavior, that is, first check whether the parent classloader can load the requested class, and try to load the class directly using the protected findclass () method only when the parent classloader cannot load the class. For the verboseloader implementation of findclass(), we first call the overloaded base class implementation, and then print a message when the call succeeds (returned without throwing an exception). The main() method of verboseloader or obtain the list of classpath URLs from the loader used to contain the class, Or simply use the current directory as the only classpath entry when used with loaders that are not instances of urlclassloader. Either way, it lists the paths that are actually being used, then creates an instance of the verboseloader class and uses that instance to load the target class specified on the command line. The rest of the logic (that is, finding and calling the main () method of the target class) is the same as the run code in. Listing 3 shows an example of the verboseloader command line and output, which is used to call the run application in Listing 1:
In this example, the only class loaded directly by verboseloader is the run class. All other classes used by run are core Java classes, which are loaded using delegates through the parent classloader. Most, if not all, of these core classes are actually loaded during the startup of the verboseloader application itself, so the parent classloader will only return a pointer to the previously created Java Lang. class instance.

The verbose class loader in shows the basic process of intercepting class loading. In order to modify the class during loading, we can go further, add code to the findclass () method, access the binary class file as a resource, and then use the binary data. Javassist actually includes code to directly complete such interception, so instead of further expanding this example, let's see how to use javassist implementation. The process of using javassist to intercept class loading depends on the same javassist we use in Classpool class. In this article, we request classes directly from classpool by name to javassist Retrieve the javassist representation of the class in the form of a ctclass instance. However, that's not the only way to use classpool -- javassist is also called javassist In the form of loader class, provide a classloader that uses classpool as its class data source. To allow you to manipulate classes when they are loaded, classpool uses an observer mode. You can pass an instance of the expected observer interface javassist. Translator to the constructor of classpool. Whenever a new class is requested from classpool, it calls the observer's onwrite() method, which can modify the class's representation before classpool delivers the class. javassist. The loader class includes a convenient run () method that loads the target class, And use the provided parameter array to call the main () method of the class (as in). 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 only prints a message about the class being requested.

= 1) { try { // set up class loader with translator
The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>