Java: Automatic Custom ClassLoader - java

My application uses the Standard Widget Toolkit (SWT) for it's GUI. My problem is that the 32-bit SWT library does not work on a 64-bit JVM. But I don't want to make people select the correct architecture when getting the software. So, I want to bundle both the 32-bit and 64-bit libraries, and auto-detect the architecture during runtime. I found out I can get the correct architecture of the JVM like so:
if (System.getProperty("os.arch").contains("64")) {
// ...
}
Now all that's left is to load the jar. But the problem is, all the examples I found require that you manually load the class before using it.
Class.forName("MyClass", false, myClassLoader);
So my question is, is it possible to "register" my class loader, so that I don't have to load classes beforehand?
Update: I created my own child class of URLClassLoader and set it as the default class loader with the command line argument -Djava.system.class.loader; but I get this error:
Error occurred during initialization of VM
java.lang.Error: java.lang.NoSuchMethodException: com.program.LibraryLoader.<init>(java.lang.ClassLoader)
at java.lang.ClassLoader.initSystemClassLoader(Unknown Source)
at java.lang.ClassLoader.getSystemClassLoader(Unknown Source)
I think LibraryLoader.<init> refers to the constructor... but it's there (public LibraryLoader(URI[] urls)).
Update 2: Almost there, the JVM runs now. I added this constructor to make it work:
public LibraryLoader(ClassLoader classLoader) {
super(new URL[0], classLoader);
}
But after adding the jars with addPath() (file:lib/jars/swt.jar), it only produces a NoClassDefFoundError. Yes, I double-checked that the file exists.

