简体   繁体   中英

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(eg /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 (eg 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 Servlet s, 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. ie:

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(eg /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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM