Android - Adding jar file dynamically to a ClassLoader like:
String strJarfile = "/data/data/com.example.classloader/files/apps/Calc/SumAndSub.jar";
URLClassLoader sysloader = (URLClassLoader) ClassLoader
.getSystemClassLoader();
try
{
File f = new File(strJarfile);
URL u = f.toURI().toURL();
Class[] parameters = new Class[] { URL.class };
Method method = URLClassLoader.class.getDeclaredMethod("addURL",
parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[] { u });
}
catch (Exception e)
{
e.printStackTrace();
}
And, error:
Caused by: java.lang.ClassCastException: dalvik.system.PathClassLoader cannot be cast to java.net.URLClassLoader
This code work fine in java-core, but not with android.
What is solution?
Thanks,
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
ClassLoader.getSystemClassLoader()
will return the PathClassLoader as a ClassLoader, so the (URLClassLoader) will cause the exception.
Hey this is a bit late but I was having trouble with this and was able to solve it by creating my own DexClassLoader. Here's an example function that works for me now:
private SomeClass loadSomeClassInJarFile(String pathToJar, String mainClassName, String codeCacheDir) {
SomeClass instanceOfSomeClass = null;
try {
DexClassLoader classLoader = new DexClassLoader(
pathToJar,
codeCacheDir,
null,
mClassLoader);
Class<?> cls = classLoader.loadClass(mainClassName);
Constructor<?> cs = cls.getConstructor();
instanceOfSomeClass = (SomeClass) cs.newInstance();
} catch (Exception ex) {
// This isn't the right class
Log.w(Tag, "Class " + mainClassName + " could not load because " + ex.getMessage());
}
return instanceOfSomeClass;
}
A couple notes about the above:
mClassLoader needs to be the class loader from your Android application and NOT the default class loader. You can get it from your Application class (.getClassLoader()).
codeCacheDir I got from my ApplicationContext (.getCodeCacheDir().getPath()) although it can be null on the more recent versions of Android.
The solution is not to assume that the system class loader is a URLClassLoader. There's nothing anywhere that says it is. If your code ever worked you got lucky.
Related
I have two java projects MASTER and PLUGIN. PLUGIN has dependencies to MASTER and its intent is to extend a class found in MASTER, called SCRIPT.
Once I have declared a SCRIPT (myScript), I want to move the .class file to a folder that MASTER can access. I want MASTER to dynamically load and instantiate that class as a SCRIPT.
I've looked for quite a bit and tried different solutions, but I always get a ClassNotFoundException exception.
I would prefer to do this without passing arguments to the JVM at startup.
Is it even possible? This is my current solution: "currentPath" is "etc/etc/myScript.class
try {
OUT.ln("initiating script " + currentPath);
File file = new File(currentPath);
File parent = file.getParentFile();
String name = file.getName().split(".class")[0];
// Convert File to a URL
URL url = parent.toURI().toURL();
URL[] urls = new URL[]{url};
// Create a new class loader with the directory
#SuppressWarnings("resource")
ClassLoader cl = new URLClassLoader(urls);
current = (SCRIPT) cl.loadClass("main.script." + name).newInstance();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Unable to load script " + currentPath);
}
if the class you want to load is defined within a package like:
main.script.myScript
and you want to load this class from a folder like c:/myclasses,
then you have to put this class to c:/myclasses/main/script/myScript.class
and then instantate the classloader with the basefolder like:
URL[] urls = new URL[]{new URL("file://c:/myclasses")};
ClassLoader cl = new URLClassLoader(urls);
then the class can be loaded by using the qualified class name:
cl.loadClass("main.script.myScript").getDeclaredConstructor().newInstance()
if you want to keep the class at a specific folder without considering the package structure you could do something like this:
public static void main(String[] args) {
try {
File file = new File("etc/etc/myScript.class");
String className = file.getName().split(".class")[0];
String packageName = "main.script.";
byte[] bytes = Files.readAllBytes(Path.of(file.getPath()));
MyClassLoader myClassLoader = new MyClassLoader(Thread.currentThread().getContextClassLoader());
Object o = myClassLoader.getClass(packageName+className, bytes).getDeclaredConstructor().newInstance();
System.out.println(o);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Unable to load script ");
}
}
public static class MyClassLoader extends ClassLoader {
public MyClassLoader(ClassLoader parent) {
super(parent);
}
public Class<?> getClass(String name, byte[] code) {
return defineClass(name, code, 0, code.length);
}
}
With the following code snippet, I am trying to load some class files into my project to create an object. Unfortunately, it seems that something is missing, because there are no classes loaded. What is it?
private static void myClassloader() throws Exception
{
File file = new File(pathGeneratedClasses);
try
{
#SuppressWarnings("deprecation")
URL url = file.toURL();
URL[] urls = new URL[]{url};
ClassLoader sqlQuery = new URLClassLoader(urls);
Class myClass = sqlQuery.loadClass("de.cimt.jaxb.JaxCodeGen");
}
catch (Exception e)
{
System.out.println(e.toString());
}
}
sqlQuery.loadClass() will be returning the class you requested for, try assigning the returned value to something.
If no class is found then ClassNotFoundException will be thrown.
I currently have the following problem:
I have created a updater jar from which a client jar is downloaded and placed in some directory (just somewhere on the disk, not associated with the directory of the updater jar). I use the following code the run the client jar from the updater:
private void startApplication() {
String url = getFilePath()+"client.jar";
URL parsedURL = null;
try {
parsedURL = new File(url).toURI().toURL();
} catch (MalformedURLException e) {
e.printStackTrace();
}
ClassLoader loader = URLClassLoader.newInstance(new URL[]{parsedURL}, getClass().getClassLoader());
Class<?> cl = null;
try {
cl = Class.forName("org.myApp.client.mainPackage.Main", true, loader);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
finally {
loader = null;
}
Class<? extends Application> runClass = cl.asSubclass(Application.class);
// Avoid Class.newInstance, for it is evil.
Constructor<? extends Application> ctor = null;
try {
ctor = runClass.getConstructor();
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
Application doRun = null;
try {
doRun = ctor.newInstance();
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
try {
doRun.start(primaryStage);
} catch (Exception e) {
e.printStackTrace();
}
}
This code seems to work, because the Main of the client.jar gets runned. However, after its Main is started, I get an exception from the client jar. The Main from the client jar tries to load a FXML file in the upper pane. This is the exception:
ClassNotFoundException: org.myApp.client.lockscreen.LockscreenController when loading a FXML file
I do not know what triggers this error. The client jar just runs as should be, when I run it standalone.
Do I need to load all classes from the client jar from the updater jar?
Any help is greatly appreciated!
Everybody thanks for your help. I was able to fix it like this (thanks Jool, you will get all the credits):
I downloaded and runned the client jar, assuming it would have its own references. However, as Jool said, I had to add the director to the class path. What I did wrong, was that I added the directory, and not the Jar file. You have to add the JAR file too ! I did that with this code:
public void addPath(String s) throws Exception {
File f = new File(s);
URI u = f.toURI();
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<URLClassLoader> urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class});
method.setAccessible(true);
method.invoke(urlClassLoader, new Object[]{u.toURL()});
}
And then I just called addPath(url) before running the client jar.
It is saying that the class cannot be found because it is not on your classpath.
This depends on how you build your application (Ant, Maven etc), since this determines how the location of the .jar file is known, and where the .jar file is.
If you are using an IDE, there would usually be some sort of Libraries placeholder in which you define .jars that you are dependent upon.
I'm trying to get my custom class loaded in FXML JS.
First of all I've added URLClassLoader with my JAR to the FXMLLoader instance:
FXMLLoader loader = new FXMLLoader();
loader.setController(TabController.this);
URLClassLoader fxmlClassLoader = (URLClassLoader) loader.getClassLoader();
loader.setClassLoader(URLClassLoader.newInstance((URL[]) ArrayUtils.addAll(new URL[]{ new File("/home/sk_/projects/mjolnirr/.hive/static/calculator/origJar.jar").toURI().toURL() }, fxmlClassLoader.getURLs())));
And then in FXML JavaScript:
importClass(com.mjolnirr.sample.SomeTestClass);
It fails with error:
sun.org.mozilla.javascript.internal.EvaluatorException: Function importClass must be called with a class; had "[JavaPackage com.mjolnirr.sample.SomeTestClass]" instead. (<Unknown source>#2) in <Unknown source> at line number 2
Anyone faced that?
Okay, I've found one hack solution there. In short - I've just added my URL to the system classloader dynamically, like this:
public static void addURLToSystemClassLoader(URL url) throws IntrospectionException {
URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<URLClassLoader> classLoaderClass = URLClassLoader.class;
try {
Method method = classLoaderClass.getDeclaredMethod("addURL", new Class[]{URL.class});
method.setAccessible(true);
method.invoke(systemClassLoader, new Object[]{url});
} catch (Throwable t) {
t.printStackTrace();
throw new IntrospectionException("Error when adding url to system ClassLoader ");
}
}
And then in my render method
FXMLLoader loader = new FXMLLoader();
loader.setController(TabController.this);
try {
addURLToSystemClassLoader(new URL("hive://" + pageURL.getHost() + ":" + pageURL.getPort() + "/" + pageURL.getApplicationName() + "/origJar.jar"));
} catch (Exception e) {
e.printStackTrace();
}
But it looks like a bad practise.
I think that the original problem is that - JavaFX scripting engine has differect class loader, FXML loader doesn't pass it's own to the script engine. Anyone knows how to set the classloader for the script engine?
How about explicitly setting the class loaded for the current thread to the new class loader?
For example:
URL[] urlsForJarFiles = getUrls();
ClassLoader myClassLoader = new URLClassLoader(urlsForJarFiles, ClassLoader.getSystemClassLoader();
Thread.currentThread.setContextClassLoader(myClassLoader);
the problem is next: i took the base classLoader code from here. but my classLoader is specific from a point, that it must be able to load classes from a filesystem(let's take WinOS), so in classLoader must be some setAdditionalPath() method, which sets a path(a directory on a filesystem), from which we'll load class(only *.class, no jars). here is code, which modifies the loader from a link(you can see, that only loadClass is modified), but it doesn't work properly:
public void setAdditionalPath(String dir) {
if(dir == null) {
throw new NullPointerException("");
}
this.Path = dir;
}
public Loader(){
super(Loader.class.getClassLoader());
}
public Class loadClass(String className) throws ClassNotFoundException {
if(Path.length() != 0) {
File file = new File(Path);
try {
// Convert File to an URL
URL url = file.toURL();
URL[] urls = new URL[]{url};
// Create a new class loader with the directory
ClassLoader cl = new URLClassLoader(urls);
ClassLoader c = cl.getSystemClassLoader();
Class cls = c.loadClass(className);
return cls;
} catch (MalformedURLException e) {
} catch (ClassNotFoundException e) {
}
}
return findClass(Path);
}
I'd grateful if anyone helps :)
You can just use framework provided java.net.URLClassLoader. No need to write your own. It supports loading of classes from directories and JAR files.
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
opened as needed.
It also supports a parent class loader. If this class loader does not suite your requirements, perhaps you can specify in more detail what you need. And in any case, you can look at the source and derive your own class loader class based on that.
Here is a short working snippet of code that should demostrate how to load a class by name from a URLClassLoader:
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// This URL for a directory will be searched *recursively*
URL classes =
new URL( "file:///D:/code/myCustomClassesAreUnderThisFolder/" );
ClassLoader custom =
new URLClassLoader( new URL[] { classes }, systemClassLoader );
// this class should be loaded from your directory
Class< ? > clazz = custom.loadClass( "my.custom.class.Name" );
// this class will be loaded as well, because you specified the system
// class loader as the parent
Class< ? > clazzString = custom.loadClass( "java.lang.String" );