在Java应用程序中,我声明了两个类,一个类(One
)在ClassLoader A中声明,另一个类(Two
)在ClassLoader B中声明.ClassLoader A是B的父级。这两个类都具有相同的包(即:org.test
)。
我似乎无法访问One
的包私有方法或来自Two
事件的变量,尽管A是B'd的父类ClassLoader,我得到IllegalAccessError
异常。我知道包私有可访问性基于包名和ClassLoader。
有没有办法重新关联One
和Two
,以便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。
答案 0 :(得分:3)
有没有办法重新关联One和Two,以便Two可以访问 One的Package Private元素?
简单回答:反思:是;没有反思:否。
详细解答: Java有自己的机制来控制访问,防止程序包或类的用户依赖于该程序包或类的实现的不必要细节。如果允许访问,则称所访问的实体是可访问的。 辅助功能是一种静态属性,可以在编译时确定;它仅取决于类型和声明修饰符。
如果成员或构造函数被声明为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进程空间并调用任何现有方法。