Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Load class from exploded module using custom classloader

I have been playing around with simple custom classloaders in Java, and so far everything works as expected for non-module related classes. However, I can't seem to find any way to load a class from a module using my classloader, even though the module-related find*() methods have been overloaded. What I'm attempting to do is load a class from a module "HelloModularWorld" and run it's main. However, when I specify the directory where the package would be, it's loaded "normally" and reports as being in the unnamed module. What am I missing?

The classloader is just loading classes from elsewhere on the filesystem, nothing particularly special.

Classloader implementation:

package arch.classloaders;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;

public class ModifiableClassLoader extends ClassLoader {
    private File searchPath;
    //...other ctors...
    public ModifiableClassLoader(File path, String name, ClassLoader parent) {
        super(name, parent); //Delegate to parent classloader as required.
        if (!path.isDirectory()) throw new IllegalArgumentException("Path must be a directory");
        searchPath = path;
    }

    public void setPath(File newPath) {...}

    public File getPath() {...}

    //Responsible for actual loading
    public Class<?> findClass(String binName) throws ClassNotFoundException {
        File classfile = new File(searchPath.getPath() + File.separator 
                + binName.replace('.', File.separatorChar) + ".class");
        byte[] buf;
        FileInputStream fis;
        try {
            fis = new FileInputStream(classfile);
            buf = fis.readAllBytes();
            fis.close();
        } catch (IOException e) {
            throw new ClassNotFoundException("Error in defining " + binName + " in " + searchPath.getPath(),e);
        }
        return defineClass(binName, buf, 0, buf.length);
    }

    //Modules are expected to be in a folder with the same name as the module.
    //e.g. module hellomodularworld is expected to be in a folder
    //<SEARCHPATH>/<MODNAME>/
    //NOTE: Module-overloaded methods return null rather than throw when class isn't found.
    public Class<?> findClass(String modName, String binName) {
        if (null == modName) {
            try {
                return findClass(binName);
            } catch (ClassNotFoundException e) {
                return null;
            }
        }
        File classfile = new File(searchPath.getPath() + File.separator 
                + modName + File.separator
                + binName.replace('.', File.separatorChar) + ".class");
        byte[] buf;
        FileInputStream fis;
        try {
            fis = new FileInputStream(classfile);
            buf = fis.readAllBytes();
            fis.close();
        } catch (IOException e) {
            return null;
        }
        return defineClass(binName, buf, 0, buf.length);
    }

    //Non-module
    public URL findResource(String resPath) {...}

    //Module version
    public URL findResource(String modName, String resPath) throws IOException {...}

    //Enumeration version; does nothing.
    public java.util.Enumeration<URL> findResources(String resPath) {...}
}

Test code:

public class Test {
    public static void main(String[] args) {
        ModifiableClassLoader mcl = new ModifiableClassLoader(
                new File("C:\\Users\\archd\\Desktop\\"));
        try {
            Class<?> clazz = mcl.loadClass(
                    "hellomodularworld/com.archdukeliamus.hellomodularworld.HelloWorld"
            );
            java.lang.reflect.Method mth = clazz.getMethod("main", String[].class);
            mth.invoke(null,new Object[] {null});
            System.out.println(clazz.getModule().getName());
        } catch (...) {
                //omitted
        }
}

On line Class<?> clazz = mcl.loadClass("hellomodularworld/com.archdukeliamus.hellomodularworld.HelloWorld"); I have tried:

