Adding files to java classpath at runtime [duplicate] - java

This question already has answers here:
How to load JAR files dynamically at Runtime?
(20 answers)
Closed 7 years ago.
Is it possible to add a file (not necessarily a jar file) to java classpath at runtime.
Specifically, the file already is present in the classpath, what I want is whether I can add a modified copy of this file to the classpath.
Thanks,

You can only add folders or jar files to a class loader. So if you have a single class file, you need to put it into the appropriate folder structure first.
Here is a rather ugly hack that adds to the SystemClassLoader at runtime:
import java.io.IOException;
import java.io.File;
import java.net.URLClassLoader;
import java.net.URL;
import java.lang.reflect.Method;
public class ClassPathHacker {
private static final Class[] parameters = new Class[]{URL.class};
public static void addFile(String s) throws IOException {
File f = new File(s);
addFile(f);
}//end method
public static void addFile(File f) throws IOException {
addURL(f.toURL());
}//end method
public static void addURL(URL u) throws IOException {
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL", parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[]{u});
} catch (Throwable t) {
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}//end try catch
}//end method
}//end class
The reflection is necessary to access the protected method addURL. This could fail if there is a SecurityManager.

Try this one on for size.
private static void addSoftwareLibrary(File file) throws Exception {
Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
method.setAccessible(true);
method.invoke(ClassLoader.getSystemClassLoader(), new Object[]{file.toURI().toURL()});
}
This edits the system class loader to include the given library jar. It is pretty ugly, but it works.

The way I have done this is by using my own class loader
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
DynamicURLClassLoader dynalLoader = new DynamicURLClassLoader(urlClassLoader);
And create the following class:
public class DynamicURLClassLoader extends URLClassLoader {
public DynamicURLClassLoader(URLClassLoader classLoader) {
super(classLoader.getURLs());
}
#Override
public void addURL(URL url) {
super.addURL(url);
}
}
Works without any reflection

You coud try java.net.URLClassloader with the url of the folder/jar where your updated class resides and use it instead of the default classloader when creating a new thread.

Yes I believe it's possible but you might have to implement your own classloader. I have never done it but that is the path I would probably look at.

yes, you can. it will need to be in its package structure in a separate directory from the rest of your compiled code if you want to isolate it. you will then just put its base dir in the front of the classpath on the command line.

My solution:
File jarToAdd = new File("/path/to/file");
new URLClassLoader(((URLClassLoader) ClassLoader.getSystemClassLoader()).getURLs()) {
#Override
public void addURL(URL url) {
super.addURL(url);
}
}.addURL(jarToAdd.toURI().toURL());

Related

Import class in Java via absolute path