You could try to inject your custom class loader by means of the "java.system.class.loader" property (see ClassLoader#getSystemClassLoader). However, I'd recommend to use OSGi and let the framework do the complicated stuff.

As part of the constructor for your custom ClassLoader, call definePackage with the appropriate information, with the URL pointing to the desired jar file.
This example shows that the custom class loader is called when I try to instantiate a class from swing, because I defined my class loader as the loader of that package.
import java.net.URL;
public class junk extends ClassLoader {
byte[] dummy = new byte[0];
public static void main(String[] args) throws Exception {
new junk();
new javax.swing.JPanel();
}
public junk() throws Exception {
definePackage("javax.swing","","","","","","",new URL("file://junk.class"));
}
public Class<?> findClass(String s) throws java.lang.ClassNotFoundException{
Class<?> retVal = super.findClass(s);
System.out.println("delegated responsibility for "+s+" to superclass");
return retVal;
}
public Package getPackage(String s) {
Package retVal = super.getPackage(s);
System.out.println("delegated responsibility for "+s+" to superclass");
return retVal;
}
}
Result:
delegated responsibility for javax.swing to superclass

Related

How to use custom classloader for subsequent class loadings?

I have a main method that creates custom classloader and instantiates a class, called Test, with it.
public class App {
public static void main(String[] args) throws Exception {
try {
Class.forName("com.mycompany.app2.Test2"); // We ensure that Test2 is not part of current classpath
System.err.println("Should have thrown ClassNotFound");
System.exit(1);
} catch (ClassNotFoundException e) {
// ignore
}
String jar = "C:\\experiments\\classloader-test2\\target\\classloader-test2-1.0-SNAPSHOT.jar"; // Contains Test2
URL[] classPaths = new URL[] { new File(jar).toURI().toURL() };
ClassLoader classLoader = new URLClassLoader(classPaths, App.class.getClassLoader());
Thread.currentThread().setContextClassLoader(classLoader);
Class.forName("com.mycompany.app2.Test2", true, classLoader); // Check that custom class loader can find the wanted class
Test test = (Test) Class.forName("com.mycompany.app.Test", true, classLoader).getDeclaredConstructor().newInstance();
test.ex(); // This throws ClassNotFound for Test2
}
}
This class then itself instantiates another class that is not part of the original classpath, but is part of the custom one.
public class Test {
public void ex() {
new Test2().test();
}
}
In my understanding of classloader, since Test was created with the custom classloader any class loadings within should be done with the same loader. But this does not seem to be the case.
Exception in thread "main" java.lang.NoClassDefFoundError: com/mycompany/app2/Test2
at com.mycompany.app.Test.ex(Test.java:7)
at com.mycompany.app.App.main(App.java:28)
What do I need to do in the main method to make Test#ex work, without changing Test?
I'm using Java 17.
You create the URLClassLoader using App.class.getClassLoader() as the parent class loader. Hence, the request to load Test through the custom class loader is resolved through the parent loader, ending up at exactly the same class you’d get with Test.class in your main method.
You could pass a different parent loader, e.g. null to denote the bootstrap loader, to forbid resolving the Test class though the parent loader but this would result in either of two unhelpful scenarios
If the custom class loader has no com.mycompany.app.Test class on its own, the loading attempt would simply fail.
If the custom class loader has a com.mycompany.app.Test class, i.e. inside classloader-test2-1.0-SNAPSHOT.jar, it would be a different class than the Test class referenced in your main method, loaded by the application class loader. In this case, the type cast (Test) would fail.
In other words, the Test class referenced by you main method can not be affected by another, unrelated class loader at all.
There is an entirely different approach which may work in some scenarios. Do not create a new class loader, when all you want to do, is to inject a new class.
byte[] code;
try(var is = new URL("jar:file:C:\\experiments\\classloader-test2\\target\\" +
"classloader-test2-1.0-SNAPSHOT.jar!/com/mycompany/app2/Test2.class").openStream())
{
code = is.readAllBytes();
}
MethodHandles.lookup().defineClass(code);
Test test = new Test();
test.ex();
This adds the class to the current class loading context, so subsequent linking will succeed. With the following catches:
No previous attempt to link this class must have been made so far
It only works for a classes without dependencies to other absent classes (as there’s no class loader resolving those to the jar file outside the class path).
In some cases, when the dependencies are non-circular or resolved lazily, you could add all the classes with multiple define calls, if you know which you need and in which order.
The class must be in the same package, otherwise, you’d have to move the lookup context to the right package, with the documented restrictions
An entirely different approach to add the classes to the existing environment, would be via Java Agents, as they can add jar files to the class path.

Code for running the method declared in DLL

I created a Java code that tries to access the method LoadProject of the class IProjectFactory defined in myAPI.dll. The description of the DLL file says: IProjectFactory is used to load a project file into memory. IProjectFactory is a static class in the myAPI.dll assembly. It exposes the LoadProject method that takes a string containing the path to the file to load, and returns a reference to the resulting IProject. Using the IProject interface you can then manipulate the loaded project in various ways.
import com.sun.jna.Library;
import com.sun.jna.Native;
public class MyClass {
public interface IProjectFactory extends Library {
public Object LoadProject(String fileName);
}
public MyClass() {
//System.loadLibrary("myAPI");
load();
}
void load() {
String fileName = "xxx.sp";
IProjectFactory api = (IProjectFactory) Native.loadLibrary("myAPI",IProjectFactory.class);
try {
Object project = api.LoadProject(fileName);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
After running this code, the following error message has been generated:
Exception in thread "AWT-EventQueue-0" java.lang.UnsatisfiedLinkError: Error looking up function 'LoadProject': The specified procedure could not be found.
at com.sun.jna.Function.<init>(Function.java:179)
at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:350)
at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:330)
at com.sun.jna.Library$Handler.invoke(Library.java:203)
at $Proxy0.LoadProject(Unknown Source)
Does it mean that myAPI does not contain the class IProjectFactory with the method LoadProject?
You can call functions in native libraries with JNI or JNA. Primitive types are mapped. Certain structures are possible also. Even callback functions are possible. See JNA's mapping table.
What is impossible, by design: Getting a class or interface that was designed for a different runtime environment (like CLR) and use it seamless within the JVM.
So if you have a native procedural/functional library that just returns pointers or primitive types then you can use it quite well.
If you need to work with objects that are returned then you are out of luck. You need to run them in their native environment and find some way of interprocess communication.

Java, problems with custom classloader

i'm writing a custom class loader to load some of my classes (not all).
Class loader are very simple:
public Class loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class cls=findLoadedClass(className);
if(cls!=null) {
return cls;
}
// Search first for encrypted classes
cls=decryptClass(className);
if(cls==null) {
// Then try with default system classloader
cls=super.loadClass(className, resolve);
}
return cls;
}
And this is how i use it:
// In my Launcher class
public static void main(String[] args) {
MyClassLoader loader=new MyClassLoader();
try {
final Class main=loader.loadClass("com.MyAppMain");
Method toInvoke=main.getMethod("main", args.getClass());
toInvoke.invoke(main, new Object[]{args});
}
catch(Exception ex) {
}
}
All seem to be fine in my small test project, but when i use this loader in my big project(client-server application that use spring+hibernate and IoC) doesn't work.
I have not a particolar exception in my classloader, but for example, new Socket instance throw a "java.net.ConnectException: Connection refused" without a real reason...
Other problems is my main form does not become visible... and other strange problems like this.
So, the question, are these problems caused by my classloader that load in different way a different kind of classes?
Edit 1
My project use spring, so i use #Autowired or sometimes
springApplicationContext.getBean(clazz);
to inject a bean.
The problem is spring cannot find my beans if these classes are encrypted(so they need to be loaded by my classloader).
There is a workaround for this mistake?
Thanks.
Edit 2
I have set my classloader in spring ClassPathXmlApplicationContext and now i notice that spring uses my classloader to load beans class, but despite this it throws an org.springframework.beans.factory.NoSuchBeanDefinitionException becouse it cannot find beans... what can i do?
Thanks
I'm not very good at class loaders, but from you code it's only possible to assume, that in case your class loader can't find a class, it will redirect to system class loader. It may work fine when you run application standalone, like in your sample, but if it's a web application that is run on application server it will fail.
Application servers usually create a big hierarchy of class loaders and has a separate class loader used to load your application classes. In this case, system class loader knows nothing about your Spring related classes and thus can't load them.
You must keep in mind, that same class may be loaded by several class loaders and if you try to compare same class from different class loaders it will fail.
In your case I would set parent class loader in MyClassLoader constructor. As a parent class loader you can use MyClassLoader.class.getClassLoader() I think.
public class MyClassLoader extends ClassLoader
{
public MyClassLoader()
{
super(MyClassLoader.class.getClassLoader());
}
// other code
}
Hope this may help :)
I see two things that may be worth investigating:
The code makes the class loader parent-last. There is always a risk that this causes subtle class loading issues.
Maybe it is just the thread context class loader that is not set properly.

