I want to create a custom ClassLoader to load all jar files in some path(e.g. /home/custom/lib).
then I expect that every time I use new operator to create a Object, it will search class in all jar files in that path, then search the class path defined by parameter (-cp).
Is it possible?
for Example, there is a jar file in /home/custom/lib/a.jar
in Main Class
public class Main {
public static void main(String[] args) {
// do something here to use custom ClassLoader
// here will search Car in /home/custom/lib/a.jar first then in java class path
Car car = new Car();
}
}
A class loader cannot do exactly what you seem to expect.
Quoting another answer of a relevant Q&A:
Java will always use the classloader that loaded the code that is executing.
So with your example:
public static void main(String[] args) {
// whatever you do here...
Car car = new Car(); // ← this code is already bound to system class loader
}
The closest you can get would be to use a child-first (parent-last) class loader such as this one, instanciate it with your jar, then use reflection to create an instance of Car from that jar.
Car class within a.jar:
package com.acme;
public class Car {
public String honk() {
return "Honk honk!";
}
}
Your main application:
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
ParentLastURLClassLoader classLoader = new ParentLastURLClassLoader(
Arrays.asList(new File("/home/lib/custom/a.jar").toURI().toURL()));
Class<?> carClass = classLoader.loadClass("com.acme.Car");
Object someCar = carClass.newInstance();
Object result = carClass.getMethod("honk").invoke(someCar);
System.out.println(result); // Honk honk!
}
To note: if you also have a com.acme.Car class in your class path, that's not the same class, because a class is identified by its full name and class loader.
To illustrate this, imagine I'd used my local Car class as below with the carClass loaded as above by my custom class loader:
Car someCar = (Car) carClass.newInstance();
// java.lang.ClassCastException: com.acme.Car cannot be cast to com.acme.Car
Might be confusing, but this is because the name alone does not identify the class. That cast is invalid because the 2 classes are different. They might have different members, or they might have same members but different implementations, or they might be byte-for-byte identical: they are not the same class.
Now, that's not a very useful thing to have.
Such things become useful when the custom classes in your jar implement a common API, that the main program knows how to use.
For example, let's say interface Vehicle (which has method String honk()) is in common class path, and your Car is in a.jar and implements Vehicle.
ParentLastURLClassLoader classLoader = new ParentLastURLClassLoader(
Arrays.asList(new File("/home/lib/custom/a.jar").toURI().toURL()));
Class<?> carClass = classLoader.loadClass("com.acme.Car");
Vehicle someCar = (Vehicle) carClass.newInstance(); // Now more useful
String result = someCar.honk(); // can use methods as normal
System.out.println(result); // Honk honk!
That's similar to what servlet containers do:
your application implements the servlet API (e.g. a class that implements javax.servlet.Servlet)
it is packaged into a war file, that the servlet container can load with a custom class loader
the deployment descriptor (web.xml file) tells the servlet container the names of the servlets (classes) that it needs to instanciate (as we did above)
those classes being Servlets, the servlet container can use them as such
In your case, you do not need to write a new ClassLoader as the only thing you wanna do is extend your classpath at runtime.
For that you get your current SystemClassLoader instance and you add the classpath entry to it using URLClassLoader.
working example with JDK 8:
Car class compiled and located in C:\Users\xxxx\Documents\sources\test\target\classes
public class Car {
public String prout() {
return "Test test!";
}
}
Main class
public static void main(String args[]) throws Exception {
addPath("C:\\Users\\xxxx\\Documents\\sources\\test\\target\\classes");
Class clazz = ClassLoader.getSystemClassLoader().loadClass("Car");
Object car = clazz.newInstance();
System.out.println(clazz.getMethod("prout").invoke(car));
}
public static void addPath(String s) throws Exception {
File f=new File(s);
URL u=f.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});
}
note that we need to use reflection because method addURL(URL u) is protected
also note that since we add the classpath entry to the SystemClassloader, you do not need to add the classpath entry everytime you need it, only once is enough and then use ClassLoader.getSystemClassLoader().loadClass(String name) to load the class from previously added classpath entry.
If you do not need that classpath entry for later use, you can instantiate your own URLClassLoader instance and load the classes accordingly, instead of setting the classpath entry on the SystemClassLoader.
i.e:
public static void main(String[] args) {
try {
File file = new File("c:\\other_classes\\");
//convert the file to URL format
URL url = file.toURI().toURL();
URL[] urls = new URL[]{ url };
//load this folder into Class loader
ClassLoader cl = new URLClassLoader(urls);
//load the Address class in 'c:\\other_classes\\'
Class cls = cl.loadClass("com.mkyong.io.Address");
} catch (Exception ex) {
ex.printStackTrace();
}
}
source:
https://www.mkyong.com/java/how-to-load-classes-which-are-not-in-your-classpath/
Question: I want to create a custom ClassLoader to load all jar files
in some path(e.g. /home/custom/lib).
then I expect that every time I use new operator to create a Object,
it will search class in all jar files in that path, then search the
class path defined by parameter (-cp).
Is it possible?
If you want to be able to use new keyword, you need to amend the classpath of the compiler javac -classpath path
otherwise at compile-time it will not know from where to load the class.
The compiler is loading classes for type checking.
(more infos here: http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html#searching)
It is not possible to use new keyword for classes loaded by a custom ClassLoader at runtime due to the compiler internal implementation of new keyword.
The compiler and JVM (runtime) have their own ClassLoaders, you cannot customize the javac classloader, the only part that can be customized from the compiler is the annotation processing as far as I know.
Related
I have a java library named testlib.jar and it has an abstract class as below:
com.test;
public abstract class AbstractClass {
public abstract void foo();
}
and a class named MyClass as below:
com.test;
public class MyClass extends AbstractClass{
#Override
public void foo() {
System.out.println("foo is called in MyClass");
}
}
that's it. In my application I have an abstract class as below:
com.test;
public abstract class AbstractClass {
public abstract void foo();
}
So it's the same as the abstract class in testlib.jar. Here I loaded the testlib.jar file and instantiated an object of MyClass and casted it to the abstract class that is known to my application (the second one):
URLClassLoader urlClassLoader = new URLClassLoader(
new URL[]{new URL("file:\\path\\to\\testlib.jar")});
Class<AbstractClass> MyClass = (Class<AbstractClass>)urlClassLoader.loadClass("com.test.MyClass");
Constructor<AbstractClass> c = MyClass.getConstructor();
AbstractClass myObject = c.newInstance();
myObject.foo();
Output:
foo is called in MyClass
Note that I'm casting MyClass to a class that is not really its parent.
Know here I've two questions:
1- Does this approach work in all JVMs?
2- How does JVM handle this cast operation internally?
I think that what you are trying to do is counter intuitive and error prone.
You don't have to use such an approach. Instead, extract the API that you want to share in a library and share it in the both sides : provider and clients.
Besides your example is flawed because you use the URLClassLoader constructor that rely on the parent class loader :
URLClassLoader urlClassLoader = new URLClassLoader(
new URL[]{new URL("file:\\path\\to\\testlib.jar")});
As stated by the documentation :
Constructs a new URLClassLoader for the specified URLs using the
default delegation parent ClassLoader. The URLs will be searched in
the order specified for classes and resources after first searching in
the parent class loader.
So it means that here :
Class<AbstractClass> MyClass = (Class<AbstractClass>)urlClassLoader.loadClass("com.test.MyClass");
you load the class defined in your current classpath, not in the JAR.
So this assignment can only work as the AbstractClass declared type refers also which one in the current classpath, not in the JAR :
AbstractClass myObject = c.newInstance();
Don't use the default parent classloader and you would see probably the throw of java.lang.ClassCastException :
URLClassLoader urlClassLoader = new URLClassLoader(
new URL[]{new URL("file:\\path\\to\\testlib.jar")}, null);
Just tested this approach on Android 7.1.1 using DexClassLoader and it worked, but it failed when testing on Android 4.2.2.
I would like to receive the results dynamically invoke a class in another jar.
For example,
'A' directory in a file named A.jar.
'B' directory in a file named B.jar.
I want to dynamically invoke a class of A.jar file to B.jar file.
This is the main class of A.jar file.
Socket and RMI are not considered because the message exchange technology.
Main Class (B.jar)
public class main {
public static void main(String[] args) {
//It dynamically creates an object of a Message Class in A.jar.
//And it invoke the getMessage function.
//And Save the return value.
}}
Message Class (A.jar)
public class message{
public String getMessage(){
return "Hello!";
}
}
First, you need to dynamically create an instance of the given class:
Class<?> clazz = Class.forName("message");
Object messageObj = clazz.newInstance();
This assumes that the .jar file which contains the message class is on the classpath so that the Java runtime is able to find it. Otherwise, you need to manually load the jar file through a class loader, e.g. like in How should I load Jars dynamically at runtime?. Assumed that your A.jar file which contains the message class is located at c:\temp\A.jar, you can use something like
URLClassLoader child = new URLClassLoader (
new URL[] {new URL("file:///c:/temp/A.jar")}, main.class.getClassLoader());
Class<?> clazz = Class.forName("message", true, child);
to load the jar file and load the message class from it.
In both cases, you can then retrieve the method from the class and invoke it on the created instance:
Method getMessage = clazz.getMethod("getMessage");
String result = (String) getMessage.invoke(messageObj);
I'm trying to create an exploit, I want to load a class from outside the netbeans project, of a subclassed cash register, which should have been made final.
I can load the LegitClass fine from within the original package badclassloader, with:
claz = "badclassloader.LegitClass"
loadClass = this.getClass().getClassLoader().loadClass(claz);
(LegitClass)loadClass.newInstance();
Now I want to load the MalClass which lives in another project and package 'Package Mal'
How do I get the Mal.MalClass into the path for the LoadClass() method to find?
I tried the following:
private void loadLibrary() {
if (library != null) {
AccessController.doPrivileged(new PrivilegedAction() {
#Override
public Object run() {
System.loadLibrary("Path/to/Project/mal.jar");
return null;
}
});
}
}
but firstly I got Directory separator should not appear in library name So that clearly isn't it, I'm pretty sure I'm missing something considerable here.
Create a new, custom class loader...
URLClassLoader cl = new URLClassLoader(
new URL[]{new File("Path/to/Project/mal.jar").toURI().toURL()}
);
Load the class from the class loader...
Class clazz = cl.loadClass("Mal.MalClass");
Create a new instance of the class...
Object obj = clazz.newInstance();
Now. You have a problem. Unless the Class you've just loaded implements a interface which is common to both projects, you won't be able to resolve the instance of the Class to an actual type, as the type would be unknown at compile time. Instead, you would be forced to use reflection, which is never pretty...
I am loading a plug-in dynamically. Both the plug-in and the software
have been created by us.
I have an Interface lets call it Foo. There is also FooImpl that just
implements that method But FooImpl is in the jar loaded dynamically
public interface Foo {
void write(..someArgument..) throws Exception; }
I have also a PluginLoader class here is the method public
Object loadPlugin(final String jarPath, final Class
pluginInterface) {
try
{
final URI uri = new File(jarPath).toURI();
final URL url = uri.toURL();
final URLClassLoader ucl = new URLClassLoader(new URL[] { url });
try
{
final Class<?> pluginClass = Class.forName("FooImpl", true, ucl);
// Verify if plugin implements plugin interface.
if (pluginClass.getInterfaces()[0].getName().equals(pluginInterface.getName()))
{
// Instantiate plugin.
return pluginClass.newInstance();
} }//[...] </code></pre>
This part is actually working well i think so because after doing some
sysout on the pluginClass i notice: the .getMethods() =
[public void FooImpl.write(..someArgumentType..) throws Exception,
public abstract void some.package.Foo.write(..someArgumentType..)
throws Exception]
the .getGenericInterfaces() = [interface some.package.Foo]
But when i try to call the method write here is what i get
java.lang.AbstractMethodError: FooImpl.write(..SomeArgumentType..;)V
I dont know why there is a ";" and a "V"
So basically i think that it try to call the interface method instead
of the implemented one. So i'm wondering What is going on!
As usual, Thank you for your time and help
An AbstractMethodError suggests that your code is trying to use a different version of a class at runtime compared to the class it was originally built against. You need to ensure that in your execution environment, there isn't a rogue version of your interface implementation on the classpath.
I have a ClassLoader which loads a class compiled by JavaCompiler from a source file.
But when I change the source file, save it and recompile it, the ClassLoader still loads the first version of the class.
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<?> compiledClass = cl.loadClass(stringClass);
What am I missing? like a newInstance or something?
A classloader can't replace a class that already has been loaded. loadClass will return the reference of the existing Class instance.
You'll have to instantiate a new classloader and use it to load the new class. And then, if you want to "replace" the class, you'll have to throw this classloader away and create another new one.
In response to your comment(s): do something like
ClassLoader cl = new UrlClassLoader(new URL[]{pathToClassAsUrl});
Class<?> compiledClass = cl.loadClass(stringClass);
This classloader will use the "default delegation parent ClassLoader" and you have to take care, the class (identified by it fully qualified classname) has not been loaded and can't be loaded by that parent classloader. So the "pathToClassAsUrl" shouldn't be on the classpath!
You have to load a new ClassLoader each time, or you have to give the class a different name each time and access it via an interface.
e.g.
interface MyWorker {
public void work();
}
class Worker1 implement MyWorker {
public void work() { /* code */ }
}
class Worker2 implement MyWorker {
public void work() { /* different code */ }
}
As it was stated before,
Each class loader remembers (caches) the classes that is has loaded before and won't reload it again - essentially each class loader defines a namespace.
Child class loader delegates class loading to the parent class loader, i.e.
Java 8 and before
Custom Class Loader(s) -> App Class Loader -> Extension Class Loader -> Bootstrap Class Loader
Java 9+
Custom Class Loader(s) -> App Class Loader -> Platform Class Loader -> Bootstrap Class Loader.
From the above we can conclude that each Class object is identified by its fully qualified class name and the loader than defined it (also known as defined loader)
From Javadocs :
Every Class object contains a reference to the ClassLoader that
defined it.
The method defineClass converts an array of bytes into an instance of
class Class. Instances of this newly defined class can be created
using Class.newInstance.
The simple solution to reload class is to either define new (for example UrlClassLoader) or your own custom class loader.
For more complex scenario where you need to substitute class dynamic proxy mechanism can be utilized.
Please see below simple solution I used for a similar problem to reload same class by defining custom class loader.
The essence - override findClass method of the parent class loader and then load the class from bytes read from the filesystem.
MyClassLoader - overrides findClass and executed defineClass
package com.example.classloader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class MyClassLoader extends ClassLoader {
private String classFileLocation;
public MyClassLoader(String classFileLocation) {
this.classFileLocation = classFileLocation;
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classBytes = loadClassBytesFromDisk(classFileLocation);
return defineClass(name, classBytes, 0, classBytes.length);
}
private byte [] loadClassBytesFromDisk(String classFileLocation) {
try {
return Files.readAllBytes(Paths.get(classFileLocation));
}
catch (IOException e) {
throw new RuntimeException("Unable to read file from disk");
}
}
}
SimpleClass - experiment subject -
** IMPORTANT : Compile with javac and then remove SimpleClass.java from class path (or just rename it)
Otherwise it will be loaded by System Class Loader due to class loading delegation mechanism.**
from src/main/java
javac com/example/classloader/SimpleClass.java
package com.example.classloader;
public class SimpleClassRenamed implements SimpleInterface {
private static long count;
public SimpleClassRenamed() {
count++;
}
#Override
public long getCount() {
return count;
}
}
SimpleInterface - subject interface : separating interface from implementation to compile and execute output from the subject.
package com.example.classloader;
public interface SimpleInterface {
long getCount();
}
Driver - execute to test
package com.example.classloader;
import java.lang.reflect.InvocationTargetException;
public class MyClassLoaderTest {
private static final String path = "src/main/java/com/example/classloader/SimpleClass.class";
private static final String className = "com.example.classloader.SimpleClass";
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException { // Exception hell due to reflection, sorry :)
MyClassLoader classLoaderOne = new MyClassLoader(path);
Class<?> classOne = classLoaderOne.loadClass(className);
// we need to instantiate object using reflection,
// otherwise if we use `new` the Class will be loaded by the System Class Loader
SimpleInterface objectOne =
(SimpleInterface) classOne.getDeclaredConstructor().newInstance();
// trying to re-load the same class using same class loader
classOne = classLoaderOne.loadClass(className);
SimpleInterface objectOneReloaded = (SimpleInterface) classOne.getDeclaredConstructor().newInstance();
// new class loader
MyClassLoader classLoaderTwo = new MyClassLoader(path);
Class<?> classTwo = classLoaderTwo.loadClass(className);
SimpleInterface ObjectTwo = (SimpleInterface) classTwo.getDeclaredConstructor().newInstance();
System.out.println(objectOne.getCount()); // Outputs 2 - as it is the same instance
System.out.println(objectOneReloaded.getCount()); // Outputs 2 - as it is the same instance
System.out.println(ObjectTwo.getCount()); // Outputs 1 - as it is a distinct new instance
}
}
I think the problem might be more basic than what the other answers suggest. It is very possible that the class loader is loading a different file than what you think it is. To test out this theory, delete the .class file (DO NOT recompile your .java source) and run your code. You should get an exception.
If you do not get the exception, then obviously the class loader is loading a different .class file than the one you think it is. So search for the location of another .class file with the same name. Delete that .class file and try again. Keep trying until you find the .class file that is actually being loaded. Once you do that, you can recompile your code and manually put the class file in the correct directory.