  • "hellomodularworld/com.archdukeliamus.hellomodularworld.HelloWorld" - Does not work. ClassNotFoundException.
  • "hellomodularworld.com.archdukeliamus.hellomodularworld.HelloWorld" - NoClassDefFoundException.
    com/archdukeliamus/hellomodularworld/HelloWorld (wrong name: hellomodularworld/com/archdukeliamus/hellomodularworld/HelloWorld)
  • "com.archdukeliamus.hellomodularworld.HelloWorld" - Works as expected (with classloading folder changed appropriately) but uses the unnamed module.

EDIT: module-info.java

module hellomodularworld {

}

The test class is not in any modules. (I'm not entirely sure why this would matter, I should get an exception to the effect of "this package isn't exported" which I am not getting.)

EDIT 2: Modified module to include exports com.archdukeliamus.hellomodularworld;. No changes in results.

like image 977
Kitten Avatar asked Dec 06 '17 23:12

Kitten


People also ask

Is it possible to load a class by two ClassLoader?

A class is always identified using its fully qualified name (package. classname). So when a class is loaded into JVM, you have an entry as (package, classname, classloader). Therefore the same class can be loaded twice by two different ClassLoader instances.

Which ClassLoader is used to load a class?

Types of ClassLoaders in Java To know the ClassLoader that loads a class the getClassLoader() method is used. All classes are loaded based on their names and if any of these classes are not found then it returns a NoClassDefFoundError or ClassNotFoundException.

When would you use a custom ClassLoader?

Custom class loaders You might want to write your own class loader so that you can load classes from an alternate repository, partition user code, or unload classes. There are three main reasons why you might want to write your own class loader. To allow class loading from alternative repositories.

Which ClassLoader loads the files from JDK directory?

The Bootstrap class loader loads the basic runtime classes provided by the JVM, plus any classes from JAR files present in the system extensions directory. It is parent to the System class loader. To add JAR files to the system extensions, directory, see Using the Java Optional Package Mechanism.


Video Answer


1 Answers

Module loading is a separate process from class loading. To load a module at runtime, you will need to create a new module layer using the ModuleFinder class, provided with a Path from a FileSystem (in this case the disk). You will then need to create a Configuration that you can use to resolve your modules. To ensure the sanity of the module loading process, you will need to derive the boot configuration. Then you will need to create your configuration, including a ModuleFinder to tell where modules are to be found, and a set of modules to be resolved. Then you instantiate the classloader you wish to use to load those modules' classes, and pass this to the define modules method. Finally you instantiate your class.

New test:

package arch.classloaders;

import java.lang.module.*;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.io.*;
import java.nio.*;
import java.nio.file.*;
import java.util.*;

public class Test2 {
    public static void main(String[] args) {
        //Get paths to module, and instantiate a ModuleFinder.
        Path pth = FileSystems.getDefault().getPath("C:\\Users\\archd\\Desktop");
        ModuleFinder mf = ModuleFinder.of(pth);
        //Create a new Configuration for a new module layer deriving from the boot configuration, and resolving
        //the "hellomodularworld" module.
        Configuration cfg = ModuleLayer.boot().configuration().resolve(mf,ModuleFinder.of(),Set.of("hellomodularworld"));
        //Create classloader
        ModifiableClassLoader mcl = new ModifiableClassLoader(
                new File("C:\\Users\\archd\\Desktop\\"));
        //make the module layer, using the configuration and classloader.
        ModuleLayer ml = ModuleLayer.boot().defineModulesWithOneLoader(cfg,mcl);
        //Show the configuration.
        System.out.println(ml.configuration()); //prints "hellomodularworld"
        try {
            //load and run class
            Class<?> clazz = ml.findLoader("hellomodularworld").loadClass(
                    "com.archdukeliamus.hellomodularworld.HelloWorld"
            );
            java.lang.reflect.Method mth = clazz.getMethod("main", String[].class);
            mth.invoke(null,new Object[] {null});
            //show the module this class is part of and list packages
            System.out.println(clazz.getModule()); //prints "module hellomodularworld"
            for (String pkgn : clazz.getModule().getPackages()) {
                System.out.println(pkgn); //prints "com.archdukeliamus.hellomodularworld"
            }
        } catch (ClassNotFoundException e) {
            ...omitted...
        }
    }
}

Interestingly this still doesn't call the overloaded module-based findClass() methods, although it seems to work.

like image 120
Kitten Avatar answered Nov 02 '22 01:11

Kitten