How to get resource files when loading a jar using an URLClassLoader? - java

I have written an application which manages several plugins which are provided as jars. I load the plugin classes using an URLClassLoader which works as supposed.
But now I am writing a plugin which loads some resources which are stored inside the jar. If I start this plugin as a standalone application everything works, but if I start it from inside my application I get a NullPointerException when I try to open the resources InputStream.
I open the stream like this:
this.getClass().getResourceAsStream("/templates/template.html");
My Eclipse project structure looks like:
src
|
+ My source files
resources
|
+ templates
|
+ template.html
The following loads my plugins:
private List<Class<?>> loadClasses(final File[] jars) {
List<Class<?>> classes = new ArrayList<Class<?>>();
URL[] urls = getJarURLs(jars);
URLClassLoader loader = new URLClassLoader(urls);
for (File jar : jars) {
JarFile jarFile = null;
try {
jarFile = new JarFile(jar);
} catch (IOException e) {
// Skip this jar if it can not be opened
continue;
}
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (isClassFile(entry.getName())) {
String className = entry.getName().replace("/", ".").replace(".class", "");
Class<?> cls = null;
try {
cls = loader.loadClass(className);
} catch (ClassNotFoundException e) {
// Skip this jar if a class inside it can not be loaded
continue;
}
classes.add(cls);
}
}
try {
jarFile.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
loader.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return classes;
}
/**
* Checks if a path points to a class file or not.
*
* #param path the filename to check
* #return {#code true} if the path points to a class file or {#code false}
* if not
*/
private boolean isClassFile(final String path) {
return path.toLowerCase().endsWith(".class") && !path.toLowerCase().contains("package-info");
}
Then I make instances from this classes using newInstance().
I think that the root path of the plugins jar is not the same as the root path of the application or that not all contents of the jar files are loaded or both...
Can someone help me?

First note, that using getClass().getResource(...) also delegates to a ClassLoader, which is also responsible for loading resources. Which class loader is used? It is the same class loader, with which the class was loaded. Point.
In your code, you build up an URLClassLoader for loading some classes. So the same URLClassLoader will then be used for loading the resources, if the above mentioned call comes from a class inside your plugin.
This all seems to be ok ... but you did a little mistake. At the end of loading you also closed the loader. This will prevent subsequent calls to loadClass or getResource from returning anything meaningful. In fact, it could null, as now the loader cannot load the resource anymore.
Conclusion: Do not close the URLClassLoader, if you still need it ofr loading purposes. Instead keep the reference to this class loader and close it at the end of your program runtime.

Related

Can't find a class using Class.forName()

This is my "test-addon"
And I'm trying to load my "main class" using:
Class<?> jarClass;
try {
ClassLoader cls = ClassLoader.getSystemClassLoader();
jarClass = cls.loadClass("main.Addon");
} catch (ClassNotFoundException e) {
throw new InvalidPluginException("\"Addon class\" was not found", e);
}
As you can see in the image, the class exists, but it still returns:
Line 21: jarClass = cls.loadClass("main.Addon");
QUESTION: why does this happen
The jar or directory that contains main.Addon isn't on the classpath.
Try Addon (no package specifier). In Maven-style projects, src/main is the root (default) package.
The problem was that I used the SystemClassLoader, not my own.
A simple fix for this was: jarClass = Class.forName("main.Addon", true, this);

Unable to run downloaded jar from other jar

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.

Get java.lang.Class object of a class by reading its source(.java) file

I want to get the java.lang.Class object of a class by reading its source file using FileReader.
Actually I want to get all methods, constructors, parent class, overridden methods and imported packages of the class by selecting its source file using JFileChooser. So, I think I got these all things by using its class Class object methods like getConstructors() etc.
I have tried this, but it gives java.lang.ClassNotFoundException...
public static void main(String[] args) {
File file = new File(
"F:\\study\\projects\\saralbhakti\\src\\signup\\SignupServlet.java");
try {
// Convert File to a URL
URL url = file.toURL(); // file:/c:/myclasses/
URL[] urls = new URL[] { url };
// Create a new class loader with the directory
ClassLoader cl = new URLClassLoader(urls);
// Load in the class; MyClass.class should be located in
// the directory file:/c:/myclasses/com/mycompany
Class cls = cl.loadClass("signup.SignupServlet");
System.out.println("Class Name : " + cls.getName());
Method[] declaredMethods = cls.getDeclaredMethods();
System.out.println("All Methods : " + declaredMethods.length);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
Classes are loaded from .class files, not .java files. You have two options:
1) Use a different API, like AST parsing, which is designed to read and understand .java files (but not execute the code in them)
2) Programmatically compile the .java file, then read the .class file. This is ugly and wonky and horrible and full of caveats and probably not what you want to do.

custom classLoader issue

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" );

Dynamically add a properties file in classpath in tomcat web application

I am trying to add a properties file to the classpath dynamically as below
try {
File fileToAdd = new File(FILE_PATH);
URL u = fileToAdd.toURL();
ClassLoader sysLoader = ClassLoader.getSystemClassLoader();
if (sysLoader instanceof URLClassLoader) {
sysLoader = (URLClassLoader) sysLoader;
Class<URLClassLoader> sysLoaderClass = URLClassLoader.class;
// use reflection to invoke the private addURL method
Method method = sysLoaderClass.getDeclaredMethod("addURL",
new Class[] { URL.class });
method.setAccessible(true);
method.invoke(sysLoader, new Object[] { u });
}
} catch (Exception e) {
logger.error(e.getMessage());
}
But i cant see this file in my classpath. When i checked it using
System.getProperty("java.class.path")
I cant see my file in this list. Am i missing anything here?
you can't add the URL of the properties file, you have to add the URL of the directory in which the properties file resides in. As in: method.invoke(sysLoader, fileToAdd.getParent().toURL());
then you can use ClassLoader.getResourceAsStream("my.properties"); and the ClassLoader will search the newly added directory for the file.
from URLClassLoader
"This class loader is used to load classes and resources from a search path of URLs referring to both JAR files and directories. 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."
Perhaps try this code, but changing java.library.path or keep it the way it is if you can live with using the library path instead.
/**
* Allows you to add a path to the library path during runtime
* #param dllLocation The path you would like to add
* #return True if the operation completed successfully, false otherwise
*/
public boolean addDllLocationToPath(final String dllLocation)
{
//our return value
boolean retVal = false;
try
{
System.setProperty("java.library.path", System.getProperty("java.library.path") + ";" + dllLocation);
//get the sys path field
Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
fieldSysPath.setAccessible(true);
fieldSysPath.set(null, null);
retVal = true;
}
catch (Exception e)
{
System.err.println("Could not modify path");
}
return retVal;
}

Categories

Resources