Reputation: 148
I am currently trying to edit a class file in runtime, Example:
Example.java with this code:
public static void execute(){
System.out.println("hello worl");
}
There is no easy way to edit the text in this example, Now i need code that edits the "hello worl" to "hello world" without having access to the Example.java
and without restarting the program to edit byte code, Is this possible? I have searched many articles and have not found a defined answer.
Upvotes: 0
Views: 1278
Reputation: 3695
This depends on how much access you have.
This simplest way is to alert that class before it loads and force JVM to load your version of it, but then it must be loaded by proper ClassLoader, by your nickname I can assume that you are trying to do some magic using Spigot minecraft engine? Then if you want to change class from other plugin all you need to do is to actually copy this class to your project and load this class in static block of your main class - just make sure that your plugin will load before that other one.
This will cause JVM to load this class before original one, and because of how spigot class loader works - it will be added to global map of plugin classes, so other classes with that name will not be loaded, that other plugin will use your class instead.
This is also possible in other places, not just spigot - but not in every application, as it must have similar class loading - with shared storage of classes for your plugin/jar and plugin/jar that you want to edit.
Other way is to use Javassist library to do something similar but in runtime:
ClassPool classPool = ClassPool.getDefault();
CtClass ctToEdit = classPool.getCtClass("my.class.to.Edit");
CtMethod execute = ctToEdit.getDeclaredMethod("execute");
execute.setBody("{System.out.println(\"hello world\");}");
ctToEdit.toClass(); // force load that class
Javassist will find .class file for that class and allow you to edit it, and then inject it to selected class loader (I think it is system one by default, you can use .toClass(ClassLoader)
method too)
Most important part of this trick is that class can't be loaded before this code executes. This is why you need to provide class name manually, never do something like MyClass.class.getName()
, this will break this trick.
Note that javassist java compiler is a bit tricky and limited, see their webpage to find more informations and useful tricks.
If class is already loaded... then you have last option.
You can do it using java agents via Instrumentation
class - as it allows to reload classes in runtime (with few limitations, just like debugger, you can't change class schema, but you can change anything you want using ClassFileTransformer
before class is loaded).
So you need to create special java agent that will edit this class before it loads, or after it load (but then with that few limitations) using https://docs.oracle.com/javase/7/docs/api/java/lang/instrument/Instrumentation.html#redefineClasses(java.lang.instrument.ClassDefinition...)
But normally to do so you need to add special flags or manifest entries to runnable .jar, if you are launching this code on JDK, then it is much easier - with few tricks you can create agent and attach it to your VM in runtime, there is a bit of code needed for that so I will just suggest to use byte-buddy-agent
library, then you can get instrumentation with single line of code:
Instrumentation install = ByteBuddyAgent.install();
And you can redefine classes all you want, in your case I would also suggest to use Javassist library to edit code - as it is the easiest of available ones, but you can also use ByteBuddy or raw ASM, for javassist your code would look like this:
Instrumentation instrumentation = ByteBuddyAgent.install();
CtClass ctClass = ClassPool.getDefault().getCtClass(Main.class.getCanonicalName());
ctClass.defrost(); // as this class is already loaded, javassist tries to protect it.
CtMethod execute = ctClass.getDeclaredMethod("execute");
execute.setBody("{System.out.println(\"hello world\");}");
ClassDefinition classDefinition = new ClassDefinition(Main.class, ctClass.toBytecode());
instrumentation.redefineClasses(classDefinition);
If class is already loaded and you are on JRE and you can't change app start arguments for some reasons - so just any of that methods are not possible - write comment and describe way - I know some other magic, but it would take much more time to describe it, so I would need more information.
Upvotes: 3