【问题标题】:Load class from exploded module using custom classloader使用自定义类加载器从分解的模块中加载类
【发布时间】:2017-12-06 23:48:01
【问题描述】:

我一直在使用 Java 中的简单自定义类加载器,到目前为止,对于非模块相关的类,一切都按预期工作。但是,即使与模块相关的find*() 方法已被重载,我似乎也找不到使用我的类加载器从模块加载类的任何方法。我试图做的是从模块“HelloModularWorld”加载一个类并运行它。但是,当我指定包所在的目录时,它会“正常”加载并报告为在未命名的模块中。我错过了什么?

类加载器只是从文件系统的其他地方加载类,没什么特别的。

类加载器实现:

package arch.classloaders;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;

public class ModifiableClassLoader extends ClassLoader {
    private File searchPath;
    //...other ctors...
    public ModifiableClassLoader(File path, String name, ClassLoader parent) {
        super(name, parent); //Delegate to parent classloader as required.
        if (!path.isDirectory()) throw new IllegalArgumentException("Path must be a directory");
        searchPath = path;
    }

    public void setPath(File newPath) {...}

    public File getPath() {...}

    //Responsible for actual loading
    public Class<?> findClass(String binName) throws ClassNotFoundException {
        File classfile = new File(searchPath.getPath() + File.separator 
                + binName.replace('.', File.separatorChar) + ".class");
        byte[] buf;
        FileInputStream fis;
        try {
            fis = new FileInputStream(classfile);
            buf = fis.readAllBytes();
            fis.close();
        } catch (IOException e) {
            throw new ClassNotFoundException("Error in defining " + binName + " in " + searchPath.getPath(),e);
        }
        return defineClass(binName, buf, 0, buf.length);
    }

    //Modules are expected to be in a folder with the same name as the module.
    //e.g. module hellomodularworld is expected to be in a folder
    //<SEARCHPATH>/<MODNAME>/
    //NOTE: Module-overloaded methods return null rather than throw when class isn't found.
    public Class<?> findClass(String modName, String binName) {
        if (null == modName) {
            try {
                return findClass(binName);
            } catch (ClassNotFoundException e) {
                return null;
            }
        }
        File classfile = new File(searchPath.getPath() + File.separator 
                + modName + File.separator
                + binName.replace('.', File.separatorChar) + ".class");
        byte[] buf;
        FileInputStream fis;
        try {
            fis = new FileInputStream(classfile);
            buf = fis.readAllBytes();
            fis.close();
        } catch (IOException e) {
            return null;
        }
        return defineClass(binName, buf, 0, buf.length);
    }

    //Non-module
    public URL findResource(String resPath) {...}

    //Module version
    public URL findResource(String modName, String resPath) throws IOException {...}

    //Enumeration version; does nothing.
    public java.util.Enumeration<URL> findResources(String resPath) {...}
}

测试代码:

public class Test {
    public static void main(String[] args) {
        ModifiableClassLoader mcl = new ModifiableClassLoader(
                new File("C:\\Users\\archd\\Desktop\\"));
        try {
            Class<?> clazz = mcl.loadClass(
                    "hellomodularworld/com.archdukeliamus.hellomodularworld.HelloWorld"
            );
            java.lang.reflect.Method mth = clazz.getMethod("main", String[].class);
            mth.invoke(null,new Object[] {null});
            System.out.println(clazz.getModule().getName());
        } catch (...) {
                //omitted
        }
}

在线Class&lt;?&gt; clazz = mcl.loadClass("hellomodularworld/com.archdukeliamus.hellomodularworld.HelloWorld");我试过了:

  • “hellomodularworld/com.archdukeliamus.hellomodularworld.HelloWorld” - 不工作。 ClassNotFoundException。
  • “hellomodularworld.com.archdukeliamus.hellomodularworld.HelloWorld” - NoClassDefFoundException。
    com/archdukeliamus/hellomodularworld/HelloWorld (wrong name: hellomodularworld/com/archdukeliamus/hellomodularworld/HelloWorld)
  • “com.archdukeliamus.hellomodularworld.HelloWorld” - 按预期工作(类加载文件夹已适当更改)但使用未命名的模块。

编辑:模块信息.java

module hellomodularworld {

}

测试类不在任何模块中。 (我不完全确定为什么这很重要,我应该得到一个例外,即我没有得到“这个包没有导出”的效果。)

编辑 2: 修改模块以包含exports com.archdukeliamus.hellomodularworld;。结果没有变化。

【问题讨论】:

