Load jar dynamically at runtime? - java

My current java project is using methods and variables from another project (same package). Right now the other project's jar has to be in the classpath to work correctly. My problem here is that the name of the jar can and will change because of increasing versions, and because you cannot use wildcards in the manifest classpath, it's impossible to add it to the classpath. So currently the only option of starting my application is using the -cp argument from the command line, manually adding the other jar my project depends on.
To improve this, I wanted to load the jar dynamically and read about using the ClassLoader. I read a lot of examples for it, however I still don't understand how to use it in my case.
What I want is it to load a jar file, lets say, myDependency-2.4.1-SNAPSHOT.jar, but it should be able to just search for a jar file starting with myDependency- because as I already said the version number can change at anytime. Then I should just be able to use it's methods and variables in my Code just like I do now (like ClassInMyDependency.exampleMethod()).
Can anyone help me with this, as I've been searching the web for a few hours now and still don't get how to use the ClassLoader to do what I just explained.
Many thanks in advance

(Applies to Java version 8 and earlier).
Indeed this is occasionally necessary. This is how I do this in production. It uses reflection to circumvent the encapsulation of addURL in the system class loader.
/*
* Adds the supplied Java Archive library to java.class.path. This is benign
* if the library is already loaded.
*/
public static synchronized void loadLibrary(java.io.File jar) throws MyException
{
try {
/*We are using reflection here to circumvent encapsulation; addURL is not public*/
java.net.URLClassLoader loader = (java.net.URLClassLoader)ClassLoader.getSystemClassLoader();
java.net.URL url = jar.toURI().toURL();
/*Disallow if already loaded*/
for (java.net.URL it : java.util.Arrays.asList(loader.getURLs())){
if (it.equals(url)){
return;
}
}
java.lang.reflect.Method method = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{java.net.URL.class});
method.setAccessible(true); /*promote the method to public access*/
method.invoke(loader, new Object[]{url});
} catch (final java.lang.NoSuchMethodException |
java.lang.IllegalAccessException |
java.net.MalformedURLException |
java.lang.reflect.InvocationTargetException e){
throw new MyException(e);
}
}

I needed to load a jar file at runtime for both java 8 and java 9+. Here is the method to do it (using Spring Boot 1.5.2 if it may relate).
public static synchronized void loadLibrary(java.io.File jar) {
try {
java.net.URL url = jar.toURI().toURL();
java.lang.reflect.Method method = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{java.net.URL.class});
method.setAccessible(true); /*promote the method to public access*/
method.invoke(Thread.currentThread().getContextClassLoader(), new Object[]{url});
} catch (Exception ex) {
throw new RuntimeException("Cannot load library from jar file '" + jar.getAbsolutePath() + "'. Reason: " + ex.getMessage());
}
}

Related

Getting an UnsatisfiedLinkError (undefined symbol) in Java while loading JNI dependencies even after successfully loading the required object file

