【问题标题】:How to use Custom ClassLoader to new Object in Java如何使用自定义类加载器在 Java 中创建新对象
【发布时间】:2017-02-17 12:06:23
【问题描述】:

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

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

有可能吗?

例如/home/custom/lib/a.jar中有一个jar文件

在主类中

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

【问题讨论】:

  • 为什么要首先创建自定义类加载器?在应用程序调用时将 lib 目录附加到类路径还不够吗?这样您就可以使用 new 来实例化对象,Java 会自动从您的 lib 目录中的依赖项中查找定义。类加载器用于在运行时将代码/类动态添加到您的应用程序中,或者在不使用时释放某些资源(即插件机制)

标签: java classloader


【解决方案1】:

类加载器无法完全按照您的预期执行。

引用相关问答的another answer

Java 将始终使用加载正在执行的代码的类加载器。

你的例子:

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) 类加载器,例如 this one,用你的 jar 实例化它,然后使用反射创建一个Car 来自那个罐子。

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

注意:如果您的类路径中还有一个com.acme.Car 类,那不是同一个类,因为一个类是由其全名和类加载器标识的。

为了说明这一点,假设我使用了我的本地 Car 类,如下所示,carClass 由我的自定义类加载器按上述方式加载:

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

可能会造成混淆,但这是因为名称本身并不能识别类。该演员表无效,因为这两个班级不同。它们可能有不同的成员,或者它们可能有相同的成员但实现不同,或者它们可能逐字节相同:它们不是同一个类。

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

例如,假设接口Vehicle(具有方法String honk())在公共类路径中,而您的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(例如,实现 javax.servlet.Servlet 的类)
  • 它被打包成一个war文件,servlet容器可以通过自定义类加载器加载
  • 部署描述符(web.xml 文件)告诉 servlet 容器它需要实例化的 servlet(类)的名称(就像我们上面所做的那样)
  • 那些是Servlets 的类,servlet 容器可以这样使用它们

【讨论】:

    【解决方案2】:

    在您的情况下,您不需要编写新的 ClassLoader,因为您唯一想做的就是在运行时扩展您的类路径。 为此,您将获得当前的 SystemClassLoader 实例,并使用 URLClassLoader 将类路径条目添加到其中。

    JDK 8 的工作示例:

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

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

    主类

    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
    • 还要注意,由于我们将类路径条目添加到 SystemClassloader,您不需要每次都添加类路径条目,只需一次即可,然后使用ClassLoader.getSystemClassLoader().loadClass(String name) 从之前添加的类路径条目中加载类。

    如果您不需要该类路径条目以供以后使用,您可以实例化您自己的 URLClassLoader 实例并相应地加载类,而不是在 SystemClassLoader 上设置类路径条目。 即:

    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/


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

    那么我希望每次使用 new 运算符创建一个对象时, 它将在该路径中的所有 jar 文件中搜索类,然后搜索 由参数(-cp)定义的类路径。

    有可能吗?

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

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

    由于new关键字的编译器内部实现,在运行时由自定义类加载器加载的类不能使用new关键字。

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

    【讨论】:

    • 由自定义类加载器加载并包含新操作的代码将使用加载该代码的类加载器来执行新操作。根据您的报价,它只是启动加载程序。由于类加载器的委托模型,启动加载器可用于检索类信息,无论它是否加载了类定义本身或其父级之一,因为它应该始终首先询问其父级是否已经知道某个类的定义。只有当它的父母都不知道定义时,它才可以加载代码本身。
    • 是的,同意-误解了这里的报价,我的错。我编辑了我的答案并删除了关于它的部分
    猜你喜欢
    • 1970-01-01
    • 2017-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-19
    • 2016-08-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多