I'm playing around with Reflection and I thought I'd make something which loads a class and prints the names of all fields in the class.
I've made a small hello world type of class to have something to inspect:
kent#rat:~/eclipsews/SmallExample/bin$ ls
IndependentClass.class
kent#rat:~/eclipsews/SmallExample/bin$ java IndependentClass
Hello! Goodbye!
kent#rat:~/eclipsews/SmallExample/bin$ pwd
/home/kent/eclipsews/SmallExample/bin
kent#rat:~/eclipsews/SmallExample/bin$
Based on the above I draw two conclusions:
It exists at /home/kent/eclipsews/SmallExample/bin/IndependentClass.class
It works! (So it must be a proper .class-file which can be loaded by a class loader)
Then the code which is to use Reflection: (Line which causes an exception is marked)
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class InspectClass {
#SuppressWarnings("unchecked")
public static void main(String[] args) throws ClassNotFoundException, MalformedURLException {
URL classUrl;
classUrl = new URL("file:///home/kent/eclipsews/SmallExample/bin/IndependentClass.class");
URL[] classUrls = { classUrl };
URLClassLoader ucl = new URLClassLoader(classUrls);
Class c = ucl.loadClass("IndependentClass"); // LINE 14
for(Field f: c.getDeclaredFields()) {
System.out.println("Field name" + f.getName());
}
}
}
But when I run it I get:
Exception in thread "main" java.lang.ClassNotFoundException: IndependentClass
at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at InspectClass.main(InspectClass.java:14)
My questions:
What am I doing wrong above? How do I fix it?
Is there a way to load several class files and iterate over them?
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:
Refer to the directory that the .class file is in
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.
You must provide the directories or the jar files containing your .class files to the URLClassLoader:
classUrl = new URL("file:///home/kent/eclipsews/SmallExample/bin/");
And yes, you can load as many classes as you like
You have to load the class by giving the fully qualified class name that is class name with with its package path as,
Class c = ucl.loadClass("com.mypackage.IndependentClass");
I was having a similar problem but my class file was residing inside a package named "customElements". In such scenarios the URL needs to be constructed till the folder just above the root package and in the load method the complete name of the class including the package should be passed. For example, in my case; URL was like:
File customElementsDir = new File("D:/My Space");
//File customElementsDir = new File("D:\\customElements");
URL[] urls = null;
try {
URL url = customElementsDir.toURI().toURL();
urls = new URL[] { url };
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
And actual loading was like:
clazz = childFirstClassLoader
.loadClass("customElements.CustomLoadableClass");
Where childFirstClassLoader is my classloader.
I had the same problem but I found this as a solution:
System.getProperty("java.class.path")
This give you the jar, and classes path. From here you are able to manage your process.
Related
I am currently making a small simple Java program for my Computer Science Final, which needs to get the path of the current running class. The class files are in the C:\2013\game\ folder.
To get this path, I call this code segment in my main class constructor:
public game(){
String testPath = this.getClass().getResource("").getPath();
//Rest of game
}
However, this command instead returns this String: "/" despite the correct output being "C:/2013/game"
Additionally, I attempted to rectify this by using this code:
public game(){
String testPath = this.getClass().getClassLoader().getResource("").getPath();
}
This returns a NullPointerException, which originates from the fact that getClassLoader() returns null, despite working on my Eclipse IDE. Any Ideas?
If you want to load a file in the same path as the code then I suggest you put it in the same root folder as the code and not the same path as the class.
Reason : class can be inside a jar, data file can be put in same jar but its more difficult to edit and update then.
Also suggest you see the preferences class suggested in comments : http://www.javacodegeeks.com/2011/09/use-javautilprefspreferences-instead-of.html though in some cases I think its okay to have your own data/ excel/csv/ java.util.Properties file
Not sure about why it is working in eclipse but I would suggest you focus on running it from a command prompt/ terminal as that is the 'real mode' when it goes live
You could just ask for your class
String s = getClass().getName();
int i = s.lastIndexOf(".");
if(i > -1) s = s.substring(i + 1);
s = s + ".class";
System.out.println("name " +s);
Object testPath = this.getClass().getResource(s);
System.out.println(testPath);
This will give you
name TstPath.class
file:/java/Projects/tests3b/build/classes/s/TstPath.class
Which is my eclipse build path ...
need to parse this to get the path where the class was loaded.
Remember:
App could be started from elsewhere
class can be in jar then path will be different (will point to a jar and file inside that
classpaths can be many at runtime and point 1
a class might be made at runtime via network/ Proxy / injection etc and thus not have a file source, so this is not a generic solution.
think what you want to acheive at a higher level and post that question. meaning why do you want this path?
do you want the app path :-
File f = new File("./");
f.getCanonicalPath();//...
So an app can be started from folder c:\app1\run\
The jar could be at c:\app1\libsMain\myapp.jar
and a helper jar could be at c:\commonlibs\set1
So this will only tell you where the JVM found your class, that may or maynot be what you need.
if inside a jar will give you some thing like this in unix or windows
jar:file:c:\app\my.jar!/s/TstPath.class
If package is s and class is TstPath, you can be sure this will work as the class has to be there ...
now to parse this you can look for your class name and remove / or \ till you get path you want. String lastIndexOf will help
You can use :
URL classURL = getClass().getProtectionDomain().getCodeSource().getLocation();
The call to getResource([String]) requires a path relative to the folder that contains the class it is being called from. So, if you have the following, anything you pass into MyClass.class.getResource([path]); must be a valid path relative to the com/putable/ package folder and it must point to a real file:
package com.putable;
public class MyClass{}
Using the empty string simply isn't valid, because there can never be a file name that equals the empty string. But, you could do getResource(getClass().getSimpleName()). Just remove the file name from the end of the path returned by that call and you will have the class directory you want.
ClassLoader loader = Test.class.getClassLoader();
System.out.println(loader.getResource("Test.class"));
also
Test.class.getProtectionDomain().getCodeSource().getLocation().getPath());
Try this.
import java.io.File;
public class TT {
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String path = TT.class.getResource("").getPath();
File file = new File(path);
System.out.println(file.getAbsolutePath());
}
}
Try use this code
public game()
{
String className = this.getClass().getSimpleName();
String testPath = this.getClass().getResource(className+".class");
System.out.println("Current Running Location is :"+testPath);
}
visit the link for more information
Find where java class is loaded from
Print out absolute path for a file in your classpath i.e. build/resources/main/someFileInClassPath.txt Disclaimer, this is similar to another solution on this page that used TT.class..., but this did not work for me instead TT..getClassLoader()... did work for me.
import java.io.File;
public class TT {
/**
* #param args
*/
public static void main(String[] args) {
String path = TT.getClassLoader().getResource("someFileInClassPath.txt").getPath();
File file = new File(path);
System.out.println(file.getAbsolutePath());
}
}
Because you used class.getResource(filePath).getpath() in a *.jar file. So the path includes "!". If you want to get content of file in *.jar file, use the following code:
MyClass.class.getResourceAsStream("/path/fileName")
I have been trying to get image resources to display on a GUI I am developing, but am having a difficult time getting them to display. I've come across other questions about loading resources such as java-swing-displaying-images-from-within-a-jar and images-will-not-work-in-a-jar-file, but they aren't working for me.
My issue appears to be the same as the first link, where images appear when run from Eclipse and don't appear when run from the command line using Jars. However the solution to those questions don't make the images appear.
The code I have for retrieving resources is:
public class R {
public static final String resourcePackage = "path/to/image/package";
/**
* Returns the resource string that refers to the <code>resource</code> file
* in the <code>path.to.image.package.png</code> package.
*
* #param resource
* the file in the <code>png</code> package
* #return the full resource string
*/
public static String png(String resource) {
return String.format("%s/png/%s", resourcePackage, resource);
}
public static ResizableIcon resizableIcon(String resource) {
return ImageWrapperResizableIcon.getIcon(R.class.getClassLoader()
.getResourceAsStream(resource), new Dimension(48, 48));
}
}
I call it when generating the GUI
JCommandButton connect = new JCommandButton(i18ln.getString("ports"),
R.resizableIcon(R.png("serial-port-32x32.png")));
A print statement indicates that the resource was found because R.class.getClassLoader().getResourceAsStream returns an instance of sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream.
I'm stumped. I have spent hours trying to figure this out, but could use some help. Any ideas?
FYI: I don't think it matters, but I am using Flamingo for my GUI.
EDIT: per Stefan's request
src/
main/
java/
com.company.project (packages)
R.java
MyGui.java
resources/
com.company.project (packages)
.png (package)
serial-port-32x32.png
(more images)
.i18ln (package)
MyGui.properties
As for more code, I don't know what else I can provide that will be of much benefit for this question. All the code for retrieving resources and how I use that code is provided above. Was there something specific you were looking for?
UPDATE:
When I create a Jar using Eclipse and run it from the command line, the image resources display properly. When I create a Jar using Gradle, the images are not displayed. So there is something being done differently when generating the Jars that allows images resources to be accessed properly via the Eclipse Jar, but not the Gradle Jar. I opened a question on the Gradle forums with respect to this issue.
Dependent on the environment in which your application is (Standalone, ApplicationServer), you will need to use the appropriate ClassLoader.
If you can have a utility class, Utils, you can try something like this:
/* Returns a instance of InputStream for the resource */
public static InputStream getResourceAsStream(String resource)
throws FileNotFoundException {
String stripped = resource.startsWith("/")?resource.substring(1):resource;
InputStream stream = null;
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader != null) {
stream = classLoader.getResourceAsStream(stripped);
}
if (stream == null) {
stream = Utils.class.getResourceAsStream(resource);
}
if (stream == null) {
stream = Utils.class.getClassLoader().getResourceAsStream(stripped);
}
if (stream == null) {
throw new FileNotFoundException("Resource not found: " + resource);
}
return stream;
}
For use:
Utils.getResourceAsStream("com/company/project/png/serial-port-32x32.png");
Here try this example HOW TO LOAD IMAGES TO YOUR ECLIPSE PROJECT. Hopefully this will explain things for you, More STEPS HERE
Don't use ClassLoader, thingy though, as described in this Java Doc , A quote from it states "All class loaders will search for a resource first as a system resource, in a manner analogous to searcing for class files."
Forget about the class loader, check the path. If feasible use getResource i.o. getResourceAsStream (question of style: more direct, and delivers null when not found).
As getResource(AsStream) is class based, the paths are relative, so try this:
R.class.getResource("/" + resource)
Create a resources package and place this class and your images in it:
public final class Resources {
public static ImageIcon getImage(String filename) {
URL resourceUrl = Resources.class.getResource(filename);
return new ImageIcon(resourceUrl);
}
}
Edit:
I have rebuild your structure with maven and flamingo (using your R class) and it works with these additions:
Change:
R.class.getClassLoader().getResourceAsStream(resource);
to:
R.class.getResource("png/"+resource);
I have used the maven-assemble-plugin to build the jar as described here.
URL imageurl = getClass().getResource("/images/serial-port-32x32.png");//relative path of the image as argument
Image myPicture = Toolkit.getDefaultToolkit().getImage(imageurl);
Use this Image 'myPicture' as the first argument of ImageWrapperResizableIcon.getIcon method
Consider this code (based entirely on flying saucer's "getting started" code, their rights reserved):
package flyingsaucerpdf;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import org.xhtmlrenderer.pdf.ITextRenderer;
public class PDFMaker {
public static void main(String[] args) throws Exception {
new PDFMaker().go();
}
public void go() throws Exception {
String inputFile = "sample.html";
String url = new File(inputFile).toURI().toURL().toString();
String outputFile = "firstdoc.pdf";
OutputStream os = new FileOutputStream(outputFile);
ITextRenderer renderer = new ITextRenderer();
renderer.setDocument(url);
renderer.layout();
renderer.createPDF(os);
os.close();
}
}
Few facts:
Running it standalone (calling main) with JDK 1.6 or 1.5 works perfectly (PDF is generated)
But when loaded via a URLClassLoader from an existing web application it fails with this error:
Caused by: org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces.
at org.apache.xerces.dom.AttrNSImpl.setName(Unknown Source)
at org.apache.xerces.dom.AttrNSImpl.(Unknown Source)
at org.apache.xerces.dom.CoreDocumentImpl.createAttributeNS(Unknown Source)
at org.apache.xerces.dom.ElementImpl.setAttributeNS(Unknown Source)
at org.apache.xml.utils.DOMBuilder.startElement(DOMBuilder.java:307)
... 19 more
After looking for a while in the wrong place (for example, I created a child-first / parent-last class loader suspecting xalan / xerces jars, but it still fails), I finally narrowed down the root cause:
It seems that the web application that loads my code, has an old xalan.jar, specification version 1.2
I did a little test, I ran the code above as standalone (which worked fine before) but this time I added the xalan.jar from the web app to it's classpath, and bingo, the same error as in the web app scenario
So I inspected that old xalan.jar and wondered, what can cause the JVM to load it's old xalan implementation instead of the JDK's? after all my child-first class loader is also parent-last e.g. system in the middle, to say: searching the system classloader before the parent (to avoid loading parent overriden JDK jars, just like this case of the parent's xalan.jar overriding the JDK's xalan implementation)
Then something cought my eyes - a file in: xalan.jar/META-INF/services/ named javax.xml.transform.TransformerFactory with this content:
org.apache.xalan.processor.TransformerFactoryImpl
So I imediately pressed Ctrl+T in eclipse and looked for the full qualified name... only in xalan.jar!
Then I searched only for "TransformerFactoryImpl", and this is what the JDK has:
com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
Easy to see the difference
So, if you read till here, my bottom line question is: How do I make my TransformerFactory use the JDK's implementation and not the old Xalan's one? (I can't remove that jar from the web app my code will be loaded from)
It seems the answer is simpler than I thought.
In your classloader, add to the classpath (jar not required) this folder: /META-INF/services/
In it, create a file named javax.xml.transform.TransformerFactory
Edit it and set this as it's content to: com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
Thats it!
Why does it work? see this class used by Java for loading the Xalan implementation.
Notice that it seems de-facto a parent-last (or child-first) loader for that specific "META-INF" entry (the oposite of how regular Java class loaders work, e.g. parent-first / child-last) but feel free to correct me if I'm wrong
Snippet from javax.xml.datatype.FactoryFinder
/*
* Try to find provider using Jar Service Provider Mechanism
*
* #return instance of provider class if found or null
*/
private static Object findJarServiceProvider(String factoryId)
throws ConfigurationError
{
String serviceId = "META-INF/services/" + factoryId;
InputStream is = null;
// First try the Context ClassLoader
ClassLoader cl = ss.getContextClassLoader();
if (cl != null) {
is = ss.getResourceAsStream(cl, serviceId);
// If no provider found then try the current ClassLoader
if (is == null) {
cl = FactoryFinder.class.getClassLoader();
is = ss.getResourceAsStream(cl, serviceId);
}
} else {
// No Context ClassLoader, try the current
// ClassLoader
cl = FactoryFinder.class.getClassLoader();
is = ss.getResourceAsStream(cl, serviceId);
}
if (is == null) {
// No provider found
return null;
}
...
You should also note that you aren't required to use the SPI mechanism at all.
You can use http://download.oracle.com/javase/6/docs/api/javax/xml/xpath/XPathFactory.html#newInstance(java.lang.String, java.lang.String, java.lang.ClassLoader) to use a copy of xalan in your own isolated class loader once you know the name of the relevant class.
If you are developing a web application and thus can't set a system property, then the most direct way is to explicitly request the JDK transformer.
Here is an example for the internal XSLTC transformer (has StAXSupport).
TransformerFactory tf = TransformerFactory.newInstance(
"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", null);
I'm using wildfly application server and 3rd jar which uses TransformerFactory.
Overriding TransformerFactory using file resources\META-INF\services\javax.xml.transform.TransformerFactory. Didn't work for me.
When I looked to FactoryFinder implementation (JDK8u201). I found the following code fragment
String systemProp = ss.getSystemProperty(factoryId);
if (systemProp != null) {
dPrint("found system property, value=" + systemProp);
return newInstance(type, systemProp, null, true);
}
Thus, solution was to set system property javax.xml.transform.TransformerFactory to com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.
Cheers
I need to add plugin functionality to an existing application for certain parts of the application. I want to be able to add a jar at runtime and the application should be able to load a class from the jar without restarting the app. So far so good. I found some samples online using URLClassLoader and it works fine.
I also wanted the ability to reload the same class when an updated version of the jar is available. I again found some samples and the key to achieving this as I understand is that I need to use a new classloader instance for each new load.
I wrote some sample code but hit a NullPointerException. First let me show you guys the code:
package test.misc;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import plugin.misc.IPlugin;
public class TestJarLoading {
public static void main(String[] args) {
IPlugin plugin = null;
while(true) {
try {
File file = new File("C:\\plugins\\test.jar");
String classToLoad = "jartest.TestPlugin";
URL jarUrl = new URL("jar", "","file:" + file.getAbsolutePath()+"!/");
URLClassLoader cl = new URLClassLoader(new URL[] {jarUrl}, TestJarLoading.class.getClassLoader());
Class loadedClass = cl.loadClass(classToLoad);
plugin = (IPlugin) loadedClass.newInstance();
plugin.doProc();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
IPlugin is a simple interface with just one method doProc:
public interface IPlugin {
void doProc();
}
and jartest.TestPlugin is an implementation of this interface where doProc just prints out some statements.
Now, I package the jartest.TestPlugin class into a jar called test.jar and place it under C:\plugins and run this code. The first iteration runs smoothly and the class loads without issues.
When the program is executing the sleep statement, I replace C:\plugins\test.jar with a new jar containing an updated version of the same class and wait for the next iteration of while. Now here's what I don't understand. Sometimes the updated class gets reloaded without issues i.e. the next iteration runs fine. But sometimes, I see an exception thrown:
java.lang.NullPointerException
at java.io.FilterInputStream.close(FilterInputStream.java:155)
at sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream.close(JarURLConnection.java:90)
at sun.misc.Resource.getBytes(Resource.java:137)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:256)
at java.net.URLClassLoader.access$000(URLClassLoader.java:56)
at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
at test.misc.TestJarLoading.main(TestJarLoading.java:22)
I have searched on the net and scratched my head but can't really arrive at any conclusion as to why this exception is thrown and that too - only sometimes, not always.
I need your experience and expertise to understand this. What's wrong with this code? Please help!!
Let me know if you need any more info. Thanks for looking!
For everyone's benefit, let me summarize the real problem and the solution that worked for me.
As Ryan pointed out, there is a bug in JVM, which affects Windows Platform. URLClassLoader does not close the open jar files after it opens them for loading classes, effectively locking the jar files. The jar files can't be deleted or replaced.
The solution is simple: close the open jar files after they've been read. However, to get a handle to the open jar files, we need to use reflection since the properties we need to traverse down are not public. So we traverse down this path
URLClassLoader -> URLClassPath ucp -> ArrayList<Loader> loaders
JarLoader -> JarFile jar -> jar.close()
The code to close the open jar files can be added to a close() method in a class extending URLClassLoader:
public class MyURLClassLoader extends URLClassLoader {
public PluginClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
/**
* Closes all open jar files
*/
public void close() {
try {
Class clazz = java.net.URLClassLoader.class;
Field ucp = clazz.getDeclaredField("ucp");
ucp.setAccessible(true);
Object sunMiscURLClassPath = ucp.get(this);
Field loaders = sunMiscURLClassPath.getClass().getDeclaredField("loaders");
loaders.setAccessible(true);
Object collection = loaders.get(sunMiscURLClassPath);
for (Object sunMiscURLClassPathJarLoader : ((Collection) collection).toArray()) {
try {
Field loader = sunMiscURLClassPathJarLoader.getClass().getDeclaredField("jar");
loader.setAccessible(true);
Object jarFile = loader.get(sunMiscURLClassPathJarLoader);
((JarFile) jarFile).close();
} catch (Throwable t) {
// if we got this far, this is probably not a JAR loader so skip it
}
}
} catch (Throwable t) {
// probably not a SUN VM
}
return;
}
}
(This code was taken from the second link that Ryan posted. This code is also posted on the bug report page.)
However, there's a catch: For this code to work and be able to get a handle to the open jar files to close them, the loader used to load the classes from the file by URLClassLoader implementation has to be a JarLoader. Looking at the source code of URLClassPath (method getLoader(URL url)), I noticed that it uses a JARLoader only if the file string used to create the URL does not end in "/". So, the URL must be defined like this:
URL jarUrl = new URL("file:" + file.getAbsolutePath());
The overall class loading code should look something like this:
void loadAndInstantiate() {
MyURLClassLoader cl = null;
try {
File file = new File("C:\\jars\\sample.jar");
String classToLoad = "com.abc.ClassToLoad";
URL jarUrl = new URL("file:" + file.getAbsolutePath());
cl = new MyURLClassLoader(new URL[] {jarUrl}, getClass().getClassLoader());
Class loadedClass = cl.loadClass(classToLoad);
Object o = loadedClass.getConstructor().newInstance();
} finally {
if(cl != null)
cl.close();
}
}
Update: JRE 7 has introduced a close() method in the class URLClassLoader which may have solved this issue. I haven't verified it.
This behaviour is related to a bug in the jvm
2 workarounds are documented here
Starting from Java 7, you indeed have a close() method in URLClassLoader but it is not enough to release completely the jar files if you call directly or indirectly methods of type ClassLoader#getResource(String), ClassLoader#getResourceAsStream(String) or ClassLoader#getResources(String). Indeed by default, the JarFile instances are automatically stored into the cache of JarFileFactory in case we call directly or indirectly one of the previous methods and those instances are not released even if we call java.net.URLClassLoader#close().
So a hack is still needed in this particular case even with Java 1.8.0_74, here is my hack https://github.com/essobedo/application-manager/blob/master/src/main/java/com/github/essobedo/appma/core/util/Classpath.java#L83 that I use here https://github.com/essobedo/application-manager/blob/master/src/main/java/com/github/essobedo/appma/core/DefaultApplicationManager.java#L388. Even with this hack, I still had to call the GC explicitly to fully release the jar files as you can see here https://github.com/essobedo/application-manager/blob/master/src/main/java/com/github/essobedo/appma/core/DefaultApplicationManager.java#L419
This is an update tested on java 7 with success. Now the URLClassLoader works fine for me
MyReloader
class MyReloaderMain {
...
//assuming ___BASE_DIRECTORY__/lib for jar and ___BASE_DIRECTORY__/conf for configuration
String dirBase = ___BASE_DIRECTORY__;
File file = new File(dirBase, "lib");
String[] jars = file.list();
URL[] jarUrls = new URL[jars.length + 1];
int i = 0;
for (String jar : jars) {
File fileJar = new File(file, jar);
jarUrls[i++] = fileJar.toURI().toURL();
System.out.println(fileJar);
}
jarUrls[i] = new File(dirBase, "conf").toURI().toURL();
URLClassLoader classLoader = new URLClassLoader(jarUrls, MyReloaderMain.class.getClassLoader());
// this is required to load file (such as spring/context.xml) into the jar
Thread.currentThread().setContextClassLoader(classLoader);
Class classToLoad = Class.forName("my.app.Main", true, classLoader);
instance = classToLoad.newInstance();
Method method = classToLoad.getDeclaredMethod("start", args.getClass());
Object result = method.invoke(instance, args);
...
}
Close and Restart the ClassReloader
then update your jar and call
classLoader.close();
then you can restart the app with the new version.
Do not include your jar into your base class loader
Do not include your jar into your base class loader "MyReloaderMain.class.getClassLoader()" of the "MyReloaderMain", in other words develop 2 project with 2 jars one for "MyReloaderMain" and the other one for your real application without dependency between the two, or you will not able to understand who i loading what.
The error is still present in jdk1.8.0_25 on Windows. Although #Nicolas' answer helps, I hit a ClassNotFound for sun.net.www.protocol.jar.JarFileFactory when running it on WildFly, and several vm crashes while debugging some box tests...
Therefore I ended up extracting the part of the code which deals with loading and unloading, to an external jar. From the main code I just call this with java -jar.... all looks fine for now.
NOTE: Windows does release the locks on the loaded jar files when the jvm exits, that is why this works.
In principle, a class that has already been loaded cannot be reloaded with the same classloader.
For a new load, it is necessary to create a new classloader and thus load the class.
Using URLClassLoader has one problem and that is that the jar file remains open.
If you have multiple classes loaded from one jar file by different instances of URLClassLoader and you change the jar file at runtime, you will usually get this error: java.util.zip.ZipException: ZipFile invalid LOC header (bad signature). The error may be different.
In order for the above errors not to occur, it is necessary to use the close method on all URLClassLoaders using the given jar file. But this is a solution that actually leads to a restart of the entire application.
A better solution is to modify the URLClassLoader so that the contents of the jar file are loaded into the RAM cache. This no longer affects other URLClassloaders that read data from the same jar file. The jar file can then be freely changed while the application is running. For example, you can use this modification of URLClassLoader for this purpose: in-memory URLClassLoader
I'm dynamically loading a class and calling a method on it. This class does JNI. When I call the class, java attempts to load the library. This causes an error because the library is not on the libpath. I'm calling from instead a jar so I can't easily change the libpath (especially since the library is not in the same directory or a sub directory of the jar). I do know the path of the library, but how can I load it before I load the class.
Current code:
public Class<?> loadClass(String name) throws ClassNotFoundException {
if(!CLASS_NAME.equals(name))
return super.loadClass(name);
try {
URL myUrl = new URL(classFileUrl);
URLConnection connection = myUrl.openConnection();
InputStream input = connection.getInputStream();
byte[] classData = readConnectionToArray(input);
return defineClass(CLASS_NAME,
classData, 0, classData.length);
} catch (MalformedURLException e) {
throw new UndeclaredThrowableException(e);
} catch (IOException e) {
throw new UndeclaredThrowableException(e);
}
}
Exception:
Can't find library libvcommon.so
java.lang.UnsatisfiedLinkError: vcommon (A file or directory in the path name does not exist.)
at java.lang.ClassLoader.loadLibraryWithPath(ClassLoader.java:998)
at java.lang.ClassLoader.loadLibraryWithClassLoader(ClassLoader.java:962)
at java.lang.System.loadLibrary(System.java:465)
at vcommon.(vcommon.java:103)
at java.lang.J9VMInternals.initializeImpl(Native Method)
at java.lang.J9VMInternals.initialize(J9VMInternals.java:200)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:37)
at java.lang.reflect.Method.invoke(Method.java:599)
at com.fortune500.fin.v.vunit.reflection.ReflectionvProcessor.calculateV(ReflectionvProcessor.java:36)
at com.fortune500.fin.v.vunit.UTLTestCase.execute(UTLTestCase.java:42)
at com.fortune500.fin.v.vunit.TestSuite.execute(TestSuite.java:15)
at com.fortune500.fin.v.vunit.batch.Testvendor.execute(Testvendor.java:101)
at com.fortune500.fin.v.vunit.batch.Testvendor.main(Testvendor.java:58)
Edit: I'm having a 64bit vs 32bit issue right now. I'll come back to this when I've sorted that out.
Related: Dynamic loading a class in java with a different package name
If you know the path to the library, you could add the path to the java.library.path environment variable in your custom class loader. A simpler approach is to compute the path and use it in your call to Runtime.loadLibrary.
The code below outlines two approaches, using loadLibrary and setting the java.library.path system property.
if(CLASS_NAME.equals(name)) {
// two ways of doing this - either load the library explicitly from the full path
if (useFullPath) {
Runtime.getRuntime().loadLibrary("/full/path/to/mylibrary");
}
else { // or tweaking the library path
System.setProperty("java.library.path",
System.getProperty("java.library.path")
+ System.getProperty("file.separator")
+ "/path/to/lib");
}
}
return super.loadClass(name);
You mention your code is being called from a jar - using a custom class loader is going to be difficult if the ClassLoader is part of the jar as well. Have you verified that your class loader is indeed being used?
A simpler approach is to change the current call to loadLibrary in your native class to use the full path. E.g. fetch from system properties, or compute it, if you know in advance where to find it. This is only an option of course if you have the source to the native class. If you can't modify the native class, then use the loadLibrary call in the classloader.
It's my understanding that calls to load library with the same library name (regardless of path) load the same library. (At least, that's the behaviour on Windows - I haven't verified on Linux.) So, even though the classloader loads the library using the full path, and the native class loads the library using it's simple name, both should resolve to the same library.
(Just for completeness, resolving equivalent libraries happens in the kernel, again, speaking from Win32 experience. Each library internally has a name, and windows only loads one instance of the library with the same internal name per process.)