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.
hellomodularworld
and of the module from which the code above is shared? – MerozoiteFileInputStream
detour? You can simply usebyte[] buf = Files.readAllBytes(searchPath.toPath().resolve(binName.replace('.', File.separatorChar) + ".class"));
. Since Java 7. – Mercado