I'm using Google OR-tools library (v6.4) for a project (though my question is not specific to this library). This consists of one jar, which has a few native dependencies (a bunch of ".so"/".dylib" object files, depending on the OS). This build for my project is being made on Ubuntu 14.04
The problem I'm facing: On trying to load a specific object file at runtime (using System.load()), I'm getting an UnsatisfiedLinkError with the message as "undefined symbol" (I've added the stacktrace below). However, I am loading the object file defining this symbol just before this, so I'm not sure why this error is being thrown.
I'm loading the dependencies in the following way: The object files are being packed into the jar created by Maven during build, and are being extracted and loaded (using System.load()) at runtime. The method for that is as follows:
public class EnvironmentUtils {
public static void loadResourceFromJar(String prefix, String suffix) {
String tempFilesDirectory = System.getProperty("java.io.tmpdir");
File tempFile = null;
try {
tempFile = new File(tempFilesDirectory + "/" + prefix + suffix);
tempFile.deleteOnExit();
try (final InputStream inputStream = EnvironmentUtils.class.getClassLoader().
getResourceAsStream(prefix+suffix)) {
if (inputStream == null) {
throw new RuntimeException(prefix + suffix + " was not found inside JAR.");
} else {
Files.copy(inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
}
System.load(tempFile.getAbsolutePath());
} catch (Exception e) {
//Log top 10 lines of stack trace
}
}
}
This method is being called inside a static block for all dependencies:
public class DummyClass {
static {
String sharedLibraryExtension = EnvironmentUtils.getSharedLibraryExtension(); //.so for linux, .dylib for Mac
String jniLibraryExtension = EnvironmentUtils.getJniLibraryExtension(); //.so for linux, .jnilib for Mac
EnvironmentUtils.loadResourceFromJar("libfap", sharedLibraryExtension);
EnvironmentUtils.loadResourceFromJar("libcvrptw_lib", sharedLibraryExtension);
EnvironmentUtils.loadResourceFromJar("libortools", sharedLibraryExtension);
EnvironmentUtils.loadResourceFromJar("libdimacs", sharedLibraryExtension);
EnvironmentUtils.loadResourceFromJar("libjniortools", jniLibraryExtension);
}
}
On running System.load() for libdimacs.so, an UnsatisfiedLinkError is thrown. Stacktrace:
java.lang.UnsatisfiedLinkError: /tmp/libdimacs.so: /tmp/libdimacs.so: undefined symbol: _ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_
at java.lang.ClassLoader$NativeLibrary.load(Native Method)
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1941)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1824)
at java.lang.Runtime.load0(Runtime.java:809)
at java.lang.System.load(System.java:1086)
at com.(PROJECT_NAME).utils.EnvironmentUtils.loadResourceFromJar(EnvironmentUtils.java:78)
at com.(PROJECT_NAME).DummyClass.<clinit>(DummyClass.java:28)
However, this symbol "_ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_" is present in libortools.so, which is being loaded before libdimacs. I verified this by running the following command:
objdump -t (LIBRARY_PATH)/libortools.so | grep _ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_
This gave me the following output:
0000000000ce12cc gw F .text 00000091 _ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_
So it would seem that the symbol should have been defined at the time of the System.load() call, unless there was some issue in loading the containing object file. To check if the object file had been loaded correctly, I used the approach detailed in this solution. Apart from the class detailed in that answer, I added the following lines after System.load() call in EnvironmentUtils.loadResourceFromJar() to print the most recently loaded library name:
public class EnvironmentUtils {
public static void loadResourceFromJar(String prefix, String suffix) {
...
System.load(tempFile.getAbsolutePath());
final String[] libraries = ClassScope.getLoadedLibraries(ClassLoader.getSystemClassLoader());
System.out.println(libraries[libraries.length - 1]);
}
}
The output (till just before the UnsatisfiedLinkError) is as follows:
/tmp/libfap.so
/tmp/libcvrptw_lib.so
/tmp/libortools.so
So libortools.so seems to be loading correctly, which means the symbol should be loaded in memory. The exact same code is working perfectly with the corresponding Mac (".dylib") dependencies (Built on MacOS Sierra 10.12.5). Would appreciate any advice on resolving this. Thank you.
I'm apologize that the java artifact may be broken currently...
you can use c++filt to demangle the symbol ;)
c++filt _ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_
google::FlagRegisterer::FlagRegisterer<bool>(char const*, char const*, char const*, bool*, bool*)
In fact gflag has recently change its namespace from google:: to gflags:: and glog or protobobuf? try to find the correct one and I guess it failed...
note: Still not completely sure whose is the bad guy who use the google:: namespace since libortools merge all its static dependencies but I guess now you understand the bug...
note2: I have a patch in mizux/shared branch https://github.com/google/or-tools/commit/805bc0600f4b5645114da704a0eb04a0b1058e28#diff-e8590fe6fb5044985c8bf8c9e73c0d88R114
warning: this branch is currently broken and not ready yet. I'm trying ,for unix, to move from static to dynamic dependencies, so I need to fix all rpath, transitives deps etc... and in the process I also had to fix this issue (that I didn't reproduced while using static dependencies)
If too long to finish (we should create a release 6.7.2 or 6.8 (i.e. new artifact) by the end of May 2018) which maybe only contains this fix and not my branch...

