Skip to content

Commit

Permalink
Update Javassist
Browse files Browse the repository at this point in the history
  • Loading branch information
T5750 committed Jan 31, 2020
1 parent e0d62ef commit dc3b941
Show file tree
Hide file tree
Showing 8 changed files with 329 additions and 5 deletions.
122 changes: 118 additions & 4 deletions doc/source/jdk8/javassistTutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,22 +88,135 @@ If `getAndRename()` is called, the `ClassPool` first reads `Point.class` for cre
3. Call `writeFile()` or `toBytecode()` on that `CtClass` object to obtain a modified class file.

### 3.1 The toClass method in CtClass

The `CtClass` provides a convenience method `toClass()`, which requests the context class loader for the current thread to load the class represented by the `CtClass` object. To call this method, the caller must have appropriate permission; otherwise, a `SecurityException` may be thrown.
```
public class Hello {
public void say() {
System.out.println("Hello");
}
}
public class Test {
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("Hello");
CtMethod m = cc.getDeclaredMethod("say");
m.insertBefore("{ System.out.println(\"Hello.say():\"); }");
Class c = cc.toClass();
Hello h = (Hello)c.newInstance();
h.say();
}
}
```

### 3.2 Class loading in Java
In Java, multiple class loaders can coexist and each class loader creates its own name space. Different class loaders can load different class files with the same class name. The loaded two classes are regarded as different ones. This feature enables us to run multiple application programs on a single JVM even if these programs include different classes with the same name.

**Note**: The JVM does not allow dynamically reloading a class. Once a class loader loads a class, it cannot reload a modified version of that class during runtime. Thus, you cannot alter the definition of a class after the JVM loads it. However, the JPDA (Java Platform Debugger Architecture) provides limited ability for reloading a class.

### 3.3 Using javassist.Loader
#### Dynamic Class Loading Example
ClassLoader working mechanism:
1. A `ClassLoader` instance checks if the class was already loaded.
2. If not loaded, it delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself.
3. If parent class loader cannot load class, it attempt to load the class or resource by itself.

### 3.3 Using javassist.Loader
Javassist provides a class loader `javassist.Loader`. This class loader uses a `javassist.ClassPool` object for reading a class file.
```
public class MyTranslator implements Translator {
void start(ClassPool pool)
throws NotFoundException, CannotCompileException {}
void onLoad(ClassPool pool, String classname)
throws NotFoundException, CannotCompileException
{
CtClass cc = pool.get(classname);
cc.setModifiers(Modifier.PUBLIC);
}
}
```
If the users want to modify a class on demand when it is loaded, the users can add an event listener to a `javassist.Loader`.

The event-listener class must implement `javassist.Translator`:
- The method `start()` is called when this event listener is added to a `javassist.Loader` object by `addTranslator() `in `javassist.Loader`.
- The method `onLoad()` is called before `javassist.Loader` loads a class. `onLoad()` can modify the definition of the loaded class.

Note that `onLoad()` does not have to call `toBytecode()` or `writeFile()` since `javassist.Loader` calls these methods to obtain a class file.
```
public class Main2 {
public static void main(String[] args) throws Throwable {
Translator t = new MyTranslator();
ClassPool pool = ClassPool.getDefault();
Loader cl = new Loader();
cl.addTranslator(pool, t);
cl.run("MyApp", args);
}
}
```

`javassist.Loader` searches for classes in a different order from `java.lang.ClassLoader`. `ClassLoader` first delegates the loading operations to the parent class loader and then attempts to load the classes only if the parent class loader cannot find them. On the other hand, `javassist.Loader` attempts to load the classes before delegating to the parent class loader. It delegates only if:
- the classes are not found by calling `get()` on a `ClassPool` object, or
- the classes have been specified by using `delegateLoadingOf()` to be loaded by the parent class loader.

### 3.4 Writing a class loader

```
public class SampleLoader extends ClassLoader {
/* Call MyApp.main().
*/
public static void main(String[] args) throws Throwable {
SampleLoader s = new SampleLoader();
Class c = s.loadClass("MyApp");
c.getDeclaredMethod("main", new Class[] { String[].class })
.invoke(null, new Object[] { args });
}
private ClassPool pool;
public SampleLoader() throws NotFoundException {
pool = new ClassPool();
pool.insertClassPath("./class"); // MyApp.class must be there.
}
/* Finds a specified class.
* The bytecode for that class can be modified.
*/
protected Class findClass(String name) throws ClassNotFoundException {
try {
CtClass cc = pool.get(name);
// modify the CtClass object here
byte[] b = cc.toBytecode();
return defineClass(name, b, 0, b.length);
} catch (NotFoundException e) {
throw new ClassNotFoundException();
} catch (IOException e) {
throw new ClassNotFoundException();
} catch (CannotCompileException e) {
throw new ClassNotFoundException();
}
}
}
```

