简体   繁体   English

URLClassLoader和package-private方法的可访问性

[英]URLClassLoader and accessibility of package-private methods

I have a class Formula , located in package javaapplication4 , which I load with a URLClassLoader. 我有一个类Formula ,位于javaapplication4包中,我使用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). 但是,当我从位于同一个包中的另一个类Test1调用它时,我无法访问其具有默认访问修饰符的方法(我可以访问公共方法)。

I get the following exception: 我得到以下异常:

java.lang.IllegalAccessException: Class javaapplication4.Test1 can not access a member of class javaapplication4.Formula with modifiers "" java.lang.IllegalAccessException:类javaapplication4.Test1无法使用修饰符“”访问类javaapplication4.Formula的成员

How can I access package-private methods of a class loaded at runtime from the same package? 如何从同一个包中访问在运行时加载的类的包私有方法?

I suppose it is a problem with using a different class loader, but not sure why (I have set the parent of the URLClassLoader). 我想使用不同的类加载器是一个问题,但不确定为什么(我已经设置了URLClassLoader的父级)。

SSCCE reproducing the issue (Windows paths) - I suppose the issue is in the loadClass method: SSCCE再现问题(Windows路径) - 我想问题是在loadClass方法中:

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

A class at runtime is identified by both its fully qualified name and its ClassLoader. 运行时的类由其完全限定名称及其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. 例如,当您测试两个Class<T>对象是否相等时,如果它们具有相同的规范名称但是从不同的ClassLoader加载,则它们将不相等。

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. 对于属于同一个包的两个类(并且反过来能够访问包私有方法),它们也需要从同一个ClassLoader加载,这不是这里的情况。 In fact Test1 is loaded by the system classloader, while the Formula is loaded by the URLClassLoader created inside loadClass() . 实际上, Test1由系统类加载器加载,而公式由loadClass()内部创建的URLClassLoader加载。

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). 如果为URLClassLoader指定父加载器以使其加载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. 我不认为你可以通过相同的Test1 ClassLoader加载Formula类(你必须使用一个众所周知的路径并把它放在CLASSPATH上),但我发现了一种相反的方法:加载另一个实例用于加载公式的ClassLoader中的Test1 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;
  }
}

Here is the pastebin . 这是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. 几个注意事项:我为URLClassLoader指定了一个null父,以避免上面列出的问题,我操纵字符串来实现目的 - 但不知道这种方法在其他部署方案中有多强大。 Also, the URLCLassLoader I used only searches in two directories to find class definitions, not all the entries listed in the CLASSPATH 另外,我使用的URLCLassLoader只搜索两个目录来查找类定义,而不是CLASSPATH中列出的所有条目

The answer is: 答案是:

In the sun.reflect.Reflection package there is a method called isSameClassPackage (the actual signature is private static boolean isSameClassPackage(ClassLoader arg0, String arg1, ClassLoader arg2, String arg3); ). sun.reflect.Reflection包有一个名为方法isSameClassPackage (实际签名是private static boolean isSameClassPackage(ClassLoader arg0, String arg1, ClassLoader arg2, String arg3); )。 This method is responsible for deciding whether two classes belong to the same package or not. 此方法负责决定两个类是否属于同一个包。

The first check this method is doing that it compares arg0 and arg2, (the two classloaders) if they are different, it returns false. 第一次检查这个方法是否比较了arg0和arg2,(两个类加载器)如果它们不同,则返回false。

So, if you use different classloaders for the two classes, it will not match. 因此,如果您为这两个类使用不同的类加载器,则它将不匹配。

EDIT: The full call chain (on request) is: 编辑:完整的调用链(根据要求)是:

Method.invoke()
Method.checkAccess() -- fallback to the real check
Method.slowCheckMemberAccess()   -- first thing to do to call
Reflection.ensureMemberAccess()  -- check some nulls, then
Reflection.verifyMemberAccess()  -- if public, it,'s OK, otherwise check further
Reflection.isSameClassPackage(Class, Class) -- get the class loaders of 2 classes
Reflection.isSameClassPackage(ClassLoader, String, ClassLoader, String) 

I found the explanation in the JVM specification 5.4.4 (emphasis mine): 我在JVM规范5.4.4 (强调我的)中找到了解释:

A field or method R is accessible to a class or interface D if and only if any of the following conditions are true: 当且仅当满足以下任一条件时,才能访问类或接口D的字段或方法R.

  • [...] [...]
  • R is either protected or has default access (that is, neither public nor protected nor private), and is declared by a class in the same runtime package as D. R受保护或具有默认访问权(即既不公开也不受保护也不是私有),并且由与D 相同的运行时包中的类声明。

And the runtime package is defined in the specs #5.3 : 运行时包在规范#5.3中定义:

The runtime package of a class or interface is determined by the package name and defining class loader of the class or interface. 类或接口的运行时包由包名称类或接口的类加载器定义

Bottom line: this is the expected behaviour. 底线:这是预期的行为。

Add c:\\temp to java classpath and load Formula.class with the same ClassLoader as Test1.class c:\\temp添加到java类路径并使用与Test1.class相同的ClassLoader加载Formula.class

Class<?> formulaClass = Class.forName(className);

this will solve your problem. 这将解决您的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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