Java 在父/子类加载器之间访问包私有元素

Java 在父/子类加载器之间访问包私有元素,java,classloader,Java,Classloader,在Java应用程序中,我声明了两个类,一个类(one)在类加载器a中声明,另一个类(two)在类加载器B中声明。类加载器a是B的父类。这两个类都有相同的包(即:org.test) 我似乎无法从Two事件访问One的包私有方法或变量。虽然A是B的父类加载器,但我得到了IllegalAccessError异常。我知道包私有可访问性是基于包名和类加载器的 是否有方法重新关联One和Two,以便Two可以访问One的包私有元素 下面是一个测试来证明这一点: package org.test; publ

在Java应用程序中,我声明了两个类,一个类(
one
)在类加载器a中声明,另一个类(
two
)在类加载器B中声明。类加载器a是B的父类。这两个类都有相同的包(即:
org.test

我似乎无法从
Two
事件访问
One
的包私有方法或变量。虽然A是B的父类加载器,但我得到了
IllegalAccessError
异常。我知道包私有可访问性是基于包名和类加载器的

是否有方法重新关联
One
Two
,以便
Two
可以访问
One
的包私有元素

下面是一个测试来证明这一点:

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;
}
package-org.test;
公共类类装入器测试{
@试验
public void testload()抛出ClassNotFoundException、IllegaAccessException、InstanceionException、NoSuchMethodException、InvocationTargetException{
一个=新的();
1.value=“测试”;
MemoryClassLoader classLoader=新的MemoryClassLoader();
字符串名称=一个.getClass().getPackage().getName()+“.Two”;
添加(名称,
“包组织测试;\n”+
“\n”+
“公共类二{\n”+
“公共静态字符串getValue(一个){\n”+
“返回一个。值;\n”+
“}\n”+
"}");
Class twoClass=classLoader.loadClass(名称);
assertEquals(“test”,twoClass.getMethod(“getValue”,One.class).invoke(null,One));
}
}
公共一级{
字符串值;
}
可以找到MemoryClassLoader

哪些错误与:

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

testLoading(org.test.ClassLoaderTest)经过的时间:0.214秒这是预期的,因为Java中任何元素的限定定义都是从类加载器开始的,然后是它们的包。也就是说,它在编译时不会给您任何错误,但由于运行时包不同,将导致访问冲突,从而生成异常

这篇文章可能会帮助您进一步了解这一问题:


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

一个解决方案是将类
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));
    }
}
公共类类装入器测试{
@试验
public void testload()抛出ClassNotFoundException、IllegaAccessException、InstanceionException、NoSuchMethodException、InvocationTargetException、NoSuchFieldException{
字符串packageName=“org.test”;
MemoryClassLoader classLoader=新的MemoryClassLoader();
Map to compile=new HashMap();
字符串oneName=packageName+“.One”;
toCompile.put(oneName,
“包组织测试;\n”+
“\n”+
“公共第一类{\n”+
“字符串值;\n”+
"    }");
字符串twoName=packageName+“.Two”;
toCompile.put(两个名称,
“包组织测试;\n”+
“\n”+
“公共类二{\n”+
“公共静态字符串getValue(一个){\n”+
“返回一个。值;\n”+
“}\n”+
"}");
添加(toCompile);
Class oneClass=classLoader.loadClass(oneName);
Object one=oneClass.newInstance();
Field valueField=oneClass.getDeclaredField(“值”);
valueField.setAccessible(true);
valueField.set(一个“测试”);
valueField.setAccessible(false);
Class twoClass=classLoader.loadClass(twoName);
assertEquals(“test”,twoClass.getMethod(“getValue”,oneClass).invoke(null,one));
}
}
这不是最好的解决方案,因为您必须将java源代码视为字符串

。。。我正试图做到这一点而不加思考约翰卡尔

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

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

有没有办法将一和二重新关联起来,这样两个人就可以访问 一个人的包是私人的吗

简单的回答:带着反思:;无反射:

详细答案:Java有自己的机制来控制对包或类的访问,防止用户依赖该包或类的实现的不必要细节。如果允许访问,则被访问的实体称为可访问可访问性是一个静态属性,可以在编译时确定;它只依赖于类型和声明修饰符

如果成员或构造函数声明为private,则访问权限为private 当且仅当其发生在顶层主体内时才允许 类,该类包含成员或构造函数的声明。A. 只能在主体内访问私有类成员或构造函数 包含成员声明的顶级类的,或 构造器。它不是由子类继承的

因此,在JavaSE中,不使用反射API是不可能的

如果使用反射API,则可访问性的静态内省将更改为动态。类是动态加载的,绑定是动态完成的,对象实例是在需要时动态创建的。从历史上看,不太活跃的是操纵的能力
Object o = hi.getClass().getMethod("foo").invoke(hi); 
Method m = o.getClass().getMethod("bar"); 
m.setAccessible(true); 
m.invoke(o);