I trying to make simple java profiler and using ClassLoader for this.
This is my implementation of ClassLoader:
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class CustomClassLoader extends ClassLoader {
private Notifier notifier;
public CustomClassLoader() {
super();
}
public CustomClassLoader(ClassLoader parent) {
super(parent);
}
private void initNotifier() {
if (notifier != null) return;
try {
System.out.println("2");
Registry registry = LocateRegistry.getRegistry(Const.registryPort);
System.out.println("3");
notifier = (Notifier) registry.lookup(Const.stubName);
System.out.println("4");
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
#Override
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
System.out.println("0");
Class clazz = super.loadClass(name, resolve);
System.out.println("1");
initNotifier();
System.out.println("5");
try {
notifier.classLoaded(name);
System.out.println("6");
} catch (RemoteException e) {
e.printStackTrace();
System.exit(1);
}
return clazz;
}
}
When i've try to use this class loader i receive this output (I've tried to use 1.6_37 and 1.7_10 jkd):
C:\Users\Scepion1d>java -cp C:\Users\Scepion1d\Dropbox\Workspace\IntellijIDEA\pr
ofiler\out\artifacts\loader\loader.jar;C:\Users\Scepion1d\Dropbox\Workspace\Inte
llijIDEA\app\out\production\app -Djava.system.class.loader=CustomClassLoader Main
0
1
2
0
1
2
3
0
1
2
3
java.lang.IllegalArgumentException: Non-positive latency: 0
at sun.misc.GC$LatencyRequest.<init>(GC.java:190)
at sun.misc.GC$LatencyRequest.<init>(GC.java:156)
at sun.misc.GC.requestLatency(GC.java:254)
at sun.rmi.transport.DGCClient$EndpointEntry.lookup(DGCClient.java:212)
at sun.rmi.transport.DGCClient.registerRefs(DGCClient.java:120)
at sun.rmi.transport.ConnectionInputStream.registerRefs(ConnectionInputS
tream.java:80)
at sun.rmi.transport.StreamRemoteCall.releaseInputStream(StreamRemoteCal
l.java:138)
at sun.rmi.transport.StreamRemoteCall.done(StreamRemoteCall.java:292)
at sun.rmi.server.UnicastRef.done(UnicastRef.java:431)
at sun.rmi.registry.RegistryImpl_Stub.lookup(Unknown Source)
at CustomClassLoader.initNotifier(CustomClassLoader.java:22)
at CustomClassLoader.loadClass(CustomClassLoader.java:35)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
at sun.security.jca.ProviderConfig$3.run(ProviderConfig.java:234)
at java.security.AccessController.doPrivileged(Native Method)
at sun.security.jca.ProviderConfig.doLoadProvider(ProviderConfig.java:22
5)
at sun.security.jca.ProviderConfig.getProvider(ProviderConfig.java:205)
at sun.security.jca.ProviderList.getProvider(ProviderList.java:215)
at sun.security.jca.ProviderList.getService(ProviderList.java:313)
at sun.security.jca.GetInstance.getInstance(GetInstance.java:140)
at java.security.Security.getImpl(Security.java:659)
at java.security.MessageDigest.getInstance(MessageDigest.java:129)
at java.rmi.dgc.VMID.computeAddressHash(VMID.java:140)
at java.rmi.dgc.VMID.<clinit>(VMID.java:27)
at sun.rmi.transport.DGCClient.<clinit>(DGCClient.java:66)
at sun.rmi.transport.ConnectionInputStream.registerRefs(ConnectionInputS
tream.java:80)
at sun.rmi.transport.StreamRemoteCall.releaseInputStream(StreamRemoteCal
l.java:138)
at sun.rmi.transport.StreamRemoteCall.done(StreamRemoteCall.java:292)
at sun.rmi.server.UnicastRef.done(UnicastRef.java:431)
at sun.rmi.registry.RegistryImpl_Stub.lookup(Unknown Source)
at CustomClassLoader.initNotifier(CustomClassLoader.java:22)
at CustomClassLoader.loadClass(CustomClassLoader.java:35)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
at sun.security.jca.ProviderConfig$3.run(ProviderConfig.java:234)
at java.security.AccessController.doPrivileged(Native Method)
at sun.security.jca.ProviderConfig.doLoadProvider(ProviderConfig.java:22
5)
at sun.security.jca.ProviderConfig.getProvider(ProviderConfig.java:205)
at sun.security.jca.ProviderList.getProvider(ProviderList.java:215)
at sun.security.jca.ProviderList$3.get(ProviderList.java:130)
at sun.security.jca.ProviderList$3.get(ProviderList.java:125)
at java.util.AbstractList$Itr.next(AbstractList.java:345)
at java.security.SecureRandom.getPrngAlgorithm(SecureRandom.java:522)
at java.security.SecureRandom.getDefaultPRNG(SecureRandom.java:165)
at java.security.SecureRandom.<init>(SecureRandom.java:133)
at java.rmi.server.UID.<init>(UID.java:92)
at java.rmi.server.ObjID.<clinit>(ObjID.java:71)
at java.rmi.registry.LocateRegistry.getRegistry(LocateRegistry.java:158)
at java.rmi.registry.LocateRegistry.getRegistry(LocateRegistry.java:106)
at java.rmi.registry.LocateRegistry.getRegistry(LocateRegistry.java:73)
at CustomClassLoader.initNotifier(CustomClassLoader.java:20)
at CustomClassLoader.loadClass(CustomClassLoader.java:35)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
I've thought that problem is in the RMI server, but i wrote another RMI client and it works good.
Does anyone know where is the problem(s) and how to solve it(them)?
TL;DR: Don't use a class loader which such heavy side effects as the root class loader.
The problem is that the const field gcInterval on class sun.rmi.transport.DGCClient is not initialized before it's used (and hence shows value 0). The reason for this is that your class loader makes the call via RMI which creates a new instance of DGCClient. During the execution of the constructor of DGCClient another class is loaded (see stack trace). This third call to the class loader triggers the RMI call again which doesn't create a new instance of DGCClient but uses the previously created one and does some call on it. That means that a call is made on a half-initialized object which leads to the use of this not-yet initialized constant field.
We can't possibly blame Sun/Oracle for this since every Java class can assume that it is loaded without such unpredictable side effects.
Related
This question already has answers here:
Does the JVM throw if an unused class is absent?
(3 answers)
How to avoid NoClassDefFoundError thrown by unused code in Java
(3 answers)
Closed 1 year ago.
We use some optional libraries. This libraries will only access if available. But the JIT produce already NoClassDefFoundError in the constructor of the class with optional access.
public Configuration {
public boolean libraryAvailable() {
return false; // some configuration that signal that the library is not available
}
}
public class Foo {
public Foo() {
... do some things
}
public void callLater() {
...
if( libraryAvailable() ) {
xyz();
}
...
}
private void xyz() {
new OptionalClass(); // available at compile time but not at runtime
}
}
How can I prevent that the JIT will load all possible dependencies of my call before calling the constructor?
java.lang.NoClassDefFoundError: com/inet/OptionalClass
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
at java.lang.Class.getConstructor0(Class.java:3075)
at java.lang.Class.newInstance(Class.java:412)
...
Caused by: java.lang.ClassNotFoundException: com.inet.OptionalClass
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at com.inet.plugin.DependencyClassLoader.loadClass(DependencyClassLoader.java:104)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at com.inet.plugin.DependencyClassLoader.loadClass(DependencyClassLoader.java:138)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 9 more
Instead of importing your optional class, use Class.forName
try {
Class<?> act = Class.forName("com.bla.TestActivity");
MyInterface driver = act.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
You could write your own custom method as shown in below code to check if a class is available in classpath.
boolean result=false;
try {
Class.forName( "your.test.class" );
result=true;
} catch( ClassNotFoundException e ) {
//Class or Library is not available
}
return result;
The decoupling of class loading for unused code with a separate method only work if the constructor empty. If there is a constructor then you need to move the unused code in a separate class. This can also be an anonymous class.
For example:
new Object () { {
new OptionalClass();
} };
The tasks and initial investigation
I try to set up two Oracle Coherence near cache instances at one java swing application. The idea a solution could be found here. My case is a bit more complicated and this is where the game starts.
Short description
In my case there is an account service. It can have two endpoints: SIT and UAT. In order to create two such services, I need to load two 'instances' of the Coherence in order to override the endpoints with system variables (tangosol.coherence.cacheconfig).
I have:
the main code of the app is located in the mainapp.jar;
the AccountService interface that is located in the account-interfaces.jar;
the AccountServiceImpl class that is located in the account-impl.jar and implements the AccountService interface;
my main application has the following structure
bin: startup.bat, startup.sh
conf: app.properties
lib: mainapp.jar, account-interfaces.jar, account-impl.jar, coherence.jar
Approach tried
I created a dedicated child-first classLoader - InverseClassLoader and made the AppLaunchClassLoader (the default Thread.currentThread().GetContextClassLoader() classLoader) it's parent. With the InverseClassLoader I load the AccountServiceImpl class:
Class<AccountServiceImpl> acImplClass = contextClassLoader.selfLoad(AccountServiceImpl.class).loadClass(AccountServiceImpl.class);
Constructor<AccountServiceImpl> acConstructor =
acImplClass .getConstructor(String.class);
AccountService acService = acConstructor .newInstance(serviceURL);
Issues and questions
I get the 'AccountServiceImpl cannot be cast to AccountService' exceptions, which means that those two classes loaded by different classloaders. But those classloaders are in the parent-child relationship. So am I right that even if a class is loaded by a parent (interface - 'abstract' type) it can't be used with a class (concrete impl) loaded by a child classloader? Why then we need this parent-child relation?
I specified the AccountService interface in a code and it got loaded by a default classloader. I tried wrap the code above is a thread and set the InverseClassLoader it's context classloader. Nothing changed. So am I right that I can't use such interface-implementation coding (as usual coding) and need to use reflection all the time to invoke concrete methods all the time? (Hope there is a solution) ;
Say, I listed both the AccountService and AccountServiceImpl classes for being loaded by the InverseClassLoader. What if I need other classes, that are accessible by those two, to be also loaded by the InverseClassLoader? It there a way to say that all 'related' classes must be loaded by the same classloader?
Update
Here is the InverseClassLoader:
public class InvertedClassLoader extends URLClassLoader {
private final Set<String> classesToNotDelegate = new HashSet<>();
public InvertedClassLoader(URL... urls) {
super(urls, Thread.currentThread().getContextClassLoader());
}
public InvertedClassLoader selfLoad(Class<?> classToNotDelegate) {
classesToNotDelegate.add(classToNotDelegate.getName());
return this;
}
#Override
public Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
if (shouldNotDelegate(className)) {
System.out.println("CHILD LOADER: " + className);
Class<?> clazz = findClass(className);
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
else {
System.out.println("PARENT LOADER: " + className);
return super.loadClass(className, resolve);
}
}
public <T> Class<T> loadClass(Class<? extends T> classToLoad) throws ClassNotFoundException {
final Class<?> clazz = loadClass(classToLoad.getName());
#SuppressWarnings("unchecked")
final Class<T> castedClass = (Class<T>) clazz;
return castedClass;
}
private boolean shouldNotDelegate(String className) {
if (classesToNotDelegate.contains(className) || className.contains("tangosol") ) {
return true;
}
return false;
}
Issue 1, part one I cannot reproduce (see below). As for part 2:
the hierarchy of class-loaders is to prevent the "X cannot be cast to X" exceptions.
But if you break the parent-first rule, you can get into trouble.
About issue 2: setting a thread's context classloader does not do anything in itself, see also this article (javaworld.com)
for some more background. Also, in relation to issue 1, part 2, a quote from the article
that describes what can happen if there is no parent-child relation between the current classloader
and the thread's context classloader:
Remember that the classloader that loads and defines a class is part of the internal JVM's ID for that class.
If the current classloader loads a class X that subsequently executes, say, a JNDI lookup for some data of type Y,
the context loader could load and define Y.
This Y definition will differ from the one by the same name but seen by the current loader.
Enter obscure class cast and loader constraint violation exceptions.
Below is a simple demo-program to show that a cast to an interface from another classloader can work
(note I'm using a simple Java project with classes in a bin-folder and the InvertedClassLoader from your question in the same (test) package):
import java.io.File;
public class ChildFirstClassLoading {
public static void main(String[] args) {
InvertedClassLoader cl = null;
try {
File classesDir = new File(new File("./bin").getCanonicalPath());
System.out.println("Classes dir: " + classesDir);
cl = new InvertedClassLoader(classesDir.toURI().toURL());
cl.selfLoad(CTest.class);
System.out.println("InvertedClassLoader configured.");
new CTest("Test 1").test();
ITest t2 = cl.loadClass(CTest.class)
.getConstructor(String.class)
.newInstance("Test 2");
t2.test();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cl != null) {
try { cl.close(); } catch (Exception ignored) {}
}
}
}
public interface ITest {
void test();
}
public static class CTest implements ITest {
static {
System.out.println("CTest initialized.");
}
private String s;
public CTest(String s) {
this.s = s;
}
public void test() {
System.out.println(s);
}
}
}
If you change ITest t2 = to CTest t2 = you will get the "CTest cannot be cast to CTest" exception,
but using the interface prevents that exception.
Since this little demo works fine, I'm guessing there is more going on in your application which somehow breaks the class-loading.
I suggest you work from a situation where the class-loading works and keep adding code until it breaks the class-loading.
The InvertedClassLoader looks a lot like the "child first classloader", see this question
for some good answers discussing this manner of class-loading.
The child first classloader can be used to load "related classes" (from your third issue) separately.
You could also update the InvertedClassLoader to always "self-load" classes in certain packages.
And remember that "once a class is loaded by a classloader it uses that classloader to load every other class it needs"
(quote from this blog article).
Whatever I do I cannot create new instance of class Serwer. Please help, somehow constructor is invisible. I don't understand why is it so. The constructor is public and everything is coded in one file.
I just get this:
java.rmi.StubNotFoundException: Stub class not found: Serwer_Stub; nested exception is:
java.lang.ClassNotFoundException: Serwer_Stub
at sun.rmi.server.Util.createStub(Unknown Source)
at sun.rmi.server.Util.createProxy(Unknown Source)
at sun.rmi.server.UnicastServerRef.exportObject(Unknown Source)
at java.rmi.server.UnicastRemoteObject.exportObject(Unknown Source)
at java.rmi.server.UnicastRemoteObject.exportObject(Unknown Source)
at com.sun.corba.se.impl.javax.rmi.PortableRemoteObject.exportObject(Unknown Source)
at javax.rmi.PortableRemoteObject.exportObject(Unknown Source)
at javax.rmi.PortableRemoteObject.<init>(Unknown Source)
at Serwer.<init>(Serwer.java:13)
at Serwer.main(Serwer.java:35)
Caused by: java.lang.ClassNotFoundException: Serwer_Stub
at java.net.URLClassLoader$1.run(Unknown Source)
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)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Unknown Source)
... 10 more
CLASS
import java.rmi.RemoteException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.rmi.PortableRemoteObject;
public class Serwer extends PortableRemoteObject implements MyInterface {
public Serwer() throws RemoteException {
super();
try{
Serwer ref =
new Serwer();
Context ctx = new InitialContext();
ctx.rebind("myinterfaceimplementacja", ref);
}catch(Exception e){e.printStackTrace();}
}
#Override
public String echo(String napis) throws RemoteException {
return "echo" + napis;
}
#Override
public int dodaj(int wrt1, int wrt2) throws RemoteException {
return wrt1 + wrt2;
}
public static void main(String[] args){
try {
new Serwer();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
There are two bugs in your code. The first one is the obvious infinite recursion in the Serwer constructor, where you are calling the constructor again and again. This can be fixed by removing that line from the constructor and replace ref with this on the following line:
public class Serwer extends PortableRemoteObject implements MyInterface {
public Serwer() throws RemoteException {
super();
}
#Override
public String echo(String napis) throws RemoteException {
return "echo" + napis;
}
#Override
public int dodaj(int wrt1, int wrt2) throws RemoteException {
return wrt1 + wrt2;
}
public static void main(String[] args){
try {
Serwer ref = new Serwer();
// Context ctx = new InitialContext();
// ctx.rebind("myinterfaceimplementacja", ref);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
However, this bug is unrelated to the ClassNotFoundException you got. What causes the exception is that you use PortableRemoteObject as the base class of your remote implementation. Normally in Java RMI, the stub class (Serwer_Stub) is generated automatically when you export (instantiate) the remote object. But the PortableRemoteObject is an exception to this case. You can solve this two ways:
As Kumar suggested, replace the javax.rmi.PortableRemoteObject with java.rmi.server.UnicastRemoteObject. This way the stub object gets created automatically, and the above code will run happily, I tested it.
public class Serwer extends UnicastRemoteObject implements MyInterface {
If for some reason you must use PortableRemoteObject, then you should generate the stub class manually by using the RMI compiler (rmic) tool that are shipped with the JDK.
First, you compile the Serwer class:
javac Serwer.java
This will generate the Serwer.class file. Then you call the RMIC tool to generate the stub class:
rmic Serwer
This will generate the Serwer_Stub.class file. Now you can run your server:
java Serwer
I also tested this, it starts without any exceptions.
Note that there is another bug in your code with the usage of the Java Naming, causing another exception (NoInitialContextException), but that is also unrelated with the question, that's why I commented it out in the code above. Since I'm no expert in javax.naming, it's up to someone else to help you with that.
Maybe you intended to use RMI registry instead of using Naming by mistake. RMI registry is the native way to bind and lookup remote objects in Java RMI. In this case you should replace the
Context ctx = new InitialContext();
ctx.rebind("myinterfaceimplementacja", ref);
lines with the appropriate RMI registry code:
Registry reg = LocateRegistry.createRegistry(1099);
reg.rebind("myinterfaceimplementacja", ref);
This will create the RMI registry for you on the standard port (1099). If you run your program, the registry will be created and your remote object will be exported and registered under the given name.
The other way is to write
Registry reg = LocateRegistry.getRegistry();
This makes your program to find an existing registry that is already running. You must start the RMI registry before running your program, by calling the remiregistry tool, that is also part of the JDK:
rmiregistry
Now you can compile and you start your program:
javac Serwer.java
java Serwer
It will start and register your remote object implementation in the registry, making it available to be looked up by the clients.
I have a little problem.
I am developing an Android applikation.
There you can dynamicly load classes from other applications (packages).
First of all, i do not want to "hack" an third-party app, i want to try to build up plugins for my own app.
So what do i have?
2 Test applications and 1 library which is in both apps included.
So the code for app1:
package com.ftpsynctest.app1;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import android.app.Activity;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import com.syncoorp.ftpsyncx.commons.SyncFile;
import dalvik.system.PathClassLoader;
public class App1Activity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
SyncFile f = new SyncFile("bla");
String classname = "com.ftpsynctest.app2.classcall";
String classpath = getApk("com.ftpsynctest.app1") + ":" + getApk("com.ftpsynctest.app2");
PathClassLoader myClassLoader = new dalvik.system.PathClassLoader(classpath, ClassLoader.getSystemClassLoader());
try {
Class c = Class.forName(classname, true, myClassLoader);
for (Method m : c.getDeclaredMethods()) {
System.out.println("Method: " + m.getName());
for (Type t : m.getGenericParameterTypes()) {
System.out.println(" - type: " + t.toString());
}
m.invoke(c.newInstance(), new Object[] {
new com.syncoorp.ftpsyncx.commons.SyncFile("bla")
});
break;
}
}
catch (ClassNotFoundException e) {e.printStackTrace();}
catch (IllegalArgumentException e) {e.printStackTrace();}
catch (IllegalAccessException e) {e.printStackTrace();}
catch (InvocationTargetException e) {e.printStackTrace();}
catch (InstantiationException e) {e.printStackTrace();}
}
private String getApk(String packageName) {
try { return this.getPackageManager().getApplicationInfo(packageName, 0).sourceDir;}
catch (NameNotFoundException e) {e.printStackTrace();}
return "";
}
}
So i want to create the class com.ftpsynctest.app2.classcall and call the method modify with a parameter of type com.syncoorp.ftpsyncx.commons.SyncFile.
My app2 code:
package com.ftpsynctest.app2;
import com.syncoorp.ftpsyncx.commons.SyncFile;
public class classcall {
public SyncFile modify(SyncFile file) {
file.change_date = 123;
return file;
}
}
I first installed app2 to provide the class to app1.
After that succeed i started app1.
My Output:
01-10 22:21:48.804: INFO/System.out(4681): Method: modify
01-10 22:21:48.809: INFO/System.out(4681): - type: class com.syncoorp.ftpsyncx.commons.SyncFile
So for now it looks good. the parameter type of the found method is com.syncoorp.ftpsyncx.commons.SyncFile
and my provided one is the same.
But i get the following error:
java.lang.IllegalArgumentException: argument type mismatch
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.ftpsynctest.app1.App1Activity.onCreate(App1Activity.java:44)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1615)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1667)
at android.app.ActivityThread.access$1500(ActivityThread.java:117)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:935)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:130)
at android.app.ActivityThread.main(ActivityThread.java:3691)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:907)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:665)
at dalvik.system.NativeStart.main(Native Method)
But why? my output tells me that it is SyncFile and i put SyncFile to the invoke command.
Whats the problem there?
Can it be that compiling app2 creates a class from SyncFile which is different from the compiled app1 ? if yes, why ? the SyncFile class is the same physical class within my "commons" library which both projects share.
Anybody has a solution or answer?
There may be two identically named classes SyncFile visible to different classloaders in this case. Even if the classes are named exactly the same, in the same package, even with the same byte code they will be considered different classes by the VM because they come from different locations (classloaders).
At runtime, the identity of a class is defined by its package, its name and the classloader instance that loaded it. It is expected that every class can only be found/loaded by exactly one classloader. If that is not the case, the result will vary based upon which classloader is in effect when the class is accessed.
new com.syncoorp.ftpsyncx.commons.SyncFile will probably use the class loaded by and associated with the app's local classloader whereas the called method expects the version associated with myClassLoader. Since both classloaders know the 'same' class (identified by package+class name), but each one only knows one of them, from the JVM's perspective, they are two different classes.
You could try and create your SyncFile instance via reflection from the SyncFile class loaded by myClassloader, i.e.
Class sfClass = Class.forName("com.syncoorp.ftpsyncx.commons.SyncFile", true, myClassLoader);
Object param = sfClass.newInstance("bla"); // param must be Object because the 'local' SyncFile is not the same as the SyncFile represented by sfClass!
Note that you will face this problem everywhere where your app and a 'plugin' exchange instances of any class they both contain, i.e. reflection everywhere. Consider if it's worth the hassle or if you want to resort to some better way, e.g. IPC.
Edit: This answer is incorrect. See below for a counterexample.
You've formatted your argument to Method.invoke() wrong. See the documentation here. Instead of passing a single Object[] array of all arguments, you just pass multiple arguments to invoke. That's what the Object... args notation means: the method accepts any number of Objects.
For your example, changing
m.invoke(c.newInstance(), new Object[] {
new com.syncoorp.ftpsyncx.commons.SyncFile("bla")
});
to
m.invoke(c.newInstance(), new com.syncoorp.ftpsyncx.commons.SyncFile("bla"));
should fix the problem.
Counterexample:
Refl.java:
package com.drfloob.so;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
public class Refl {
public static void main(String[] args) {
try {
Class c = Class.forName("com.drfloob.so.Refl2", true, ClassLoader.getSystemClassLoader());
for (Method m : c.getDeclaredMethods()) {
System.out.println("Method: " + m.getName());
for (Type t : m.getGenericParameterTypes()) {
System.out.println(" - type: " + t.toString());
}
m.invoke(c.newInstance(), "test 1");
m.invoke(c.newInstance(), new Object[] {"test 2"});
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Refl2.java:
package com.drfloob.so;
public class Refl2 {
public Refl2() {
System.out.println(Refl2.class);
}
public void doStuff(String str) {
System.out.println(str);
}
}
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.