How to Use Urlclassloader to Load a *.Class File

How to use URLClassLoader to load a *.class file?

From the Javadocs for the URLClassLoader(URL[]) constructor:

Constructs a new URLClassLoader for the specified URLs using the default delegation parent ClassLoader. The URLs will be searched in the order specified for classes and resources after first searching in the parent class loader. Any URL that ends with a '/' is assumed to refer to a directory. Otherwise, the URL is assumed to refer to a JAR file which will be downloaded and opened as needed.

So you have two options:

  1. Refer to the directory that the .class file is in
  2. Put the .class file into a JAR and refer to that

(1) is easier in this case, but (2) can be handy if you're using networked resources.

Using URLClassLoader to load .class file

In the directory /home/shackle/somedir/classes/pkg I have a file Test.class created from a java file with package pkg; eg :

package pkg;

public class Test {

public String toString() {
return "secret_string";
}
}

Then I load it with :

System.out.println(new URLClassLoader(
new URL[]{new File("/home/shackle/somedir/classes").toURI().toURL()}
).loadClass("pkg.Test").newInstance().toString());

Notice that I do not put the pkg/Test in the URL string but the load class argument has the pkg. prefix.

You can get the class name directly from the file like this:

Class clsReaderClss = ClassLoader.getSystemClassLoader().loadClass("jdk.internal.org.objectweb.asm.ClassReader");
System.out.println("clsReaderClss = " + clsReaderClss);
Constructor con = clsReaderClss.getConstructor(InputStream.class);
Object reader = con.newInstance(new FileInputStream(directFile));
Method m = clsReaderClss.getMethod("getClassName");
String name = m.invoke(reader).toString().replace('/', '.');
System.out.println("name = " + name);

An alternative that doesn't require access to internal classes.

String pathToClassFile = "/home/shackle/somedir/classes/pkg/Test.class";
ProcessBuilder pb = new ProcessBuilder("javap",pathToClassFile);
Process p = pb.start();
String classname = null;
try(BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line;
while(null != (line = br.readLine())) {
if(line.startsWith("public class")) {
classname = line.split(" ")[2];
break;
}
}
}
System.out.println("classname = " + classname);

Class can then be loaded with:

String pathToPackageBase = pathToClassFile.substring(0, pathToClassFile.length() - (classname + ".class").length());
System.out.println("pathToPackagBase = " + pathToPackageBase);
Class clss = new URLClassLoader(
new URL[]{new File(pathToPackageBase).toURI().toURL()}
).loadClass(classname);
System.out.println(clss.newInstance().toString());

Loading .class files via URLClassLoader - NoClassDefFoundError

It appears someone has moved .class files around, without preserving the required directory structure.

A Java class declared with package ea_6_1; must reside in a directory named ea_6_1 (in every Java implementation I know of, at least).

How to load JAR files dynamically at Runtime?

The reason it's hard is security. Classloaders are meant to be immutable; you shouldn't be able to willy-nilly add classes to it at runtime. I'm actually very surprised that works with the system classloader. Here's how you do it making your own child classloader:

URLClassLoader child = new URLClassLoader(
new URL[] {myJar.toURI().toURL()},
this.getClass().getClassLoader()
);
Class classToLoad = Class.forName("com.MyClass", true, child);
Method method = classToLoad.getDeclaredMethod("myMethod");
Object instance = classToLoad.newInstance();
Object result = method.invoke(instance);

Painful, but there it is.

Load SPI class with URLClassLoader rise ClassNotFoundException

ClassLoader looks for classes in its parent first, and the parent delegates to its parent and so on. With that said, ClassLoaders that are siblings cannot see eachothers classes.

Also the method DriverManager#getDrivers() internally validates if the caller ClassLoader can load the class with DriverManager#isDriverAllowed(Driver, ClassLoader).
this means that even if your Driver is added to the registration list, it is only added as an instance of DriverInfo, this means that it would only be loaded on demand (Lazy), and still might not register when loading is attempted, that's why you get an empty list.

Loading a remote Java class

You need to create your own ClassLoader, give a look at How to use URLClassLoader to load a *.class file?

Here is an example (from http://www.programcreek.com/java-api-examples/index.php?api=java.net.URLClassLoader, there is others with hosted urls):

private void invokeClass(String path, String name, String[] args) throws
ClassNotFoundException,
NoSuchMethodException,
InvocationTargetException,
MalformedURLException,
InterruptedException,
IllegalAccessException {
File f = new File(path);
URLClassLoader u = new URLClassLoader(new URL[]{f.toURI().toURL()});
Class c = u.loadClass(name);
Method m = c.getMethod("main", new Class[]{args.getClass()});
m.setAccessible(true);
int mods = m.getModifiers();
if (m.getReturnType() != void.class || !Modifier.isStatic(mods) || !Modifier.isPublic(mods)) {
throw new NoSuchMethodException("main");
}
m.invoke(null, new Object[]{args});
m = null;
System.gc(); // uh-oh
}

Reflecting *.class file

If classFilesFolder is /home/user and the files inside the folder have names like /home/user/com/something/My.class, the class name (including the package) will be com.something.My. So the class name is the full file name, with classFilesFolder removed, then change all forward slashes to dots and remove .class at the end.

How to write a Java program which will compile, load, and use code from the web

A desktop program could use a shell script to download, compile and run a Java program. Android does not have a compiler, and adding one is non-trivial. The easiest way would be to also make a server program. The Android program would then tell the server program to download and compile the Java source code, and then send the result to the Android program, which would then load it using its ClassLoader.

One caveat is that the JDK compiler produces bytecode for the standard Java Virtual Machine, whereas Android's JVM is uses the Dalvik VM, so when you compile the Java class, you can't just use the JDK; you have to use the Android SDK to produce compatible bytecode that the Android ClassLoader can use.



Related Topics



Leave a reply



Submit