Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Custom ClassLoader to new Object in Java

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(); 
    }
}
like image 505
fudy Avatar asked Jan 04 '23 12:01

fudy


1 Answers

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
like image 56
Hugues M. Avatar answered Jan 16 '23 10:01

Hugues M.