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:
com/archdukeliamus/hellomodularworld/HelloWorld (wrong name: hellomodularworld/com/archdukeliamus/hellomodularworld/HelloWorld)
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.
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With