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:
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