22

I would like to replace the content of some methods at runtime.

I know I can use javassist for this but it does not work because the classes I would like to enhance are already loaded by the system classLoader.

How can I do, to replace the content of a method at runtime ? Should I try to unload the class ? How can I do that ? I saw it was possible but I could not figure out how to do it.

If possible, I would like to avoid using an external lib for this, I would like to code it my-self.

More information: - The class I would like to enhance is contained in a framework (in a jar file) - My code is actually a plugin of this framework - The framework in which my plugin runs has its own classLoader, but this classLoader does not load its own classes (it delegates them to the system class loader) - The framework I'm using is Play.

Thank you for your help !

Fabien Henon
  • 783
  • 15
  • 37

2 Answers2

8

You can do it with Javaassist, as well as any other bytecode engineering library out there. The magic lies in the Java Attach API, which allows programs to attach to running JVMs (and modify loaded classes).

It can be found in the com.sun.tools.attach package, and as the name implies, is specific to the Oracle JVM. Nonetheless, JDK tools like jstack and jmap use it to support their "attach to running JVM" feature, so it's safe to say it's here to stay.

The docs on the Attach API are fairly descriptive, and this Oracle blog post demonstrates attaching an agent at runtime. In general, it boils down to:

  • Make a retransforming program the "regular" -javaagent way, with premain et al
  • Rename premain to agentmain
  • Create a temporary JAR file containing your agent classes and having a manifest pointing Agent-Class to your agent (agentmain-containing) class, and Can-Retransform-Classes set to true
  • Obtain the PID of the target JVM (potentially the same process), and attach the temporary jar to it

Thankfully, the API can do this without much work on your part, though if you are doing the JAR generation at runtime it may be a bit tricky to package all the classes needed by your agent.

I was hoping to include a demo agent demonstrating attaching a profiler at runtime, but it ended up being too lengthy to post. Nonetheless, I've put it up in a Github repo.

A caveat of this approach is that it makes your program dependent on the tools.jar that ships with JDKs, and which is not present in JREs. You can get around this by shipping tools.jar with (or extracted in) your application, but you will still need to provide the attach native library required by the Attach API with your application. I've included the libraries for all platforms I could find in the repository linked above, though you may obtain them by yourself too.

Depending on your usecase, this may or may not be ideal. But it certainly works!


This isn't clear in the question, but if what you wish to do is completely "hotswap" a class at runtime with your own, you do not need to use any bytecode manipulation library. Instead, you may compile your class separately (ensuring the same package, class name, etc.) and simply return the bytes for your new class when transform is called on your target class.

Xyene
  • 2,304
  • 20
  • 36
4

Normal ClassLoaders don't support undefining or modifying classes once they have become defined. So the plugin cannot modify the behaviour of the framework unless that framework provides hooks for such customizations.

You can create a custom class loader which hides some classes from its parent class loader, and instead redefines them, adding any instrumentation you might whish for. But the framework gets loaded before the plugin, and will resolve classes using its own class loader. So it will continue to use the uninstrumented versions of the classes.

The only reasonable way to avoid this (that I can think of) is to be there first: if your code gets launched first, it can introduce a class loader to be used to load the framework. But this means that you'll have to have some way to get your code into the chain as a wrapper around the framework. Not sure whether this is feasible in your situation.

Update in reply to comment:
In order to create a class Loader which ides some classes, you have to override its loadClass method. If your licensing allows the use of GPL code, you can look at how OpenJDK does this in the default implementation. You'd only defer to the parent class loader for those classes you don't want to hide.

You'll still have to modify the class after hiding the parent version. Perhaps the BCEL class loader can help you there. Or you load the class from a jar file containing a modified version. Or something like this.

MvG
  • 57,380
  • 22
  • 148
  • 276
  • I can't "be there first". However you said something about "hiding" some classes. Do you mean that I could create another classLoader, make it inherits from the framework's classLoader and when the class I want to enhance is asked for, I load it my-self? (and if another class is asked, I let the super class load it?). Would it work? If yes, how can I do to load the class? Having a copy of this class elsewhere and loading this copy? Finding the class, enhancing it and returning the newly created class? – Fabien Henon Jul 31 '12 at 22:20
  • I tried to add log in the classLoader defined by play framework but it does not seem to be called (the loadClass method) when the class I want to enhance is used. Is it because it's loaded from the system class loader ? How could I do then ? Is it because it's a class with only static methods ? – Fabien Henon Aug 01 '12 at 10:34
  • @FabienHenon, you can query each class about the class loader with which it was loaded, using `Class.getClassLoader()`. You can use that to see who loaded the class in question. I guess it will be the system class loader. As I said, you *have* to come before the framework to decide which class loader gets used. – MvG Aug 01 '12 at 12:00
  • So it's not possible to replace the content of a method if I can't replace the classLoader before the framework ? I can't just set a new loader ? Or release the system loader ? – Fabien Henon Aug 01 '12 at 14:04
  • @FabienHenon, any new loader only affects classes you explicitely load through it, or the classes they refer to. So you'd have to replace the whole framework as well. Which is not what you want, as far as I understand you. The kind of modification you have in mind is called “monkey-patching” in languages such as python. There was [a question](http://stackoverflow.com/q/381226/1468366) about this with respect to java, but I doubt the solution will apply to your case either. – MvG Aug 01 '12 at 15:20
  • Ok. I will try to find another solution then. Thank you for your help ! – Fabien Henon Aug 01 '12 at 16:52