Loading a class twice in JVM using different loaders - java

I have one question regarding concepts of class loading. How to load a .class file twice in JVM. I am also writing an excerpt of code that I have written to accomplish this..
1) Loader 1 code
public class MyClassLoader extends ClassLoader {
public MyClassLoader(){
super(MyClassLoader.class.getClassLoader());
}
public Class loadClass(String classname){
try {
return super.loadClass(classname);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
2) Loader 2 code
public class AnotherClassLoader extends ClassLoader {
public AnotherClassLoader(){
super(AnotherClassLoader.class.getClassLoader());
}
public Class loadClass(String classname){
try {
return super.loadClass(classname);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
3) Now i am loading a class named A using this two different class loaders. I suppose the operation classA1==newClassA should return false. Here is the code:
public static void main(String[] args) {
MyClassLoader loader1 = new MyClassLoader();
AnotherClassLoader newLoader = new AnotherClassLoader();
System.out.println("Load with Custom Class Loader instance");
Class classA1 = loader1.loadClass("com.hitesh.coreJava.A");
System.out.println("Class Loader:::"+classA1.getClassLoader());
Class newClassA = newLoader.loadClass("com.hitesh.coreJava.A");
System.out.println("Class Loader:::"+newClassA.getClassLoader());
System.out.println(classA1==newClassA);
System.out.println(classA1.hashCode() + " , " + newClassA.hashCode());
}
4) Result of executing above code:
Load with Custom Class Loader instance
Class Loader:::sun.misc.Launcher$AppClassLoader#11b86e7
Class Loader:::sun.misc.Launcher$AppClassLoader#11b86e7
true
1641745 , 1641745
Could you please explain this

Try this
public class Test1 {
static class TestClassLoader1 extends ClassLoader {
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (!name.equals("Test1")) {
return super.loadClass(name);
}
try {
InputStream in = ClassLoader.getSystemResourceAsStream("Test1.class");
byte[] a = new byte[10000];
int len = in.read(a);
in.close();
return defineClass(name, a, 0, len);
} catch (IOException e) {
throw new ClassNotFoundException();
}
}
}
public static void main(String[] args) throws Exception {
Class<?> c1 = new TestClassLoader1().loadClass("Test1");
Class<?> c2 = new TestClassLoader1().loadClass("Test1");
System.out.println(c1);
System.out.println(c2);
System.out.println(c1 == c2);
}
}
output
class Test1
class Test1
false

Both classloaders start the lookup in their parent classloader (that's what the super() call is about). So actually the super classloader loads it in both cases.
You can try this:
String pathToJar = "C:\\path\\to\\my.jar";
String className = "com.mypackage.ClassA";
URLClassLoader cl1 = new URLClassLoader(new URL[] { new URL(pathToJar) });
URLClassLoader cl2 = new URLClassLoader(new URL[] { new URL(pathToJar) });
Class<?> c1 = cl1.loadClass(className);
Class<?> c2 = cl2.loadClass(className);
System.out.println(c1);
System.out.println(c2);
System.out.println(c1==c2 ? "Parent classloader loads" : "Parent classloader does not load");
cl1.close();
cl2.close();
Make sure that my.jar is NOT on your classpath.

In both cases you are using the same ClassLoader to perform the loading. You have two ClassLoaders but each just call super.loadClass() which delegates to the same parent ClassLoader which is AppClassLoader.

Related

Java runtime compiler using reflections not working properly

I have a JavaFX app where there is an editor. In the editor, the user will be able to write java code and I have a button to compile this code and run the main method. For example the editor will contain this code:
public class Test {
public static void main(String[] args) {
System.out.println("hello");
}
}
The button on click, will run this code:
runButton.setOnAction(e -> {
compiler.set(editor.getText());
try {
compiler.createFile();
} catch (IOException e1) {
e1.printStackTrace();
}
compiler.compile();
compiler.run();
});
In the compiler class, there is the following implementation:
public class CodeCompiler {
public String className;
public String code;
public void set(String code) {
try {
this.className = code.substring(code.indexOf(" class ") + 6, code.indexOf(" {")).trim();
} catch(StringIndexOutOfBoundsException e) {
}
this.code = code;
}
public void createFile() throws IOException {
PrintWriter pw = new PrintWriter("speech2code/src/main/java/" + className + ".java");
pw.close();
FileWriter writer = new FileWriter("speech2code/src/main/java/" + className + ".java", true);
writer.write(code);
writer.flush();
writer.close();
}
public void compile() {
File file = new File("speech2code/src/main/java/" + className + ".java");
File classFile = new File("speech2code/src/main/java/" + className + ".class");
classFile.delete(); // delete class file f it exists
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, null, file.getPath());
}
public void run() {
Class<?> cls = null;
try {
cls = Class.forName(className);
System.out.println(cls == null);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Method meth = null;
try {
meth = cls.getMethod("main", String[].class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
String[] params = null;
try {
meth.invoke(null, (Object) params);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
Now the code above successfully creates the java file, class file and runs correctly the first time. Now when I change the editor code to print something else, it outputs the result of the first time the code was running. So, in this case, it will still print 'hello' instead of whatever it's current value.
Any problem what might be wrong?
Thanks!
You need to create a new classloader for the new class. The class does not get reloaded just because you compiled it.
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] {classFile});
Then you can ask this loader for the class:
Class<?> cls = Class.forName(className, true, classLoader);

Importing a subclass when interface is not in subclass .jar

So i've decided to try to write a thing that will loads a specific .class file from a .jar that anyone puts into a folder. (Right now it only loads my Test.jar)
The thing is, that these classes implements an interface in the java project that i'm loading them from, and i get an error telling me that the class "Klass" doesn't exist.
How can i get the loader to load that one instead of trying to use it's own?
(Also, this is what i think it's doing, i have no idea if it's true)
The code for loading the .jar:
String filePath = new String("C:/classes/Test.jar");
URL myJarFile = null;
try {
myJarFile = new URL("file:///"+filePath);
URL[] urls = new URL[]{myJarFile};
// Create a new class loader with the directory
ClassLoader cl = new URLClassLoader(urls);
// Load in the class; MyClass.class should be located in
// the directory file:/c:/myclasses/com/mycompan
Class cls = cl.loadClass("me.bluejelly.survivalgames.classes.Test");
try {
Klass klass = (Klass) cls.newInstance();
klass.create(arg0.getName());
Main.listeners.put(arg0.getName(), klass);
Bukkit.getPluginManager().registerEvents(klass, instance);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
And this it the code in the file i'm trying to load:
package me.bluejelly.survivalgames.classes;
import me.bluejelly.survivalgames.Main;
import me.bluejelly.survivalgames.def.Klass;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.inventory.ItemStack;
public class Test implements Klass {
private String ownerName;
#Override
public void create(String pName) {
this.ownerName = pName;
Main.listeners.put(this.ownerName, this);
}
#Override
public void destroy() {
if(Main.listeners.containsKey(this.ownerName)) {
Main.listeners.remove(ownerName);
}
HandlerList.unregisterAll(this);
}
#Override
public Material getIcon() {
return null;
}
#Override
public ItemStack[] getStartingItems() {
return null;
}
#EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
if(event.getPlayer().getName().equals(ownerName)) {
Bukkit.broadcastMessage("test");
}
}
}
You need to set the main class loader as the parent of your new URLClassLoader. That way it will pick up your local Klass class.
Something like...
ClassLoader cl = new URLClassLoader(urls, getClass().getClassLoader());

Can I pass className and methodName at runtime to ClassVisitor() and methodVisitor() in ASM parser after started the server?

I have started learning (I am new to this) , ASM API for a compiler project . I am using java Instrumentation and ASM ByteCode Library for developing a Javaagent.
I am passing classname and method name through properties.My goal is to change my className and methodName at run runtime ( means that after server started or premain() is called).
But , It works only for whatever the className or packageName passed at before started server.
I understand that , while calling the javaagent ( premain() ) , ASM set visitor for method for given pakage/class.
Even after server started or premain() is called , I wanted to visit to a specific class and method.
It Will be very helpful , if any one help on this.
This is my currently running program.
public class AddPrintlnAgent implements ClassFileTransformer {
public static void premain(String agentArgs, Instrumentation inst) {
Properties prop = new Properties();
try {
prop.load(new FileInputStream("C:\\locator.properties"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
inst.addTransformer(new AddPrintlnAgent());
}
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] retVal = null;
if (className.equals(className)) {
ClassWriter cw = new ClassWriter(0);
ClassVisitor ca = new MyClassAdapter(cw);
ClassReader cr = new ClassReader(classfileBuffer);
cr.accept(ca, 0);
retVal = cw.toByteArray();
}
return retVal;
}
public class MyClassAdapter extends ClassNode implements Opcodes {
private ClassVisitor cv;
Properties prop = new Properties();
public MyClassAdapter(ClassVisitor cv) {
this.cv = cv;
}
#Override
public void visitEnd() {
try {
prop.load(new FileInputStream("C:\\locator.properties"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
for (MethodNode mn : (List<MethodNode>) methods) {
if (mn.name.equals(prop.getProperty("methodName").trim())) {
InsnList il = new InsnList();
il.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
il.add(new LdcInsnNode(prop.getProperty("message")));
il.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream",
"println", "(Ljava/lang/String;)V"));
mn.instructions.insert(il);
mn.maxStack +=2;
}
}
accept(cv);
}
}
}
Thanks in advance
Sathish V J
You should cleanup your code. In your premain method you load a properties file into a Properties object but that instance is never used. Instead you reload that file into another Properties instance at every visitEnd() invocation. I’m not sure whether this is really what you want.
However, transformers are invoked during class loading but not for classes that have been loaded already. You can try to retransform or redefine loaded classes, but changing class or method names after loading is not supported by instrumentation.

Loading external classes

I am trying to load a class from a directory which I specify. I have done some research and made this:
ArrayList<Object> plugins = new ArrayList<Object>();
ClassLoader loader = Reflection.class.getClassLoader();
public Reflection()
{
load();
}
public void load()
{
File f = new File(Full directary);
ClassLoader loader = null;
try
{
loader = new URLClassLoader(new URL[]
{ f.toURI().toURL() }, getClass().getClassLoader());
}
catch (MalformedURLException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
for (File classFile : f.listFiles(new FilenameFilter()
{
public boolean accept(File dir, String name)
{
return name.endsWith(".class");
}
}))
// Start for loop.
{
try
{
String filename = classFile.getName();
System.out.println(filename);
Class<?> cls = loader.loadClass(filename.replace(".class", ""));
System.out.println(cls.getSuperclass().getName());
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
I am getting aNoClassDefFoundError error:
Exception in thread "main" java.lang.NoClassDefFoundError: mTest123g (wrong name: net/xcaliber/reflection/Test)
It finds the class fine, but then can't load it. I have made a basic interface:
String getName();
That's all that's in it; here is the class I am loading:
public Test()
{
// TODO Auto-generated constructor stub
}
#Override
public String getName()
{
return "Test";
}
This does implement the interface.Let me know the problem with it .
Obviously you have a mess up with class and file names.
Exception in thread "main" java.lang.NoClassDefFoundError: mTest123g (wrong name: net/xcaliber/reflection/Test)
So, you loading it as java mTest123g. Class loader expects a mTest123g.class not a Test class inside some net.xcaliber.reflection package.

Instrumentation retransformation doesn't appear to be working

I'm just experimenting with Java Instrumentation because it's very interesting and I'd like to know more about it. I'm using it in conjunction with the javassist library to make bytecode manipulation much easier, and the "tools" library which is included in JDK install.
Here is my main class:
public class MainClass {
public static boolean first = true;
static{
AgentClass.initialize();
}
public static void loadAgent(){
String path = System.getProperty("user.dir") + "\\AgentJar.jar";
String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName();
int p = nameOfRunningVM.indexOf('#');
String pid = nameOfRunningVM.substring(0, p);
try {
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(path, "");
vm.detach();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static void main(String[] args){
System.out.println("First run-through, code should be modified once.");
new Hello().hello();
first = false;
try {
AgentClass.getInstrumentation().retransformClasses(Class.forName("test.Hello"));
} catch (Exception e){
e.printStackTrace();
}
System.out.println("Second run-through, code should be modified twice.");
new Hello().hello();
}
}
Here is the "Hello" class:
public class Hello {
public void hello(){
System.out.println("Hello World!");
}
}
Here is the FileTransformer class:
public class FileTransformer implements ClassFileTransformer{
private static boolean first = true;
#Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
if (!className.contains("Hello"))
return null;
else{
byte[] result;
CtClass cc = null;
try {
cc = ClassPool.getDefault().get("test.Hello");
CtMethod method = cc.getDeclaredMethod("hello");
if (MainClass.first){
System.out.println("In transformer: first");
method.insertAfter("System.out.println(\"Modified First Time!\");");
}else{
System.out.println("In transformer: second");
method.insertAfter("System.out.println(\"I modified it again.!\");");
}
cc.writeFile();
result = cc.toBytecode();
} catch (Exception e) {
e.printStackTrace();
return null;
}
return result;
}
}
}
The agent class is in another jar, it's a basic implementation of it:
public class AgentClass {
protected static Instrumentation inst;
private static boolean added = false;
public static void agentmain(String args, Instrumentation inst){
AgentClass.inst = inst;
if (!added)
inst.addTransformer(new FileTransformer());
}
public static void premain(String args, Instrumentation inst){
AgentClass.inst = inst;
inst.addTransformer(new FileTransformer());
added = true;
}
public static void initialize(){
if (inst == null){
MainClass.loadAgent();
}
}
public static Instrumentation getInstrumentation(){
return inst;
}
}
When I run, I encounter no errors. However, the output is not how I would expect it to be.
Here is the output I get:
First run-through, code should be modified once.
In transformer: first
Hello World!
Modified First Time!
Second run-through, code should be modified twice.
Hello World!
Modified First Time!
You might notice that there is no line that reads "I modified it again!"
Any help is appreciated.
Not sure if there are any other issues here, but if you want to retransform classes, you need to register the ClassFileTransformer using the method that allows you to specify that the transformer can retransform. i.e.
if you call instrumentation.addTransformer(ClassFileTraIsformer), then you are stating that the transformer does not supports retransforms.
You need to call instrumentation.addTransformer(ClassFileTraIsformer, true) and this will make your transformer kick in.

Categories

Resources