Java make class available from JAR - java

I am making a program that operates as multiple JAR file dependencies. Basically, the thing loops through the .class files in a JAR file and gets a Class object for each of them. Each JAR has a Plugin.class file that I don't want to be available, but I want all the Classes to be accessible by other JAR dependencies as well as the main program. For example, in one JAR I have the class something.somethingelse.SomeClass, and from a second one (I made sure it is loaded second) I want to be able to import (at execution because it's in a separate JARfile) something.somethingelse.SomeClass and use it. I Have tried this after loading it into a Class object but it gives me ClassNotFound errors. I am using the newest java update and the newest version of eclipse IDE. I have three projects, "main", "aaa", and "aab". I have aaa and aab exported to JARs of which the contents are loaded into Class objects by main. aaa is loaded before aab, and I want aab to be able to access the classes from aaa through import aaa.Class. How can I (from main) make the classes of both jarfiles available to each other?
Here is my load plugin function:
public static void load(File file) throws Exception
{
JarFile jarFile = new JarFile(file);
Enumeration e = jarFile.entries();
URL[] urls = new URL[] { file.toURI().toURL() };
ClassLoader cl = new URLClassLoader(urls);
while (e.hasMoreElements()) {
JarEntry je = (JarEntry) e.nextElement();
if(je.isDirectory() || !je.getName().endsWith(".class") || je.getName() == "Plugin.class"){
continue;
}
// -6 because of .class
String className = je.getName().substring(0,je.getName().length()-6);
className = className.replace('/', '.');
Class c = cl.loadClass(className);
}
ClassLoader loader = new URLClassLoader(urls);
Class c = loader.loadClass("Plugin");
Object cobj = c.newInstance();
Method[] allMethods = c.getDeclaredMethods();
Method method = null;
boolean found = false;
for (Method m : allMethods) {
String mname = m.getName();
if (mname == "startPlugin"){
method = m;
found = true;
}
}
if(found)
{
method.invoke(cobj);
}
else
{
//skip class
}
}
And then my first JAR (aaa.jar) declares a class called hlfl.ui.UserInterface.
My second JAR's Plugin class is as follows:
import hlfl.ui.*;
public class Plugin {
//THIS DEPENDENCY EXPORTS TO: aab.jar
public void startPlugin()
{
System.out.println("Plugin Loading Interface Loaded [AAB]");
UserInterface c = new UserInterface();
}
}
But when I run it it gives me the following:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun. reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at sf.htmlguy.hlaunch.PluginLoader.load(PluginLoader.java:58)
at sf.htmlguy.hlaunch.PluginLoader.loadAll(PluginLoader.java:22)
at sf.htmlguy.hlaunch.HLaunch.main(HLaunch.java:14)
Caused by: java.lang.NoClassDefFoundError: hlfl/ui/UserInterface
at Plugin.startPlugin(Plugin.java:7)
... 7 more
Caused by: java.lang.ClassNotFoundException: hlfl.ui.UserInterface
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 8 more
Just in case, the code is on SourceForge (the three projects are in subdirectories, "hlaunch for linux" is the main one.):
https://sourceforge.net/p/hlaunch/code

As far as I can tell your load method is creating a URLClassLoader containing just one JAR file. So you're going to end up with a classloader structure like this
main
/ \
/ \
UCL with aaa.jar UCL with aab.jar
thus classes in aaa and in aab can both see classes in main, but aaa and aab cannot see each other. If you want each plugin to be able to see the classes of those plugins that were loaded before it, then you need to arrange things so that each plugin you load uses the classloader of the previous plugin as its parent
main
|
UCL with aaa.jar
|
UCL with aab.jar
To do this you'd have to cache the loader you create when you load one plugin, and then pass that as a parameter when you create the next plugin's classloader.
private static ClassLoader lastPluginClassLoader = null;
public static void load(File file) throws Exception {
//...
ClassLoader loader = null;
if(lastPluginClassLoader == null) {
loader = new URLClassLoader(urls);
} else {
loader = new URLClassLoader(urls, lastPluginClassLoader);
}
lastPluginClassLoader = loader;
// ...
}
But all this (a) is not thread safe unless synchronized and (b) makes the behaviour critically dependent on the order in which the plugins are loaded. To do things properly you'd need some way to declare which plugins depend on which other plugins, and set up the classloader tree appropriately, etc. etc.
... and if you go too far down that road you've just re-invented OSGi.

Related

Remove folder from Java classpath at runtime