How to access java file which resides inside SDCard in Android

I am trying to load a Java file to my android project using URLClassLoader class.
But I got a ClassNotFoundError when I run the below code:
package com.xyz.abc;
public class ConstantClassReader
{
public void readFile() throws MalformedURLException
{
String url = "file://"+Environment.getExternalStorageDirectory().getPath()+"/StringConstants.class";
URLClassLoader urlClassLoader = URLClassLoader.newInstance(new URL[]{new URL(url)});
Class simpleClass = null;
try {
simpleClass = urlClassLoader.loadClass("com.xyz.abc.StringConstants");
Constructor simpleConstructor = simpleClass.getConstructor();
Object simpleClassObj = simpleConstructor.newInstance();
Method method = simpleClass.getMethod("myMethod");
method.invoke(simpleClassObj);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
The below Java file is resides inside my SDCard:
package com.xyz.abc;
public class StringConstants{
public void myMethod(){
System.out.println("myMethod Loaded");
}
}
A class loader loads classes.
Putting a Java source file somewhere will not do.
You have to compile that file; and maybe you are lucky then. But I would be rather surprised if the Android JVM allows you to load classes from arbitrary places. This screams: "security problem" all over the place.
Given your requirement, I would suggest a different solution, something on top of ordinary Java properties. Meaning: it seems that you simply want to provide some "configuration" information dynamically to your Java app. Then have that app read a properties file that contains key/value pairs. That is (almost) business as usual; and not leading to a need to load class files in arbitrary places.
http://www.beanshell.org/
you can embed beanshell, load the java file, instance and execute whatever.
access the java file as any other plain file from the sd
How can I read a text file from the SD card in Android?

Building a ServiceLoader file with gradle: howto?

I am starting to switch from a well-known Java build system to Gradle to build all my projects, and after barely two hours into it I have already been able to publish a new version of one of my projects without a problem -- a breeze.
But now I encounter a difficulty. In short, I need to replicate the functionality of this Maven plugin which generates the necessary files for a ServiceLoader-enabled service.
In short: given a base class foo.bar.MyClass, it generates a file named META-INF/services/foo.bar.MyClass whose content is a set of classes in the current project which implement that interface/extend that base class. Such a file would look like:
com.mycompany.MyClassImpl
org.othercompany.MyClassImpl
In order to do this, it uses I don't know what as a classloader, loads the Class objects for com.myCompany.MyClassImpl or whatever and checks whether this class implements the wanted interface.
I am trying to do the same in Gradle. Hours of googling led me to this plugin, but after discussing with its author a little, it appears this plugin is able to merge such files, not create them. So, I have to do that myself...
And I am a real beginner both with Gradle and Groovy, which does not help! Here is my current code, link to the full build.gradle here; output (which I managed to get somehow; doesn't work from a clean dir) shown below (and please bear with me... I do Java, and I am final happy; Groovy is totally new to me):
/*
* TEST CODE
*/
final int CLASS_SUFFIX = ".class".length();
final URLClassLoader classLoader = this.class.classLoader;
// Where the classes are: OK
final File classesDir = sourceSets.main.output.classesDir;
final String basePath = classesDir.getCanonicalPath();
// Add them to the classloader: OK
classLoader.addURL(classesDir.toURI().toURL())
// Recurse over each file
classesDir.eachFileRecurse {
// You "return" from a closure, you do not "continue"...
if (!isPotentialClass(it))
return;
// Transform into a class name
final String path = it.getAbsolutePath();
final String name = path.substring(basePath.length() + 1);
final String className = name.substring(0, name.length() - CLASS_SUFFIX)
.replace('/', '.');
// Try and load it
try {
classLoader.loadClass(className);
println(className);
} catch (NoClassDefFoundError ignored) {
println("failed to load " + className + ": " + ignored);
}
}
boolean isPotentialClass(final File file)
{
return file.isFile() && file.name.endsWith(".class")
}
The output:
com.github.fge.msgsimple.InternalBundle
failed to load com.github.fge.msgsimple.bundle.MessageBundle: java.lang.NoClassDefFoundError: com/github/fge/Frozen
failed to load com.github.fge.msgsimple.bundle.MessageBundleBuilder: java.lang.NoClassDefFoundError: com/github/fge/Thawed
com.github.fge.msgsimple.bundle.PropertiesBundle$1
com.github.fge.msgsimple.bundle.PropertiesBundle
com.github.fge.msgsimple.provider.MessageSourceProvider
com.github.fge.msgsimple.provider.LoadingMessageSourceProvider$1
com.github.fge.msgsimple.provider.LoadingMessageSourceProvider$2
com.github.fge.msgsimple.provider.LoadingMessageSourceProvider$3
com.github.fge.msgsimple.provider.LoadingMessageSourceProvider$Builder
com.github.fge.msgsimple.provider.LoadingMessageSourceProvider
com.github.fge.msgsimple.provider.MessageSourceLoader
com.github.fge.msgsimple.provider.StaticMessageSourceProvider$Builder
com.github.fge.msgsimple.provider.StaticMessageSourceProvider$1
com.github.fge.msgsimple.provider.StaticMessageSourceProvider
com.github.fge.msgsimple.source.MessageSource
com.github.fge.msgsimple.source.MapMessageSource$Builder
com.github.fge.msgsimple.source.MapMessageSource$1
com.github.fge.msgsimple.source.MapMessageSource
com.github.fge.msgsimple.source.PropertiesMessageSource
com.github.fge.msgsimple.locale.LocaleUtils
com.github.fge.msgsimple.serviceloader.MessageBundleFactory
com.github.fge.msgsimple.serviceloader.MessageBundleProvider
:compileJava UP-TO-DATE
The problem is in the two first lines: Frozen and Thawed are in a different project, which is in the compile classpath but not in the classpath I managed to grab so far... As such, these classes cannot even load.
How do I modify that code so as to have the full compile classpath availabe? Is my first question. Second question: how do I plug that code, when it works, into the build process?
Here are some hints:
Create a new URLClassLoader, rather than reusing an existing one.
Initialize the class loader with sourceSets.main.compileClasspath (which is an Iterable<File>) rather than classesDir.
Turn the code into a Gradle task class. For more information, see "Writing a simple task class" in the Gradle User Guide.
Ideally, you'd use a library like ASM to analyze the code, rather than using a class loader. To avoid the case where you cannot load a class because it internally references a class that's not on the compile class path, you may want to initialize the class loader with sourceSets.main.runtimeClasspath instead.

Class not found exception when trying to create web service client

I am trying to consume a web service from a Java client.
I generated the classes using wsimport:
wsimport -keep -verbose http://localhost:5382/Service1.svc?wsdl
Code looks something like:
private String CreateSalesforceIssue() {
IssueService service = new IssueService();
IIssueService binding = service.getBasicHttpBindingIIssueService();
String issueID = binding.createIssue(type, description, steps,
expected, workaround, storage,
docType, actions, tools, external,
repeatability, workaroundType, severity,
pmSeverity, products, extensions, versions,
os, status, project, resolution, fixversions);
return issueID;
}
When it hits this line:
IssueService service = new IssueService();
Stepping into the code far enough and it gets to javax.xml.ws.spi.Provider and fails there.
On
public static Provider provider() {
try {
Object provider =
FactoryFinder.find(JAXWSPROVIDER_PROPERTY,
DEFAULT_JAXWSPROVIDER);
if (!(provider instanceof Provider)) {
Class pClass = Provider.class;
String classnameAsResource = pClass.getName().replace('.', '/') + ".class";
ClassLoader loader = pClass.getClassLoader();
if(loader == null) {
loader = ClassLoader.getSystemClassLoader();
}
URL targetTypeURL = loader.getResource(classnameAsResource);
throw new LinkageError("ClassCastException: attempting to cast" +
provider.getClass().getClassLoader().getResource(classnameAsResource) +
"to" + targetTypeURL.toString() );
}
return (Provider) provider;
} catch (WebServiceException ex) {
throw ex;
} catch (Exception ex) {
throw new WebServiceException("Unable to createEndpointReference Provider", ex);
}
}
on this line:
if (!(provider instanceof Provider)) {
with a ClassNotFoundException: Provider com.sun.xml.ws.spi.ProviderImpl
I feel like I am missing something, unfortunately I am not sure what... Do I need to initialize the provider anywhere?
You should add Provider class into your classpath.
If you use IDE you can add the library easily by right click on library and choose "add Jar file" (or something like that!). But if you try to compile and run your application via terminal use the following commands:
javac -d [bin folder] -cp [jar files] [java source files]
java -classpath [jar files] [Main class]
you should split the jar files using : in Linux and MAC OS and ; in MS Windows. As you said this is a web service, I think you are using IDE and first solution may help you.
P.S. If you also added this jar file into class path and this exception occurs again, please add this jar file into your web server/container library directory. (for example lib folder in tomcat.

How to launch a resource jar from inside another java app - with Solution

EDITED with solution (below...)
I have a Splash screen that is packaged into it's own jar. It works.
I can call the Splash.jar from inside another java app by:
Desktop.getDesktop().open(new File("/Applications/Eclipse/Splash.jar"));
and it works just fine. But, that's pretty limited. So, I created a res folder in the new app and dragged the Splash.jar into it.
Now, how do I call it/run it from the main class of the new app??
I looked at all the related posts and see no clear approach...
Thanks
SOLUTION:
I found the solution - so simple. First, the credit to avjava.com for their clear and excellent tutorial on doing this ( Run Another Jar from a Jar ). So, now I can run the Splash (or other .jar) just as hoped for.
The answer is here - posted in the form of complete code:
package test;
import java.io.IOException;
public class RuntimeExecTest1 {
public static void main(String[] args) {
try {
System.out.println("TextEdit I hope");
Runtime runTime = Runtime.getRuntime();
Process process = runTime.exec(
"java -jar /your directory/your app.jar");
try {
Thread.sleep(5000); // keep in open 5000ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Closing TextEdit, I hope");
process.destroy(); // kill the process of running the .jar
} catch (IOException e) {
e.printStackTrace();
}
}
}
We don't know how your existing Splash Screen works...
Java AWT support for Splash Screen:
If you are using the Java built-in support for splash screens (as specified in SplashScreen Javadoc) then you need to use a command line switch, or better yet, modify your MANIFEST file in order to reference your Splash Screen:
Manifest-Version: 1.0
Main-Class: Test
SplashScreen-Image: filename.gif
I don't know if, for this particular case, you can reference files in a different JAR. In the worst case, you can unpack the existing JAR (they are just ZIP files) and get the image file in order to include it in your own main jar.
Possibly custom Splash:
If your Splash is created using custom code, then you need the documentation about how to load it. At least, you'd need to add Splash.jar to the classpath of your application and, from your app, call the necessary method or load the appropriate resource.
All the resources and classes contained in .jar files that are added to the classpath are available from your app.
You could create a new URLClassLoader with the Splash.jar and then use reflections to execute the main method.
URL[] urls = new URL[] { new URL("res/Splash.jar") };
ClassLoader cl = new URLClassLoader(urls);
Class<?> clazz = cl.loadClass("splash.Main");
Method method = clazz.getMethod("main", String[].class);
method.invoke(null, new String[0]);
add the resource path to your CLASSPATH envoirment variable and you can use it without modifying your existing code
if your running linux
export CLASSPATH=yourpath;
and if your running windows:
from My Computer right click > properties
OR
if you dont want to add it to CLASSPATH ENV variable,
then
java -classpath="your path to jar" yourclass
Why not define Splash.jar as an external jar and go about using all its routines. Much like JDBC.

Categories