URLClassLoader 和包私有方法的可访问性

2022-01-13 00:00:00 package java classloader classpath

我有一个类 Formula,位于包 javaapplication4 中,我使用 URLClassLoader 加载它.但是,当我从位于同一包中的另一个类 Test1 调用它时,我无法访问其具有默认访问修饰符的方法(我可以访问公共方法).

I have a class Formula, located in package javaapplication4, which I load with a URLClassLoader. However, when I call it from another class Test1, located in the same package, I can't access its methods that have a default access modifier (I can access public methods).

我得到以下异常:

java.lang.IllegalAccessException:类 javaapplication4.Test1 无法访问带有修饰符"的类 javaapplication4.Formula 的成员

java.lang.IllegalAccessException: Class javaapplication4.Test1 can not access a member of class javaapplication4.Formula with modifiers ""

如何访问在运行时从同一个包加载的类的包私有方法?

我想这是使用不同的类加载器的问题,但不知道为什么(我已经设置了 URLClassLoader 的父级).

I suppose it is a problem with using a different class loader, but not sure why (I have set the parent of the URLClassLoader).

SSCCE 重现问题(Windows 路径) - 我想问题出在 loadClass 方法中:

SSCCE reproducing the issue (Windows paths) - I suppose the issue is in the loadClass method:

public class Test1 {

    private static final Path TEMP_PATH = Paths.get("C:/temp/");

    public static void main(String[] args) throws Exception {
        String thisPackage = Test1.class.getPackage().getName();
        String className = thisPackage + ".Formula"; //javaapplication4.Formula
        String body = "package " + thisPackage + ";   "
                    + "public class Formula {         "
                    + "    double calculateFails() {  "
                    + "        return 123;            "
                    + "    }                          "
                    + "    public double calculate() {"
                    + "        return 123;            "
                    + "    }                          "
                    + "}                              ";

        compile(className, body, TEMP_PATH);
        Class<?> formulaClass = loadClass(className, TEMP_PATH);

        Method calculate = formulaClass.getDeclaredMethod("calculate");
        double value = (double) calculate.invoke(formulaClass.newInstance());
        //next line prints 123
        System.out.println("value = " + value);

        Method calculateFails = formulaClass.getDeclaredMethod("calculateFails");
        //next line throws exception:
        double valueFails = (double) calculateFails.invoke(formulaClass.newInstance());
        System.out.println("valueFails = " + valueFails);
    }

    private static Class<?> loadClass(String className, Path path) throws Exception {
        URLClassLoader loader = new URLClassLoader(new URL[]{path.toUri().toURL()}, Test1.class.getClassLoader());
        return loader.loadClass(className);
    }

    private static void compile(String className, String body, Path path) throws Exception {
        List<JavaSourceFromString> sourceCode = Arrays.asList(new JavaSourceFromString(className, body));

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(path.toFile()));
        boolean ok = compiler.getTask(null, fileManager, null, null, null, sourceCode).call();

        System.out.println("compilation ok = " + ok);
    }

    public static class JavaSourceFromString extends SimpleJavaFileObject {
        final String code;

        JavaSourceFromString(String name, String code) {
            super(URI.create("string:///" + name.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension),
                    JavaFileObject.Kind.SOURCE);
            this.code = code;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return code;
        }
    }
}

推荐答案

运行时的类由其完全限定名和 ClassLoader 标识.

A class at runtime is identified by both its fully qualified name and its ClassLoader.

例如,当您测试两个 Class<T> 对象的相等性时,如果它们具有相同的规范名称但从不同的 ClassLoader 加载,则它们将不相等.

For example, when you test two Class<T> objects for equality, if they have the same canonical name but were loaded from different ClassLoaders, they won't be equal.

对于属于同一个包的两个类(进而能够访问包私有方法),它们也需要从同一个 ClassLoader 加载,这里不是这种情况.实际上 Test1 是由系统类加载器加载的,而 Formula 是由 loadClass() 内部创建的 URLClassLoader 加载的.

For two classes to belong to the same package (and in turn being able to access package-private methods), they need to be loaded from the same ClassLoader too, which is not the case here. In fact Test1 is loaded by the system classloader, while the Formula is loaded by the URLClassLoader created inside loadClass().

如果您为 URLClassLoader 指定父加载器以使其加载 Test1,仍然会使用两个不同的加载器(您可以通过断言加载器相等性来检查它).

If you specify a parent loader for your URLClassLoader in order to make it load Test1, still two different loaders are used (you can check it by asserting loaders equality).

我认为你不能让 Formula 类由同一个 Test1 ClassLoader 加载(你必须使用众所周知的路径并将其放在CLASSPATH),但我找到了一种相反的方法:在用于加载公式的 ClassLoader 中加载 Test1 的另一个实例.这是伪代码中的布局:

I don't think you can make the Formula class loaded by the same Test1 ClassLoader (you'd have to use a well-known path and put it on the CLASSPATH), but I found a way to do the opposite: loading another instance of Test1 in the ClassLoader used for loading the formula. This is the layout in pseudocode:

class Test1 {

  public static void main(String... args) {
    loadClass(formula);
  }

  static void loadClass(location) {
    ClassLoader loader = new ClassLoader();
    Class formula = loader.load(location);
    Class test1 = loader.load(Test1);
    // ...
    Method compute = test1.getMethod("compute");
    compute.invoke(test1, formula);
  }

  static void compute(formula) {
    print formula;
  }
}

这里是 pastebin.几点注意事项:我为 URLClassLoader 指定了一个 null 父级以避免上面列出的问题,并且我操纵了字符串来达到目的 - 但不知道这种方法在其他部署中的鲁棒性如何情景.另外,我使用的 URLCLassLoader 仅在两个目录中搜索以查找类定义,而不是 CLASSPATH 中列出的所有条目

Here is the pastebin. A couple of notes: I specifed a null parent for the URLClassLoader to avoid the issue listed above, and I manipulated strings to achieve the purpose - but don't know how robust this approach can be in other deployment scenarios. Also, the URLCLassLoader I used only searches in two directories to find class definitions, not all the entries listed in the CLASSPATH

相关文章