  • 我还没有真正使用过 Java 9 模块,但我很确定你不能将那种字符串传递给 loadClass。见here
  • 虽然这确实澄清了二进制名称中似乎不允许使用模块,但它仍然引出了类加载器如何获取模块开头的问题。 ClassLoader 页面中似乎没有指定这一点。
  • 我要求这样做的原因是 如果请求加载其包未在任何已知模块中定义的类型,则模块系统将尝试从 类路径。如果成功,则该类型被认为是称为未命名模块的特殊模块的成员,以确保每个类型都与某个模块相关联
  • 代码片段只是一个类加载器,用于加载部署为模块的类。您创建配置并将模块映射到类加载器的代码在哪里,您必须在某个地方调用 ModuleLayer 的 defineModule,对吧?
  • 顺便说一句,你为什么要绕道FileInputStream?您可以简单地使用byte[] buf = Files.readAllBytes(searchPath.toPath().resolve(binName.replace('.', File.separatorChar) + ".class"));。从 Java 7 开始。

标签: java classloader java-9 java-platform-module-system java-module


【解决方案1】:

模块加载是一个独立于类加载的过程。要在运行时加载模块,您需要使用ModuleFinder 类创建一个新的模块层,并提供来自FileSystemPath(在本例中为磁盘)。然后您需要创建一个Configuration,您可以使用它来解析您的模块。为了确保模块加载过程的完整性,您需要派生 引导配置。然后你需要创建你的配置,包括一个ModuleFinder 来告诉你在哪里可以找到模块,以及一组要解析的模块。然后实例化你希望用来加载这些模块的类的类加载器,并将其传递给定义模块方法。最后实例化你的类。

新测试:

package arch.classloaders;

import java.lang.module.*;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.io.*;
import java.nio.*;
import java.nio.file.*;
import java.util.*;

public class Test2 {
    public static void main(String[] args) {
        //Get paths to module, and instantiate a ModuleFinder.
        Path pth = FileSystems.getDefault().getPath("C:\\Users\\archd\\Desktop");
        ModuleFinder mf = ModuleFinder.of(pth);
        //Create a new Configuration for a new module layer deriving from the boot configuration, and resolving
        //the "hellomodularworld" module.
        Configuration cfg = ModuleLayer.boot().configuration().resolve(mf,ModuleFinder.of(),Set.of("hellomodularworld"));
        //Create classloader
        ModifiableClassLoader mcl = new ModifiableClassLoader(
                new File("C:\\Users\\archd\\Desktop\\"));
        //make the module layer, using the configuration and classloader.
        ModuleLayer ml = ModuleLayer.boot().defineModulesWithOneLoader(cfg,mcl);
        //Show the configuration.
        System.out.println(ml.configuration()); //prints "hellomodularworld"
        try {
            //load and run class
            Class<?> clazz = ml.findLoader("hellomodularworld").loadClass(
                    "com.archdukeliamus.hellomodularworld.HelloWorld"
            );
            java.lang.reflect.Method mth = clazz.getMethod("main", String[].class);
            mth.invoke(null,new Object[] {null});
            //show the module this class is part of and list packages
            System.out.println(clazz.getModule()); //prints "module hellomodularworld"
            for (String pkgn : clazz.getModule().getPackages()) {
                System.out.println(pkgn); //prints "com.archdukeliamus.hellomodularworld"
            }
        } catch (ClassNotFoundException e) {
            ...omitted...
        }
    }
}

有趣的是,这仍然没有调用重载的基于模块的 findClass() 方法,尽管它似乎有效。

【讨论】:

  • API 中没有将模块限制在文件系统中的任何内容,它们当然可以从任何地方加载。无论如何,这个答案中的代码片段没有提到任何关于动态配置和模块层的内容——这些是在使用自定义类加载器从模块加载类之前需要的概念。
  • 我明白了。 JEP 并没有完全启发自定义类加载器如何与模块系统交互。是否有正确解释模块层的链接?
  • 在 javadoc 搜索中,查找“ModuleLayer”。请注意,这是一个非常高级的主题,因此请花时间熟悉这些概念。
猜你喜欢
  • 1970-01-01
  • 2011-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-08
相关资源
最近更新 更多