I have a simple plugin system, that loads external JAR plugins into main application. I am using Mountainblade Modular to do so. Not sure how they do it "under the hood" but probably it's something standard.
This works fine, I instantiate classes from external jar and it all works. Except that some plugins come with icons/images. I am a bit unsure on how do I load/refer to images from that external JAR (with code inside that external JAR, as it is ran in context of the main JAR, kind of)
How should I approach this?
This issue is not as straightforward as it seems to be.
When you load classes from external jar, they are "loaded" into JVM. By "loading" into JVM I mean that JVM is responsible for their storage in memory. Usually it is done like this:
ClassLoader myClassLoader = new MyClassLoader(jarFileContent);
Class myExtClass = myClassLoader.loadClass(myClassName);
Resources from classpath jars can be accessed easily with
InputStream resourceStream = myClass.getResourceAsStream("/myFile.txt");
You can do that, because these jars are in classpath, I mean their location is known. These files are not stored in memory. When resource is accessed, JVM can search for it in classpath jars (for example on file system).
But for external jars it is completely different: jar comes from nowhere, is once processed and forgotten. JVM does not load resources from it in memory. In order to access these files, you have to manually organize their storage. I've done this once so I can share the code. It will help you to understand the basic idea (but probably won't help you with your specific library).
// Method from custom UrlClassLoader class.
// jarContent here is byte array of loaded jar file.
// important notes:
// resources can be accesed only with this custom class loader
// resource content is provided with the help of custom URLStreamHandler
#Override
protected URL findResource(String name) {
JarInputStream jarInputStream;
try {
jarInputStream = new JarInputStream(new ByteArrayInputStream(jarContent));
JarEntry jarEntry;
while (true) {
jarEntry = jarInputStream.getNextJarEntry();
if (jarEntry == null) {
break;
}
if (name.equals(jarEntry.getName())) {
final byte[] bytes = IOUtils.toByteArray(jarInputStream);
return new URL(null, "in-memory-bytes", new URLStreamHandler() {
#Override
protected URLConnection openConnection(URL u) throws IOException {
return new URLConnection(u) {
#Override
public void connect() throws IOException {
// nothing to do here
}
#Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(bytes);
}
};
}
});
}
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
Related
I have seen many class loader questions, but still was not able to figure why, the error here.
I am writing a program which uses 2 versions of jars. One is needed to get content from older storage, and another to store content in new storage.
Since, I need either of the jar to be loaded at a time, I used JarClassLoader to create a proxy for adding one jar and loading its classes. But I face ClassNotFoundException.
public class HbaseMigrator implements Runnable {
public void run() {
JarClassLoader jcl = new JarClassLoader();
jcl.add("hadoop-0.13.0-core-modified-1.jar");
Object obj1 = JclObjectFactory.getInstance().create(jcl, "UserMigThreadImpl", toProcessQueue,threadName, latch,DBUtil,lock);
MigThread mig = JclUtils.cast(obj1, MigThread.class, jcl);
Thread.currentThread().setContextClassLoader(jcl);
try {
Method method = MigThread.class.getMethod("callthis", new Class[]{});
method.invoke(mig, new Object[]{});
// mig.callthis();
} catch( Exception e) {
e.printStackTrace();
} catch(Error er) {
er.printStackTrace();
}
}
}
Method called is:
public void callthis() {
DFSUtil = new DFSAccessAPIImpl();
.........
}
This class instantiation internally uses hadoop modified jar, which is not picked up from my classloader and it throws ClassNotFoundException.
What is that I am doing wrong ?
JarClassLoader used here is jcloader :
org.xeustechnologies.jcl.JarClassLoader
I experienced this problem with loading plugins to my application, so I decided to try to load all .class files from all jars in path. Maybe this code snipped from my app will help you.
https://bitbucket.org/rsohlich/plagdetector/src/432b52f252ff7647221b7e91b08731bd9cbe2a70/PlagDetectorSpring/src/main/java/cz/sohlich/app/service/impl/PluginHolderImpl.java
I'm trying to load html pages stored inside the jar file into a help JEditorPane. So far it works when I run it in eclipse but when i make a runnable jar it wont work, except if i put the map res/pages/... in the same map with the jar file
class HelpButtonHandler implements ActionListener{
#Override
public void actionPerformed(ActionEvent arg0) {
infodex = new JEditorPane();
helpDialog = new JDialog();
URL url1 = null;
try {
url1 = (new java.io.File("res/pages/help.html")).toURI().toURL();
} catch (MalformedURLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
infodex.setPage(url1);
} catch (IOException e) {
e.printStackTrace();
}
helpDialog.getContentPane().add(new JScrollPane(infodex));
helpDialog.setBounds(400,200,700,600);
helpDialog.show();
infodex.setEditable(false);
Hyperactive hyper = new Hyperactive();
infodex.addHyperlinkListener(hyper);
}
}
A file packaged inside a .jar is not a file on the file system. You cannot access it with the File class.
A file inside a .jar is called an application resource. You access it using the Class.getResource method:
url1 = HelpButtonHandler.class.getResource("/res/pages/help.html");
It is up to you to make sure the files are properly packaged in your .jar. If url1 is null, check the structure of your .jar file.
When you put resources in a jar, you cannot access them using File. You need to access them as a resource through the (more precisely: a) classloader. For example:
HelpButtonHandler.class.getResource("/res/pages/help.html");
Make sure you put the resource in the right place: if you leave out the first slash ('/'), the classloader will try to locate it relative to your class (which is usually not what you want).
use gerResource() method...
url = getClass().getClassLoader().getResource("res/pages/help.html");
check this link
http://oakgreen.blogspot.com/2011/12/java-getclassgetclassloadergetresourcem.html
I'm writing an application that needs to reload a previously loaded class during runtime. The reason is that the class is auto-generated during runtime, and the new implementation changes the way the app works. I generate only one object of the said class, and I stripped it from all dependencies but an interface that defines constant values. There's no problem reseting the values of any or all of the members when reloading. I know exactly when it changes and I can control it. The only problem I have is the reload itself.
From what I read, I should use a ClassLoader. I tried to do so, but I can't make it work.
I tried the following:
Getting the current ClassLoader (myClassObject.getClass().getClassLoader()) and using it to reload the class - Doesn't work. It probably keeps loading the old implementation.
Generating my own (AKA copy-paste from SO with modifications) - Doesn't work because the ClassLoader I generate is different than the one that generated the class (Exception: myClass cannot be casted to myClass).
Creating a constructor that sets the ClassLoader of the superclass doesn't seem to have any effect.
Using my new ClassLoader to generate the class that has myClassObject as a member solved the ClassLoader mismatch for myClassObject, but created a new mismatch one level up. I used getClassLoader() everytime and I see they don't match.
I tried adding -Djava.system.class.loader=com.test.Reoader com.test.myMainClass to make it my default reloader, but I get an error from the compiler.
Google keeps pointing me back to the same stuff I read already.
EDIT: I tried creating an interface and reload the class implementing it. That didn't solve it either.
I know I should override the default ClassLoader, but nothing I do seems to succeed at that.
My ClassLoader:
public class Reloader extends ClassLoader {
public Reloader(){
super(Reloader.class.getClassLoader());
}
#Override
public Class<?> loadClass(String s) {
return findClass(s);
}
#Override
public Class<?> findClass(String s) {
try {
byte[] bytes = loadClassData(s);
return defineClass(s, bytes, 0, bytes.length);
} catch (IOException ioe) {
try {
return super.loadClass(s);
} catch (ClassNotFoundException ignore) {}
ioe.printStackTrace(java.lang.System.out);
return null;
}
}
private byte[] loadClassData(String className) throws IOException {
File f = new File("out\\production\\ManoCPU\\" + className.replaceAll("\\.", "/") + ".class");
int size = (int) f.length();
byte buff[] = new byte[size];
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
dis.readFully(buff);
dis.close();
return buff;
}
}
Thanks very much to anyone that can help.
You can only load a class once (per instance of a classloader). That means you have to throw away the classloader you have loaded your class with and instantiate a new one for your updated version of the class.
When dealing with multiple class loaders also have in mind that if you load the same class with several classloaders they are NOT recognized as being the same class.
I'm using JarFile and JarURLConnection to load files out of a jar file. I'm then taking the classes, and loading them via BCEL (ByteCode Engineering Library, apache library). I cant just directly use a class loader because im modifying some classes slightly with the BCEL. I need to load the classes by their bytes into my bcel loader. However, one of the classes I'm loading references a resource. This resource is inside of the jar, so I can get the file (When iterating over the entries in the JarFile, I ignore the regular files, and take the class files for loading later). But just having the file won't do me any good, as the class loads it as a resource. Is there any way I can take that resource from the jar (well I can take it and load it into a byte[], the next part is the issue) and dynamically add it as a resource for my program, so that the classes that I load wont be missing their resources?
Got a lot of stuff here, if anythings confusing, ask in comments, I might've said something wrong, or missed something altogether :) Thanks
I'll show a little of my class loader here (extends ClassLoader):
#Override
public URL getResource(String name) {
System.out.println("LOADING RESOURCE: " + name);
try {
return new URL(null, name, new Handler(files));
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
Now, it is printing out "LOADING RESOURCE: filename", but its then giving me a MalformedURLException (I have no protocol atm, just a file path, that's not a true valid path, but it's just an attempt to give it to my Handler class below).
class Handler extends URLStreamHandler {
#Override
protected URLConnection openConnection(URL u) throws IOException {
return new URLConnection(u) {
#Override
public void connect() throws IOException {
}
#Override
public InputStream getInputStream() throws IOException {
System.out.println("IS: " + url);
return /*method to get input steam*/;
}
};
}
}
The /*method to get input steam*/ is set in my real code, but that's not relevant here. So any further ideas with this?
I have two programs: one CLI program, and one GUI. The GUI is a frontend for the CLI, but also a GUI for another program as well.
I am importing the CLI's classes and extending them in the GUI to add GUI elements to the classes, and all is great.
But now I want to split the CLI that I currently have embedded in the GUI (as an included JAR). The JAR is in a fixed location (/opt/program/prog.jar), and the application will only be used on Linux, so I realize that this breaks traditional Java thought.
I've edited the ClassPath in the Manifest file to reflect this change, and it works fine. However, when I remove the file, the GUI fails to load, citing not being able to load the class.
Is there a way to try to load a class and if it does not work, then do something else? In essence, I'm trying to catch the ClassNotFound exception, but have not had any luck yet.
One common way to check for class existence is to just do a Class.forName("my.Class"). You can wrap that with a try/catch that catches ClassNotFoundException and decide what to do. If you want, you could do that in a wrapper class that has a main(). You could try to load the class and if it succeeds, then call main() on the loaded class and if not, do something else.
public static void main(String arg[]) {
try {
Class.forName("my.OtherMain");
// worked, call it
OtherMain.main();
} catch(ClassNotFoundException e) {
// fallback to some other behavior
doOtherThing();
}
}
Is there a way to try to load a class and if it does not work, then do something else?
Assuming you had a class file in C:\ called Foo.class
public static void main(String[] args) {
File f = new File("c:\\");
if (f.exists()) {
URLClassLoader CLoader;
try {
CLoader = new URLClassLoader(new URL[]{f.toURL()});
Class loadedClass = CLoader.loadClass("Foo");
} catch (ClassNotFoundException ex) {
} catch (MalformedURLException ex) {
}
} else {
//do something else...
}
}