Is there a way to remove a folder from the classpath similar to adding a folder at runtime (Can a directory be added to the class path at runtime?)
Please find below a snippet as technical example to demonstrate adding / removing a path.
create following source files in any directory
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Stack;
import sun.misc.URLClassPath;
public class EvilPathDemo {
public static void addPath(String path) throws Exception {
URL u = new File(path).toURI().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});
}
public static void removePath(String path) throws Exception {
URL url = new File(path).toURI().toURL();
URLClassLoader urlClassLoader = (URLClassLoader)
ClassLoader.getSystemClassLoader();
Class<?> urlClass = URLClassLoader.class;
Field ucpField = urlClass.getDeclaredField("ucp");
ucpField.setAccessible(true);
URLClassPath ucp = (URLClassPath) ucpField.get(urlClassLoader);
Class<?> ucpClass = URLClassPath.class;
Field urlsField = ucpClass.getDeclaredField("urls");
urlsField.setAccessible(true);
Stack urls = (Stack) urlsField.get(ucp);
urls.remove(url);
}
public static void main(String[] args) throws Exception {
String parm = args.length == 1 ? args[0] : "";
String evilPath = "/tmp";
String classpath = System.getProperty("java.class.path");
boolean isEvilPathSet = false;
for (String path : classpath.split(File.pathSeparator)) {
if (path.equalsIgnoreCase(evilPath)) {
System.out.printf("evil path '%s' in classpath%n", evilPath);
isEvilPathSet = true;
break;
}
}
if (isEvilPathSet && parm.equalsIgnoreCase("REMOVE")) {
System.out.printf("evil path '%s' will be removed%n", evilPath);
removePath(evilPath);
}
tryToLoad("Foo");
if (parm.equalsIgnoreCase("ADD")) {
System.out.printf("evil path '%s' will be added%n", evilPath);
addPath(evilPath);
}
tryToLoad("Bar");
}
private static void tryToLoad(String className) {
try {
Class<?> foo = Class.forName(className);
System.out.printf("class loaded: %s%n", foo.getName());
} catch (ClassNotFoundException ex) {
System.out.println(ex);
}
}
}
.
public class Foo {
static {
System.out.println("I'm foo...");
}
}
.
public class Bar {
static {
System.out.println("I'm bar...");
}
}
compile them as follow
javac EvilPathDemo.java
javac -d /tmp Foo.java Bar.java
During the test we will try to load the classes Foo and Bar.
without /tmp in the classpath
java -cp . EvilPathDemo
java.lang.ClassNotFoundException: Foo
java.lang.ClassNotFoundException: Bar
adding /tmp to the classpath
java -cp . EvilPathDemo add
java.lang.ClassNotFoundException: Foo
evil path '/tmp' will be added
I'm bar...
class loaded: Bar
with /tmp in the classpath
java -cp .:/tmp EvilPathDemo
evil path '/tmp' in the classpath
I'm foo...
class loaded: Foo
I'm bar...
class loaded: Bar
remove /tmp from the classpath
java -cp .:/tmp EvilPathDemo remove
evil path '/tmp' in the classpath
evil path '/tmp' will be removed
java.lang.ClassNotFoundException: Foo
java.lang.ClassNotFoundException: Bar
During the testing I found out that following cases are not working.
addPath(evilPath);
tryToLoad("Foo");
removePath(evilPath); // had not effect
tryToLoad("Bar");
removePath(evilPath);
tryToLoad("Foo");
addPath(evilPath); // had no effect
tryToLoad("Bar");
tryToLoad("Foo");
removePath(evilPath); // had no effect
tryToLoad("Bar");
I did not spent time to find out why. Because I don't see any practical use in it. If you really need/wish to play with the classpaths have a look how classloaders are working.
The removePath method from above did not work for me and my Weld Container, the url stack was always emtpy.
The following ugly smugly method worked:
public static void removeLastClasspathEntry() throws Exception {
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<?> urlClass = URLClassLoader.class;
Field ucpField = urlClass.getDeclaredField("ucp");
ucpField.setAccessible(true);
URLClassPath ucp = (URLClassPath) ucpField.get(urlClassLoader);
Field loadersField = URLClassPath.class.getDeclaredField("loaders");
loadersField.setAccessible(true);
List jarEntries = (List) loadersField.get(ucp);
jarEntries.remove(jarEntries.size() - 1);
Field pathField = URLClassPath.class.getDeclaredField("path");
pathField.setAccessible(true);
List pathList = (List) pathField.get(ucp);
URL jarUrl = (URL) pathList.get(pathList.size() - 1);
String jarName = jarUrl.toString();
pathList.remove(pathList.size() - 1);
Field lmapField = URLClassPath.class.getDeclaredField("lmap");
lmapField.setAccessible(true);
Map lmapMap = (Map) lmapField.get(ucp);
lmapMap.remove(jarName.replaceFirst("file:/", "file:///"));
}
Class loaders can be nested so instead of modifying the system class loader which is the root of the tree of class loaders, it is better to simply create a nested classloader and use that to load classes.
The system classloader itself is immutable (for good reasons) but you can do whatever you want in nested class loaders, including destroying them to unload classes and resources. This is commonly used in e.g. osgi and application servers to load/unload e.g. plugins, applications, etc.
For nested class loaders you can completely customize how to load classes. The URLClassloader is probably a good starting point for what you want.
I dont think there is a straight forward way to do it. You can follow :
Get class path variables using : System.getenv("CLASSPATH"). It will return semi colon separated values.
String classPath = System.getenv("CLASSPATH")
Take the folder path as input and replace it with "" like :
String remainigPath = classPath.replace(inputpath,"");
Put the remaining paths in an array using split method.
String[] paths = remainigPath .split(";");
For adding classPath, You already have the code.
I had the same issue, so I tackled it by creating a library that works on every ClassLoader that uses a URLClassPath (so, currently, URLClassLoader).
The library has methods for:
Adding new entries in front
Appending new entries
Remove existing entries
Please note that, since this library accesses internal and proprietary APIs, it is no guaranteed to work in future versions of the JDK. It currently does for Java 7 and Java 8 (Oracle and OpenJDK).
Here is the GitHub page (contribution is appreciated), and here is the Maven Central artifact

