Reputation: 1629
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();
}
}
Upvotes: 6
Views: 9884
Reputation: 331
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.
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});
}
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.
Upvotes: 0
Reputation: 20467
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:
javax.servlet.Servlet
)web.xml
file) tells the servlet container the names of the servlets (classes) that it needs to instanciate (as we did above)Servlet
s, the servlet container can use them as suchUpvotes: 12