### 3.5 Modifying a system class
The system classes like `java.lang.String` cannot be loaded by a class loader other than the system class loader.

the system classes must be **statically** modified.
```
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("java.lang.String");
CtField f = new CtField(CtClass.intType, "hiddenValue", cc);
f.setModifiers(Modifier.PUBLIC);
cc.addField(f);
cc.writeFile(".");
```
```
java -Xbootclasspath/p:. MyApp arg1 arg2...
```
_Note: Applications that use this technique for the purpose of overriding a system class in `rt.jar` should not be deployed as doing so would contravene the Java 2 Runtime Environment binary code license._

### 3.6 Reloading a class at runtime
If the JVM is launched with the JPDA (Java Platform Debugger Architecture) enabled, a class is dynamically reloadable. After the JVM loads a class, the old version of the class definition can be unloaded and a new one can be reloaded again. That is, the definition of that class can be dynamically modified during runtime. However, the new class definition must be somewhat compatible to the old one. **The JVM does not allow schema changes between the two versions.** They have the same set of methods and fields.

Javassist provides a convenient class for reloading a class at runtime. For more information, see the API documentation of `javassist.tools.HotSwapper`.

## 4. Introspection and customization

Expand Down Expand Up @@ -133,4 +246,5 @@ If `getAndRename()` is called, the `ClassPool` first reads `Point.class` for cre


