0

I'm trying to make app to load dynamically certain classes and then invoke startup method, but the problem is that one class cannot invoke method of another because of different ClassLoader, however as I googled already, I created both classloaders with parent. Here's my test class:

public class Plugin {
    public static void main(String[] args) throws Exception {
        Class<?> fii = loadClass(new File("Fii.class"), "ua.i0xhex.plugin.Fii");
        Class<?> goo = loadClass(new File("Goo.class"), "ua.i0xhex.plugin.Goo");
        goo.getMethod("hello", new Class<?>[0]).invoke(goo.newInstance(), (Object[]) null);
    }

    public static Class<?> loadClass(File file, String name) throws Exception {
        DLoader loader = new DLoader(Plugin.class.getClassLoader());
        byte[] data = toByte(file);
        Class<?> clazz = loader.defineClass(name, data);
        return clazz;
    }

    public static byte[] toByte(File file) throws Exception {
        FileInputStream inputStream = new FileInputStream(file);
        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
        int count;
        byte[] bytes = new byte[1024];
        while ((count = inputStream.read(bytes, 0, bytes.length)) != -1) 
            byteOutputStream.write(bytes, 0, count);
        inputStream.close();
        return byteOutputStream.toByteArray();
    }

    public static class DLoader extends ClassLoader {
        public DLoader(ClassLoader parentLoader) {
            super(parentLoader);
        }
        public Class<?> defineClass(String name, byte[] b) {
            return super.defineClass(name, b, 0, b.length);
        }
    }
}

I have two classes already compiled and copied near my test app. Goo.hello() must print "Hello World" and then invoke Fii.hi().

Output:

Hello World!
Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at ua.i0xhex.plugin.Plugin.main(Plugin.java:11)
Caused by: java.lang.NoClassDefFoundError: ua/i0xhex/plugin/Fii
    at ua.i0xhex.plugin.Goo.hello(Goo.java:11)
    ... 5 more
Caused by: java.lang.ClassNotFoundException: ua.i0xhex.plugin.Fii
    at java.lang.ClassLoader.findClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    ... 6 more

How to make sure that my dynamic loaded classes will have ability to invoke methods of another loaded classes? All works if I make 1 ClassLoader for both classes, but in real work there will be not only one.

5
  • Are the class files for the other 2 classes in the correct directory? Commented Mar 6, 2018 at 11:32
  • Of course, as you can see, Goo.class loaded and method hello() invoked successfully, but Fii.class not found. If I use same DLoader instance for both loads, then all will work, but if I make different instances, then no. Commented Mar 6, 2018 at 11:35
  • Can you make one of the loaders a parent of another? It looks like when the loader of Goo tries to find Fii it fails, as it is not available to it Commented Mar 6, 2018 at 11:37
  • 2
    The classes get defined in sibling class loaders, that means they can't find each other. I guess it could work if you make the first loader a parent of the second. Commented Mar 6, 2018 at 11:37
  • DLoader loaderA is parent, loaderB is child. If I first load Fii with loaderA and then Goo with loaderB, this will work. But in another way - Fii with loaderB and Goo with loaderA - error. That's my problem. I'm trying to make plugin for Bukkit which will load some classes dynamically after decrypting, and I'm aware, that plugins loaded by Bukkit would not be able to interact with my classes. Is there a way to solve this? Commented Mar 6, 2018 at 11:48

2 Answers 2

1

Your loadClass method defines a new classloader each time you call it

 DLoader loader = new DLoader(Plugin.class.getClassLoader());

And you use that classloader to load your classes. In that way you get one classloader for Fii and one for Goo, with the same parent classloader.

You probably want the same classloader for both of them, so you probably want to pass it as a parameter to your loadclass method, something like this:

public static void main(String[] args) throws Exception {
    DLoader loader = new DLoader(Plugin.class.getClassLoader())
    Class<?> fii = loadClass(loader, new File("Fii.class"), "ua.i0xhex.plugin.Fii");
    Class<?> goo = loadClass(loader, new File("Goo.class"), "ua.i0xhex.plugin.Goo");
    goo.getMethod("hello", new Class<?>[0]).invoke(goo.newInstance(), (Object[]) null);
}

public static Class<?> loadClass(DLoader loader, File file, String name) throws Exception {
    byte[] data = toByte(file);
    Class<?> clazz = loader.defineClass(name, data);
    return clazz;
}

You could of course also store your classloader as a static variable if you prefer that route

Sign up to request clarification or add additional context in comments.

1 Comment

Yes, you are right. But I need compatibility with other class loaders. Why? Bukkit has their own class loader. Will plugins loaded by default bukkit plugin loader be able to access to my DLoader loaded classes? Seems not. Because of tests with loaderA and loaderB (see comments on my question). If not, what else ways are? Or the only way is to inject code to server startup?
0

A class can access classes loaded by it's classloader or by (any of the) parent classloader(s).

It can not access classes loaded by child or sibling classloaders (what you're actually trying to do is access a class loaded by sibling classloader, i.e. a child of your parent classloader).

Imagine you have two applications running on one application server. the application server uses the parent classloader and loads classes for each app in a different child classloader. You don't want one of the applications to be able to access classes from the other.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.