Dynamic Class Generation with the help of Javassist

You are here

We have server application that saves data and receives requests for reading/modification from clients using Java API. Requests are objects of a class that implements certain interface (lets call this class “Request”). Problem may occur when we need to edit Request...

13 March 2015

We have server application that saves data and receives requests for reading/modification from clients using Java API. Requests are objects of a class that implements certain interface (lets call this class “Request”). Upon request the object is serialized for network transmission. Class code may be absent on server side, in this case appropriate bytecode is requested from the client according to internal interaction protocol. After that the new class is uploaded on server, request is successfully deserialized and performed.

Problem may occur when we need to edit Request. As the class is uploaded on server already, it will execute old code, in spite of the fact that there is new version available on the client. Hot code swap is not realized on server.

In case of rare editing server reboot may be acceptable solution. But if the class is in the process of development/testing, it may need too frequent server reboots, that is inconvenient, especially if server is shared among developers.

Another possible variant – rename class before testing. For example, index may be added to the name (Request 1, Request 2, …). It is not convenient to do it manually, nevertheless solution described in this article is based on this approach.

 

Solution 

My first idea was to use some library for dynamic request proxying, for example, JDK proxy or cqlib. My aim was to get new name for proxy class every time and load it as new class of the type RequestProxy$1. Both libraries are appropriate for this task, so we tried both of them. 

But only after approbation we found out that it won't work that way. As, in spite of the fact that proxy is new every time, base class Request, that includes edited code, stays with the same name. In such way server will load Request Proxy$1 for the first request and its ancestors: Request, and maybe others. At the second request server loads RequestProxy$2, but it doesn't load its ancestors, as their names didn't change. In such way every time initial code will be executed. 

We have one more somewhat straightforward variant left – simply rename the class. Of course, we can't just rename it like we do with a file. But we can try creating its copy with other name. Unfortunately, I didn't manage to find the way to do it easily with the help of cglib. And I used a wonderful library Javassist

So, the following code creates a class copy with new random name:

 

// cls — original class 
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get(cls.getCanonicalName());
cc.setName(cls.getCanonicalName() + "Copy" + rnd.nextInt(Integer.MAX_VALUE));

Class queryClass = cc.toClass();

queryClass now points at the copy of initial class with the name like RequestCopy123

The peculiarity of the client part of aforementioned library is that for class bytecode transmission it finds class-file of the requested class on the disk and reads it by itself. That's why in our case it is necessary to generate new class-file and update class loader:

File tempClassDir = new File(FileUtils.getTempDirectory(), "QueryGeneratorClasses");
// to write a new file
cc.writeFile(tempClassDir.getCanonicalPath());
// to update loader
ClassLoader currentThreadClassLoader = Thread.currentThread().getContextClassLoader();
try {
   URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{tempClassDir.toURI().toURL()}, currentThreadClassLoader);
   Thread.currentThread().setContextClassLoader(urlClassLoader);
} catch (MalformedURLException e) {
   e.printStackTrace();
}

Conclusion

I'm sure that described solution may be useful not only in such specific situation, but also in other domains connected with testing or dynamic class loading.

You may download a file containing class-realization of described approach with some improvements like cleaning of folder with created classes after JVM process is finished and parameterized typing to avoid explicit casts.

Strictly speaking, described solution is an automation of process of manual renaming of class each time it needs to be tested after editing. That's why it also has the same disadvantages of the manual approach. For instance, old class versions are left in memory of server part and in the case of long time testing may cause overflow of PermGen. That's why when testing is over and you have more or less stable class version, server should be rebooted. It follows that this approach should be used in production code very cautiously, only when the number of requests is small.