I've been trying to import a .class via absolute path while code is running and I don't know how to do it.
I found a way to import a class when it's already in project's build path by Class.forName();but I need to find a way to load a class that is not in build path.
The goal is:
User is able to upload his own .class file which is then saved locally to a specific folder and path is saved in database
Via GUI user can select this file to be used while code is running
My code should load a class via this given absolute path while code is running
The problem is with 3rd point because I don't know if it is possible to load a class while code is running.
I've tried using URLClassLoader but I'm getting ClassNotFound error.
EDIT:
Basically, I have this static function which should return Class by it's name, but urlClassLoader.loadClass() throws error.
Name of a file is J48.class so for className argument I've tried using "J48", "J48.class" but none work.
Additionaly I've tried setting folder classifiers to build path and setting argument to "weka.classifiers.trees.J48" which is full path with package to this class (package structure is weka.classifiers.trees).
`public static Class getClassByName(String className) throws MalformedURLException, ClassNotFoundException
{
URLClassLoader urlClassLoader = URLClassLoader.newInstance(new URL[] {
new URL("file:///D:\\xampp\\htdocs\\prog-ing\\classifiers\\")
});
Class class = urlClassLoader.loadClass(className);
return class;
}`
I think I have a suggestion to solve your problem...
I know two options:
Option 1: Read a class file from directory.
Example:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class Test5 extends ClassLoader {
private static final String PATH = "C://myFiles//";
public static void main(String[] args) {
Class clazz = getClassFromName("Test4");
System.out.println(clazz);
}
private static Class getClassFromName(String className) {
File file = new File(PATH + className + ".class");
try {
FileInputStream fileInputStream = new FileInputStream(file);
byte[] bytes = new byte[fileInputStream.available()];
fileInputStream.read(bytes);
return defineClass(className, bytes, 0, bytes.length);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
This will print something like this:
class Test4
- Option 2: Read a class file from JAR.
Example:
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class Test5 {
private static final String PATH = "C://myFiles//";
public static void main(String[] args) {
Class clazz = getClassFromFile("myJar.jar", "com.mypackage.Test4");
System.out.println(clazz);
}
private static Class getClassFromFile(String fromFile, String className) {
try {
URL url = new URL("file:////" + PATH + fromFile);
URLClassLoader urlClassLoader = URLClassLoader.newInstance(
new URL[] {
url
});
return urlClassLoader.loadClass(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}
}
This will print something like this:
class com.mypackage.Test4
Note that to read a jar file I had to put the full path of package to the class file.
I hope I've helped.
Okay so after thinking a bit, I only got to the one solution (still not satisfied with it) which is following:
every class that needs to be uploaded by user is saved into workspace of this project and therefore I am able to get class using Class.forName(); pointing out this "folder" of uploaded classes, in my case: Class.forName("classifiers.className");

Java 9, compatability issue with ClassLoader.getSystemClassLoader

The following code adds jar file to the build path, it works fine with Java 8. However, it throws exception with Java 9, the exception is related to the cast to URLClassLoader. Any ideas how this can be solved? an optimal solution will edit it to work with both Java 8 & 9.
private static int AddtoBuildPath(File f) {
try {
URI u = f.toURI();
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<URLClassLoader> urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(urlClassLoader, u.toURL());
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException | MalformedURLException | IllegalAccessException ex) {
return 1;
}
return 0;
}
You've run into the fact that the system class loader is no longer a URLClassLoader. As indicated by ClassLoader::getSystemClassLoader's return type, this was an implementation detail, albeit one that a non-negligible amount of code relied upon.
Judging by the comments, you are looking for a way to dynamically load classes at run time. As Alan Bateman points out, this can not be done in Java 9 by appending to the class path.
You should instead consider creating a new class loader for that. This has the added advantage that you'll be able to get rid of the new classes as they are not loaded into the application class loader. If you're compiling against Java 9, you should read up on layers - they give you a clean abstraction for loading an entirely new module graph.
I have stumbled over this issue a while ago. As many, I had used a method similar to that in the question
private static int AddtoBuildPath(File f)
to dynamically add paths to the classpath at runtime. The code in the question is probably bad style in multiple aspects: 1) assuming that ClassLoader.getSystemClassLoader() returns an URLClassLoader is an undocumented implementation detail and 2) using reflection to make addURL public is maybe another one.
Cleaner way to dynamically add classpaths
In case that you need to use the additional classpath URLs for class loading through „Class.forName“, a clean, elegant and compatible (Java 8 to 10) solution is the following:
1) Write your own class loader by extending URL classloader, having a public addURL method
public class MyClassloader extends URLClassLoader {
public MyClassloader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public void addURL(URL url) {
super.addURL(url);
}
}
2) Declare a (singleton/app wide) object of your classloader
private final MyClassloader classLoader;
and instanciate it via
classLoader = new MyClassloader(new URL[0], this.getClass().getClassLoader());
Note: The system class loader is the parent. Classes loaded though classLoader know those who can be loaded through this.getClass().getClassLoader() but not the other way around.
3) Add additional classpaths whenever needed (dynamically):
File file = new File(path);
if(file.exists()) {
URL url = file.toURI().toURL();
classLoader.addURL(url);
}
4) Instanciate objects or your app though your singleton classloader via
cls = Class.forName(name, true, classLoader);
Note: Since class loaders try a delegation to the parent class loader prior loading a class (and the parent to its parent), you have to make sure that the class to load is not visible to the parent class loader to make sure that it is loaded through the given class loader. To make this clearer: if you have ClassPathB on your system class path and later add ClassPathB and some ClassPathA to your custom classLoader, then classes under ClassPathB will be loaded through the system classloader and classes under ClassPathA are not known to them. However, if you remove ClassPathB from you system class path, such classes will be loaded through your custom classLoader, and then classes under ClassPathA are known to those under ClassPathB.
5) You may consider passing your class loader to a thread via
setContextClassLoader(classLoader)
in case that thread uses getContextClassLoader.
If you're just looking to read the current classpath, for example because you want to spin up another JVM with the same classpath as the current one, you can do the following:
object ClassloaderHelper {
def getURLs(classloader: ClassLoader) = {
// jdk9+ need to use reflection
val clazz = classloader.getClass
val field = clazz.getDeclaredField("ucp")
field.setAccessible(true)
val value = field.get(classloader)
value.asInstanceOf[URLClassPath].getURLs
}
}
val classpath =
(
// jdk8
// ClassLoader.getSystemClassLoader.asInstanceOf[URLClassLoader].getURLs ++
// getClass.getClassLoader.asInstanceOf[URLClassLoader].getURLs
// jdk9+
ClassloaderHelper.getURLs(ClassLoader.getSystemClassLoader) ++
ClassloaderHelper.getURLs(getClass.getClassLoader)
)
By default the final fields in the $AppClassLoader class cannot be accesed via reflection, an extra flag needs to be passed to the JVM:
--add-opens java.base/jdk.internal.loader=ALL-UNNAMED
I was given a spring boot application that runs in Java 8. I had the task to upgrade it to Java 11 version.
Issue faced:
Caused by: java.lang.ClassCastException: jdk.internal.loader.ClassLoaders$AppClassLoader (in module: java.base) cannot be cast to java.net.URLClassLoader (in module: java.base)
Way around used:
Create a class:
import java.net.URL;
/**
* This class has been created to make the code compatible after migration to Java 11
* From the JDK 9 release notes: "The application class loader is no longer an instance of
* java.net.URLClassLoader (an implementation detail that was never specified in previous releases).
* Code that assumes that ClassLoader.getSytemClassLoader() returns a URLClassLoader object will
* need to be updated. Note that Java SE and the JDK do not provide an API for applications or
* libraries to dynamically augment the class path at run-time."
*/
public class ClassLoaderConfig {
private final MockClassLoader classLoader;
ClassLoaderConfig() {
this.classLoader = new MockClassLoader(new URL[0], this.getClass().getClassLoader());
}
public MockClassLoader getClassLoader() {
return this.classLoader;
}
}
Create Another class:
import java.net.URL;
import java.net.URLClassLoader;
public class MockClassLoader extends URLClassLoader {
public MockClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public void addURL(URL url) {
super.addURL(url);
}
}
Now set it in the current thread from your main class (Right at the beginning of your application)
Thread.currentThread().setContextClassLoader(new ClassLoaderConfig().getClassLoader());
Hope this solution works for your!!!
Shadov pointed to a thread at the oracle community. There is the correct answer:
Class.forName("nameofclass", true, new URLClassLoader(urlarrayofextrajarsordirs));
The caveats mentioned there are also important:
Caveats:
java.util.ServiceLoader uses the thread's ClassLoader context Thread.currentThread().setContextClassLoader(specialloader);
java.sql.DriverManager does honors the calling class' ClassLoader, -not- the Thread's ClassLoader. Create Driver directly using Class.forName("drivername", true, new URLClassLoader(urlarrayofextrajarsordirs).newInstance();
javax.activation uses the thread's ClassLoader context (important for javax.mail).
Referring to Edi's Solution this worked for me:
public final class IndependentClassLoader extends URLClassLoader {
private static final ClassLoader INSTANCE = new IndependentClassLoader();
/**
* #return instance
*/
public static ClassLoader getInstance() {
return INSTANCE;
}
private IndependentClassLoader() {
super(getAppClassLoaderUrls(), null);
}
private static URL[] getAppClassLoaderUrls() {
return getURLs(IndependentClassLoader.class.getClassLoader());
}
private static URL[] getURLs(ClassLoader classLoader) {
Class<?> clazz = classLoader.getClass();
try {
Field field = null;
field = clazz.getDeclaredField("ucp");
field.setAccessible(true);
Object urlClassPath = field.get(classLoader);
Method method = urlClassPath.getClass().getDeclaredMethod("getURLs", new Class[] {});
method.setAccessible(true);
URL[] urls = (URL[]) method.invoke(urlClassPath, new Object[] {});
return urls;
} catch (Exception e) {
throw new NestableRuntimeException(e);
}
}
}
Running within Eclipse, you need to set VM Arguments to JUnit Launch/Debug Configuration.
Running with maven via command line you have two options:
Option 1
Add following lines to pom.xml :
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.16</version>
<configuration>
<argLine>--add-opens java.base/jdk.internal.loader=ALL-UNNAMED</argLine>
</configuration>
</plugin>
Option 2
run mvn test -DargLine="-Dsystem.test.property=--add-opens java.base/jdk.internal.loader=ALL-UNNAMED"
There's also this guys article that helped me.
I could not find the article but... here: https://github.com/CGJennings/jar-loader
Here's a part of guide inside there there's a jar at release you could read his guide & setup it up.
I just tried it myself download the jar file which include the class file
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarFile;
public final class classname{
public static void premain(String agentArgs, Instrumentation instrumentation) {
loadedViaPreMain = true;
agentmain(agentArgs,instrumentation);
}
public final static void addToClassPath(File jarfile)throws IOException{inst.appendToSystemClassLoaderSearch(new JarFile(jarfile));}
public final static void agentmain(String agentArgs, Instrumentation instrumentation) {
if (instrumentation == null){throw new NullPointerException("instrumentation");}
if (inst == null) {inst = instrumentation;}
}
private static Instrumentation inst;
private static boolean loadedViaPreMain = false;
}
I just try it out myself package these code as a package then start the application class with -javaagent:plugin......jar option then call this function.It doesn't change my classpath.I am probably missing some details here.
Hope you can make it work though.
i found this, and worked for me.
String pathSeparator = Syste .getProperty("path.separator");
String[] classPathEntries = System.getProperty("java.class.path") .split(pathSeparator);
from the web site https://blog.codefx.org/java/java-11-migration-guide/#Casting-To-URL-Class-Loader

JBoss 7: how to dynamically load jars

I am using JBoss 7 (dependency loading was changed in this version).
My war-application uploads to server jars and need to use classes inside of them, but it gets ClassNotFoundException.
So I can't find a way to add jar-dependencies to modules dynamically - MANIFEST.MF, jboss-deployment-structure.xml are static way of doing this.
Just rephrasing the question to make sure I it correctly;
You want to be able to upload an arbitrary jar file to the server and then use the contained classes/resources in the JVM? Without restarting the JVM and/or editing your configuration ofcourse.
If that's the case, then you should load the jar into a classloader (chaining your current classloader if needed) and then load the class from there.
Assuming you store the jar-file physically on the server you could for example do something like:
public static Class<?> loadClass(String className, String jarFileLocation)
throws MalformedURLException, ClassNotFoundException {
URL jarUrl = new File(jarFileLocation).toURI().toURL();
ClassLoader classLoader = new URLClassLoader(new URL[] {jarUrl }, MyClass.class.getClassLoader());
return classLoader.loadClass(className);
}
public static Object executeMethodOndClass(String methodName, Class<?>[] parameterTypes,
Object[] parameters, String className, String jarFileLocation)
throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException,
NoSuchMethodException, InvocationTargetException {
Class<?> loadedClass = loadClass(className, jarFileLocation);
Method method = loadedClass.getMethod(methodName, parameterTypes);
Object instance = loadedClass.newInstance();
return method.invoke(instance, parameters);
}
Ps. this is crude code, I didn't even compile or test it; it should work, but nothing more then that and there is the chance I overlooked something or made a typo ;-)
Pps. allowing custom jar files to be uploaded and classes from it to be executed does bring a number of (security) risks with it.
#Rage: This question on stackoverflow asked earlier might give you some inputs how to organize jars: be it your own or third-party jars.
Try this (I've grabbed it somewhere on the Internet):
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class ClassPathHacker {
private static final Class<?>[] PARAMS = new Class<?>[] { URL.class };
private static final Logger LOG_CPH = LoggerFactory.getLogger(ClassPathHacker.class);
private ClassPathHacker() {}
public static void addFile(final String pFileName) throws IOException {
final File myFile = new File(pFileName);
ClassPathHacker.addFile(myFile);
}
public static void addFile(final File pFile) throws IOException {
ClassPathHacker.addURL(pFile.toURI().toURL());
}
public static void addURL(final URL pFileUrl) throws IOException {
/* variables definition */
final URLClassLoader sysLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
final Class<?> sysClass = URLClassLoader.class;
try {
final Method myMethod = sysClass.getDeclaredMethod("addURL", PARAMS);
myMethod.setAccessible(true);
myMethod.invoke(sysLoader, new Object[] { pFileUrl });
} catch (final Exception exc) {
ClassPathHacker.LOG_CPH.error(exc.getLocalizedMessage(), exc);
throw new IOException(exc.getLocalizedMessage());
}
}
}
Together with this method:
private static void hackClassPath(final File myData) {
if (myData.isDirectory()) {
/* block variables */
final File[] myList = myData.listFiles();
/* hacking classpath... */
for (final File tmp : myList) {
try {
ClassPathHacker.addFile(tmp.getAbsolutePath());
MainApplication.MAIN_LOG.trace("{} added to classpath",
tmp.getAbsolutePath());
} catch (final IOException iOE) {
MainApplication.MAIN_LOG.error(iOE.getLocalizedMessage(),
iOE);
}
}
}
}
And with this sample call:
MainApplication.hackClassPath(new File("test/data"));
MainApplication.hackClassPath(new File("data"));
A bit hacky, but maybe it works... it runtime adds all JAr files available in the data or test/data directory to the running classpath.

Can a directory be added to the class path at runtime?

In order to better understand how things works in Java, I'd like to know if I can dynamically add, at runtime, a directory to the class path.
For example, if I launch a .jar using "java -jar mycp.jar" and output the java.class.path property, I may get:
java.class.path: '.:/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java'
Now can I modify this class path at runtime to add another directory? (for example before making the first call to a class using a .jar located in that directory I want to add).
You can use the following method:
URLClassLoader.addURL(URL url)
But you'll need to do this with reflection since the method is protected:
public static void addPath(String s) throws Exception {
File f = new File(s);
URL u = f.toURL();
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class});
method.setAccessible(true);
method.invoke(urlClassLoader, new Object[]{u});
}
See the Java Trail on Reflection. Especially the section Drawbacks of Reflection
Update 2014: this is the code from the accepted answer, by Jonathan Spooner from 2011, slightly rewritten to have Eclipse's validators no longer create warnings (deprecation, rawtypes)
//need to do add path to Classpath with reflection since the URLClassLoader.addURL(URL url) method is protected:
public static 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()});
}
Yes, you can use URLClassLoader.. see example here. Doesn't use reflection.
-- edit --
Copying example from the link as suggested.
import javax.naming.*;
import java.util.Hashtable;
import java.net.URLClassLoader;
import java.net.URL;
import java.net.MalformedURLException;
public class ChangeLoader {
public static void main(String[] args) throws MalformedURLException {
if (args.length != 1) {
System.err.println("usage: java ChangeLoader codebase_url");
System.exit(-1);
}
String url = args[0];
ClassLoader prevCl = Thread.currentThread().getContextClassLoader();
// Create class loader using given codebase
// Use prevCl as parent to maintain current visibility
ClassLoader urlCl = URLClassLoader.newInstance(new URL[]{new URL(url)}, prevCl);
try {
// Save class loader so that we can restore later
Thread.currentThread().setContextClassLoader(urlCl);
// Expect that environment properties are in
// application resource file found at "url"
Context ctx = new InitialContext();
System.out.println(ctx.lookup("tutorial/report.txt"));
// Close context when no longer needed
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
} finally {
// Restore
Thread.currentThread().setContextClassLoader(prevCl);
}
}
}

