URLClassLoader的和包私有方法的无障碍(URLClassLoader and acces

2019-07-18 03:28发布

我有一个类Formula ,位于包javaapplication4 ,其余与URLClassLoader的加载。 然而,当我把它从另一个类Test1 ,位于同一个包,我无法访问其方法有默认访问修饰符(我可以访问公共方法)。

我得到以下异常:

java.lang.IllegalAccessException:类javaapplication4.Test1不能访问类javaapplication4.Formula的成员与改性剂“”

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

我想这是与使用不同的类加载器有问题,但不知道为什么(我已经设置了一个URLClassLoader的父母)。

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

Answer 1:

在运行时类是由它的全名及其ClassLoader的这两个标识。

例如,当你测试两种Class<T>相等的对象,如果它们具有相同的规范名称但来自不同类加载器加载,它们将不相等。

对于两类属于同一个包(进而能够访问包私有方法),他们需要从同一个类加载器加载过,这是不是这里的情况。 事实上Test1是由系统类加载器加载,而公式由内部创建的URLClassLoader的加载loadClass()

如果您在订货时说明您的URLClassLoader父加载器,使其加载Test1 ,仍使用两种不同的装载机(您可以通过声明装载机平等检查)。

我不认为你可以使Formula由同一个加载的类Test1类加载器(你必须使用一个众所周知的路径,并把它放在CLASSPATH),但我找到了一种方法做相反的:加载另一个实例的Test1在类加载器用于加载式。 这是伪代码的布局:

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

下面是引擎收录 。 有两点要注意:我specifed一个null父为的URLClassLoader避免上面列出的问题,我操纵串来达到目的-但不知道这种方法是如何强大的是其他的部署方案。 另外,我用了一个URLClassLoader只在两个目录搜索找到类的定义,不是所有的条目中列出的CLASSPATH



Answer 2:

答案是:

sun.reflect.Reflection包有一个名为方法isSameClassPackage (实际签名是private static boolean isSameClassPackage(ClassLoader arg0, String arg1, ClassLoader arg2, String arg3); )。 这种方法是负责决定两个类是否属于同一个包或没有。

首先检查这个方法做它比较为arg0和ARG2,(这两个类加载器),如果他们是不同的,它返回false。

所以,如果你使用不同的类加载器的两个类,它不会匹配。

编辑:完整的调用链(收费)为:

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) 


Answer 3:

我发现在解释JVM规范5.4.4 (重点煤矿):

字段或方法R是一个类或接口d访问当且仅当以下任一条件,则:

  • [...]
  • R是受保护的或具有默认的访问(即,既没有公开也没有保护也不私人),并且由一类在同一运行时包为D.声明

而运行时包中定义的规格#5.3 :

一个类或接口的运行时包由包名称类或接口的定义类装载程序来确定。

底线:这是预期的行为。



Answer 4:

添加c:\temp到Java类路径和相同的类加载器加载Test1.class Formula.class

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

这将解决您的问题。



文章来源: URLClassLoader and accessibility of package-private methods