## References
- [Getting Started with Javassist](http://www.javassist.org/tutorial/tutorial.html)
- [Getting Started with Javassist](http://www.javassist.org/tutorial/tutorial.html)
- [Dynamic Class Loading Example](https://examples.javacodegeeks.com/core-java/dynamic-class-loading-example/)
96 changes: 96 additions & 0 deletions jdk8/src/test/java/t5750/javassist/ClassLoaderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package t5750.javassist;

import org.junit.Before;
import org.junit.Test;

import javassist.*;
import t5750.javassist.loader.JavaClassLoader;
import t5750.javassist.loader.SampleLoader;
import t5750.javassist.service.Hello;
import t5750.javassist.service.impl.MyTranslator;
import t5750.javassist.util.JavassistUtil;

public class ClassLoaderTest {
private ClassPool pool;

@Before
public void setup() {
pool = ClassPool.getDefault();
}

/**
* 3.1 The toClass method in CtClass
*/
@Test
public void toClassMethod() throws Exception {
CtClass cc = pool.get(JavassistUtil.SERVICE + "Hello");
CtMethod m = cc.getDeclaredMethod("say");
m.insertBefore("{ System.out.println(\"Hello.say():\"); }");
Class c = cc.toClass();
Hello h = (Hello) c.newInstance();
h.say();
}

/**
* 3.2 Class loading in Java
*/
@Test
public void javaClassLoader() throws Exception {
ClassLoader loader = this.getClass().getClassLoader();
Class clazz = loader.loadClass(JavassistUtil.SERVICE + "Hello");
Object obj = clazz.newInstance();
Hello b = (Hello) obj; // this always throws ClassCastException.
}

/**
* 3.3 Using javassist.Loader
*/
@Test
public void javassistLoader() throws Throwable {
Loader cl = new Loader(pool);
CtClass ct = pool.get(JavassistUtil.DOMAIN + "Rectangle");
ct.setSuperclass(pool.get("java.awt.Point"));
Class c = cl.loadClass(JavassistUtil.DOMAIN + "Rectangle");
Object rect = c.newInstance();
System.out.println(rect.toString());
// To run an application class MyApp with a MyTranslator object
Translator t = new MyTranslator();
cl.addTranslator(pool, t);
cl.run(JavassistUtil.SERVICE + "MyApp", JavassistUtil.ARGS);
}

/**
* 3.4 Writing a class loader
*/
@Test
public void testSampleLoader() throws Throwable {
SampleLoader.main(JavassistUtil.ARGS);
}

/**
* 3.5 Modifying a system class
*/
@Test
public void modifySystemClass() throws Exception {
CtClass cc = pool.get("java.lang.String");
CtField f = new CtField(CtClass.intType, "hiddenValue", cc);
f.setModifiers(Modifier.PUBLIC);
cc.addField(f);
cc.writeFile(".");
CtClass ccMyApp = pool.get(JavassistUtil.SERVICE + "MyApp");
CtMethod m = ccMyApp.getDeclaredMethod("main");
m.insertBefore("{ getHiddenValue(); }");
ccMyApp.writeFile();
// cd java-repositories/jdk8
// java -Xbootclasspath/p:. t5750.javassist.service.MyApp
}

/**
* Dynamic Class Loading Example
*/
@Test
public void dynamicJavaClassLoader() throws Exception {
JavaClassLoader loader = new JavaClassLoader();
loader.invokeClassMethod(JavassistUtil.SERVICE + "Hello", "say");
}
}
28 changes: 28 additions & 0 deletions jdk8/src/test/java/t5750/javassist/loader/JavaClassLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package t5750.javassist.loader;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class JavaClassLoader extends ClassLoader {
public void invokeClassMethod(String classBinName, String methodName) {
try {
// Create a new JavaClassLoader
ClassLoader classLoader = this.getClass().getClassLoader();
// Load the target class using its binary name
Class loadedMyClass = classLoader.loadClass(classBinName);
System.out.println("Loaded class name: " + loadedMyClass.getName());
// Create a new instance from the loaded class
Constructor constructor = loadedMyClass.getConstructor();
Object myClassObject = constructor.newInstance();
// Getting the target method from the loaded class and invoke it
// using its name
Method method = loadedMyClass.getMethod(methodName);
System.out.println("Invoked method name: " + method.getName());
method.invoke(myClassObject);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
46 changes: 46 additions & 0 deletions jdk8/src/test/java/t5750/javassist/loader/SampleLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package t5750.javassist.loader;

import java.io.IOException;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import t5750.javassist.util.JavassistUtil;

public class SampleLoader extends ClassLoader {
/*
* Call MyApp.main().
*/
public static void main(String[] args) throws Throwable {
SampleLoader s = new SampleLoader();
Class c = s.loadClass(JavassistUtil.SERVICE + "MyApp");
c.getDeclaredMethod("main", new Class[] { String[].class }).invoke(null,
new Object[] { args });
}

private ClassPool pool;

public SampleLoader() throws NotFoundException {
pool = new ClassPool();
pool.insertClassPath("./class"); // MyApp.class must be there.
}

/*
* Finds a specified class. The bytecode for that class can be modified.
*/
protected Class findClass(String name) throws ClassNotFoundException {
try {
CtClass cc = pool.get(name);
// modify the CtClass object here
byte[] b = cc.toBytecode();
return defineClass(name, b, 0, b.length);
} catch (NotFoundException e) {
throw new ClassNotFoundException();
} catch (IOException e) {
throw new ClassNotFoundException();
} catch (CannotCompileException e) {
throw new ClassNotFoundException();
}
}
}
7 changes: 7 additions & 0 deletions jdk8/src/test/java/t5750/javassist/service/Hello.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package t5750.javassist.service;

public class Hello {
public void say() {
System.out.println("Hello");
}
}
13 changes: 13 additions & 0 deletions jdk8/src/test/java/t5750/javassist/service/MyApp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package t5750.javassist.service;

public class MyApp {
public static void main(String[] args) throws Exception {
for (String str : args) {
System.out.println("Hello " + str);
}
}

public static void getHiddenValue() throws Exception {
System.out.println(String.class.getField("hiddenValue").getName());
}
}
17 changes: 17 additions & 0 deletions jdk8/src/test/java/t5750/javassist/service/impl/MyTranslator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package t5750.javassist.service.impl;

import javassist.*;

public class MyTranslator implements Translator {
@Override
public void start(ClassPool pool)
throws NotFoundException, CannotCompileException {
}

@Override
public void onLoad(ClassPool pool, String classname)
throws NotFoundException, CannotCompileException {
CtClass cc = pool.get(classname);
cc.setModifiers(Modifier.PUBLIC);
}
}
5 changes: 4 additions & 1 deletion jdk8/src/test/java/t5750/javassist/util/JavassistUtil.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package t5750.javassist.util;

public final class JavassistUtil {
public static final String DOMAIN = "t5750.javassist.domain.";
public static final String JAVASSIST = "t5750.javassist.";
public static final String DOMAIN = JAVASSIST + "domain.";
public static final String SERVICE = JAVASSIST + "service.";
public static final String POINT = "java.awt.Point";
public static final String[] ARGS = new String[] { "World", "T5750" };
}

0 comments on commit dc3b941

Please sign in to comment.