Custom URLClassLoader, NoClassDefFoundError when run

I've created my own URLClassLoader, and set it as the system classloader via java.system.class.loader. It's initialized and everything, but the classes I'm trying to load aren't found. Here's the URLClassLoader:
public class LibraryLoader extends URLClassLoader
{
public LibraryLoader(ClassLoader classLoader)
{
super(new URL[0], classLoader);
}
synchronized public void addJarToClasspath(String jarName) throws MalformedURLException, ClassNotFoundException
{
File filePath = new File(jarName);
URI uriPath = filePath.toURI();
URL urlPath = uriPath.toURL();
System.out.println(filePath.exists());
System.out.println(urlPath.getFile());
addURL(urlPath);
}
}
I've confirmed that the jar exists, and that the path is correct. This is how I call it in my program:
LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();
loader.addJarToClasspath("swt.jar");
This is the exception that I get (line 166 refers to the line at which I try to create a new Point:
Exception in thread "main" java.lang.NoClassDefFoundError: org/eclipse/swt/graphics/Point
at mp.MyProgram.loadArchitectureLibraries(MyProgram.java:116)
at mp.MyProgram.main(MyProgram.java:90)
Caused by: java.lang.ClassNotFoundException: org.eclipse.swt.graphics.Point
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 2 more
I even tried explicitly loading the class like so:
Class.forName("org.eclipse.swt.graphics.Point", false, loader);
What might be causing this? Shouldn't it "just work"?
Update: Here's the important code from MyProgram
public class MyProgram
{
// ...
public static void main(String[] args)
{
loadArchitectureLibraries();
// ...
}
public static void loadArchitectureLibraries()
{
LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();
String architecture = System.getProperty("os.arch");
try {
if (architecture.contains("64")) {
loader.addJarToClasspath("swt-3.6.1-win32-win32-x86_64.jar");
} else {
loader.addJarToClasspath("swt-3.6.1-win32-win32-x86.jar");
}
Class.forName("org.eclipse.swt.graphics.Point", false, loader);
org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);
} catch (Exception exception) {
exception.printStackTrace();
System.out.println("Could not load SWT library");
System.exit(1);
}
}
}
Update 2: Here's an SSCCE: http://nucleussystems.com/files/myprogram.zip . Call java -Djava.system.class.loader=mp.LibraryLoader -jar myprogram.jar.
I would have to agree with the comments on this question. Based on the code you have provided, it would appear that you are getting the error due to the JAR files not being where you expect them to be. As mentioned by #Andrew, you are not checking the existence of the file in your addJarToClasspath method. As a result, if the file does not exist, you will receive a ClassNotFound exception as you are seeing. I verified this problem by taking your ClassLoader logic and passing it a valid and an invalid JAR. When a valid JAR/path was provided, the ClassLoader loaded the class as expected. When an invalid JAR/path was specified, I received the error you mentioned. The URLClassLoader does not throw an exception if an URL is specified that does not point to a valid file.
To validate the scenario, print out the path of the full path of your File and see if it is correct for the execution environment.
Edit
It appears that even if you override the system ClassLoader, the VM will still use the default sun.misc.Launcher$AppClassLoader to load some classes. In my testing this includes the classes that are referenced from the main application. I'm sure there is a reason for this process, however, I am unable to ascertain it at this time. I have come up with a few solutions for you:
Use a script to detect the environment and set the classpath accordingly. This is perhaps the simplest solution, but one you may or may not want to take based on your particular requirements.
Similar to what was mentioned in other answers, specifically load and execute your application using your custom ClassLoader. This does not mean creating a single class that will be loaded and then invoke your application. It means that any class that needs to interact with the dynamically loaded swt libraries and any classes that need to reference your application classes should be loaded from your custom ClassLoader. Any application dependencies, such as log4j, etc, can be referenced by the default application ClassLoader. Here is an example of how this would work:
JAR 1 (launcher.jar):
public class AppLauncher {
public static void main(String… args) throws Exception {
ClassLoader loader = initClassLoader();
Class<?> mpClass = loader.loadClass("mp.MyProgram");
// using Runnable as an example of how it can be done
Runnable mpClass = (Runnable) mpClass.newInstance();
}
public static ClassLoader initClassLoader() {
// assuming still configured as system classloader, could also be initialized directly
LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();
// add the main application jar to the classpath.
// You may want to dynamically determine this value (lib folder) or pass it in as a parameter
loader.addJarToClasspath("myapp.jar");
String architecture = System.getProperty("os.arch");
try {
if (architecture.contains("64")) {
loader.addJarToClasspath("swt-3.6.1-win32-win32-x86_64.jar");
} else {
loader.addJarToClasspath("swt-3.6.1-win32-win32-x86.jar");
}
Class.forName("org.eclipse.swt.graphics.Point", false, loader);
org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);
} catch (Exception exception) {
exception.printStackTrace();
System.out.println("Could not load SWT library");
System.exit(1);
}
return loader;
}
JAR 2 (myapp.jar): Includes all class which depend on swt
public class MyProgram implements Runnable {
//…
public void run() {
// perform application execution
// this logic should now work
org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0,0);
}
}
The AppLauncher class would be executed by the VM without the rest of your application being included in the execution Jar.
java -Djava.system.class.loader=test.LibraryLoader -cp <dependency jars>:launcher.jar mp.AppLauncher
I see that there have been updates to the other answers. Since I already had typed up the above comments, I figured that I should still post it for your perusal.
It's visible from a (few) mile(s) away you are not using the custom classloader beside Class.forName
The ClassNoDefFoundError occurs since the classloader that has loaded current class MyProgram attempts to load org.eclipse.swt.graphics.Point.
You need to load another class (call it launcher) via Class.forName and then start from there - implement some interface (even runnable will do) and call it.
edit
How to do it, a simplistic scenario.
1. Create another class called mp.loader.Launcher that implements Runnable like that.
public class Launcher implements Runnable{
public void run(){
org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);
//whatever, start from here.
}
}
2. Place it in another jar called swt-loader.jar.
in MyProgram class use:
loader.addJarToClasspath("swt-loader.jar");
Runnable r = (Runnable) Class.forName("mp.loader.Launcher", true, loader);
r.run();//there you have
Since the offending line is not the Class.forName but the actual initialization of an instance of Point, we'll have to make sure that the class, that tries to load the Point class, was created by the Library class loader. Therefore, I made some minor changes in the LibraryLoader accordingt to this blog entry
public class LibraryLoader extends URLClassLoader {
public LibraryLoader(ClassLoader classLoader) {
super(new URL[0], classLoader);
}
synchronized public void addJarToClasspath(String jarName)
throws MalformedURLException, ClassNotFoundException {
File filePath = new File(jarName);
URI uriPath = filePath.toURI();
URL urlPath = uriPath.toURL();
System.out.println(filePath.exists());
System.out.println(urlPath.getFile());
addURL(urlPath);
}
#Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if ("mp.MyProgram".equals(name)) {
return getClass(name);
}
return super.loadClass(name, resolve);
}
private Class<?> getClass(String name) throws ClassNotFoundException {
String file = name.replace('.', File.separatorChar) + ".class";
byte[] b = null;
try {
b = loadClassData(file);
Class<?> c = defineClass(name, b, 0, b.length);
resolveClass(c);
return c;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
private byte[] loadClassData(String name) throws IOException {
InputStream stream = getClass().getClassLoader().getResourceAsStream(
name);
int size = stream.available();
byte buff[] = new byte[size];
DataInputStream in = new DataInputStream(stream);
in.readFully(buff);
in.close();
return buff;
}
}
In the program itself, we have to extract a new method since all the classes, that are used from within a method, seem to be loaded up-front:
public class MyProgram {
public static void main(String[] args) {
LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();
String architecture = System.getProperty("os.arch");
try {
loader.addJarToClasspath("swt.jar");
otherMethod();
} catch (Throwable exception) {
// println instead of logger because logging is useless at this level
exception.printStackTrace();
System.out.println("Could not load SWT library");
System.exit(1);
}
}
protected static void otherMethod() {
org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);
System.out.println("Works!");
}
}
That should work for you.

Categories