在父/子类加载器之间访问Package Private元素

时间:2014-02-22 20:41:34

标签: java classloader

在Java应用程序中,我声明了两个类,一个类(One)在ClassLoader A中声明,另一个类(Two)在ClassLoader B中声明.ClassLoader A是B的父级。这两个类都具有相同的包(即:org.test)。

我似乎无法访问One的包私有方法或来自Two事件的变量,尽管A是B'd的父类ClassLoader,我得到IllegalAccessError异常。我知道包私有可访问性基于包名和ClassLoader。

有没有办法重新关联OneTwo,以便Two可以访问One的Package Private元素?

以下是用于证明这一点的测试:

package org.test;

public class ClassLoaderTest {

    @Test
    public void testLoading() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        One one = new One();
        one.value = "test";

        MemoryClassLoader classLoader = new MemoryClassLoader();

        String name = one.getClass().getPackage().getName() + ".Two";

        classLoader.add(name,
            "package org.test;\n" +
            "\n" +
            "public class Two{\n" +
            "    public static String getValue(One one){\n" +
            "        return one.value;\n" +
            "    }\n" +
            "}");

        Class<?> twoClass = classLoader.loadClass(name);

        assertEquals("test", twoClass.getMethod("getValue", One.class).invoke(null, one));
    }
}

public class One{
    String value;
}

可以找到MemoryClassLoader here

错误:

testLoading(org.test.ClassLoaderTest)  Time elapsed: 0.214 sec  <<< ERROR!
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.test.ClassLoaderTest.testLoading(ClassLoaderTest.java:37)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    ....
Caused by: java.lang.IllegalAccessError: tried to access field org.test.One.value from class org.test.Two
    at org.test.Two.getValue(Two.java:5)
    ... 34 more

感谢。

修改

我制作了一个带有自我测试的Gist,展示了这个here

4 个答案:

答案 0 :(得分:3)

  

有没有办法重新关联One和Two,以便Two可以访问   One的Package Private元素?

简单回答:反思:;没有反思:

详细解答: Java有自己的机制来控制访问,防止程序包或类的用户依赖于该程序包或类的实现的不必要细节。如果允许访问,则称所访问的实体是可访问的。 辅助功能是一种静态属性,可以在编译时确定;它仅取决于类型和声明修饰符。

来自Java SE specs

  

如果成员或构造函数被声明为private,则访问权限为   当且仅当它出现在顶层的主体内时才被允许   包含成员或构造函数声明的类。一个   私有类成员或构造函数只能在正文中访问   封闭成员或的声明的顶级类   构造函数。它不是由子类继承的。

因此,在Java SE 中没有使用Reflection API是不可能的。

如果使用Reflection API,则可访问性的静态内省将更改为动态。动态加载类,动态完成绑定,并在需要时动态创建对象实例。历史上不是很有活力的是操纵“匿名”类的能力。在此上下文中,匿名类是在运行时加载或呈现给Java类的类,其类型以前是Java程序所不知道的。

班级和级别的片段方法访问代码使用Reflection API

Object o = hi.getClass().getMethod("foo").invoke(hi); 
Method m = o.getClass().getMethod("bar"); 
m.setAccessible(true); 
m.invoke(o);

深入了解Java Reflection API

并不总是有必要证明不可能本身说我可能。在这种情况下,Reflection API为我们重新设计了轮子,我们不应该从头开始重新将它们重新组合在一起。(更轻松的说明,没有什么可以进攻的。)

Shishir

答案 1 :(得分:2)

这是预期的,因为Java中任何元素的限定的定义都是从它的类加载器开始,然后是它们的包。也就是说,它不会在编译时给你任何错误,但由于运行时包不同,将导致访问冲突,从而产生异常。

本文可以帮助您更加了解此问题:http://www.cooljeff.co.uk/2009/05/03/the-subtleties-of-overriding-package-private-methods/

虽然,您仍然可以通过反射访问这些方法,在方法调用之前调用Method.setAccessible(boolean)

答案 2 :(得分:0)

一种解决方案是将类One编译到类加载器中:

public class ClassLoaderTest {

    @Test
    public void testLoading() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        String packageName = "org.test";

        MemoryClassLoader classLoader = new MemoryClassLoader();

        Map<String, String> toCompile = new HashMap<String, String>();
        String oneName = packageName + ".One";
        toCompile.put(oneName,
                "package org.test;\n" +
                        "\n" +
                        "public class One{\n" +
                        "        String value;\n" +
                        "    }");

        String twoName = packageName + ".Two";
        toCompile.put(twoName,
                "package org.test;\n" +
                        "\n" +
                        "public class Two{\n" +
                        "    public static String getValue(One one){\n" +
                        "        return one.value;\n" +
                        "    }\n" +
                        "}");

        classLoader.add(toCompile);

        Class<?> oneClass = classLoader.loadClass(oneName);
        Object one = oneClass.newInstance();

        Field valueField = oneClass.getDeclaredField("value");
        valueField.setAccessible(true);
        valueField.set(one, "test");
        valueField.setAccessible(false);

        Class<?> twoClass = classLoader.loadClass(twoName);

        assertEquals("test", twoClass.getMethod("getValue", oneClass).invoke(null, one));
    }
}

不是最佳解决方案,因为您必须将Java源代码视为字符串。

答案 3 :(得分:0)

  

......我试图在没有反思的情况下这样做。 - 约翰卡尔......

只是一个疯狂的想法,它肯定是一个非常错误的事情(从Java的角度来看),但是:

您可以将库文件( *。dll,* .so 等)加载到Java进程中(通过JNI)。然后,您可以从C / C ++端访问完整的Java进程空间并调用任何现有方法。