如何在 Java 中使用自定义类加载器到新对象

How to use Custom ClassLoader to new Object in Java

我想创建一个自定义 ClassLoader 来加载某个路径中的所有 jar 文件(例如 /home/custom/lib)。

那么我希望每次我使用new操作符创建一个对象时,它会在该路径下的所有jar文件中搜索class,然后搜索定义的class路径按参数 (-cp).

可能吗?

例如/home/custom/lib/a.jar

中有一个jar文件

主要 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(); 
    }
}

class 加载程序无法完全按照您的预期进行。

引用 another answer 相关问答:

Java will always use the classloader that loaded the code that is executing.

以你的例子为例:

public static void main(String[] args) {
    // whatever you do here...
    Car car = new Car(); // ← this code is already bound to system class loader
}

最接近的方法是使用 child-first(parent-last)class loader 例如 this one,用你的 jar,然后使用反射从那个 jar 创建一个 Car 的实例。

Car class a.jar:

package com.acme;
public class Car {
    public String honk() {
        return "Honk honk!";
    }
}

您的主要应用:

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!
}

注意:如果您的 class 路径中也有一个 com.acme.Car class,那是不一样的 class,因为 class 是由其全名和 class 加载程序标识。

为了说明这一点,假设我使用我的本地 Car class 如下所示,而 carClass 由我的自定义 class 加载程序如上加载:

Car someCar = (Car) carClass.newInstance();
// java.lang.ClassCastException: com.acme.Car cannot be cast to com.acme.Car

可能会造成混淆,但这是因为名称本身并不能识别 class。该转换无效,因为 2 classes 不同。它们可能具有不同的成员,或者它们可能具有相同的成员但实现不同,或者它们可能逐字节相同:它们不相同 class.

现在,这不是一个很有用的东西。
当您的 jar 中的自定义 classes 实现一个通用 API 时,这些东西变得 有用,主程序知道如何使用 .

例如,假设接口 Vehicle(具有方法 String honk())在公共 class 路径中,而您的 Cara.jar 中并实现 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!

这类似于 servlet 容器的作用:

  • 您的应用程序实现了 servlet API(例如 class 实现了 javax.servlet.Servlet
  • 它被打包到一个 war 文件中,servlet 容器可以使用自定义 class 加载器加载
  • 部署描述符(web.xml 文件)告诉 servlet 容器它需要实例化的 servlet(classes)的名称(正如我们上面所做的)
  • 那些 class 是 Servlet,servlet 容器可以这样使用它们

在您的情况下,您不需要编写新的 ClassLoader,因为您唯一想做的就是在运行时扩展 classpath。 为此,您获取当前的 SystemClassLoader 实例,并使用 URLClassLoader.

添加 classpath 条目。

JDK8 的工作示例:

汽车class编译并位于C:\Users\xxxx\Documents\sources\test\target\classes

public class Car {
    public String prout() {
        return "Test test!";
    }
}

主要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});
}
  • 注意我们需要使用反射,因为方法 addURL(URL u)protected
  • 还请注意,由于我们将 classpath 条目添加到 SystemClassloader,因此您无需在每次需要时都添加 classpath 条目,只需一次就足够了,然后使用 ClassLoader.getSystemClassLoader().loadClass(String name) 从先前添加的 class 路径条目加载 class。

如果您以后不需要那个 class 路径条目,您可以实例化您自己的 URLClassLoader 实例并相应地加载 classes,而不是设置 classSystemClassLoader 上的路径条目。 即:

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();
        }
}

来源: 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?

如果要能够使用new关键字,需要修改class编译器的路径javac -classpath path 否则在编译时它将不知道从哪里加载 class.

编译器正在加载 classes 以进行类型检查。 (更多信息在这里:http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html#searching

由于 new 关键字的编译器内部实现,无法在运行时将 new 关键字用于自定义类加载器加载的 classes。

编译器和JVM(运行时)都有自己的ClassLoader,你不能自定义javac classloader,据我所知唯一可以从编译器自定义的部分是注解处理。