Get dependencies Using ASM5.0.2

I have a Java program based on ASM 5.0.2 to extract dependency between classes. The program works fine with an ordinary Java application. However, when I run the program as a plugin then it crashes with the bug: java.lang.ClassNotFoundException.
As an example if the example class uses junit.Assert, then when I run the project as an ordinary java application, it find this dependency, but when as plugin the below error:
java.lang.ClassNotFoundException: org.junit.Assert
at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.net.FactoryURLClassLoader.loadClass(URLClassLoader.java:798)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:340)
Part of code that I think error is because of that is as below, and the whole code can be find in this link:
class ClassCollector extends Remapper {
static Set<Class<?>> getClassesUsedBy(final String name, final String prefix, File root) throws IOException {
final ClassReader reader = new ClassReader(name);
final Set<Class<?>> classes = new TreeSet<Class<?>> (new Comparator<Class<?>>() {
#Override
public int compare (final Class<?> o1, final Class<?> o2) {
return o1.getName().compareTo (o2.getName());
}
});
final Remapper remapper = new ClassCollector(classes, prefix, root);
final ClassWriter inner = new ClassWriter(ClassWriter.COMPUTE_MAXS);
final RemappingClassAdapter visitor = new RemappingClassAdapter(inner, remapper);
try {
reader.accept(visitor, ClassReader.EXPAND_FRAMES);
}
catch (Exception ex) {
ex.toString();
}
return classes;
}
Important: when I initialized inner (as below) with null, then the program does not crash, but cannot detect all dependencies, and for example cannot detect assert dependency in the above example.
final ClassVisitor inner = null; //new ClassWriter(ClassWriter.COMPUTE_MAXS);
Please let me know if any one knows why the program is correct as an ordinary java application, but crash as plugin.
ClassReader(String name) uses the ClassLoader.loadSystemResourceAsStream() method to access the bytes for a requested class. If the classes you want to analyze are not in the class path, this won't work, since the class path is what loadSystemResourceAsStream searches.

Loading classes dynamically from jar

I know that we can load classes dynamically by using custom class loaders.
But here my problem is my Class itself is depends upon other classes
My task is to get PigServer object .So I have used following code to load PigServer class
_pigServerClass = _classLoader.loadClass("org.apache.pig.PigServer");
But here PigServer class itself is depends upon so many other classes.
So when i am trying to get instance of PigServer class then it is showing following errors
java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory
java.lang.ClassNotFoundException:org.apache.log4j.AppenderSkeleton
etc..
Can anyone tell how to solve this?
There seems to be a misunderstanding. If you have all the jars required in a folder, say "lib", you can for example set up a class loader like this:
File libs = new File("lib");
File[] jars = libs.listFiles(new FileFilter() {
public boolean accept(File pathname) {
return pathname.getName().toLowerCase().endsWith(".jar");
}
});
URL[] urls = new URL[jars.length];
for (int i=0; i<jars.length; i++) {
urls[i] = jars[i].toURI().toURL();
}
ClassLoader uc = new URLClassLoader(urls,this.getClass().getClassLoader());
Class<?> pigServerClz = Class.forName("org.apache.pig.PigServer", false, uc);
Object pigServer = pigServerClz.newInstance();
// etc...
How you created your ClassLoader?
Did you specified another "parent" classloader, on wich classloading can be delegated?

custom classLoader issue

the problem is next: i took the base classLoader code from here. but my classLoader is specific from a point, that it must be able to load classes from a filesystem(let's take WinOS), so in classLoader must be some setAdditionalPath() method, which sets a path(a directory on a filesystem), from which we'll load class(only *.class, no jars). here is code, which modifies the loader from a link(you can see, that only loadClass is modified), but it doesn't work properly:
public void setAdditionalPath(String dir) {
if(dir == null) {
throw new NullPointerException("");
}
this.Path = dir;
}
public Loader(){
super(Loader.class.getClassLoader());
}
public Class loadClass(String className) throws ClassNotFoundException {
if(Path.length() != 0) {
File file = new File(Path);
try {
// Convert File to an URL
URL url = file.toURL();
URL[] urls = new URL[]{url};
// Create a new class loader with the directory
ClassLoader cl = new URLClassLoader(urls);
ClassLoader c = cl.getSystemClassLoader();
Class cls = c.loadClass(className);
return cls;
} catch (MalformedURLException e) {
} catch (ClassNotFoundException e) {
}
}
return findClass(Path);
}
I'd grateful if anyone helps :)
You can just use framework provided java.net.URLClassLoader. No need to write your own. It supports loading of classes from directories and JAR files.
Any URL that ends with a '/' is assumed to refer to a directory.
Otherwise, the URL is assumed to refer to a JAR file which will be
opened as needed.
It also supports a parent class loader. If this class loader does not suite your requirements, perhaps you can specify in more detail what you need. And in any case, you can look at the source and derive your own class loader class based on that.
Here is a short working snippet of code that should demostrate how to load a class by name from a URLClassLoader:
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// This URL for a directory will be searched *recursively*
URL classes =
new URL( "file:///D:/code/myCustomClassesAreUnderThisFolder/" );
ClassLoader custom =
new URLClassLoader( new URL[] { classes }, systemClassLoader );
// this class should be loaded from your directory
Class< ? > clazz = custom.loadClass( "my.custom.class.Name" );
// this class will be loaded as well, because you specified the system
// class loader as the parent
Class< ? > clazzString = custom.loadClass( "java.lang.String" );

Method to dynamically load java class files

What would be a good way to dynamically load java class files so that a program compiled into a jar can read all the class files in a directory and use them, and how can one write the files so that they have the necessary package name in relation to the jar?
I believe it's a ClassLoader you're after.
I suggest you start by looking at the example below which loads class files that are not on the class path.
// Create a File object on the root of the directory containing the class file
File file = new File("c:\\myclasses\\");
try {
// Convert File to a URL
URL url = file.toURI().toURL(); // file:/c:/myclasses/
URL[] urls = new URL[]{url};
// Create a new class loader with the directory
ClassLoader cl = new URLClassLoader(urls);
// Load in the class; MyClass.class should be located in
// the directory file:/c:/myclasses/com/mycompany
Class cls = cl.loadClass("com.mycompany.MyClass");
} catch (MalformedURLException e) {
} catch (ClassNotFoundException e) {
}
Class myclass = ClassLoader.getSystemClassLoader().loadClass("package.MyClass");
or
Class myclass = Class.forName("package.MyClass");
or loading the class from different folder which is not in the classpath:
File f = new File("C:/dir");
URL[] cp = {f.toURI().toURL()};
URLClassLoader urlcl = new URLClassLoader(cp);
Class myclass = urlcl.loadClass("package.MyClass");
For further usage of the loaded Class you can use Reflection if the loaded Class is not in your classpath and you can not import and cast it. For example if you want to call a static method with the name "main":
Method m = myclass.getMethod("main", String[].class);
String[] args = new String[0];
m.invoke(null, args); // invoke the method
If you add a directory to your class path, you can add classes after the application starts and those classes can be loaded as soon as they have been written to the directory.

Categories

Resources