java.lang.IllegalAccessError: tried to access method

I am getting an exception and I can't find the reason of it.
The exception I get is :
java.lang.IllegalAccessError: tried to access method Connected.getData(Ljava/lang/String;)Ljava/sql/ResultSet; from class B
The method is public.
public class B
{
public void myMethod()
{
Connected conn = new Connected(); // create a connected class in order to connect to The DB
ResultSet rs = null; // create a result set to get the query result
rs = conn.getData(sql); // do sql query
}
}
public class Connected
{
public ResultSet getData(String sql)
{
ResultSet rs = null;
try
{
prepareConnection();
stmt = conn.createStatement();
stmt.execute(sql);
rs = stmt.getResultSet();
}
catch (SQLException E)
{
System.out.println("Content.getData Error");
E.printStackTrace();
}
return rs;
}
i am using apache tomcat 5.5.12
and JAVA 1.6
This happens when accessing a package scoped method of a class that is in the same package but is in a different jar and classloader.
This was my source, but the link is now broken. Following is full text from google cache:
Packages (as in package access) are scoped per ClassLoader.
You state that the parent ClassLoader loads the interface and the child
ClassLoader loads the implementation. This won't work because of the
ClassLoader-specific nature of package scoping. The interface isn't visible to
the implementation class because, even though it's the same package name,
they're in different ClassLoaders.
I only skimmed the posts in this thread, but I think you've already discovered
that this will work if you declare the interface to be public. It would also
work to have both interface and implementation loaded by the same ClassLoader.
Really, if you expect arbitrary folks to implement the interface (which you
apparently do if the implementation is being loaded by a different
ClassLoader), then you should make the interface public.
The ClassLoader-scoping of package scope (which applies to accessing package
methods, variables, etc.) is similar to the general ClassLoader-scoping of
class names. For example, I can define two classes, both named com.foo.Bar,
with entirely different implementation code if I define them in separate
ClassLoaders.
Joel
You are almost certainly using a different version of the class at runtime to the one you expect. In particular, the runtime class would be different to the one you've compiled against (else this would have caused a compile-time error) - has that method ever been private? Do you have old versions of the classes/jars on your system anywhere?
As the javadocs for IllegalAccessError state,
Normally, this error is caught by the compiler; this error can only occur at run time if the definition of a class has incompatibly changed.
I'd definitely look at your classpath and check whether it holds any surprises.
This happened to me when I had a class in one jar trying to access a private method in a class from another jar. I simply changed the private method to public, recompiled and deployed, and it worked ok afterwards.
I was getting this error on a Spring Boot application where a #RestController ApplicationInfoResource had a nested class ApplicationInfo.
It seems the Spring Boot Dev Tools was using a different class loader.
The exception I was getting
2017-05-01 17:47:39.588 WARN 1516 --- [nio-8080-exec-9]
.m.m.a.ExceptionHandlerExceptionResolver : Resolved exception caused
by Handler execution:
org.springframework.web.util.NestedServletException: Handler dispatch
failed; nested exception is java.lang.IllegalAccessError: tried to
access class com.gt.web.rest.ApplicationInfo from class
com.gt.web.rest.ApplicationInfoResource$$EnhancerBySpringCGLIB$$59ce500c
Solution
I moved the nested class ApplicationInfo to a separate .java file and got rid of the problem.
If getData is protected then try making it public. The problem could exist in JAVA 1.6 and be absent in 1.5x
I got this for your problem. Illegal access error
I was getting same error because of configuration issue in intellij.
As shown in screenshot.
Main and test module was pointing to two different JDK. (Press F12 on the intellij project to open module settings)
Also all my dto's were using #lombok.Builder which I changed it to #Data.
From Android perspective:
Method not available in api version
I was getting this Issue primarily because i was using some thing that is not available/deprecated in that Android version
Wrong way:
Notification.Builder nBuilder = new Notification.Builder(mContext);
nBuilder.addAction(new Notification.Action(android.R.drawable.ic_menu_view,"PAUSE",pendingIntent));
Right way:
Notification.Builder nBuilder = new Notification.Builder(mContext);
nBuilder.addAction(android.R.drawable.ic_media_pause,"PAUSE",pendingIntent);
here Notification.Action is not available prior to API 20 and my min version was API 16
Just an addition to the solved answer:
This COULD be a problem with Android Studio's Instant Run feature, for example, if you realized you forgot to add the line of code: finish() to your activity after opening another one, and you already re-opened the activity you shouldn't have reopened (which the finish() solved), then you add finish() and Instant Run occurs, then the app will crash since the logic has been broken.
TL:DR;
This is not necessarily a code problem, just an Instant Run problem
In my case the problem was that a method was defined in some Interface A as default, while its sub-class overrode it as private. Then when the method was called, the java Runtime realized it was calling a private method.
I am still puzzled as to why the compiler didn't complain about the private override..
public interface A {
default void doStuff() {
// doing stuff
}
}
public class B {
private void doStuff() {
// do other stuff instead
}
}
public static final main(String... args) {
A someB = new B();
someB.doStuff();
}
In my case I was getting this error running my app in wildfly with the .ear deployed from eclipse. Because it was deployed from eclipse, the deployment folder did not contain an .ear file, but a folder representing it, and inside of it all the jars that would have been contained in the .ear file; like if the ear was unzipped.
So I had in on jar:
class MySuperClass {
protected void mySuperMethod {}
}
And in another jar:
class MyExtendingClass extends MySuperClass {
class MyChildrenClass {
public void doSomething{
mySuperMethod();
}
}
}
The solution for this was adding a new method to MyExtendingClass:
class MyExtendingClass extends MySuperClass {
class MyChildrenClass {
public void doSomething{
mySuperMethod();
}
}
#Override
protected void mySuperMethod() {
super.mySuperMethod();
}
}
I was getting similar exception but at class level
e.g. Caused by: java.lang.IllegalAccessError: tried to access class ....
I fixed this by making my class public.

NoClassDefFoundError

I have an issue where NoClasDefFoundError is being thrown. It puzzles me since I am using interfaces, and no class definition should be available. I have read through some posts which point to Classpath, but I don't believe that to be the issue here (although I may be wrong). I am using NetBeans 6.9.1 IDE.
I have created a sample setup to reproduce the issue. Four projects: Interfaces, Objects, Locator and Consumer. Below you will find the implementations.
At runtime consumer coplains about missing SomeObject implementation, which it should not be aware of since it is accepting interface.
Exception in thread "main"
java.lang.NoClassDefFoundError:
objects/SomeObject
What am I missing?
package interfaces;
public interface ISomeInterface { }
package objects;
import interfaces.ISomeInterface;
public class SomeObject implements ISomeInterface{ }
package locator;
import interfaces.ISomeInterface;
import objects.SomeObject;
public class Locator { public static ISomeInterface LocateImplementation() { return new SomeObject(); }}
package consumer;
import interfaces.ISomeInterface;
import locator.Locator;
public class Main { public static void main(String[] args) { ISomeInterface object = Locator.LocateImplementation(); }}
You can get a NoClassDefFoundError exception with interfaces just as you can with classes. Consider the "Class" in the name of the exception to be the .class file that is generated from compiling a class or interface, not a Java class.
This is saying that the class/interface objects.SomeObject isn't visible on your classpath. Check the location of that .class file and ensure that it's on your classpath - if you're positive it's there, give us some screen shots or something that might help to debug the problem.
Think of NoClassDefFoundError as a runtime linkage problem. JRE loaded one class (or an interface) and it references another class (or an interface), but that referenced class isn't found.
The only way this can happen if you have packaging/classpath issues such that your runtime environment doesn't reflect how things are at build time.
If you are launching this from IDE, make sure that you aren't ignoring any errors and launching anyway. Some classes will not be generated that way.
Usually I run into these problems not when a class is missing, but when there is an error in the static initializers.
Try running your code in a debugger, and set the exception breakpoint to break when any exception is thrown, whether caught or not. I bet you have an uncaught exception in the static initializer for some reason.
In the locateImplementation() method you are returning "new SomeObject()",
JVM needs to have its definition when called. I think it is missing.
You should check if your SomeObject class is in class path because -
Well the JVM will be running the below code -
ISomeInterface object = Locator.LocateImplementation();
and when it does that it will call Locator.LocateImplementation(). This code internally tries to instantiate your SomeObject class which it does not find in the classpath.
So your below understanding
It puzzles me since I am using
interfaces, and no class definition
should be available.
Is not really valid.
Any Interface must be declared inside class
public class Calbacks {
public interface IBaseFragmentInterface {
void NotifyMainActivity();
}
}

Categories

Resources